The keycloak project ended with four open questions that design could not
settle. A two-VM lab was built to answer them by measurement, and this is
that material: 26 experiments, 125 raw command outputs, 22 browser captures.
Follows the import procedure in README.md.
source/ the originating repository verbatim — 78 documents, 28 SVGs,
8 manifests, plus .source-revision recording the commit
final/ the SSOT
document.md 729 lines written from the 29 experiment documents, not
concatenated: what was predicted, what was measured, and
where the measurement itself was wrong
evidence/raw 125 outputs, flattened to <experiment>__<file> because
the originals collided (01-baseline.txt appeared three
times) and the audit only globs the top level
evidence/meta one per raw file; command and exitCode are null and the
README says why rather than inventing them
evidence/browser 22 captures
assets/ three diagrams through techviz
.techviz/ their VizSpecs
A separate project rather than an addition to keycloak: the B-layer answers
that project's four questions, but the A, C and D layers are about cluster
failure, SSO and operations, and one document.md should hold one subject.
The four question records there can point here through 관계.
Recorded rather than papered over: only three of the 28 diagrams were
remade. The repository forbids hand-drawn SVG and forbids titles inside the
canvas; all 28 originals carry both, so converting them is redrawing, not
reformatting. They stay in source/ and the gap is written into the document.
verify-pipeline.py passes. audit-records.py reports no issues.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
144 lines
11 KiB
Plaintext
144 lines
11 KiB
Plaintext
Sub-scope 06 (mongo) transaction (+retry, +session) — manifest and negative-space probes
|
|
revision=a24ece9cf797f7ea647e33bf846b115208ed1ba5
|
|
generatedAt=2026-08-29T23:59:14+00:00
|
|
cwd=/shared/codebase/clean-architecture-backend-template/src
|
|
|
|
=== OWNED FILES ===
|
|
873cb3db4ee900e7638124c59f6091948fbfd0f6 main/java/dev/caskeleton/adapter/outbound/mongo/transaction/MongoTransactionExecutor.java 23
|
|
23a83b409d3e42360e212b7899f04a96ec5dacce main/java/dev/caskeleton/adapter/outbound/mongo/transaction/MongoTransactionProfile.java 77
|
|
9ae3e814bfc78daccebc1b0dce2a3ef953f834f8 main/java/dev/caskeleton/adapter/outbound/mongo/transaction/MongoTransactionScope.java 86
|
|
1d0b146c9ba9268d8dd7ec8cb6e4c50b4c6bb7f8 main/java/dev/caskeleton/adapter/outbound/mongo/transaction/MongoTransactionSession.java 43
|
|
d5937274e6f7ac95abf66459dd5b728e5e5d3316 main/java/dev/caskeleton/adapter/outbound/mongo/transaction/MongoTransactionSessionFactory.java 15
|
|
bef7501ef1742d8114f289f1c44ff1f25f209906 main/java/dev/caskeleton/adapter/outbound/mongo/transaction/ReactiveMongoTransactionExecutor.java 31
|
|
ffe70abd80b87c2fb0dd2f69e96cff659f34b026 main/java/dev/caskeleton/adapter/outbound/mongo/transaction/ReactiveMongoTransactionSession.java 38
|
|
7cb9136000f9746a9bb33e3c4357148498fb1e0e main/java/dev/caskeleton/adapter/outbound/mongo/transaction/ReactiveMongoTransactionSessionFactory.java 16
|
|
5f96fa86ea42eab9306cc73f02feccf7f6683e88 main/java/dev/caskeleton/adapter/outbound/mongo/transaction/SpringMongoTransactionExecutor.java 59
|
|
92bda406cc21352893e4b9a839eac8163ea153ae main/java/dev/caskeleton/adapter/outbound/mongo/transaction/SpringMongoTransactionSessionFactory.java 263
|
|
6d96b51eb94a60f92fe251a9b0452430bc0f3b49 main/java/dev/caskeleton/adapter/outbound/mongo/transaction/SpringReactiveMongoTransactionExecutor.java 175
|
|
b6ef0091d7849f646e8949b2cfa3da3c21791a8f main/java/dev/caskeleton/adapter/outbound/mongo/transaction/SpringReactiveMongoTransactionSessionFactory.java 214
|
|
dfb46a08cb5d404e721005514cf0f98900da6d8a main/java/dev/caskeleton/adapter/outbound/mongo/transaction/retry/MongoCommitReconciler.java 27
|
|
b6df5b2ebf39793d5dc8bf38c222e59069b46b9a main/java/dev/caskeleton/adapter/outbound/mongo/transaction/retry/MongoRetryBudget.java 80
|
|
692a21a115dfa0ea183c625bb5653a96025016b5 main/java/dev/caskeleton/adapter/outbound/mongo/transaction/retry/MongoRetryDecision.java 68
|
|
028037fa1a84eea4b12018d906d801aac0d2dd85 main/java/dev/caskeleton/adapter/outbound/mongo/transaction/retry/MongoTransactionRetryCoordinator.java 189
|
|
774883c35befc8249cfadc1a7ae5de9c45a6fdbb main/java/dev/caskeleton/adapter/outbound/mongo/transaction/session/MongoCausalSessionContext.java 49
|
|
8a25a7c246398a8a1bded005280c6d2c6ca6a290 main/java/dev/caskeleton/adapter/outbound/mongo/transaction/session/MongoCausalSessionExecutor.java 16
|
|
795c67ae7615f079513485cd5041ed388215b6ca main/java/dev/caskeleton/adapter/outbound/mongo/transaction/session/ReactiveMongoCausalSessionExecutor.java 69
|
|
806d3be20120b8e599ea97a967fb342fc9e8fa4e main/java/dev/caskeleton/adapter/outbound/mongo/transaction/session/SpringMongoCausalSessionExecutor.java 79
|
|
0e1323f4abb3933a88d1b6c9fb78457feda4c7be test/java/dev/caskeleton/adapter/outbound/mongo/transaction/MongoSessionAcquisitionTest.java 77
|
|
30619b8f143dc38c06fa5123f34768823edc58ba test/java/dev/caskeleton/adapter/outbound/mongo/transaction/MongoTransactionProfileTest.java 55
|
|
c56553a2d953a98df8d0d7772a9d33a8084903b7 test/java/dev/caskeleton/adapter/outbound/mongo/transaction/MongoTransactionScopeTest.java 76
|
|
344c75f520992801a82a926afdb29437a6980fef test/java/dev/caskeleton/adapter/outbound/mongo/transaction/SpringReactiveMongoTransactionExecutorTest.java 236
|
|
22d6be5d8e4222d45e01a631887da99bcccc6616 test/java/dev/caskeleton/adapter/outbound/mongo/transaction/retry/CleanupFailureIsReportedTest.java 156
|
|
2fde8127fa3874c2a81bb00d2275f645d3d5edaa test/java/dev/caskeleton/adapter/outbound/mongo/transaction/retry/MongoTransactionRetryCoordinatorTest.java 220
|
|
f49adb300b1efa68e93e3f07e1e993bac13ca6bf test/java/dev/caskeleton/adapter/outbound/mongo/transaction/session/MongoCausalSessionExecutorTest.java 43
|
|
|
|
production: 20
|
|
test: 7
|
|
production LOC: 1617 total
|
|
|
|
=== 8.1 reachability: transaction beans and cross-package production references ===
|
|
$ grep -n 'Transaction\|CausalSession\|RetryCoordinator' adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/autoconfigure/MongoPlatformAutoConfiguration.java
|
|
exit=1
|
|
|
|
$ git grep -n 'MongoTransactionExecutor\|MongoTransactionRetryCoordinator\|SpringMongoTransactionSessionFactory\|MongoCausalSessionExecutor\|MongoTransactionScope\|ReactiveMongoTransactionExecutor' -- 'adapter/outbound/persistence-mongo/src/main' | grep -v '/transaction/'
|
|
exit=1
|
|
|
|
=== 8.1b the transactions flag: what it gates and what it does not compose ===
|
|
$ grep -n 'transactionsEnabled' adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/autoconfigure/MongoStartupValidator.java
|
|
32: private final boolean transactionsEnabled;
|
|
43: boolean transactionsEnabled,
|
|
50: this.transactionsEnabled = transactionsEnabled;
|
|
97: if (transactionsEnabled && !capabilities.isStable(MongoCapability.TRANSACTION)) {
|
|
exit=0
|
|
|
|
$ grep -n 'properties.transactions()' adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/autoconfigure/MongoPlatformAutoConfiguration.java
|
|
362: properties.transactions(),
|
|
exit=0
|
|
|
|
$ grep -n 'boolean transactions' adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/autoconfigure/MongoPlatformSettings.java
|
|
29: boolean transactions,
|
|
exit=0
|
|
|
|
=== 8.2 the central rule: body retry and commit retry are separate loops ===
|
|
$ grep -n 'catch (MongoTransactionTransientException transientFailure)' -A 6 adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/transaction/retry/MongoTransactionRetryCoordinator.java
|
|
87: } catch (MongoTransactionTransientException transientFailure) {
|
|
88- lastTransient = transientFailure;
|
|
89- decisionRecorder.accept(
|
|
90- MongoRetryDecision.retryBody(budget.delayBefore(attempt + 1, random)));
|
|
91- continue;
|
|
92- }
|
|
93- return commitWithRetry(session, value, startedAt);
|
|
exit=0
|
|
|
|
$ grep -n 'private <T> T commitWithRetry' -A 8 adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/transaction/retry/MongoTransactionRetryCoordinator.java
|
|
145: private <T> T commitWithRetry(MongoTransactionSession session, T value, long startedAt) {
|
|
146- MongoTransactionCommitUnknownException lastUnknown = null;
|
|
147- for (int commitAttempt = 1;
|
|
148- budget.allowsAttempt(commitAttempt, elapsedSince(startedAt));
|
|
149- commitAttempt++) {
|
|
150- sleep(budget.delayBefore(commitAttempt, random));
|
|
151- try {
|
|
152- session.commit();
|
|
153- return value;
|
|
exit=0
|
|
|
|
$ grep -n 'MongoTransactionCommitUnknownException' adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/transaction/SpringReactiveMongoTransactionExecutor.java
|
|
4:import dev.caskeleton.adapter.outbound.mongo.api.error.MongoTransactionCommitUnknownException;
|
|
130: failure instanceof MongoTransactionCommitUnknownException
|
|
155: MongoTransactionCommitUnknownException.class,
|
|
160: new MongoTransactionCommitUnknownException(
|
|
exit=0
|
|
|
|
=== 8.2b why MongoTransactionManager / TransactionTemplate are not used ===
|
|
$ grep -n 'Deliberately not built on' -A 3 adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/transaction/SpringMongoTransactionSessionFactory.java
|
|
32: * <p>Deliberately not built on {@code MongoTransactionManager} or {@code TransactionTemplate}. Both
|
|
33- * commit implicitly when the callback returns, which collapses body and commit into one step — and
|
|
34- * the whole design rests on those two steps failing differently and being retried differently.
|
|
35- *
|
|
exit=0
|
|
|
|
=== 8.3 scope binding: nested bind restores rather than clears ===
|
|
$ grep -n 'Registration bind' -B 8 adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/transaction/MongoTransactionScope.java
|
|
46- * Binds operations for the current thread and returns what undoes it.
|
|
47- *
|
|
48- * <p>Binding used to be {@code set} and unbinding {@code remove}, which is only correct when
|
|
49- * scopes never nest. They do: an outer transaction body that calls a second transactional
|
|
50- * component ran an inner bind, and the inner {@code remove} left the outer body with nothing
|
|
51- * bound — so every subsequent {@code require()} in the outer body failed, and any code that fell
|
|
52- * back to an ordinary template wrote outside the transaction it believed it was in.
|
|
53- */
|
|
54: static Registration bind(MongoOperations operations) {
|
|
exit=0
|
|
|
|
$ grep -n 'ThreadLocal' adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/transaction/MongoTransactionScope.java adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/transaction/session/SpringMongoCausalSessionExecutor.java
|
|
adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/transaction/MongoTransactionScope.java:20: private static final ThreadLocal<MongoOperations> CURRENT = new ThreadLocal<>();
|
|
adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/transaction/session/SpringMongoCausalSessionExecutor.java:21: private static final ThreadLocal<MongoOperations> CURRENT = new ThreadLocal<>();
|
|
exit=0
|
|
|
|
=== 8.4 profile bounds ===
|
|
$ grep -n 'SERVER_LIFETIME_LIMIT' -A 4 adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/transaction/MongoTransactionProfile.java
|
|
23: public static final Duration SERVER_LIFETIME_LIMIT = Duration.ofSeconds(60);
|
|
24-
|
|
25- public MongoTransactionProfile {
|
|
26- Objects.requireNonNull(consistency, "consistency");
|
|
27- Objects.requireNonNull(timeout, "timeout");
|
|
--
|
|
38: if (timeout.compareTo(SERVER_LIFETIME_LIMIT) > 0) {
|
|
39- throw new IllegalArgumentException(
|
|
40- "a transaction timeout of "
|
|
41- + timeout
|
|
42- + " exceeds the server's default lifetime limit of "
|
|
43: + SERVER_LIFETIME_LIMIT);
|
|
44- }
|
|
45- if (maxAttempts < 1) {
|
|
46- throw new IllegalArgumentException("maxAttempts must be at least 1");
|
|
47- }
|
|
exit=0
|
|
|
|
$ grep -n 'STALE_READ_ALLOWED\|staleReadsPossible\|readsFromSecondary' adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/transaction/MongoTransactionProfile.java
|
|
12: * transaction's snapshot, and neither is what the caller who wrote {@code STALE_READ_ALLOWED}
|
|
29: if (consistency == MongoConsistencyProfile.STALE_READ_ALLOWED) {
|
|
32: + MongoConsistencyProfile.STALE_READ_ALLOWED
|
|
exit=0
|
|
|