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>
206 lines
15 KiB
Plaintext
206 lines
15 KiB
Plaintext
# 규약이 강제라고 적는 문장
|
|
MongoObservationConvention.java:1 package dev.caskeleton.adapter.outbound.mongo.observation;
|
|
MongoObservationConvention.java:2
|
|
MongoObservationConvention.java:3 import java.util.Objects;
|
|
MongoObservationConvention.java:4 import java.util.Set;
|
|
MongoObservationConvention.java:5
|
|
MongoObservationConvention.java:6 /**
|
|
MongoObservationConvention.java:7 * The only tags MongoDB telemetry may carry (design §27).
|
|
MongoObservationConvention.java:8 *
|
|
MongoObservationConvention.java:9 * <p>Two problems, one allowlist. High-cardinality tags — a document id, a tenant id, a query
|
|
MongoObservationConvention.java:10 * parameter — multiply time series until the metrics backend either drops data or bills for it. And
|
|
MongoObservationConvention.java:11 * the same values are the ones that turn a metrics store into an unmanaged copy of production data,
|
|
MongoObservationConvention.java:12 * outside whatever retention and access rules the database is under.
|
|
MongoObservationConvention.java:13 *
|
|
MongoObservationConvention.java:14 * <p>Both are prevented the same way: a tag not on this list cannot be attached, so the mistake has
|
|
MongoObservationConvention.java:15 * to be made in this file rather than at a call site.
|
|
MongoObservationConvention.java:16 */
|
|
MongoObservationConvention.java:17 public final class MongoObservationConvention {
|
|
MongoObservationConvention.java:18
|
|
MongoObservationConvention.java:19 private static final Set<String> ALLOWED_TAGS =
|
|
MongoObservationConvention.java:20 Set.of(
|
|
MongoObservationConvention.java:21 "mongoProfile",
|
|
MongoObservationConvention.java:22 "databaseProfile",
|
|
MongoObservationConvention.java:23 "collectionProfile",
|
|
MongoObservationConvention.java:24 "operationName",
|
|
MongoObservationConvention.java:25 "operationType",
|
|
MongoObservationConvention.java:26 "result",
|
|
MongoObservationConvention.java:27 "failureCategory",
|
|
MongoObservationConvention.java:28 "consistencyProfile");
|
|
MongoObservationConvention.java:29
|
|
MongoObservationConvention.java:30 private static final Set<String> FORBIDDEN_TAGS =
|
|
MongoObservationConvention.java:31 Set.of(
|
|
MongoObservationConvention.java:32 "documentId",
|
|
MongoObservationConvention.java:33 "rawTenantId",
|
|
MongoObservationConvention.java:34 "tenantId",
|
|
MongoObservationConvention.java:35 "dynamicCollectionName",
|
|
MongoObservationConvention.java:36 "queryParameter",
|
|
MongoObservationConvention.java:37 "query",
|
|
MongoObservationConvention.java:38 "fullBson",
|
|
MongoObservationConvention.java:39 "resumeToken",
|
|
MongoObservationConvention.java:40 "shardKeyValue",
|
|
MongoObservationConvention.java:41 "plaintextPII",
|
|
MongoObservationConvention.java:42 "credential");
|
|
MongoObservationConvention.java:43
|
|
MongoObservationConvention.java:44 private MongoObservationConvention() {}
|
|
MongoObservationConvention.java:45
|
|
MongoObservationConvention.java:46 /** The platform's standard convention. */
|
|
MongoObservationConvention.java:47 public static MongoObservationConvention standard() {
|
|
MongoObservationConvention.java:48 return new MongoObservationConvention();
|
|
MongoObservationConvention.java:49 }
|
|
MongoObservationConvention.java:50
|
|
MongoObservationConvention.java:51 /** The tag names telemetry may use. */
|
|
MongoObservationConvention.java:52 public Set<String> allowedTagNames() {
|
|
MongoObservationConvention.java:53 return ALLOWED_TAGS;
|
|
MongoObservationConvention.java:54 }
|
|
MongoObservationConvention.java:55
|
|
MongoObservationConvention.java:56 /** The tag names the design explicitly forbids, kept for assertions and review. */
|
|
MongoObservationConvention.java:57 public Set<String> forbiddenTagNames() {
|
|
|
|
# 그 강제를 수행하는 메서드
|
|
MongoObservationConvention.java:58 return FORBIDDEN_TAGS;
|
|
MongoObservationConvention.java:59 }
|
|
MongoObservationConvention.java:60
|
|
MongoObservationConvention.java:61 /**
|
|
MongoObservationConvention.java:62 * Rejects a tag that is not on the allowlist.
|
|
MongoObservationConvention.java:63 *
|
|
MongoObservationConvention.java:64 * @throws IllegalArgumentException naming the rejected tag
|
|
MongoObservationConvention.java:65 */
|
|
MongoObservationConvention.java:66 public void requireAllowed(String tagName) {
|
|
MongoObservationConvention.java:67 Objects.requireNonNull(tagName, "tagName");
|
|
MongoObservationConvention.java:68 if (!ALLOWED_TAGS.contains(tagName)) {
|
|
MongoObservationConvention.java:69 throw new IllegalArgumentException(
|
|
MongoObservationConvention.java:70 "tag '"
|
|
MongoObservationConvention.java:71 + tagName
|
|
MongoObservationConvention.java:72 + "' is not on the MongoDB observation allowlist; the allowed tags are "
|
|
MongoObservationConvention.java:73 + ALLOWED_TAGS);
|
|
MongoObservationConvention.java:74 }
|
|
MongoObservationConvention.java:75 }
|
|
MongoObservationConvention.java:76 }
|
|
|
|
# 그것을 부르는 자리
|
|
MongoObservationConvention 을 언급하는 main 파일 : 2 개
|
|
MongoDriverObservabilityConfiguration.java
|
|
MongoObservationConvention.java
|
|
이 리프 main 에서 requireAllowed 를 부르는 줄 : 0 개
|
|
requireAllowed 가 이 리프에 나오는 자리 전부 :
|
|
main · MongoSearchQuery.java:48 public void requireAllowedPaths(Set<String> allowedPaths) {
|
|
main · ShardAwareQueryValidator.java:59 public MongoRoutingClassification requireAllowedRead(
|
|
main · MongoAggregationProfile.java:86 public void requireAllowed(MongoAggregationStageDescriptor descriptor) {
|
|
main · PolicyAwareMongoAggregationExecutor.java:49 plan.stages().forEach(profile::requireAllowed);
|
|
main · MongoObservationConvention.java:66 public void requireAllowed(String tagName) {
|
|
test · MongoSearchReadinessGateTest.java:52 assertThatCode(() -> query.requireAllowedPaths(Set.of("customerName")))
|
|
test · MongoSearchReadinessGateTest.java:54 assertThatThrownBy(() -> query.requireAllowedPaths(Set.of("orderNumber")))
|
|
test · ShardAwareQueryValidatorTest.java:57 assertThatThrownBy(() -> validator.requireAllowedRead(key, Set.of("status"), false))
|
|
test · ShardAwareQueryValidatorTest.java:60 assertThatCode(() -> validator.requireAllowedRead(key, Set.of("status"), true))
|
|
test · PolicyAwareMongoAggregationExecutorTest.java:20 assertThatThrownBy(() -> profile.requireAllowed(MongoAggregationStageDescriptor.of("$merge")))
|
|
test · PolicyAwareMongoAggregationExecutorTest.java:29 .requireAllowed(MongoAggregationStageDescriptor.of("$out")))
|
|
test · PolicyAwareMongoAggregationExecutorTest.java:37 assertThatCode(() -> profile.requireAllowed(MongoAggregationStageDescriptor.of("$match")))
|
|
test · PolicyAwareMongoAggregationExecutorTest.java:39 assertThatCode(() -> profile.requireAllowed(MongoAggregationStageDescriptor.of("$project")))
|
|
test · PolicyAwareMongoAggregationExecutorTest.java:48 .requireAllowed(MongoAggregationStageDescriptor.of("$group")))
|
|
test · PolicyAwareMongoAggregationExecutorTest.java:55 .requireAllowed(MongoAggregationStageDescriptor.of("$group")))
|
|
test · PolicyAwareMongoAggregationExecutorTest.java:64 assertThatCode(() -> reviewed.requireAllowed(MongoAggregationStageDescriptor.of("$facet")))
|
|
test · PolicyAwareMongoAggregationExecutorTest.java:67 () -> reviewed.requireAllowed(MongoAggregationStageDescriptor.of("$graphLookup")))
|
|
test · MongoObservationConventionTest.java:38 assertThatCode(() -> convention.requireAllowed("operationName")).doesNotThrowAnyException();
|
|
test · MongoObservationConventionTest.java:39 assertThatThrownBy(() -> convention.requireAllowed("shardKeyValue"))
|
|
test · MongoSecurityIntegrationLaneTest.java:163 assertThatThrownBy(() -> convention.requireAllowed("documentId"))
|
|
test · MongoSecurityIntegrationLaneTest.java:165 assertThatThrownBy(() -> convention.requireAllowed("tenantId"))
|
|
test · MongoSecurityIntegrationLaneTest.java:167 assertThatCode(() -> convention.requireAllowed("operationName")).doesNotThrowAnyException();
|
|
[대조] 저장소 전체 main 에서 그 이름을 부르는 줄 : 3 개
|
|
GraphQlRSocketAdmission.java:47 routePolicy.requireAllowed(route);
|
|
BlockingRedirectCoordinator.java:93 targetGuard.requireAllowed(follow.target(), metadata);
|
|
PublishPosterImageUseCase.java:106 policy.requireAllowed(command.contentIdentity(), command.mediaType());
|
|
[대조] 같은 pathspec 으로 Tags.of 를 세면 : 9 개
|
|
|
|
# 그 규약을 언급하는 다른 한 파일이 하는 일
|
|
MongoDriverObservabilityConfiguration.java:20 public MongoDriverObservabilityConfiguration(MeterRegistry registry, String mongoProfile) {
|
|
MongoDriverObservabilityConfiguration.java:21 this.registry = Objects.requireNonNull(registry, "registry");
|
|
MongoDriverObservabilityConfiguration.java:22 this.mongoProfile = Objects.requireNonNull(mongoProfile, "mongoProfile");
|
|
MongoDriverObservabilityConfiguration.java:23 }
|
|
MongoDriverObservabilityConfiguration.java:24
|
|
MongoDriverObservabilityConfiguration.java:25 /** Adds the command, SDAM and pool listeners to a client settings builder. */
|
|
MongoDriverObservabilityConfiguration.java:26 public MongoClientSettings.Builder apply(MongoClientSettings.Builder builder) {
|
|
MongoDriverObservabilityConfiguration.java:27 Objects.requireNonNull(builder, "builder");
|
|
MongoDriverObservabilityConfiguration.java:28 builder.addCommandListener(new MongoCommandObservationListener(registry, mongoProfile));
|
|
MongoDriverObservabilityConfiguration.java:29 builder.applyToClusterSettings(
|
|
MongoDriverObservabilityConfiguration.java:30 cluster ->
|
|
MongoDriverObservabilityConfiguration.java:31 cluster.addClusterListener(new MongoSdamObservationListener(registry, mongoProfile)));
|
|
MongoDriverObservabilityConfiguration.java:32 builder.applyToConnectionPoolSettings(
|
|
MongoDriverObservabilityConfiguration.java:33 pool ->
|
|
MongoDriverObservabilityConfiguration.java:34 pool.addConnectionPoolListener(
|
|
MongoDriverObservabilityConfiguration.java:35 new MongoPoolObservationListener(registry, mongoProfile)));
|
|
MongoDriverObservabilityConfiguration.java:36 return builder;
|
|
MongoDriverObservabilityConfiguration.java:37 }
|
|
MongoDriverObservabilityConfiguration.java:38
|
|
MongoDriverObservabilityConfiguration.java:39 /** The tag convention these listeners obey. */
|
|
MongoDriverObservabilityConfiguration.java:40 public MongoObservationConvention convention() {
|
|
MongoDriverObservabilityConfiguration.java:41 return MongoObservationConvention.standard();
|
|
MongoDriverObservabilityConfiguration.java:42 }
|
|
MongoDriverObservabilityConfiguration.java:43 }
|
|
standard() 를 부르는 main 줄 : 1 개
|
|
convention() 을 부르는 main 줄 : 0 개
|
|
|
|
# 관측 클래스들이 실제로 태그를 붙이는 방법
|
|
이 패키지에서 Tags.of 를 부르는 줄 : 9 개
|
|
MicrometerMongoOperationObserver.java:46 Tags.of(
|
|
MicrometerMongoOperationObserver.java:72 this.outcomeTags = Tags.of("result", "unknown", "failureCategory", "none");
|
|
MicrometerMongoOperationObserver.java:77 outcomeTags = Tags.of("result", outcome.name(), "failureCategory", "none");
|
|
MicrometerMongoOperationObserver.java:83 Tags.of(
|
|
MongoCommandObservationListener.java:63 Tags.of(
|
|
MongoPoolObservationListener.java:40 Tags tags = Tags.of("mongoProfile", mongoProfile);
|
|
MongoPoolObservationListener.java:60 .tags(Tags.of("mongoProfile", mongoProfile, "failureCategory", event.getReason().name()))
|
|
MongoSdamObservationListener.java:46 .tags(Tags.of("mongoProfile", mongoProfile))
|
|
MongoSdamObservationListener.java:61 .tags(Tags.of("mongoProfile", mongoProfile))
|
|
[대조] 저장소 전체에서 Tags.of 를 부르는 줄 : 30 개
|
|
|
|
# 편집기의 세 갈래 가운데 쓰이는 것
|
|
MongoObservationRedactor.java:18 /** Commands whose arguments are structural rather than data-bearing. */
|
|
MongoObservationRedactor.java:19 private static final Set<String> DESCRIBABLE_COMMANDS =
|
|
MongoObservationRedactor.java:20 Set.of("ping", "hello", "buildInfo", "listCollections", "listIndexes", "collStats");
|
|
MongoObservationRedactor.java:21
|
|
MongoObservationRedactor.java:22 /** Commands whose arguments always carry credentials or key material. */
|
|
MongoObservationRedactor.java:23 private static final Set<String> ALWAYS_REDACTED =
|
|
MongoObservationRedactor.java:24 Set.of(
|
|
MongoObservationRedactor.java:25 "authenticate",
|
|
MongoObservationRedactor.java:26 "saslStart",
|
|
MongoObservationRedactor.java:27 "saslContinue",
|
|
MongoObservationRedactor.java:28 "getnonce",
|
|
MongoObservationRedactor.java:29 "createUser",
|
|
MongoObservationRedactor.java:30 "updateUser",
|
|
MongoObservationRedactor.java:31 "copydbgetnonce",
|
|
MongoObservationRedactor.java:32 "copydbsaslstart",
|
|
MongoObservationRedactor.java:33 "copydb");
|
|
MongoObservationRedactor.java:34
|
|
MongoObservationRedactor.java:35 /** The redacted rendering of a command. */
|
|
MongoObservationRedactor.java:36 public String describe(String commandName) {
|
|
MongoObservationRedactor.java:37 Objects.requireNonNull(commandName, "commandName");
|
|
MongoObservationRedactor.java:38 if (ALWAYS_REDACTED.contains(commandName)) {
|
|
MongoObservationRedactor.java:39 return "<redacted>";
|
|
MongoObservationRedactor.java:40 }
|
|
MongoObservationRedactor.java:41 return DESCRIBABLE_COMMANDS.contains(commandName) ? commandName : commandName + "(...)";
|
|
MongoObservationRedactor.java:42 }
|
|
MongoObservationRedactor.java:43
|
|
MongoObservationRedactor.java:44 /** True when a command's arguments must never be described. */
|
|
MongoObservationRedactor.java:45 public boolean isAlwaysRedacted(String commandName) {
|
|
MongoObservationRedactor.java:46 return ALWAYS_REDACTED.contains(Objects.requireNonNull(commandName, "commandName"));
|
|
MongoObservationRedactor.java:47 }
|
|
MongoObservationRedactor.java:48 }
|
|
redactor.describe 를 부르는 main 줄 : 0 개
|
|
redactor.isAlwaysRedacted 를 부르는 main 줄 : 1 개
|
|
[대조] 시험에서 redactor.describe 는 : 6 개
|
|
리스너가 그 둘 중 무엇을 쓰는가 :
|
|
MongoCommandObservationListener.java:59 private void record(String commandName, String result, long elapsedNanos) {
|
|
MongoCommandObservationListener.java:60 registry
|
|
MongoCommandObservationListener.java:61 .timer(
|
|
MongoCommandObservationListener.java:62 METRIC_NAME,
|
|
MongoCommandObservationListener.java:63 Tags.of(
|
|
MongoCommandObservationListener.java:64 "mongoProfile",
|
|
MongoCommandObservationListener.java:65 mongoProfile,
|
|
MongoCommandObservationListener.java:66 "operationType",
|
|
MongoCommandObservationListener.java:67 redactor.isAlwaysRedacted(commandName) ? "<redacted>" : commandName,
|
|
MongoCommandObservationListener.java:68 "result",
|
|
MongoCommandObservationListener.java:69 result))
|
|
MongoCommandObservationListener.java:70 .record(elapsedNanos, TimeUnit.NANOSECONDS);
|
|
MongoCommandObservationListener.java:71 }
|
|
MongoCommandObservationListener.java:72 }
|