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>
70 lines
3.3 KiB
Plaintext
70 lines
3.3 KiB
Plaintext
# 조립된 경로에서 갱신과 원장 기록 사이에 있는 것
|
|
// Still refreshed after execute, but the refresh is no longer what protects the run: the
|
|
// heartbeat inside the context is. This one only proves the lease survived to the end, before
|
|
// anything is written to the ledger under it.
|
|
lock.refresh(template.maxTime());
|
|
if (!migration.postcondition().isSatisfied(context, result)) {
|
|
throw MongoOperationRejectedException.of(
|
|
"migration.postcondition",
|
|
"migration "
|
|
+ migration.id()
|
|
...
|
|
result.resumePoint().ifPresent(checkpoint -> ledger.saveCheckpoint(checkpoint, lock.fence()));
|
|
if (result.status() == MongoMigrationResult.Status.COMPLETED && !context.dryRun()) {
|
|
ledger.recordApplied(
|
|
migration.id(), migration.checksum(), context.operator(), clock.instant(), lock.fence());
|
|
}
|
|
|
|
# 완료를 만드는 팩토리는 체크포인트를 남기지 않는다
|
|
/** The migration finished. */
|
|
public static MongoMigrationResult completed(long processedCount) {
|
|
return new MongoMigrationResult(Status.COMPLETED, processedCount, null, "");
|
|
}
|
|
|
|
/** The migration made progress and stored a checkpoint to resume from. */
|
|
public static MongoMigrationResult incomplete(
|
|
long processedCount, MongoMigrationCheckpoint checkpoint) {
|
|
return new MongoMigrationResult(
|
|
Status.INCOMPLETE, processedCount, Objects.requireNonNull(checkpoint, "checkpoint"), "");
|
|
}
|
|
|
|
# 그 갱신이 던지는 것
|
|
if (stillOwned != 1) {
|
|
held = false;
|
|
throw new IllegalStateException(
|
|
"the migration lease was lost before it could be refreshed; another runner may have "
|
|
+ "taken it after this one appeared to stall");
|
|
}
|
|
# 같은 인터페이스의 다른 잠금 구현이 같은 상황에 던지는 것
|
|
public void refresh(Duration extension) {
|
|
Objects.requireNonNull(extension, "extension");
|
|
if (!held.get()) {
|
|
throw MongoOperationRejectedException.of(
|
|
"migration.lock", "the migration lease has been released and cannot be extended");
|
|
}
|
|
extend.accept(extension);
|
|
|
|
# 다른 원장 구현은 펜스 인자를 쓰지 않는다
|
|
long fence) {
|
|
recorded.put(
|
|
Objects.requireNonNull(migrationId, "migrationId"),
|
|
new AppliedMigration(migrationId, checksum, operator, appliedAt));
|
|
}
|
|
|
|
# recordApplied 를 부르는 시험 전수
|
|
MongoMigrationRunnerTest.java:23: ledger.recordApplied(
|
|
MongoMigrationRunnerTest.java:40: ledger.recordApplied(
|
|
MongoMigrationLaneTest.java:130: ledger.recordApplied(
|
|
MongoMigrationLaneTest.java:227: ledger.recordApplied(id, new MongoMigrationChecksum("abc"), "operator", FIXED.instant(), 1L);
|
|
MongoMigrationLaneTest.java:231: ledger.recordApplied(
|
|
# 그중 실서버에서 두 번 부르는 시험이 단언하는 것
|
|
() ->
|
|
ledger.recordApplied(
|
|
id, new MongoMigrationChecksum("abc"), "operator", FIXED.instant(), 1L))
|
|
.as("the unique index, not the read-then-write check, is what makes this impossible")
|
|
.isInstanceOf(RuntimeException.class);
|
|
|
|
# 고유 색인을 만드는 메서드를 부르는 곳 전수
|
|
test/java/dev/caskeleton/adapter/outbound/mongo/migration/MongoMigrationLaneTest.java
|
|
main/java/dev/caskeleton/adapter/outbound/mongo/migration/MongoCollectionMigrationLedger.java
|