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
+62
@@ -0,0 +1,62 @@
|
||||
---
|
||||
kind: REFERENCE
|
||||
slug: cas-tuple-in-the-where-clause
|
||||
title: CAS 튜플을 where 절에 전부 반복하고 update count를 답으로 쓴다
|
||||
topic: owner-safe-state-machines
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: reference:cas-tuple-in-the-where-clause
|
||||
verifiedOn: # 이 기록은 이번 회차에 실행 확인을 하지 않았다
|
||||
---
|
||||
|
||||
# CAS 튜플을 where 절에 전부 반복하고 update count를 답으로 쓴다
|
||||
|
||||
## 목적
|
||||
|
||||
행을 읽고 나서 갱신하는 형태를 없애, 리스가 만료된 작업자가 인계받은 작업자의 상태를 덮는 것을 막는다.
|
||||
|
||||
## 규칙
|
||||
|
||||
1. 소유자 튜플 전체를 where 절에 반복한다
|
||||
스코프만으로 갱신하지 않는다. 소유자와 토큰과 시도와 상태 리비전을 전부 조건에 넣는다.
|
||||
|
||||
2. 갱신 건수가 답이다
|
||||
한 건이면 이 소유자가 이 리비전에서 여전히 소유자였다는 뜻이고, 0 이면 다른 무언가가 레코드를 움직였다는 뜻이다.
|
||||
|
||||
3. 0 건은 예외가 아니라 정상 경로다
|
||||
경합을 예외 처리로 다루면 그 경로가 테스트되지 않는다. 건수를 값으로 받아 호출자가 해석한다.
|
||||
|
||||
4. 전이마다 상태 리비전을 올린다
|
||||
소유권만으로는 부족하다. 자기가 본 시점까지 맞아야 한다.
|
||||
|
||||
5. 같은 가드를 쓰는 문장을 한 자리에 모은다
|
||||
흩어져 있으면 그중 하나가 조건을 짧게 쓰는 것을 막을 수 없다.
|
||||
|
||||
## 적용 조건
|
||||
|
||||
여러 작업자가 같은 행을 놓고 경합하는 모든 상태 기계
|
||||
|
||||
리스나 청구로 소유권을 표현하는 테이블
|
||||
|
||||
## 예외
|
||||
|
||||
단일 작업자만 접근하는 것이 구조적으로 보장되는 테이블은 대상이 아니다. 그 보장이 무엇인지 적혀 있어야 한다.
|
||||
|
||||
## 예시
|
||||
|
||||
멱등성 전이 문장들이 스코프와 토큰과 시도와 청구 연산과 상태 리비전을 전부 조건에 반복한다.
|
||||
|
||||
outbox 폴링 전달 어댑터의 완료 문장 셋이 같은 형태다.
|
||||
|
||||
네이티브 청구 문장이 JPA 버전 컬럼을 함께 올린다. 그러지 않으면 청구 이전에 로드된 엔티티의 플러시가 청구를 덮는다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **CAS 튜플과 update count가 답이 되는 구조**
|
||||
이 규칙이 나온 개념이다.
|
||||
- **lease가 만료 시각만 기록하고 소유자를 기록하지 않아 terminal state가 되돌려졌다**
|
||||
이 규칙이 없을 때의 결과다.
|
||||
- **native claim이 Version을 올리지 않아 충돌을 보고하지 않는 낙관적 잠금이 됐다**
|
||||
튜플을 반복해도 다른 잠금 장치와 어긋날 수 있다는 사례다.
|
||||
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
---
|
||||
kind: REFERENCE
|
||||
slug: digest-must-be-length-framed-and-versioned
|
||||
title: digest는 길이 프레이밍하고 버전을 붙인다
|
||||
topic: owner-safe-state-machines
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: reference:digest-must-be-length-framed-and-versioned
|
||||
verifiedOn: # 이 기록은 이번 회차에 실행 확인을 하지 않았다
|
||||
---
|
||||
|
||||
# digest는 길이 프레이밍하고 버전을 붙인다
|
||||
|
||||
## 목적
|
||||
|
||||
다이제스트가 서로 다른 입력에 대해 같은 값을 내거나, 구성이 바뀐 뒤 옛 값과 비교되는 것을 막는다.
|
||||
|
||||
## 규칙
|
||||
|
||||
1. 무엇을 덮는지가 정책이다
|
||||
다이제스트가 빠뜨린 입력은 두 개의 다른 대상이 같은 값을 낼 수 있는 입력이다. 그 목록은 구현 세부가 아니라 정책이므로 자기 타입을 갖는다.
|
||||
|
||||
2. 결과를 덮는다
|
||||
누가 언제 했는지만 덮으면 무엇을 했는지가 다른 두 전이가 같아진다.
|
||||
|
||||
3. 길이 프레이밍한다
|
||||
구성 요소가 가변 길이 텍스트이고 그중 하나라도 이 플랫폼이 제약할 수 없는 값이면, 구분자로 이었을 때 서로 다른 목록이 한 문자열로 렌더링될 수 있다.
|
||||
|
||||
4. 버전을 붙이고 구성이 바뀌면 올린다
|
||||
저장된 다이제스트가 구성 경계를 넘어 비교되지 않게 한다.
|
||||
|
||||
5. 비교 실패를 조용히 처리하지 않는다
|
||||
버전이 다르면 같다고도 다르다고도 결론 내리지 않는다.
|
||||
|
||||
## 적용 조건
|
||||
|
||||
재생 판정이나 중복 판정에 쓰이는 모든 다이제스트
|
||||
|
||||
멱등성 키와 요청 지문
|
||||
|
||||
## 예외
|
||||
|
||||
캐시 키처럼 충돌이 성능 문제일 뿐 정확성 문제가 아닌 경우는 이 규칙이 과하다.
|
||||
|
||||
## 예시
|
||||
|
||||
전이 다이제스트가 전이 종류와 연산과 소유자와 시도와 리비전만 덮어, 재시도 가능한 실패와 포기한 실패가 같은 값을 냈다. 서로 다른 응답을 담은 두 완료도 마찬가지였다.
|
||||
|
||||
소유자 토큰은 이 플랫폼이 형식을 제약하는 값이 아니므로 길이 프레이밍이 필요하다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **transition digest가 누가와 언제만 덮고 무엇을 덮지 않아 다른 전이를 같다고 보고했다**
|
||||
이 규칙을 만든 사례다.
|
||||
- **서명된 커서의 구조와 검증 순서**
|
||||
같은 계열의 형식 결정을 다룬다.
|
||||
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
---
|
||||
kind: REFERENCE
|
||||
slug: expired-claim-and-expired-execution-differ
|
||||
title: 만료된 claim과 만료된 실행은 다르게 다뤄야 한다
|
||||
topic: owner-safe-state-machines
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: reference:expired-claim-and-expired-execution-differ
|
||||
verifiedOn: # 이 기록은 이번 회차에 실행 확인을 하지 않았다
|
||||
---
|
||||
|
||||
# 만료된 claim과 만료된 실행은 다르게 다뤄야 한다
|
||||
|
||||
## 목적
|
||||
|
||||
리스 만료를 한 가지로 처리해, 실행을 시작했던 소유자의 작업을 두 번 수행하는 것을 막는다.
|
||||
|
||||
## 규칙
|
||||
|
||||
1. 만료된 청구는 인계한다
|
||||
자리를 잡았지만 아직 아무것도 실행하지 않은 소유자를 밀어내도 외부 효과가 없다.
|
||||
|
||||
2. 만료된 실행은 조정으로 넘긴다
|
||||
그 소유자가 무엇을 어디까지 했는지 알 수 없다. 다시 실행하면 그 작업이 두 번 일어날 수 있다.
|
||||
|
||||
3. 조정 결과는 별도 값이어야 한다
|
||||
성공이나 실패로 접으면 그 구별이 사라진다. 결과 타입에 세 번째 변형이 필요하다.
|
||||
|
||||
4. 해제도 같은 구별을 따른다
|
||||
실행이 시작된 청구는 해제할 수 없다. 해제는 아직 실행하지 않은 청구에만 허용한다.
|
||||
|
||||
5. 재시도 가능 실패는 인계 대상이다
|
||||
그 상태는 이미 결과가 확정된 것이므로 새 소유자가 처음부터 시작해도 된다.
|
||||
|
||||
## 적용 조건
|
||||
|
||||
청구와 실행을 별도 상태로 갖는 모든 상태 기계
|
||||
|
||||
멱등성 저장소와 인박스와 아웃박스
|
||||
|
||||
## 예외
|
||||
|
||||
실행이 외부 효과를 남기지 않는 것이 구조적으로 보장되면 두 상태를 같이 다뤄도 된다. 그 보장을 적어 둔다.
|
||||
|
||||
## 예시
|
||||
|
||||
청구 결정 트리가 만료된 청구는 재설정하고 만료된 실행은 복구 필요로 답한다. 복구 필요 결과는 시도 번호를 함께 들고 간다.
|
||||
|
||||
해제 경로는 이미 실행이 시작된 경우 실행 시작됨으로 답하고 해제하지 않는다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **만료된 CLAIMED는 takeover하고 만료된 EXECUTING은 조정을 요구하도록 갈랐다**
|
||||
이 규칙을 만든 사례다.
|
||||
- **모르는 것은 성공도 실패도 아닌 세 번째 결과여야 한다**
|
||||
세 번째 규칙이 기대는 상위 규칙이다.
|
||||
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
---
|
||||
kind: REFERENCE
|
||||
slug: read-the-clock-after-the-lock
|
||||
title: 시간은 DB에서, 그리고 행을 잠근 다음에 읽는다
|
||||
topic: owner-safe-state-machines
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: reference:read-the-clock-after-the-lock
|
||||
verifiedOn: # 이 기록은 이번 회차에 실행 확인을 하지 않았다
|
||||
---
|
||||
|
||||
# 시간은 DB에서, 그리고 행을 잠근 다음에 읽는다
|
||||
|
||||
## 목적
|
||||
|
||||
애플리케이션 시계로 리스 만료를 판단하거나 잠그기 전의 시각으로 판단해, 서로 다른 노드가 같은 행에 대해 다른 답을 내는 것을 막는다.
|
||||
|
||||
## 규칙
|
||||
|
||||
1. 시각은 데이터베이스에서 읽는다
|
||||
여러 노드의 시계는 서로 다르다. 리스 만료 판정의 기준 시각이 노드마다 다르면 두 노드가 동시에 소유자가 될 수 있다.
|
||||
|
||||
2. 행을 잠근 다음에 읽는다
|
||||
잠그기 전의 시각으로 판단하면 잠금을 기다리는 동안 리스가 만료될 수 있다.
|
||||
|
||||
3. 판정과 갱신을 한 문장 안에 둔다
|
||||
시각 비교를 where 절에 넣으면 판정과 갱신 사이에 시간이 흐르지 않는다.
|
||||
|
||||
4. 만료 시각을 계산할 때도 같은 시계를 쓴다
|
||||
읽은 시각과 쓰는 시각의 출처가 다르면 리스 길이가 의도와 달라진다.
|
||||
|
||||
## 적용 조건
|
||||
|
||||
리스와 청구와 예약처럼 시각이 소유권을 정하는 모든 상태 기계
|
||||
|
||||
여러 인스턴스가 같은 테이블을 폴링하는 구조
|
||||
|
||||
## 예외
|
||||
|
||||
단일 인스턴스만 접근하고 그 보장이 구조적인 경우는 애플리케이션 시계로 충분하다. 그 보장을 적어 둔다.
|
||||
|
||||
## 예시
|
||||
|
||||
청구 결정 트리가 데이터베이스에서 읽은 현재 시각으로 리스 만료를 판정한다.
|
||||
|
||||
리스를 발행 타임아웃보다 길게 두는 것은 확률을 낮출 뿐이고, GC 정지나 스케줄러 지연을 데이터 제약으로 바꾸지 않는다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **CAS 튜플을 where 절에 전부 반복하고 update count를 답으로 쓴다**
|
||||
같은 문장 안에서 함께 쓰이는 규칙이다.
|
||||
- **fenced lease — 만료 시각만으로는 부족한 이유**
|
||||
시각만으로 부족한 이유를 다룬 개념이다.
|
||||
|
||||
Reference in New Issue
Block a user