Files
document-haness/docs/clean-architecture-backend-template/final/evidence/raw/analysis-finding-a06-f009.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

172 lines
12 KiB
Plaintext

# 관측 인터페이스가 세우는 계약
MongoOperationObservation.java:6 /**
MongoOperationObservation.java:7 * One in-flight operation's observation scope (design §27).
MongoOperationObservation.java:8 *
MongoOperationObservation.java:9 * <p>{@link AutoCloseable} so the scope closes on every path, including the one where the callback
MongoOperationObservation.java:10 * throws something the platform never classified. An observation that only closes on success
MongoOperationObservation.java:11 * produces metrics that look healthy precisely when the system is not.
MongoOperationObservation.java:12 */
MongoOperationObservation.java:13 public interface MongoOperationObservation extends AutoCloseable {
MongoOperationObservation.java:14
MongoOperationObservation.java:15 /** Records a successful completion and its write outcome. */
MongoOperationObservation.java:16 void success(MongoExecutionOutcome outcome);
MongoOperationObservation.java:17
MongoOperationObservation.java:18 /** Records a failure, using only the bounded metadata the failure context is allowed to carry. */
MongoOperationObservation.java:19 void failure(MongoFailureContext failureContext);
MongoOperationObservation.java:20
MongoOperationObservation.java:21 /** The trace identifier of this observation, or an empty string when tracing is off. */
MongoOperationObservation.java:22 String traceId();
MongoOperationObservation.java:23
MongoOperationObservation.java:24 @Override
MongoOperationObservation.java:25 void close();
MongoOperationObservation.java:26 }
# 예산 초과 분기
DefaultMongoImperativeExecutor.java:85 long startedAt = System.nanoTime();
DefaultMongoImperativeExecutor.java:86 try (MongoOperationObservation observation = observer.start(context, operationType)) {
DefaultMongoImperativeExecutor.java:87 try {
DefaultMongoImperativeExecutor.java:88 T value = callback.doInMongo(access);
DefaultMongoImperativeExecutor.java:89 Duration elapsed = Duration.ofNanos(System.nanoTime() - startedAt);
DefaultMongoImperativeExecutor.java:90 // A read is not a confirmed write. Recording every successful FIND as WRITE_CONFIRMED made
DefaultMongoImperativeExecutor.java:91 // the metric that answers "are writes being acknowledged" count reads as well.
DefaultMongoImperativeExecutor.java:92 MongoExecutionOutcome outcome = MongoCompletion.successOutcomeFor(operationType);
DefaultMongoImperativeExecutor.java:93 if (elapsed.compareTo(context.timeout()) > 0) {
DefaultMongoImperativeExecutor.java:94 // The context declares a deadline on every operation and nothing compared anything to it.
DefaultMongoImperativeExecutor.java:95 // A callback cannot be interrupted mid-driver-call, so this cannot cut the work short —
DefaultMongoImperativeExecutor.java:96 // but reporting a breach beats reporting success for work that ran past its budget.
DefaultMongoImperativeExecutor.java:97 observation.success(outcome);
DefaultMongoImperativeExecutor.java:98 throw MongoOperationRejectedException.of(
DefaultMongoImperativeExecutor.java:99 context.operationName().value(),
DefaultMongoImperativeExecutor.java:100 "the operation ran for "
DefaultMongoImperativeExecutor.java:101 + elapsed.toMillis()
DefaultMongoImperativeExecutor.java:102 + "ms against a declared timeout of "
DefaultMongoImperativeExecutor.java:103 + context.timeout().toMillis()
DefaultMongoImperativeExecutor.java:104 + "ms");
DefaultMongoImperativeExecutor.java:105 }
DefaultMongoImperativeExecutor.java:106 observation.success(outcome);
DefaultMongoImperativeExecutor.java:107 return new MongoOperationResult<>(context, value, outcome, elapsed);
DefaultMongoImperativeExecutor.java:108 } catch (MongoPersistenceException alreadyTranslated) {
DefaultMongoImperativeExecutor.java:109 observation.failure(alreadyTranslated.failureContext());
DefaultMongoImperativeExecutor.java:110 throw alreadyTranslated;
DefaultMongoImperativeExecutor.java:111 } catch (MongoException driverFailure) {
DefaultMongoImperativeExecutor.java:112 throw translated(context, operationType, startedAt, observation, driverFailure);
DefaultMongoImperativeExecutor.java:113 } catch (DataAccessException springFailure) {
DefaultMongoImperativeExecutor.java:114 throw translated(context, operationType, startedAt, observation, unwrap(springFailure));
DefaultMongoImperativeExecutor.java:115 }
DefaultMongoImperativeExecutor.java:116 }
DefaultMongoImperativeExecutor.java:117 }
DefaultMongoImperativeExecutor.java:118
# 그 예외가 무엇을 상속하는가
MongoOperationRejectedException.java:1 package dev.caskeleton.adapter.outbound.mongo.api.error;
MongoOperationRejectedException.java:2
MongoOperationRejectedException.java:3 import dev.caskeleton.adapter.outbound.mongo.api.MongoOperationName;
MongoOperationRejectedException.java:4 import java.io.Serial;
MongoOperationRejectedException.java:5
MongoOperationRejectedException.java:6 /**
MongoOperationRejectedException.java:7 * The platform refused the operation locally, before any server was contacted (design §15, §16.2).
MongoOperationRejectedException.java:8 *
MongoOperationRejectedException.java:9 * <p>This is the guardrail exception: unregistered field, unregistered operator, raised budget,
MongoOperationRejectedException.java:10 * write stage in a read API, deep skip, missing tenant context, admin command on a runtime client.
MongoOperationRejectedException.java:11 * It is always {@code NOT_SENT} and never retryable, because retrying a rejected request only
MongoOperationRejectedException.java:12 * repeats the rejection.
MongoOperationRejectedException.java:13 */
MongoOperationRejectedException.java:14 public final class MongoOperationRejectedException extends MongoPersistenceException {
MongoOperationRejectedException.java:15
MongoOperationRejectedException.java:16 @Serial private static final long serialVersionUID = 1L;
MongoOperationRejectedException.java:17
MongoOperationRejectedException.java:18 public MongoOperationRejectedException(String summary, MongoFailureContext failureContext) {
MongoOperationRejectedException.java:19 super(summary, failureContext);
MongoOperationRejectedException.java:20 }
MongoOperationRejectedException.java:21
MongoOperationRejectedException.java:22 /** Rejects an operation identified only by name, before profiles were resolved. */
MongoOperationRejectedException.java:23 public static MongoOperationRejectedException of(String operationName, String summary) {
MongoOperationRejectedException.java:24 return new MongoOperationRejectedException(
MongoOperationRejectedException.java:25 summary, MongoFailureContext.rejected(new MongoOperationName(operationName)));
MongoOperationRejectedException.java:26 }
MongoOperationRejectedException.java:27
# 한 관측에 두 번 기록되는가
try 블록 안에서 observation.success 를 부르는 줄 : 2 개
같은 try 의 catch 가 observation.failure 를 부르는 줄 : 1 개
이 파일에서 두 메서드를 부르는 자리 전부 :
DefaultMongoImperativeExecutor.java:97 observation.success(outcome);
DefaultMongoImperativeExecutor.java:106 observation.success(outcome);
DefaultMongoImperativeExecutor.java:109 observation.failure(alreadyTranslated.failureContext());
DefaultMongoImperativeExecutor.java:129 observation.failure(translated.failureContext().withTraceId(observation.traceId()));
# 출하 구현이 그것을 어떻게 다루는가
MicrometerMongoOperationObserver.java:62 private final Tags baseTags;
MicrometerMongoOperationObserver.java:63
MicrometerMongoOperationObserver.java:64 private Tags outcomeTags;
MicrometerMongoOperationObserver.java:65
MicrometerMongoOperationObserver.java:66 private boolean stopped;
MicrometerMongoOperationObserver.java:67
MicrometerMongoOperationObserver.java:68 private MicrometerObservation(MeterRegistry registry, Timer.Sample sample, Tags baseTags) {
MicrometerMongoOperationObserver.java:69 this.registry = registry;
MicrometerMongoOperationObserver.java:70 this.sample = sample;
MicrometerMongoOperationObserver.java:71 this.baseTags = baseTags;
MicrometerMongoOperationObserver.java:72 this.outcomeTags = Tags.of("result", "unknown", "failureCategory", "none");
MicrometerMongoOperationObserver.java:73 }
MicrometerMongoOperationObserver.java:74
MicrometerMongoOperationObserver.java:75 @Override
MicrometerMongoOperationObserver.java:76 public void success(MongoExecutionOutcome outcome) {
MicrometerMongoOperationObserver.java:77 outcomeTags = Tags.of("result", outcome.name(), "failureCategory", "none");
MicrometerMongoOperationObserver.java:78 }
MicrometerMongoOperationObserver.java:79
MicrometerMongoOperationObserver.java:80 @Override
MicrometerMongoOperationObserver.java:81 public void failure(MongoFailureContext failureContext) {
MicrometerMongoOperationObserver.java:82 outcomeTags =
MicrometerMongoOperationObserver.java:83 Tags.of(
MicrometerMongoOperationObserver.java:84 "result",
MicrometerMongoOperationObserver.java:85 failureContext.outcome().name(),
MicrometerMongoOperationObserver.java:86 "failureCategory",
MicrometerMongoOperationObserver.java:87 failureContext.category().name());
MicrometerMongoOperationObserver.java:88 }
MicrometerMongoOperationObserver.java:89
MicrometerMongoOperationObserver.java:90 @Override
MicrometerMongoOperationObserver.java:91 public String traceId() {
MicrometerMongoOperationObserver.java:92 return "";
MicrometerMongoOperationObserver.java:93 }
MicrometerMongoOperationObserver.java:94
MicrometerMongoOperationObserver.java:95 @Override
MicrometerMongoOperationObserver.java:96 public void close() {
MicrometerMongoOperationObserver.java:97 if (stopped) {
MicrometerMongoOperationObserver.java:98 return;
MicrometerMongoOperationObserver.java:99 }
MicrometerMongoOperationObserver.java:100 stopped = true;
MicrometerMongoOperationObserver.java:101 sample.stop(registry.timer(TIMER_NAME, baseTags.and(outcomeTags)));
MicrometerMongoOperationObserver.java:102 }
MicrometerMongoOperationObserver.java:103 }
# 다른 구현이 있는가
MongoOperationObservation 을 구현하는 자리 : 2 개
main · NoOpMongoOperationObserver.java:29 private static final class NoOpObservation implements MongoOperationObservation {
main · MicrometerMongoOperationObserver.java:56 private static final class MicrometerObservation implements MongoOperationObservation {
[대조] MongoOperationObserver 를 구현하는 자리 : 2 개
NoOpMongoOperationObserver.java:27 }
NoOpMongoOperationObserver.java:28
NoOpMongoOperationObserver.java:29 private static final class NoOpObservation implements MongoOperationObservation {
NoOpMongoOperationObserver.java:30
NoOpMongoOperationObserver.java:31 @Override
NoOpMongoOperationObserver.java:32 public void success(MongoExecutionOutcome outcome) {
NoOpMongoOperationObserver.java:33 // Nothing is recorded when telemetry is not configured.
NoOpMongoOperationObserver.java:34 }
NoOpMongoOperationObserver.java:35
NoOpMongoOperationObserver.java:36 @Override
NoOpMongoOperationObserver.java:37 public void failure(MongoFailureContext failureContext) {
NoOpMongoOperationObserver.java:38 // Nothing is recorded when telemetry is not configured.
NoOpMongoOperationObserver.java:39 }
NoOpMongoOperationObserver.java:40
NoOpMongoOperationObserver.java:41 @Override
NoOpMongoOperationObserver.java:42 public String traceId() {
NoOpMongoOperationObserver.java:43 return "";
NoOpMongoOperationObserver.java:44 }
NoOpMongoOperationObserver.java:45
NoOpMongoOperationObserver.java:46 @Override
NoOpMongoOperationObserver.java:47 public void close() {
NoOpMongoOperationObserver.java:48 // Nothing to release.
NoOpMongoOperationObserver.java:49 }
NoOpMongoOperationObserver.java:50 }