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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
43bccd08a8
commit
b2963105a8
+73
@@ -0,0 +1,73 @@
|
||||
# V2 가 더한 컬럼과 제약
|
||||
ALTER TABLE messaging_outbox
|
||||
ADD COLUMN lease_owner VARCHAR(160),
|
||||
ADD COLUMN lease_token BIGINT NOT NULL DEFAULT 0,
|
||||
ADD COLUMN next_attempt_at TIMESTAMPTZ;
|
||||
|
||||
-- Backfill is unnecessary for correctness — the default is 0 and the first claim increments it —
|
||||
-- but the constraint states the invariant the code depends on.
|
||||
ALTER TABLE messaging_outbox
|
||||
ADD CONSTRAINT ck_messaging_outbox_lease_token CHECK (lease_token >= 0);
|
||||
|
||||
# 다섯 상태에서 여섯으로
|
||||
23: CHECK (status IN ('PENDING', 'IN_FLIGHT', 'PUBLISHED', 'AMBIGUOUS', 'FAILED')),
|
||||
CHECK (status IN ('PENDING', 'IN_FLIGHT', 'PUBLISHED', 'AMBIGUOUS', 'FAILED', 'EXHAUSTED'));
|
||||
|
||||
# 청구가 소유자와 토큰을 같은 문장에서 쓴다
|
||||
UPDATE messaging_outbox o
|
||||
SET status = 'IN_FLIGHT',
|
||||
lease_expires_at = ?,
|
||||
lease_owner = ?,
|
||||
lease_token = o.lease_token + 1
|
||||
# 그 청구가 읽는 술어
|
||||
WHERE status IN ('PENDING', 'AMBIGUOUS', 'IN_FLIGHT')
|
||||
AND (lease_expires_at IS NULL OR lease_expires_at <= ?)
|
||||
-- The retry clock lives in the row, not in the relay's memory. Without these two
|
||||
-- predicates an AMBIGUOUS row became claimable again on the very next pass, so a
|
||||
-- broker outage meant the whole backlog was republished every poll interval and the
|
||||
-- configured attempt budget was a number nothing consulted.
|
||||
AND (next_attempt_at IS NULL OR next_attempt_at <= ?)
|
||||
AND attempts < ?
|
||||
# V2 가 그 술어에 맞춰 만든 부분 인덱스
|
||||
CREATE INDEX ix_messaging_outbox_next_attempt
|
||||
ON messaging_outbox (next_attempt_at, created_at)
|
||||
WHERE status IN ('PENDING', 'AMBIGUOUS', 'IN_FLIGHT');
|
||||
|
||||
# 최종 쓰기는 소유자와 토큰을 조건으로 걸고, 0행을 삼키지 않는다
|
||||
354: + "WHERE message_id = ? AND status = 'IN_FLIGHT' AND lease_owner = ? AND lease_token = ?";
|
||||
403: + "WHERE message_id = ? AND status = 'IN_FLIGHT' AND lease_owner = ? AND lease_token = ?";
|
||||
* past its lease writes nothing: another relay's claim incremented the token, and this update
|
||||
* matches zero rows. Zero is reported rather than swallowed — a stale write means this worker may
|
||||
* have produced a duplicate publication, which is exactly what an operator needs to see.
|
||||
|
||||
# 릴레이가 부르는 것은 전부 신세대다
|
||||
OutboxRelay.java:158: repository.claimBatch(owner, batchSize, leaseDuration, now, scheduler.maxAttempts());
|
||||
OutboxRelay.java:171: if (repository.markPublished(lease, now) == OutboxTransitionResult.APPLIED) {
|
||||
OutboxRelay.java:189: .map(reason -> repository.markExhausted(lease, reason, now))
|
||||
OutboxRelay.java:192: repository.markAmbiguous(
|
||||
OutboxRelay.java:205: if (repository.markFailed(
|
||||
|
||||
# 펜싱 없는 옛 경로는 인터페이스에 남아 있다
|
||||
36: List<OutboxRecord> leaseBatch(int batchSize, Duration leaseDuration, Instant now);
|
||||
# 그 메서드 자신의 javadoc 은 아직 안전을 주장한다
|
||||
* <p>Leasing rather than simply selecting is what makes multiple relay instances safe: a record
|
||||
* claimed by one relay is invisible to the others until its lease expires, so the same message is
|
||||
* not published concurrently by two processes.
|
||||
# 폐기를 적은 것은 신세대 쪽 javadoc 이다
|
||||
* <p>The token is what a terminal write is checked against. {@link #leaseBatch} returns records
|
||||
* without one, so its callers cannot prove a write belongs to their claim; it remains for
|
||||
* inspection paths and is deprecated for the relay's use.
|
||||
*
|
||||
# messaging 트리 전체의 @Deprecated 매치: 0
|
||||
# .leaseBatch( 를 부르는 main 코드: 0
|
||||
|
||||
# 두 세대가 AMBIGUOUS 에서 남기는 것이 다르다
|
||||
"UPDATE messaging_outbox SET status = 'AMBIGUOUS', lease_expires_at = NULL, "
|
||||
+ "lease_owner = NULL, last_failure_code = ?, attempts = attempts + 1, "
|
||||
+ "next_attempt_at = ? ",
|
||||
---구세대
|
||||
"UPDATE messaging_outbox SET status = 'AMBIGUOUS', lease_expires_at = NULL, "
|
||||
+ "last_failure_code = ?, attempts = attempts + 1 WHERE message_id = ?",
|
||||
|
||||
# 같은 형태가 다른 곳에도 있었다고 파일서버 마이그레이션이 적는다
|
||||
12:-- part-way through — the same fenced-lease problem the notification dispatcher had.
|
||||
Reference in New Issue
Block a user