8000 Evaluate TiDB as a MySQL database in tests by ahus1 · Pull Request #54725 · keycloak/keycloak · GitHub
[go: up one dir, main page]

Skip to content

Evaluate TiDB as a MySQL database in tests - #54725

Draft
ahus1 wants to merge 6 commits into
keycloak:mainfrom
ahus1:is-54724-spike-tidb
Draft

ahus1 wants to merge 6 commits into
keycloak:mainfrom
ahus1:is-54724-spike-tidb

Conversation

@ahus1
@ahus1 ahus1 commented Nov 5, 2026 •
Copy link
Copy Markdown
Member

Closes #54724

Spike to find out if Keycloak works with TiDB when it is treated as a plain MySQL database (vendor mysql, MySQL dialect and driver). The tests run against TiDB v8.5.8 via KC_TEST_DATABASE=tidb, and the CI job runs nightly, manually, or when the commit message contains [tidb ci].

Finding: TiDB accepts FOR UPDATE SKIP LOCKED but silently ignores the SKIP LOCKED part (pingcap/tidb#18207). A row locked by another transaction is returned instead of being skipped. JpaUserSessionPersisterProvider.lockUserSession and lockClientSession use it to check that the row is locked by the current transaction, so on TiDB they reported rows as locked that were not. The versioned update then matched 0 rows, which failed ConcurrentLoginTest with OptimisticLockException and an HTTP 500 "Database operation failed".

Quick fix in this PR: JpaUtils.isSkipLockedSupported detects TiDB via the JDBC product version (8.0.11-TiDB-v8.5.8), as the dialect and product name are MySQL. On TiDB, both lock methods use FOR UPDATE NOWAIT instead of SKIP LOCKED. If the row is locked by another transaction, TiDB fails immediately (error 3572), and the persister treats this as "not locked", so the existing retry path in a separate transaction applies. Plain FOR UPDATE was tried first, but it deadlocked in ConcurrentLoginTest.concurrentLoginSingleUserSingleClient on CI when requests locked rows in a different order. With NOWAIT, StatelessTestSuite passes on TiDB v8.5.8 (150 tests, 0 failures). This relies on how Hibernate handles the failed lock: with a lock timeout and the MySQL dialect, it doesn't mark the transaction as rollback-only, while other dialects such as PostgreSQL do. Therefore the fallback is intended for TiDB only and throws an UnsupportedOperationException on other dialects. UserSessionPersisterLockTest (MySQL dialect only) verifies that a failed lock returns false and leaves the transaction usable, and will fail if a Hibernate upgrade changes this.

Other places using SKIP LOCKED (event store expiry, outbox drain and poll) were reviewed but not changed. On TiDB they should only wait for locks instead of skipping rows, and not produce wrong results.

@ahus1 ahus1 self-assigned this Nov 5, 2026
@ahus1 ahus1 changed the title Treat TiDB as a MySQL database Evaluate TiDB as a MySQL database in tests Nov 5, 2026
@ahus1
ahus1 commented Nov 11, 2026
Copy link
Copy Markdown
Member Author

TODO: possible write-optimization when using

-- On a per-connection basis 
SET SESSION tidb_enable_async_commit = ON;
SET SESSION tidb_enable_1pc = ON;

Trade-off: A read penalty if the same row is read immediately after.

ahus1 and others added 5 commits November 12, 2026 10:24
[tidb ci]

Signed-off-by: Alexander Schwartz <alexander.schwartz@ibm.com>
TiDB accepts FOR UPDATE SKIP LOCKED but ignores the SKIP LOCKED part, so the
session lock check reported rows as locked that were not, which led to
optimistic lock failures under concurrent logins.
On TiDB, use a plain FOR UPDATE that waits for the lock, the version comparison
afterward detects a changed row.

See pingcap/tidb#18207

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Alexander Schwartz <alexander.schwartz@ibm.com>
TiDB ignores SKIP LOCKED, and plain FOR UPDATE can deadlock when concurrent
requests lock session rows in different orders. Use NOWAIT instead and treat a
row locked by another transaction as not locked, so the caller retries in a
separate transaction. Add a test that a failed lock leaves the transaction usable.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Alexander Schwartz <alexander.schwartz@ibm.com>
Explain why NOWAIT works on the MySQL dialect only, as it depends on how
Hibernate marks transactions for rollback after a failed lock. Throw an
exception if the fallback is reached on another dialect, and check the dialect
instead of the product name in the test.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Alexander Schwartz <alexander.schwartz@ibm.com>
Compute the TiDB flag when the EntityManagerFactory is initialized instead of lazily on first use, so the shared properties map is no longer written concurrently.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Alexander Schwartz <alexander.schwartz@ibm.com>
@ahus1
ahus1 force-pushed the is-54724-spike-tidb branch from d13a849 to ef54011 Compare November 12, 2026 09:25
< 8000 div class="pr-1 flex-shrink-0" style="width: 16px;">
Both settings roughly halve the latency of writes. They are enabled by default on TiDB clusters, but can be off on upgraded clusters.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Alexander Schwartz <alexander.schwartz@ibm.com>
@ahus1
ahus1 commented Nov 12, 2026
Copy link
Copy Markdown
Member Author

Those settings are already "on" by default when running a cluster. Keycloak will warn if they are not.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Evaluate current compatibility with TiDB

1 participant

0