Repository navigation
Replies: 5 comments 12 replies
|
Thanks @ahus1. Glad to see this getting some interest. For your testing of CockroachDB, this is our Apache v2 licensed fork that we've shipped for ~4 years. Each branch is named with the Keycloak version + Couple of notes for CRDB:
Happy to help with this if you have an idea of what you want a contribution to look like and how it will be tested/validated. |
|
Hi @ahus1 , thanks for bringing that up, we are very interested!
yes, but not due to load (at least at the moment)
persistent sessions
yes
TiDB
TiDB support partial indexes which is basically apply a
TiDB fixed the major blocker to use MySQL Hibernate Dialect in Keycloak: pingcap/tidb#63035
That makes total sense. Especially embedding region info directly into the identifiers (cookies, tokens, authorization codes) that Keycloak already issues. Everyone will have their own opinionated approach to multi-region load balancing and traffic routing, so it's reasonable to leave the actual routing logic up to whoever operates Keycloak. Keycloak just needs to expose the region info. |
|
TiDB now has support for partial indexes: See also https://docs.pingcap.com/tidb/stable/sql-statement-create-index/#partial-indexes-new-in-v857 |
|
What is good for B-Tree/B+Tree based databases is often not good for TiDB. For a B-Tree/B+Tree you usually want to avoid page splits that happen because a page gets too big that sits in the middle of the keyspace. The way to do this is to have incrementing ID's that all get written to the same page in the end of the keyspace. What TiDB/TiKV does is this: A table is split into data ranges based on either the primary key or an auto generated rowid. Each data region is a raft group with a leader and usually two followers. There are many more regions than nodes as a region is about 256MiB. So records with PK's close to each other (e.g. For example a table t1 of 1GiB would have 4 data regions. With 6 TiKV nodes this could lead to the following situation: Regions: (P=Primary, F=Follower) So if you insert 5 records with
These would all end up in region 4, with a leader on kv4. With
These would end up in all 4 regions, with leaders on 4 separate nodes. Incremental PK:
Random PK:
Note that this is usually not too much of an actual problem in practice as TiDB/TiKV is able to split data regions quick enough. And multiple tables might have hotspots on different nodes, leading to a more even load with many tables. More details: https://docs.pingcap.com/best-practices/high-concurrency-best-practices/ |
|
@ahus1 a few points from the TiDB side (I work on TiDB at PingCAP) on "one table, one shard key, all session data for a given session ID guaranteed to be on the same shard": this holds for the rows, but not for the secondary indexes. In short: the single table does not make a login single-range, but it does make the most frequent write, the token refresh, a 1PC. Below, "range" means a TiKV Region (a key range replicated as one Raft group, target size is a quarter of a gigabyte), and "cloud region" means a geographic region. How indexes are stored in TiDB
What this means for the single table
CockroachDB and YugabyteDB (not my expertise)
Cloud-regional vs global cluster
|
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
TL;DR: I'm proposing a denormalized single-table session store that works with distributed SQL databases (CockroachDB, YugabyteDB, Spanner, TiDB), enabling horizontal scaling and geo-sharding of session data. The existing 4-table schema stays as the default for traditional databases.
Note: the shortlisted databases are candidates — any of them may be dropped as we progress if they prove not to be a good fit for reasons listed here, or other reasons that we discover. There is no commitment to any timeline.
We'd love your input on:
Description
Keycloak currently supports single-node relational databases (PostgreSQL, MySQL, MariaDB, Oracle, MSSQL) for session persistence. As deployments grow, session storage can outgrow a single database node — in stateless mode (where all session operations hit the database directly), and with persistent user sessions enabled (where session data is written through to the database even in clustered mode).
Distributed databases can horizontally shard session data across nodes and geo-shard it across regions, enabling Keycloak to scale session throughput linearly with the number of database nodes — with low operational overhead compared to manually sharding or replicating traditional databases. This proposal introduces a denormalized session storage SPI implementation optimized for distributed databases, alongside the existing normalized 4-table implementation for traditional databases.
Key trade-off: region failure means re-authentication
With geo-sharding, sessions are pinned to the region where the user authenticated. If that region goes down, sessions are lost and users must re-authenticate. No distributed SQL database today supports consensus-based automatic failover with async cross-region replication, and sync replication is not viable due to write latency. This assumes re-authentication on region failure is an acceptable trade-off for transient session data, but we need community feedback on this assumption — does your deployment require session survival across region failures?
Value Proposition
Goals
ROOT_AUTH_SESSION,AUTH_SESSION,OFFLINE_USER_SESSION,OFFLINE_CLIENT_SESSION) keyed by session ID.Shortlisted databases
WHERE)CockroachDialect(core)WHERE)PostgreSQLDialectNULL_FILTERED)SpannerDialect(core)We would value true Open Source, self-managed variants over other variants as Keycloak is also an Open Source project.
Using an extra Hibernate dialect is a big burden for maintenance, and will be counted into the decision.
Expect this list of databases to shrink as this develops. Other databases might appear if they provide the features we ask for.
Non-Goals
Notes
Why a single denormalized table?
During login, Keycloak touches 4 session tables in one transaction, all keyed by the same session ID:
ROOT_AUTH_SESSION(PK:ID)AUTH_SESSION(PK:ROOT_AUTH_SESSION_ID, TAB_ID)OFFLINE_USER_SESSION(PK:USER_SESSION_ID, OFFLINE_FLAG)OFFLINE_CLIENT_SESSION(PK:USER_SESSION_ID, CLIENT_ID, ...)In a distributed database, each table has its own independent set of shards. Even though all 4 tables have the session ID as the leading PK column, there is no mechanism to guarantee that rows with the same session ID across different tables land on the same node ("co-partitioning"). Without co-partitioning, every login becomes a distributed transaction spanning 4+ shards across potentially 4 different nodes, adding significant latency.
A single denormalized table eliminates this problem: one table, one shard key, all session data for a given session ID guaranteed to be on the same shard. This works on every distributed database without any vendor-specific feature.
Co-partitioning landscape
Co-partitioning is a tracked feature request in YugabyteDB (#79, #11219) but not implemented. CockroachDB had it (interleaved tables) but removed it in v22.2 due to implementation complexity. Only Citus (PostgreSQL extension) and Google Spanner support it today, but Citus lacks geo-sharding and Spanner is cloud-only.
Impact on traditional databases
The denormalized table is not suitable for traditional single-node databases:
AUTH_SESSION->ROOT_AUTH_SESSION ON DELETE CASCADE) are lost.SESSION_BUCKETandLAST_SESSION_REFRESH_COARSEoptimizations (designed for B-tree page latch contention on MSSQL/Oracle) would need redesign for mixed row types.This is why the denormalized store is a separate SPI implementation, not a replacement for the existing schema.
Filtered indexes on the denormalized table
Type-specific columns are naturally NULL for other entry types, enabling efficient filtered indexes:
Geo-sharding
With an optional
regioncolumn, sessions can be pinned to the authenticating region:All entries share
session_id+region— they shard together and geo-locate together automatically. Cross-region access remains transparent (any node can query any data), with added latency for cross-region reads.Region-aware routing (optional): An intelligent reverse proxy in front of Keycloak could reduce cross-region latency by routing requests to the Keycloak instance in the region that owns the session. The region can be derived from identifiers already present in cookies, tokens, and authorization codes. This avoids cross-region database reads for the common case (user continues in the same region they authenticated in), while still allowing transparent fallback when routing is imprecise.
Architecture
The SPI selection is driven by the
Database.Vendorenum, adding new vendors (COCKROACH,YUGABYTE,SPANNER,TIDB) alongside the existing ones.Cross-region disaster recovery
The ideal cross-region DR model for Keycloak is async replication with consensus-based automatic failover: one region owns session data, writes are fast (local only), and ownership transfers automatically via consensus when a region fails. Sync replication is not viable (50-150ms per write). Manual failover has too high an operational cost.
Current state: No distributed SQL database supports this today. Raft-based systems (CockroachDB, YugabyteDB, TiDB) require manual operator intervention to promote a standby region during async replication, because async mode breaks Raft's consistency guarantees and automatic promotion risks split-brain.
Leaderless databases (Cassandra): Offer automatic failover but use a fundamentally different model — any node in any DC accepts writes, with last-write-wins conflict resolution. This means a misdirected request (e.g., a load balancer routing to the wrong region) silently writes in the wrong place, and the owning region's data can be overwritten. For session data this is a correctness risk that makes leaderless replication a poor fit for Keycloak.
Outlook: Consensus-based async cross-region failover is an active area of development. If a distributed SQL database adds this capability, Keycloak's denormalized session store would be ready to use it with no schema changes — only a database configuration change.
All reactions