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>
188 lines
12 KiB
Plaintext
188 lines
12 KiB
Plaintext
# 매니페스트 쪽 TTL
|
|
MongoIndexManifest.java:28 String partialFilterExpression,
|
|
MongoIndexManifest.java:29 String collationProfile,
|
|
MongoIndexManifest.java:30 Duration expireAfter,
|
|
MongoIndexManifest.java:31 String wildcardProjection,
|
|
MongoIndexManifest.java:32 boolean shardKeySupport,
|
|
MongoIndexManifest.java:50 }
|
|
MongoIndexManifest.java:51 if (keys.isEmpty()) {
|
|
MongoIndexManifest.java:52 throw new IllegalArgumentException("index '" + name + "' declares no keys");
|
|
MongoIndexManifest.java:53 }
|
|
MongoIndexManifest.java:54 if (expireAfter != null && expireAfter.isNegative()) {
|
|
MongoIndexManifest.java:55 throw new IllegalArgumentException("index '" + name + "' declares a negative TTL");
|
|
MongoIndexManifest.java:56 }
|
|
MongoIndexManifest.java:57 if (metadataOwnership == MongoMetadataOwnership.APPLICATION && expectedUsage.isEmpty()) {
|
|
MongoIndexManifest.java:58 throw new IllegalArgumentException(
|
|
MongoIndexManifest.java:66 /** Starts an index declaration. */
|
|
MongoIndexManifest.java:67 public static Builder named(String name) {
|
|
MongoIndexManifest.java:68 return new Builder(name);
|
|
MongoIndexManifest.java:69 }
|
|
MongoIndexManifest.java:70
|
|
MongoIndexManifest.java:71 /** The TTL, when this is a TTL index. */
|
|
MongoIndexManifest.java:72 public Optional<Duration> ttl() {
|
|
MongoIndexManifest.java:73 return Optional.ofNullable(expireAfter);
|
|
MongoIndexManifest.java:74 }
|
|
MongoIndexManifest.java:75
|
|
MongoIndexManifest.java:76 /** True when this index is a TTL index. */
|
|
MongoIndexManifest.java:77 public boolean isTtlIndex() {
|
|
MongoIndexManifest.java:78 return expireAfter != null;
|
|
MongoIndexManifest.java:79 }
|
|
MongoIndexManifest.java:80
|
|
MongoIndexManifest.java:186 }
|
|
MongoIndexManifest.java:187
|
|
MongoIndexManifest.java:188 /** Declares this a TTL index with the given retention. */
|
|
MongoIndexManifest.java:189 public Builder expireAfter(Duration retention) {
|
|
MongoIndexManifest.java:190 this.expireAfter = Objects.requireNonNull(retention, "retention");
|
|
MongoIndexManifest.java:191 return this;
|
|
MongoIndexManifest.java:192 }
|
|
MongoIndexManifest.java:193
|
|
MongoIndexManifest.java:194 /** Declares a wildcard projection, as canonical extended JSON. */
|
|
expireAfter 라는 이름이 그 파일에 나오는 줄 : 8 개
|
|
그중 값을 거절하는 줄 : 1 개
|
|
|
|
# 규칙을 가진 쪽
|
|
MongoTtlPolicyValidator.java:7 /**
|
|
MongoTtlPolicyValidator.java:8 * Rejects the two ways a TTL index is misused (design §21.1, D-13).
|
|
MongoTtlPolicyValidator.java:9 *
|
|
MongoTtlPolicyValidator.java:10 * <p>The first is treating it as a scheduler: the TTL monitor's sweep interval is unspecified and
|
|
MongoTtlPolicyValidator.java:11 * load-dependent, so "delete this at 09:00" is not something it promises. The second is relying on
|
|
MongoTtlPolicyValidator.java:12 * it for access control: a document that still exists is still readable, so if expiry must hide
|
|
MongoTtlPolicyValidator.java:13 * data the read has to say so.
|
|
MongoTtlPolicyValidator.java:14 */
|
|
MongoTtlPolicyValidator.java:15 public final class MongoTtlPolicyValidator {
|
|
MongoTtlPolicyValidator.java:16
|
|
MongoTtlPolicyValidator.java:17 /**
|
|
MongoTtlPolicyValidator.java:18 * A TTL reduction below this threshold would expire a large population in one sweep, turning a
|
|
MongoTtlPolicyValidator.java:19 * configuration change into an unplanned bulk delete.
|
|
MongoTtlPolicyValidator.java:20 */
|
|
MongoTtlPolicyValidator.java:21 public static final Duration MINIMUM_SAFE_RETENTION = Duration.ofMinutes(1);
|
|
MongoTtlPolicyValidator.java:22
|
|
MongoTtlPolicyValidator.java:23 /**
|
|
MongoTtlPolicyValidator.java:24 * Validates one TTL policy.
|
|
MongoTtlPolicyValidator.java:25 *
|
|
MongoTtlPolicyValidator.java:26 * @throws MongoOperationRejectedException when the policy claims a guarantee TTL does not provide
|
|
MongoTtlPolicyValidator.java:27 */
|
|
MongoTtlPolicyValidator.java:28 public void validate(MongoTtlPolicy policy) {
|
|
MongoTtlPolicyValidator.java:29 Objects.requireNonNull(policy, "policy");
|
|
MongoTtlPolicyValidator.java:30 if (policy.claimsExactExpiry()) {
|
|
MongoTtlPolicyValidator.java:31 throw MongoOperationRejectedException.of(
|
|
MongoTtlPolicyValidator.java:32 "ttl.policy",
|
|
MongoTtlPolicyValidator.java:33 "the TTL policy on '"
|
|
MongoTtlPolicyValidator.java:34 + policy.field()
|
|
MongoTtlPolicyValidator.java:35 + "' claims an exact business transition; MongoDB's TTL monitor sweeps on an "
|
|
MongoTtlPolicyValidator.java:36 + "unspecified interval, so an exact transition needs its own scheduler and the TTL "
|
|
MongoTtlPolicyValidator.java:37 + "index stays physical cleanup");
|
|
MongoTtlPolicyValidator.java:38 }
|
|
MongoTtlPolicyValidator.java:39 if (!policy.queryChecksLogicalExpiry()) {
|
|
MongoTtlPolicyValidator.java:40 throw MongoOperationRejectedException.of(
|
|
MongoTtlPolicyValidator.java:41 "ttl.policy",
|
|
MongoTtlPolicyValidator.java:42 "the TTL policy on '"
|
|
MongoTtlPolicyValidator.java:43 + policy.field()
|
|
MongoTtlPolicyValidator.java:44 + "' does not declare a query-time expiry check; an expired document remains readable "
|
|
MongoTtlPolicyValidator.java:45 + "until the monitor deletes it, so reads must filter on "
|
|
MongoTtlPolicyValidator.java:46 + policy.field()
|
|
MongoTtlPolicyValidator.java:47 + " > applicationNow");
|
|
MongoTtlPolicyValidator.java:48 }
|
|
MongoTtlPolicyValidator.java:49 if (!policy.retention().isZero() && policy.retention().compareTo(MINIMUM_SAFE_RETENTION) < 0) {
|
|
MongoTtlPolicyValidator.java:50 throw MongoOperationRejectedException.of(
|
|
MongoTtlPolicyValidator.java:51 "ttl.policy",
|
|
MongoTtlPolicyValidator.java:52 "a TTL retention of "
|
|
MongoTtlPolicyValidator.java:53 + policy.retention()
|
|
MongoTtlPolicyValidator.java:54 + " is below the safe minimum of "
|
|
MongoTtlPolicyValidator.java:55 + MINIMUM_SAFE_RETENTION
|
|
MongoTtlPolicyValidator.java:56 + "; it would expire the existing population in a single sweep");
|
|
MongoTtlPolicyValidator.java:57 }
|
|
MongoTtlPolicyValidator.java:58 }
|
|
MongoTtlPolicyValidator.java:59
|
|
MongoTtlPolicyValidator.java:60 /**
|
|
MongoTtlPolicyValidator.java:61 * Validates a TTL index descriptor, including the BSON type of its expiry field.
|
|
MongoTtlPolicyValidator.java:62 *
|
|
MongoTtlPolicyValidator.java:63 * @throws MongoOperationRejectedException when the field type cannot be expired
|
|
MongoTtlPolicyValidator.java:64 */
|
|
MongoTtlPolicyValidator.java:65 public void validate(MongoTtlIndexDescriptor descriptor) {
|
|
MongoTtlPolicyValidator.java:66 Objects.requireNonNull(descriptor, "descriptor");
|
|
MongoTtlPolicyValidator.java:67 validate(descriptor.policy());
|
|
MongoTtlPolicyValidator.java:68 if (!descriptor.expiryFieldTypeIsSupported()) {
|
|
MongoTtlPolicyValidator.java:69 throw MongoOperationRejectedException.of(
|
|
MongoTtlPolicyValidator.java:70 "ttl.policy",
|
|
MongoTtlPolicyValidator.java:71 "TTL index '"
|
|
MongoTtlPolicyValidator.java:72 + descriptor.indexName()
|
|
MongoTtlPolicyValidator.java:73 + "' expires on a field stored as "
|
|
MongoTtlPolicyValidator.java:74 + descriptor.expiryFieldBsonType()
|
|
MongoTtlPolicyValidator.java:75 + "; MongoDB only expires BSON date fields and ignores the rest silently");
|
|
MongoTtlPolicyValidator.java:76 }
|
|
MongoTtlPolicyValidator.java:77 }
|
|
MongoTtlPolicyValidator.java:78
|
|
MongoTtlPolicyValidator.java:79 /**
|
|
MongoTtlPolicyValidator.java:80 * Validates the read-side predicate that gives expiry its actual guarantee.
|
|
MongoTtlPolicyValidator.java:81 *
|
|
MongoTtlPolicyValidator.java:82 * @throws MongoOperationRejectedException when reads do not filter on logical expiry
|
|
MongoTtlPolicyValidator.java:83 */
|
|
MongoTtlPolicyValidator.java:84 public void validate(MongoExpirationAccessPolicy accessPolicy) {
|
|
MongoTtlPolicyValidator.java:85 Objects.requireNonNull(accessPolicy, "accessPolicy");
|
|
MongoTtlPolicyValidator.java:86 if (!accessPolicy.applied()) {
|
|
MongoTtlPolicyValidator.java:87 throw MongoOperationRejectedException.of(
|
|
MongoTtlPolicyValidator.java:88 "ttl.access",
|
|
MongoTtlPolicyValidator.java:89 "reads of this collection do not apply '"
|
|
MongoTtlPolicyValidator.java:90 + accessPolicy.describePredicate()
|
|
MongoTtlPolicyValidator.java:91 + "', so an expired document stays visible until the TTL monitor happens to delete it");
|
|
MongoTtlPolicyValidator.java:92 }
|
|
MongoTtlPolicyValidator.java:93 }
|
|
MongoTtlPolicyValidator.java:94 }
|
|
|
|
# 그 정책 타입이 요구하는 것
|
|
MongoTtlPolicy.java:1 package dev.caskeleton.adapter.outbound.mongo.schema.ttl;
|
|
MongoTtlPolicy.java:2
|
|
MongoTtlPolicy.java:3 import java.time.Duration;
|
|
MongoTtlPolicy.java:4 import java.util.Objects;
|
|
MongoTtlPolicy.java:5
|
|
MongoTtlPolicy.java:6 /**
|
|
MongoTtlPolicy.java:7 * What a TTL index is being used for (design §21.1, D-13).
|
|
MongoTtlPolicy.java:8 *
|
|
MongoTtlPolicy.java:9 * <p>MongoDB's TTL monitor runs roughly once a minute and deletes in batches, so an expired
|
|
MongoTtlPolicy.java:10 * document stays readable for an unspecified interval after its expiry — longer under load, longer
|
|
MongoTtlPolicy.java:11 * still on a busy secondary. That is fine for reclaiming space and wrong for anything that must
|
|
MongoTtlPolicy.java:12 * stop being visible at a particular moment.
|
|
MongoTtlPolicy.java:13 *
|
|
MongoTtlPolicy.java:14 * <p>Both booleans are therefore part of the declaration. {@code physicalCleanupOnly} says the TTL
|
|
MongoTtlPolicy.java:15 * is only reclaiming space; {@code queryChecksLogicalExpiry} says the application filters on {@code
|
|
MongoTtlPolicy.java:16 * expiresAt > now} so visibility does not depend on the monitor's timing.
|
|
MongoTtlPolicy.java:17 */
|
|
MongoTtlPolicy.java:18 public record MongoTtlPolicy(
|
|
MongoTtlPolicy.java:19 String field,
|
|
MongoTtlPolicy.java:20 Duration retention,
|
|
MongoTtlPolicy.java:21 boolean physicalCleanupOnly,
|
|
MongoTtlPolicy.java:22 boolean queryChecksLogicalExpiry) {
|
|
MongoTtlPolicy.java:23
|
|
MongoTtlPolicy.java:24 public MongoTtlPolicy {
|
|
MongoTtlPolicy.java:25 Objects.requireNonNull(field, "field");
|
|
MongoTtlPolicy.java:26 Objects.requireNonNull(retention, "retention");
|
|
MongoTtlPolicy.java:27 if (field.isBlank()) {
|
|
MongoTtlPolicy.java:28 throw new IllegalArgumentException("a TTL policy needs an expiry field");
|
|
MongoTtlPolicy.java:29 }
|
|
MongoTtlPolicy.java:30 if (retention.isNegative()) {
|
|
MongoTtlPolicy.java:31 throw new IllegalArgumentException("a TTL retention must not be negative");
|
|
MongoTtlPolicy.java:32 }
|
|
MongoTtlPolicy.java:33 }
|
|
MongoTtlPolicy.java:34
|
|
MongoTtlPolicy.java:35 /** The supported shape: physical cleanup, with the application filtering on logical expiry. */
|
|
MongoTtlPolicy.java:36 public static MongoTtlPolicy physicalCleanup(String field, Duration retention) {
|
|
MongoTtlPolicy.java:37 return new MongoTtlPolicy(field, retention, true, true);
|
|
MongoTtlPolicy.java:38 }
|
|
MongoTtlPolicy.java:39
|
|
MongoTtlPolicy.java:40 /**
|
|
|
|
# 두 표현 사이에 참조가 있는가
|
|
schema/ttl 밖 main 에서 그 네 타입을 언급하는 줄 : 0 개
|
|
[대조] schema/ttl 안에서는 : 22 개
|
|
MongoIndexManifest 가 그 네 타입을 언급하는 줄 : 0 개
|
|
isTtlIndex() 나 ttl() 을 부르는 줄 : 0 개
|
|
[대조] 그 두 이름이 나오는 줄 (선언 포함) : 2 개
|
|
|
|
# 두 표현을 조립하는 main 코드가 있는가
|
|
MongoIndexManifest 를 만드는 main 줄 : 1 개
|
|
MongoIndexManifest.java:226 return new MongoIndexManifest(
|
|
[대조] 시험에서 만드는 줄 : 7 개
|
|
MongoTtlPolicyValidator 를 만드는 main 줄 : 0 개
|
|
[대조] 시험에서 검증기를 만드는 줄 : 1 개
|