Files
document-haness/docs/clean-architecture-backend-template/final/evidence/raw/analysis-finding-a06-f005.txt
T
DongHyeonkaandClaude Opus 5 b2963105a8 docs(keycloak-session-store): import the session-storage lab as a new project
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>
2026-09-04 22:51:59 +09:00

239 lines
17 KiB
Plaintext

# 루트 예외가 세우는 두 규칙
MongoPersistenceException.java:6 /**
MongoPersistenceException.java:7 * Root of the provider-stable MongoDB error hierarchy (design §15).
MongoPersistenceException.java:8 *
MongoPersistenceException.java:9 * <p>Two rules shape this class. First, the message is derived from {@link MongoFailureContext},
MongoPersistenceException.java:10 * which can only hold bounded, data-free values — so no caller can accidentally put a document or a
MongoPersistenceException.java:11 * query parameter into an exception message. Second, no constructor accepts a {@link Throwable}
MongoPersistenceException.java:12 * cause: attaching the driver exception would re-expose everything the failure context deliberately
MongoPersistenceException.java:13 * dropped, through {@code getCause()} and through every stack trace printer. The driver's
MongoPersistenceException.java:14 * information survives as the error labels and server code already carried by the context.
MongoPersistenceException.java:15 */
MongoPersistenceException.java:16 public abstract class MongoPersistenceException extends RuntimeException {
MongoPersistenceException.java:17
MongoPersistenceException.java:18 @Serial private static final long serialVersionUID = 1L;
MongoPersistenceException.java:19
MongoPersistenceException.java:20 private final transient MongoFailureContext failureContext;
MongoPersistenceException.java:21
MongoPersistenceException.java:22 protected MongoPersistenceException(String summary, MongoFailureContext failureContext) {
MongoPersistenceException.java:23 super(
MongoPersistenceException.java:24 summary + " [" + Objects.requireNonNull(failureContext, "failureContext").describe() + "]");
MongoPersistenceException.java:25 this.failureContext = failureContext;
MongoPersistenceException.java:26 }
MongoPersistenceException.java:27
MongoPersistenceException.java:28 /** The bounded metadata for this failure. Never contains document or query data. */
MongoPersistenceException.java:29 public MongoFailureContext failureContext() {
MongoPersistenceException.java:30 return failureContext;
MongoPersistenceException.java:31 }
# 그 규칙이 적용되는 범위
MongoPersistenceException 을 상속하는 main 타입 : 22 개
MongoBulkPartialFailureException.java
MongoChangeHistoryLostException.java
MongoConnectionException.java
MongoCursorException.java
MongoDataSchemaUnsupportedException.java
MongoDocumentNotFoundException.java
MongoDocumentTooLargeException.java
MongoDuplicateKeyException.java
MongoEncryptionException.java
MongoOperationRejectedException.java
MongoOptimisticConflictException.java
MongoReadConcernException.java
MongoResumeException.java
MongoSchemaValidationException.java
MongoServerSelectionException.java
MongoShardRoutingException.java
MongoTimeoutException.java
MongoTransactionCommitUnknownException.java
MongoTransactionTransientException.java
MongoUnclassifiedFailureException.java
MongoWriteConcernException.java
MongoWriteConflictException.java
# 이 계층에서 원인을 받거나 설정하는 자리 전수
Throwable 을 인자로 받는 생성자 줄 : 1 개
initCause 를 부르는 줄 : 1 개
MongoTimeoutException.java:25 public MongoTimeoutException(MongoFailureContext failureContext, Throwable cause) {
MongoTimeoutException.java:27 initCause(cause);
[대조] 그 검색이 훑은 파일 : 351 개
[대조] 옆 계층 persistence-jpa 에서 같은 검색 : 19 줄
# 그 하나의 타입
MongoTimeoutException.java:5 /**
MongoTimeoutException.java:6 * The operation exceeded its deadline (design §15).
MongoTimeoutException.java:7 *
MongoTimeoutException.java:8 * <p>A timeout says when the client stopped waiting, not what the server did. A write that timed
MongoTimeoutException.java:9 * out after being sent is {@code WRITE_RESULT_UNKNOWN} and must be reconciled; only a timeout that
MongoTimeoutException.java:10 * fired before the command left is safe to replay.
MongoTimeoutException.java:11 */
MongoTimeoutException.java:12 public final class MongoTimeoutException extends MongoPersistenceException {
MongoTimeoutException.java:13
MongoTimeoutException.java:14 @Serial private static final long serialVersionUID = 1L;
MongoTimeoutException.java:15
MongoTimeoutException.java:16 public MongoTimeoutException(MongoFailureContext failureContext) {
MongoTimeoutException.java:17 super("the MongoDB operation exceeded its deadline", failureContext);
MongoTimeoutException.java:18 }
MongoTimeoutException.java:19
MongoTimeoutException.java:20 /**
MongoTimeoutException.java:21 * Creates the exception from a client-side timeout.
MongoTimeoutException.java:22 *
MongoTimeoutException.java:23 * @param cause the underlying timeout, kept so a reactive stack trace still names its source
MongoTimeoutException.java:24 */
MongoTimeoutException.java:25 public MongoTimeoutException(MongoFailureContext failureContext, Throwable cause) {
MongoTimeoutException.java:26 super("the MongoDB operation exceeded its deadline", failureContext);
MongoTimeoutException.java:27 initCause(cause);
MongoTimeoutException.java:28 }
MongoTimeoutException.java:29 }
# 2 인자 생성자를 부르는 자리
MongoTimeoutException.java:16 public MongoTimeoutException(MongoFailureContext failureContext) {
MongoTimeoutException.java:25 public MongoTimeoutException(MongoFailureContext failureContext, Throwable cause) {
DefaultMongoFailureTranslator.java:104 case TIMEOUT -> new MongoTimeoutException(failureContext);
DefaultReactiveMongoExecutor.java:152 new dev.caskeleton.adapter.outbound.mongo.api.error.MongoTimeoutException(
DefaultReactiveMongoExecutor.java:138 MongoOperationObservation observation,
DefaultReactiveMongoExecutor.java:139 Throwable failure) {
DefaultReactiveMongoExecutor.java:140 if (failure instanceof MongoPersistenceException alreadyTranslated) {
DefaultReactiveMongoExecutor.java:141 if (observation != null) {
DefaultReactiveMongoExecutor.java:142 observation.failure(alreadyTranslated.failureContext());
DefaultReactiveMongoExecutor.java:143 }
DefaultReactiveMongoExecutor.java:144 return alreadyTranslated;
DefaultReactiveMongoExecutor.java:145 }
DefaultReactiveMongoExecutor.java:146 if (failure instanceof java.util.concurrent.TimeoutException reactorTimeout) {
DefaultReactiveMongoExecutor.java:147 // Reactor's own timeout, not the driver's. It used to escape as a raw
DefaultReactiveMongoExecutor.java:148 // java.util.concurrent.TimeoutException — a type no caller of this platform is told about,
DefaultReactiveMongoExecutor.java:149 // and one that carries no operation, no outcome and no observation. The failure was also
DefaultReactiveMongoExecutor.java:150 // never recorded, because this method returned before reaching the observer.
DefaultReactiveMongoExecutor.java:151 MongoPersistenceException timedOut =
DefaultReactiveMongoExecutor.java:152 new dev.caskeleton.adapter.outbound.mongo.api.error.MongoTimeoutException(
DefaultReactiveMongoExecutor.java:153 dev.caskeleton.adapter.outbound.mongo.api.error.MongoFailureContext.timedOut(
DefaultReactiveMongoExecutor.java:154 context.operationName(), operationType, context.timeout()),
DefaultReactiveMongoExecutor.java:155 reactorTimeout);
DefaultReactiveMongoExecutor.java:156 if (observation != null) {
DefaultReactiveMongoExecutor.java:157 observation.failure(timedOut.failureContext());
DefaultReactiveMongoExecutor.java:158 }
DefaultReactiveMongoExecutor.java:159 return timedOut;
DefaultReactiveMongoExecutor.java:160 }
DefaultReactiveMongoExecutor.java:161 MongoException driverFailure = asDriverFailure(failure);
DefaultReactiveMongoExecutor.java:162 if (driverFailure == null) {
DefaultReactiveMongoExecutor.java:163 return failure;
DefaultReactiveMongoExecutor.java:164 }
DefaultReactiveMongoExecutor.java:165 MongoPersistenceException translated =
DefaultReactiveMongoExecutor.java:166 translator.translate(
DefaultReactiveMongoExecutor.java:167 context, operationType, Duration.ZERO, MongoDriverFailureView.from(driverFailure), 1);
DefaultReactiveMongoExecutor.java:168 if (observation != null) {
DefaultReactiveMongoExecutor.java:169 observation.failure(translated.failureContext().withTraceId(observation.traceId()));
DefaultReactiveMongoExecutor.java:170 }
DefaultReactiveMongoExecutor.java:171 return translated;
DefaultReactiveMongoExecutor.java:172 }
# 생성자를 지나지 않고 드라이버 예외가 붙는 자리
이 계층에서 addSuppressed 를 부르는 줄 : 3 개
SpringMongoTransactionSessionFactory.java:88 startFailed.addSuppressed(closeFailed);
SpringMongoTransactionSessionFactory.java:194 inFlight.addSuppressed(failure);
SpringMongoTransactionSessionFactory.java:200 cleanupFailure.addSuppressed(failure);
SpringMongoTransactionSessionFactory.java:159 public void abort() {
SpringMongoTransactionSessionFactory.java:160 if (!committed && session.hasActiveTransaction()) {
SpringMongoTransactionSessionFactory.java:161 session.abortTransaction();
SpringMongoTransactionSessionFactory.java:162 }
SpringMongoTransactionSessionFactory.java:163 }
SpringMongoTransactionSessionFactory.java:164
SpringMongoTransactionSessionFactory.java:165 @Override
SpringMongoTransactionSessionFactory.java:166 public void close() {
SpringMongoTransactionSessionFactory.java:167 try {
SpringMongoTransactionSessionFactory.java:168 abort();
SpringMongoTransactionSessionFactory.java:169 } catch (RuntimeException abortFailed) {
SpringMongoTransactionSessionFactory.java:170 recordCleanupFailure(abortFailed);
SpringMongoTransactionSessionFactory.java:171 } finally {
SpringMongoTransactionSessionFactory.java:172 try {
SpringMongoTransactionSessionFactory.java:173 session.close();
SpringMongoTransactionSessionFactory.java:174 } catch (RuntimeException closeFailed) {
SpringMongoTransactionSessionFactory.java:175 recordCleanupFailure(closeFailed);
SpringMongoTransactionSessionFactory.java:176 }
SpringMongoTransactionSessionFactory.java:185 /**
SpringMongoTransactionSessionFactory.java:186 * Keeps a cleanup failure without letting it become the outcome.
SpringMongoTransactionSessionFactory.java:187 *
SpringMongoTransactionSessionFactory.java:188 * <p>Attached to the failure already in flight when there is one, so the stack the caller sees
SpringMongoTransactionSessionFactory.java:189 * still names what actually went wrong, with the failed abort alongside it rather than in place
SpringMongoTransactionSessionFactory.java:190 * of it.
SpringMongoTransactionSessionFactory.java:191 */
SpringMongoTransactionSessionFactory.java:192 private void recordCleanupFailure(RuntimeException failure) {
SpringMongoTransactionSessionFactory.java:193 if (inFlight != null) {
SpringMongoTransactionSessionFactory.java:194 inFlight.addSuppressed(failure);
SpringMongoTransactionSessionFactory.java:195 return;
SpringMongoTransactionSessionFactory.java:196 }
SpringMongoTransactionSessionFactory.java:197 if (cleanupFailure == null) {
SpringMongoTransactionSessionFactory.java:198 cleanupFailure = failure;
SpringMongoTransactionSessionFactory.java:199 } else {
SpringMongoTransactionSessionFactory.java:212 private RuntimeException translate(Throwable failure, MongoFailurePhase phase) {
SpringMongoTransactionSessionFactory.java:213 RuntimeException translated = classify(failure, phase);
SpringMongoTransactionSessionFactory.java:214 inFlight = translated;
SpringMongoTransactionSessionFactory.java:215 return translated;
SpringMongoTransactionSessionFactory.java:216 }
SpringMongoTransactionSessionFactory.java:217
SpringMongoTransactionSessionFactory.java:218 private RuntimeException translateCommit(Throwable failure) {
SpringMongoTransactionSessionFactory.java:219 RuntimeException translated = classify(failure, MongoFailurePhase.TRANSACTION_COMMIT);
SpringMongoTransactionSessionFactory.java:220 inFlight = translated;
SpringMongoTransactionSessionFactory.java:221 return translated;
SpringMongoTransactionSessionFactory.java:222 }
SpringMongoTransactionSessionFactory.java:223
SpringMongoTransactionSessionFactory.java:224 private RuntimeException classify(Throwable failure, MongoFailurePhase phase) {
SpringMongoTransactionSessionFactory.java:225 MongoDriverFailureView view = MongoFailureExtractor.viewOf(failure).orElse(null);
SpringMongoTransactionSessionFactory.java:226 if (view == null) {
SpringMongoTransactionSessionFactory.java:227 // Not a Mongo failure, or a cause chain that cannot be walked. Terminal is the fail-closed
SpringMongoTransactionSessionFactory.java:228 // verdict when the alternative is replaying work.
SpringMongoTransactionSessionFactory.java:229 return failure instanceof RuntimeException runtime
SpringMongoTransactionSessionFactory.java:230 ? runtime
SpringMongoTransactionSessionFactory.java:231 : new IllegalStateException("unclassifiable Mongo failure", failure);
SpringMongoTransactionSessionFactory.java:232 }
SpringMongoTransactionSessionFactory.java:233 // The phase is passed through: the same socket failure is a body replay before the commit
SpringMongoTransactionSessionFactory.java:234 // and a commit-unknown during it, and the classifier could not tell them apart.
SpringMongoTransactionSessionFactory.java:235 MongoRetryScope scope =
SpringMongoTransactionSessionFactory.java:236 classifier
SpringMongoTransactionSessionFactory.java:237 .classify(
SpringMongoTransactionSessionFactory.java:238 phase == MongoFailurePhase.TRANSACTION_COMMIT
SpringMongoTransactionSessionFactory.java:239 ? MongoOperationType.TRANSACTION_COMMIT
SpringMongoTransactionSessionFactory.java:240 : MongoOperationType.UPDATE,
SpringMongoTransactionSessionFactory.java:241 phase,
SpringMongoTransactionSessionFactory.java:242 view)
SpringMongoTransactionSessionFactory.java:243 .retryScope();
SpringMongoTransactionSessionFactory.java:244 String serverCode = view.hasServerCode() ? Integer.toString(view.serverCode()) : "";
SpringMongoTransactionSessionFactory.java:245 // The context is derived from the scope, not built first and then labelled. Building a
SpringMongoTransactionSessionFactory.java:246 // commit-unknown context and wrapping it in whichever exception the classifier chose is how
SpringMongoTransactionSessionFactory.java:247 // a "replay the body" exception came to carry "unknown commit, not retryable, ambiguous".
SpringMongoTransactionSessionFactory.java:248 if (scope == MongoRetryScope.COMMIT_ONLY) {
SpringMongoTransactionSessionFactory.java:249 return new MongoTransactionCommitUnknownException(
SpringMongoTransactionSessionFactory.java:250 MongoFailureContext.commitUnknown(SESSION_OPERATION, serverCode, Duration.ZERO),
SpringMongoTransactionSessionFactory.java:251 "read the transaction record, version or idempotency key");
SpringMongoTransactionSessionFactory.java:252 }
SpringMongoTransactionSessionFactory.java:253 if (scope == MongoRetryScope.WHOLE_TRANSACTION) {
SpringMongoTransactionSessionFactory.java:254 return new MongoTransactionTransientException(
SpringMongoTransactionSessionFactory.java:255 MongoFailureContext.transientTransaction(SESSION_OPERATION, serverCode, Duration.ZERO));
SpringMongoTransactionSessionFactory.java:256 }
# 그 규칙을 검사하는 시험
이 계층의 시험에서 getCause 를 단언하는 줄 : 1 개
MongoFailureContextTest.java:65 assertThat(exception.getCause()).isNull();
[대조] test 소스 세트에서는 : 9 줄
[대조] 시험 소스 세트를 모두 훑으면 : 9 줄
[대조] 이 계층의 하위 타입 가운데 final 로 닫힌 것 : 22 개
MongoFailureContextTest.java:56
MongoFailureContextTest.java:57 @Test
MongoFailureContextTest.java:58 void exceptionsDoNotExposeADriverCause() {
MongoFailureContextTest.java:59 MongoPersistenceException exception =
MongoFailureContextTest.java:60 new MongoTransactionCommitUnknownException(
MongoFailureContextTest.java:61 MongoFailureContext.commitUnknown(
MongoFailureContextTest.java:62 new MongoOperationName("order.reserve"), "251", Duration.ZERO),
MongoFailureContextTest.java:63 "read the transaction record");
MongoFailureContextTest.java:64
MongoFailureContextTest.java:65 assertThat(exception.getCause()).isNull();
MongoFailureContextTest.java:66 assertThat(exception.category()).isEqualTo(MongoFailureCategory.TRANSACTION_COMMIT_UNKNOWN);
MongoFailureContextTest.java:67 }
MongoTimeoutException 이 나오는 시험 줄 : 0 개
[대조] MongoTransactionCommitUnknownException 은 : 4 줄