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
+125
@@ -0,0 +1,125 @@
|
||||
---
|
||||
kind: CASE
|
||||
slug: a-build-gate-that-is-not-in-the-build
|
||||
title: '"build gate"라 불리는 catalog drift 검사가 어디에서도 실행되지 않는다'
|
||||
topic: redis-command-admission
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: case:a-build-gate-that-is-not-in-the-build
|
||||
evidenceCapturedOn: 2026-09-02
|
||||
assets:
|
||||
- key: a-build-gate-that-is-not-in-the-build
|
||||
file: ../../../final/evidence/rendered/a-build-gate-that-is-not-in-the-build.svg
|
||||
evidence:
|
||||
- ../../../final/evidence/raw/a-build-gate-that-is-not-in-the-build.txt
|
||||
source:
|
||||
- 원본 분석 절은 analysis/10 §47 · final/document.md#4-4 이다. 참조 수는 §8.1 의 원문 측정과 같다.
|
||||
- Gradle 태스크와 CI 단계가 없다는 것은 이 기록에 붙은 자산에 있다. 토폴로지 레인의 두 설정은 analysis/10 §0 과 evidence/raw/158-cache-redis-module-inventory.txt 에, 레인의 결합 상태는 `cache-redis/build.gradle` 과 `.github/ci-gate-matrix.yml` 에 있다.
|
||||
module: adapter-outbound-cache-redis
|
||||
priority: P2
|
||||
---
|
||||
|
||||
# "build gate"라 불리는 catalog drift 검사가 어디에서도 실행되지 않는다
|
||||
|
||||
명령 메타데이터 드리프트를 검사하는 코드가 있고 정책 파일 머리 주석이 그것을 빌드 게이트라고 부른다. 그 검사를 실행하는 Gradle 태스크도 CI 단계도 없다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **산문이 선언한 게이트는 빌드에 있는 게이트가 아니다**
|
||||
이 사례가 그 규칙을 만든 형태다.
|
||||
- **두 파일이 같은 검증기를 "빌드를 실패시키는 것"이라 적고, 어떤 빌드도 그것을 부르지 않는다**
|
||||
같은 형태가 gRPC 가족에서 나타난 사례다.
|
||||
- **서버 메타데이터가 명령의 정의이고 정책 파일은 허용 범위다**
|
||||
이 검사가 지키려는 관계다.
|
||||
|
||||
## 문제
|
||||
|
||||
명령 카탈로그의 정의는 서버 메타데이터에서 온다. 정책 파일은 그 위에서 허용 범위를 정한다.
|
||||
|
||||
두 쪽이 어긋날 수 있다. 서버 버전이 올라가면서 명령의 키 스펙이나 플래그가 바뀌면 정책 파일이 옛 정의 위에 서 있게 된다.
|
||||
|
||||
그 드리프트를 검사하는 코드가 있다. 정책 파일 머리 주석이 그것을 빌드 게이트라고 부른다.
|
||||
|
||||
## 결론
|
||||
|
||||
빌드에 없다.
|
||||
|
||||
그 검사를 실행하는 Gradle 태스크가 등록되어 있지 않고, CI 워크플로에도 그것을 부르는 단계가 없다. 그 코드는 누군가 수동으로 부를 때만 돌고, 부르는 절차는 문서에 없다.
|
||||
|
||||
이름은 게이트인데 빌드에서 그것을 부르는 곳이 없다.
|
||||
|
||||
## 검증 환경
|
||||
|
||||
OpenJDK : 21.0.12
|
||||
Gradle : 9.0.0
|
||||
확인 방식 : 검사 코드 확인과 태스크 및 CI 단계 이름 검색
|
||||
소스 수정 : x
|
||||
|
||||
## 재현 조건
|
||||
|
||||
1. 명령 메타데이터 드리프트 검사 클래스를 확인한다.
|
||||
2. 정책 파일 머리 주석에서 그것을 부르는 이름을 확인한다.
|
||||
3. 그 검사를 실행하는 Gradle 태스크를 찾는다.
|
||||
4. CI 워크플로에서 그 검사를 부르는 단계를 찾는다.
|
||||
|
||||
1~2 단계의 원문 근거는 final/evidence/raw/163-cache-redis-guard-connection-codec-probes.txt §8.1 이다. 3~4 단계의 출력은 이 기록에 붙은 자산에 있다.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
cache-redis 어댑터의 명령 정책 파일이 머리 주석에서 이 저장소에서 가장 강한 governance 주장을 한다 — 명령이 무엇인지는 서버 메타데이터가 정하고, 이 SDK 가 그것으로 무엇을 할지는 이 파일이 정하며, **"드리프트 게이트가 둘을 비교해 서버가 이 파일이 판정하지 않은 명령을 추가하면 빌드를 실패시킨다"**. `RedisCommandMetadataDiff` 의 javadoc 도 자신을 "The build gate" 라고 부른다.
|
||||
|
||||
비교 로직은 완성돼 있다. 테스트 여섯 개가 다섯 버킷을 덮는다.
|
||||
|
||||
## 이 이름을 참조하는 파일이 둘뿐이다
|
||||
|
||||
:::evidence key="a-build-gate-that-is-not-in-the-build" alt="코드베이스에서 RedisCommandMetadataDiff 를 검색한 출력 9줄. 이 이름을 참조하는 파일이 선언 자신과 자기 단위 테스트 둘뿐이고, 이 비교를 실행하는 Gradle 태스크와 CI 단계가 없다는 것이 그 출력에 그대로 보인다." caption="RedisCommandMetadataDiff 참조 · Gradle 태스크 · CI 단계 — 9줄 · exit 0" zoom="true"
|
||||
:::
|
||||
|
||||
선언 자신과 자기 단위 테스트. 이 비교를 실행하는 Gradle 태스크가 없고, CI 워크플로에서 부르는 단계도 없다. 실제 서버 메타데이터를 이 함수에 넣는 코드가 저장소에 없다.
|
||||
|
||||
빌드를 깨는 게이트는 존재하지 않는다. 존재하는 것은 게이트가 쓸 비교 함수와 그 함수의 단위 테스트다.
|
||||
|
||||
## 다섯 버킷이 막기로 되어 있던 것
|
||||
|
||||
1. 아무도 분류하지 않은 새 명령
|
||||
2. 서버에서 사라진 명령
|
||||
3. 키 추출이 엉뚱한 인자를 가리키게 만드는 key spec 이동
|
||||
4. 계정을 조용히 넓히는 ACL 카테고리 변경
|
||||
5. 타입 있는 API 가 아직 노출하는 deprecation
|
||||
|
||||
여섯 번째 테스트는 버킷이 아니라 드리프트가 없을 때를 고정하는 음성 케이스다.
|
||||
|
||||
## 첫 버킷은 다른 장치가 대신 막는다
|
||||
|
||||
새 명령이 조용히 통과하지는 않는다. `RedisCommandCatalog.require` 가 분류되지 않은 명령을 fail-closed 로 거부하고, `theCatalogFailsClosedForAnUnclassifiedCommand` 가 그것을 고정한다. 그래서 이 결함의 데이터 위험이 즉각적이지 않다.
|
||||
|
||||
원본 분석이 위험으로 지목한 것은 3·4·5 번 셋이다. key spec 이 이동하면 이 SDK 의 네임스페이스·슬롯 검사가 잘못된 인자를 키로 본다. ACL 카테고리가 넓어지면 계정 분리 가정이 조용히 약해진다. deprecation 은 타입 있는 API 가 사라질 명령을 계속 노출하게 둔다.
|
||||
|
||||
2 번(서버에서 사라진 명령)은 어느 쪽으로도 논의되지 않았다. 카탈로그가 그것을 잡는다는 근거도, 위험 목록에 든다는 근거도 원본에 없다.
|
||||
|
||||
## 게이트라는 이름이 정책 파일의 주의 수준을 낮춘다
|
||||
|
||||
정책 파일을 읽는 사람이 그 주석을 보고 드리프트가 자동으로 잡힌다고 이해할 수 있다 — 이것은 원본 분석에 적힌 판정이 아니라 이 기록의 추론이다. 다만 주석의 문장이 조건 없는 단정이라는 것은 사실이고, 그 문장을 읽고 나서 정책 파일을 손볼 때 확인해야 할 것이 하나 줄어든다.
|
||||
|
||||
## 이 리프의 토폴로지 레인과 비교하면
|
||||
|
||||
같은 리프에 `redisTopologyTest` 레인이 있다. 그 레인은 `failOnNoDiscoveredTests = true` 와 `outputs.upToDateWhen { false }` 를 갖는다 — 발견 0 이 성공이 되지 않고, 이전 실행 결과를 다시 내놓지도 않는다.
|
||||
|
||||
다만 그 레인도 릴리스 게이트 Gradle 태스크에 묶여 있지는 않다. 기본 `test` 는 `excludeTags 'redis-topology'` 로 그것을 제외하고, 실행하는 것은 CI 워크플로 하나다. gate matrix 에서 그 항목은 `mechanism: workflow-job` 이고 `release_blocking: conditional` 이다 — 같은 매트릭스의 `httpclient-stable-contract` 가 `gradle-custom-task` 에 `release_blocking: true` 인 것과 다르다.
|
||||
|
||||
드리프트 검사에는 그 둘 중 아무것도 없다. 레인도 아니고 워크플로 항목도 아니다.
|
||||
|
||||
## 없는 것은 연결 한 줄이다
|
||||
|
||||
토폴로지 레인은 이미 실제 서버에 붙어 있고 비교 함수도 완성돼 있다. 그 레인에서 `COMMAND DOCS` 와 `COMMAND INFO` 를 읽어 `RedisCommandMetadataDiff.compare(...)` 를 돌리고, 결과가 비어 있지 않으면 실패시키면 된다.
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
지금 드리프트가 나 있는지는 직접 돌려 보지 않았다. 그 검사를 부르는 실행 경로가 없다는 것까지만 확인했다.
|
||||
|
||||
태스크와 CI 단계의 부재는 이름 기반 검색으로 판정했다. 리플렉션이나 서비스 로더처럼 이름이 문자열로만 등장하는 호출 형태는 배제하지 못했다.
|
||||
|
||||
<!-- body:end -->
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
---
|
||||
kind: CASE
|
||||
slug: a-startup-probe-that-never-runs
|
||||
title: startup probe가 production에서 한 번도 실행되지 않는다
|
||||
topic: redis-command-admission
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: case:a-startup-probe-that-never-runs
|
||||
evidenceCapturedOn: 2026-09-02
|
||||
assets:
|
||||
- key: a-startup-probe-that-never-runs
|
||||
file: ../../../final/evidence/rendered/a-startup-probe-that-never-runs.svg
|
||||
evidence:
|
||||
- ../../../final/evidence/raw/a-startup-probe-that-never-runs.txt
|
||||
source:
|
||||
- 분석 문서는 cache-redis 어댑터 편 §6 이다. 그 절이 탐침이 확인하는 넷을 나열하고, 넷째의 javadoc 을 인용하고, 두 탐침의 프로덕션 참조가 javadoc 링크 하나뿐이라는 것과 자동설정이 탐침을 부르지 않는다는 것을 기록한다. 판정은 P2 이고, 수정은 자동설정에 탐침을 실행하는 빈 하나를 더하는 것이다.
|
||||
- 같은 사고의 더 상세한 기록은 저장소 문서 쪽에 있다. 지원 매트릭스가 승격과 강등 시각을 초 단위로 적고, 운영 문서와 런북이 가드를 적용한 뒤 같은 승격에서 무엇이 달라졌는지 적는다.
|
||||
---
|
||||
|
||||
# startup probe가 production에서 한 번도 실행되지 않는다
|
||||
|
||||
기동 시 서버 능력을 확인하는 탐침이 있다. 그것을 만들거나 부르는 프로덕션 코드가 없어 한 번도 실행되지 않는다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **@Bean이 있다는 것은 조립 증거가 아니다**
|
||||
이 사례가 그 규칙의 형태다.
|
||||
- **시작 검증기가 도는지는 그 능력에 자동설정 루트가 있는지와 일치한다**
|
||||
시작 검증기가 도는지를 자동설정 루트로 확인하는 절차다.
|
||||
- **하위 시스템 전체가 미배선인데 그것을 켜는 플래그는 시작 검사를 수행한다**
|
||||
플래그와 실행의 어긋남이 반대 방향으로 나타난 사례다.
|
||||
|
||||
## 문제
|
||||
|
||||
기동 탐침은 서버가 실제로 무엇인지 묻는다. 그것이 확인하는 것은 넷이다. 서버 버전, 클러스터와 데이터베이스 번호, 명시적으로 켠 능력의 실재, 그리고 복제 배포의 쓰기 내구성이다.
|
||||
|
||||
넷째의 javadoc 이 이 저장소의 실측 사고 기록을 숫자째로 담고 있다.
|
||||
|
||||
## 결론
|
||||
|
||||
그 기동 실패는 일어나지 않는다.
|
||||
|
||||
두 탐침 클래스를 언급하는 프로덕션 코드는 다른 설정 클래스의 javadoc 링크 하나뿐이고, 확인 메서드를 부르는 프로덕션 코드는 0 이다. 자동설정이 만드는 빈 일곱에 탐침은 없다.
|
||||
|
||||
로직은 완성되어 있고 테스트 열여덟 케이스가 네 규칙을 고정한다. 없는 것은 호출 지점 하나다.
|
||||
|
||||
## 검증 환경
|
||||
|
||||
OpenJDK : 21.0.12
|
||||
확인 방식 : 정적 도달성 확인
|
||||
소스 수정 : x
|
||||
|
||||
## 재현 조건
|
||||
|
||||
1. 기동 탐침의 클래스 javadoc 과 확인 메서드 본문을 읽고, 무엇을 몇 가지 확인하는지 센다.
|
||||
2. 넷째 항목의 javadoc 에 적힌 사고 기록과 결론, 그리고 검사가 두 겹인 이유를 읽는다.
|
||||
3. 두 탐침 클래스를 언급하는 프로덕션 코드를 세고 그것이 무엇인지 확인한다.
|
||||
4. 확인 메서드를 부르는 프로덕션 코드를 센다.
|
||||
5. 자동설정이 만드는 빈을 나열하고 그중 탐침이 있는지 센다.
|
||||
6. 두 테스트 파일의 케이스 수를 센다.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
기동 탐침은 서버가 실제로 무엇인지 한 번, 기동 시에 묻는다.
|
||||
|
||||
이 탐침이 무엇을 막는지부터 본다. 그것이 실행되지 않는다는 판정이 무엇을 잃는 것인지가 거기서 정해진다.
|
||||
|
||||
## 확인하는 것은 넷이다
|
||||
|
||||
:::evidence key="a-startup-probe-that-never-runs" alt="코드베이스에서 기동 탐침이 자기를 소개하는 문단과 확인 메서드 본문이 부르는 두 검사, 넷째 항목의 javadoc 이 담은 실측과 결론과 검사가 두 겹인 이유, 두 탐침을 언급하는 프로덕션 코드 하나와 그것이 javadoc 링크라는 것과 확인 메서드를 부르는 코드 수, 자동설정이 만드는 빈 일곱과 그중 탐침의 수, 그리고 로직을 고정하는 테스트 케이스 수를 뽑은 출력 70줄. 탐침을 부르는 프로덕션 코드가 0 이고 자동설정의 빈 일곱에 탐침이 없다는 것이 그 출력에 보인다." caption="탐침의 소개 문단과 두 검사 · 넷째의 실측과 두 겹 검사 · 언급 1(javadoc 링크) · 호출 0 · 빈 일곱에 탐침 없음 · 테스트 18케이스 — 70줄" zoom="true"
|
||||
:::
|
||||
|
||||
확인 메서드가 두 호출로 넷을 덮는다. 앞의 호출이 서버 버전과 배포 모드와 데이터베이스 번호와 명령 목록으로 능력을 판정하고, 뒤의 호출이 복제 배포의 쓰기 내구성을 요구한다.
|
||||
|
||||
클래스 javadoc 이 왜 서버에 묻는지 적는다. 설정은 배포가 무엇을 의도하는지 말하고 서버만이 무엇이 참인지 말한다는 것, 관리형 Redis 가 광고하는 버전이 모듈의 존재를 뜻하지 않는다는 것, 그리고 복제 배포의 쓰기 내구성은 어떤 클라이언트도 보상할 수 없는 서버 설정이라는 것이다.
|
||||
|
||||
같은 문단이 시점의 값도 적는다. 빠진 능력을 첫 요청에서 발견하면 장애이고, 여기서 발견하면 실패한 배포다.
|
||||
|
||||
## 넷째 항목의 javadoc 은 실측을 숫자째로 담는다
|
||||
|
||||
그 javadoc 은 이것이 SDK 가 보상할 수 없는 유일한 서버 설정이라고 적는다.
|
||||
|
||||
승격으로 밀려난 주 노드는 그 사실을 즉시 알지 못한다. 아는 순간까지 쓰기에 계속 성공을 답하고, 새 주 노드에서 재동기화할 때 그 쓰기들이 버려진다.
|
||||
|
||||
센티널 레인이 그것을 쟀다. 11초 동안 2,086건이 승인된 뒤 폐기됐고, 같은 실행에서 호출자에게 실패로 보인 명령은 통틀어 하나뿐이었다.
|
||||
|
||||
그 아래 문장이 왜 아무도 못 보는지 적는다. 서버가 답했으므로 드라이버도, 이 SDK 도, 호출자도 전부 성공으로 기록한다. 더할 지표도, 재시도할 실패도, 그것을 서술할 확신 값도 없다.
|
||||
|
||||
## 그래서 경고가 아니라 기동 실패다
|
||||
|
||||
복제 최소 개수와 상한이 걸린 최대 지연이 그 상황을 호출자가 대응할 수 있는 거절로 바꾼다. 같은 승격이 그때는 2,086건 대신 한 건을 잃었다.
|
||||
|
||||
그래서 그 설정 없는 복제 배포는 경고가 아니라 기동 실패라고 적는다. 보증을 무의미하게 만드는 설정은 조용히 성능을 낮추는 대신 컨텍스트를 멈춘다는 것이 이 SDK 의 원칙이라는 것이다.
|
||||
|
||||
면제는 가능하다. 배포가 정말로 쓰기 하나를 잃어도 괜찮을 수 있고 이 SDK 가 서버를 소유하지 않기 때문이다. 다만 면제에는 명시적 설정이 필요해서, 그 거래가 사고 중에 발견되는 대신 기록으로 남는다.
|
||||
|
||||
## 그 검사 자체도 한 번 고쳐졌다
|
||||
|
||||
같은 javadoc 이 검사가 두 겹인 이유를 적는다.
|
||||
|
||||
복제 개수만으로는 몇 개가 연결되어 있어야 하는지만 정해진다. 얼마나 뒤처져도 되는지는 최대 지연이 정하고, Redis 는 그 값 0 을 지연 요구 없음으로 다룬다.
|
||||
|
||||
그래서 복제 둘을 요구하면서 지연 상한을 0 으로 둔 배포는, 임의로 뒤처진 복제 둘이 붙어 있기만 하면 쓰기를 받는다. 개수 요구가 없애려던 바로 그 노출이 그대로 남는다.
|
||||
|
||||
개수만 검사하던 동안 그 설정이 통과했다. 그러면서 실패 메시지는 운영자에게 지연 상한을 설정하라고 말하고 있었다.
|
||||
|
||||
## 그 기동 실패는 일어나지 않는다
|
||||
|
||||
두 탐침 클래스를 언급하는 프로덕션 코드가 하나뿐이다. 그것도 다른 설정 클래스의 javadoc 안에 있는 링크다.
|
||||
|
||||
확인 메서드를 부르는 프로덕션 코드는 0 이다.
|
||||
|
||||
자동설정은 빈 일곱을 만든다. 설정과 그 검증, 자격증명, 런타임 클라이언트와 소유자, 그리고 헬스 지표 둘이다. 그중 탐침은 0 이다.
|
||||
|
||||
## 없는 것은 호출 지점 하나다
|
||||
|
||||
로직은 완성되어 있다. 두 테스트 파일의 열여덟 케이스가 네 규칙을 고정한다.
|
||||
|
||||
탐침이 연결을 들고 있지 않은 것은 설계된 것이다. 서버 사실을 인자로 받아서, 같은 로직을 단위 테스트와 실제 레인이 함께 쓰고 어느 계정이 물을지를 호출자가 정한다.
|
||||
|
||||
그 사실 중 둘을 읽는 명령이 관리 평면이라 애플리케이션 계정은 거부된다. 그래서 조립은 관리 계정이 설정된 배포에서만 완전하다.
|
||||
|
||||
## 지금 남은 것은 장애 쪽이다
|
||||
|
||||
빠진 능력을 첫 요청에서 발견하면 장애이고 여기서 발견하면 실패한 배포다. 조립이 없으므로 남은 것은 앞쪽이다.
|
||||
|
||||
복제 내구성은 그마저도 아니다. 서버가 성공을 답하므로 장애로도 나타나지 않는다.
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
애플리케이션을 부팅해 탐침이 실행되지 않는 것을 관측하지 않았다. 정적으로 도달성만 확인했고, 위 실측값은 이 저장소의 센티널 레인이 잰 것을 javadoc 에서 인용한 것이지 이 기록을 위해 다시 잰 것이 아니다.
|
||||
|
||||
<!-- body:end -->
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
---
|
||||
kind: CASE
|
||||
slug: five-adapters-bypass-the-single-admission-point
|
||||
title: 의미 어댑터 다섯이 gateway를 직접 불러 admission 아홉 단계를 건너뛴다
|
||||
topic: redis-command-admission
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: case:five-adapters-bypass-the-single-admission-point
|
||||
evidenceCapturedOn: 2026-09-01
|
||||
body: case-five-adapters-bypass-the-single-admission-point.body.md
|
||||
assets:
|
||||
- key: five-adapters-bypass-the-single-admission-point
|
||||
file: ../../../final/evidence/rendered/five-adapters-bypass-the-single-admission-point.svg
|
||||
evidence:
|
||||
- ../../../final/evidence/raw/five-adapters-bypass-the-single-admission-point.txt
|
||||
source:
|
||||
- 원본 분석 절은 final/document.md#4-4 · analysis/10 §64 이다.
|
||||
---
|
||||
|
||||
# 의미 어댑터 다섯이 gateway를 직접 불러 admission 아홉 단계를 건너뛴다
|
||||
|
||||
명령 정책 가드는 자기를 모든 명령이 지나는 단일 승인 지점이라고 적는다. 캐시와 멱등성과 리스와 레이트리밋과 실시간 어댑터 다섯은 그 가드도 실행기도 타입 API 도 참조하지 않고 게이트웨이를 직접 부른다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **명령 카탈로그와 admission 아홉 단계**
|
||||
우회되는 대상이다.
|
||||
- **단일 admission point는 우회 경로를 세어야 성립한다**
|
||||
이 사례에서 끌어낸 규칙이다.
|
||||
- **중복 장치를 찾으면 어느 쪽이 조립됐는지 먼저 확인한다**
|
||||
같은 계열의 확인 규칙이다.
|
||||
|
||||
## 문제
|
||||
|
||||
가드의 클래스 javadoc 은 자기를 모든 명령이 지나는 단일 승인 지점이라고 선언한다.
|
||||
|
||||
그 선언이 참인지는 우회 경로를 세어야 알 수 있다.
|
||||
|
||||
## 결론
|
||||
|
||||
다섯 어댑터가 우회한다.
|
||||
|
||||
가드나 실행기나 명령 요청 타입을 참조하는 파일을 다섯 어댑터 패키지에서 찾으면 일치가 없다.
|
||||
|
||||
타입 지정 연산 API 를 참조하는 파일도 일치가 없다.
|
||||
|
||||
대신 게이트웨이를 직접 부른다. 호출 수는 이렇다.
|
||||
|
||||
캐시 리전 어댑터 6
|
||||
멱등성 저장소 어댑터 6
|
||||
분산 리스 어댑터 4
|
||||
엣지 레이트리밋 어댑터 1
|
||||
커넥션 레지스트리 어댑터 13
|
||||
|
||||
합계 30 이다.
|
||||
|
||||
임포트 목록도 같은 그림을 보여 준다. 다섯 어댑터가 SDK 에서 가져오는 것은 네임스페이스와 커넥션 소유자와 리스와 커넥션 종류와 게이트웨이 계열이다. 가드나 실행기는 없다.
|
||||
|
||||
즉 아홉 단계는 우회하지 않은 호출에만 적용되는 규칙이다. 능력 확인도 허가 출처 확인도 네임스페이스 검사도 예산도 이 다섯 경로에는 걸리지 않는다.
|
||||
|
||||
가드의 javadoc 이 틀린 것은 아니다. 그 문장은 가드를 지나는 명령에 대해 참이다. 틀린 것은 그 문장을 읽고 모든 명령이 지난다고 이해하는 것이다.
|
||||
|
||||
## 검증 환경
|
||||
|
||||
OpenJDK : 21.0.12
|
||||
확인 방식 : 다섯 어댑터 패키지에 대한 참조 검색과 호출 계수
|
||||
소스 수정 : x
|
||||
|
||||
## 재현 조건
|
||||
|
||||
원문은 final/evidence/raw/165-cache-redis-semantic-adapters-probes.txt 8.1 절에 있다.
|
||||
|
||||
1. 다섯 어댑터 패키지에서 가드와 실행기와 명령 요청 타입을 참조하는 파일을 찾는다. 일치 0 이다.
|
||||
2. 같은 패키지에서 타입 지정 연산 API 를 참조하는 파일을 찾는다. 일치 0 이다.
|
||||
3. 같은 패키지에서 게이트웨이 호출 수를 센다.
|
||||
4. 다섯 어댑터가 SDK 에서 가져오는 임포트를 집계한다.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
두 javadoc이 "The single admission point every command passes through"와 "everything routed through it has already passed `CommandPolicyGuard`"를 주장한다.
|
||||
|
||||
## CommandPolicyGuard 참조 위치
|
||||
|
||||
:::evidence key="five-adapters-bypass-the-single-admission-point" alt="코드베이스에서 CommandPolicyGuard 를 검색한 출력 26줄. 이 기록이 세는 참조가 그 출력에 그대로 보인다." caption="CommandPolicyGuard 코드베이스 검색 — 26줄 · exit 0" zoom="true"
|
||||
:::
|
||||
|
||||
## 다섯 어댑터가 gateway를 직접 부른다
|
||||
|
||||
`RedisRuntimeOwner` → `RedisLease` → `lease.gateway()`를 30곳에서 부른다. `SyncRedisCommandExecutor`·`ReactiveRedisCommandExecutor`·`CommandPolicyGuard`·`CommandRequest`를 참조하는 파일이 0이고 타입 있는 API 참조도 0이다.
|
||||
|
||||
## 아홉 단계 중 남는 것 하나
|
||||
|
||||
네임스페이스만 `CapabilityKeyspace`가 같은 `RedisNamespace`에서 키를 조립해 구성으로 유지된다.
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
우회 경로로 실제로 거부되어야 할 명령이 통과하는 것을 재현하지 않았다. 이 기록은 참조 계수에 근거한다.
|
||||
|
||||
실제 Redis에 붙여 우회 경로의 명령이 정책 없이 나가는지 관측하지 않았다
|
||||
|
||||
<!-- body:end -->
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
---
|
||||
kind: CASE
|
||||
slug: five-copies-of-noscript-recovery
|
||||
title: NOSCRIPT 복구가 다섯 벌이고 넷은 스크립트 레지스트리를 지나지 않는다
|
||||
topic: redis-command-admission
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: case:five-copies-of-noscript-recovery
|
||||
evidenceCapturedOn: 2026-09-01
|
||||
assets:
|
||||
- key: five-copies-of-noscript-recovery
|
||||
file: ../../../final/evidence/rendered/five-copies-of-noscript-recovery.svg
|
||||
evidence:
|
||||
- ../../../final/evidence/raw/five-copies-of-noscript-recovery.txt
|
||||
source:
|
||||
- 원본 분석 절은 final/document.md#4-4 · analysis/10 §56 이다.
|
||||
---
|
||||
|
||||
# NOSCRIPT 복구가 다섯 벌이고 넷은 스크립트 레지스트리를 지나지 않는다
|
||||
|
||||
서버가 스크립트를 모른다고 답할 때의 복구 경로가 다섯 곳에 있다. 그중 넷은 스크립트 레지스트리를 거치지 않고 자기 자리에서 다시 적재한다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **같은 개념의 두 어휘가 공존하면 하나를 죽은 것으로 표시한다**
|
||||
같은 계열의 중복 문제다.
|
||||
- **단일 admission point는 우회 경로를 세어야 성립한다**
|
||||
같은 리프에서 반복되는 형태다.
|
||||
- **중복 장치를 찾으면 어느 쪽이 조립됐는지 먼저 확인한다**
|
||||
다섯 벌 중 어느 것이 도는지 확인해야 하는 이유다.
|
||||
|
||||
## 문제
|
||||
|
||||
Redis 는 스크립트를 해시로 실행한다. 서버가 그 해시를 모르면 특정 오류로 답하고, 클라이언트는 스크립트를 다시 적재한 뒤 재시도해야 한다.
|
||||
|
||||
이 복구는 흔한 경로다. 서버 재시작이나 스크립트 캐시 정리 후에 반드시 일어난다.
|
||||
|
||||
## 결론
|
||||
|
||||
복구 경로가 다섯 벌이다.
|
||||
|
||||
그중 하나만 스크립트 레지스트리를 지난다. 나머지 넷은 자기 자리에서 스크립트를 다시 적재한다.
|
||||
|
||||
이 상태의 비용은 셋이다.
|
||||
|
||||
레지스트리가 어떤 스크립트가 적재되어 있는지에 대한 정본이 아니게 된다. 넷이 그것을 우회해 적재하기 때문이다.
|
||||
|
||||
스크립트 내용이 바뀔 때 다섯 곳을 함께 고쳐야 한다. 하나를 놓치면 그 경로만 옛 스크립트를 다시 적재한다.
|
||||
|
||||
복구 정책을 바꾸기 어렵다. 재시도 횟수나 백오프를 조정하려면 다섯 곳을 봐야 한다.
|
||||
|
||||
증상이 드러나지 않는 것이 이 계열의 특징이다. 다섯 벌이 각각 동작하므로 기능은 정상이다. 비용은 전부 다음에 고치는 사람에게 간다.
|
||||
|
||||
## 검증 환경
|
||||
|
||||
OpenJDK : 21.0.12
|
||||
Redis 클라이언트 : Lettuce
|
||||
확인 방식 : 복구 경로 전수 확인
|
||||
소스 수정 : x
|
||||
|
||||
## 재현 조건
|
||||
|
||||
1. 스크립트 없음 오류를 처리하는 코드를 리프 전체에서 찾는다.
|
||||
2. 각각이 스크립트를 어떻게 다시 적재하는지 확인한다.
|
||||
3. 스크립트 레지스트리를 지나는 것과 그렇지 않은 것을 구분한다.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
Redis가 `NOSCRIPT`를 반환했을 때의 복구(스크립트 재등록 후 재시도)가 다섯 벌로 구현돼 있고 그중 넷이 스크립트 레지스트리를 지나지 않는다.
|
||||
|
||||
## NOSCRIPT 복구가 구현된 다섯 곳
|
||||
|
||||
:::evidence key="five-copies-of-noscript-recovery" alt="분석 문서 final/document.md 에서 이 기록의 근거 절을 그대로 잘라낸 18줄. 코드베이스를 측정한 것이 아니라 원본 판정이 무엇을 적었는지를 보여 준다." caption="final/document.md 발췌 — 18줄" zoom="true"
|
||||
:::
|
||||
|
||||
## 레지스트리를 지나는 한 벌만 대응을 유지한다
|
||||
|
||||
레지스트리가 스크립트 SHA와 소스의 대응을 유지하므로, 나머지 넷은 재등록하는 스크립트가 레지스트리가 아는 것과 같다는 보장이 없다.
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
서버 재시작 후 다섯 경로가 각각 어떻게 동작하는지 실행으로 확인하지 않았다. 이 기록은 코드 경로 전수 확인에 근거한다.
|
||||
|
||||
실제 SCRIPT FLUSH 이후 다섯 경로의 동작 차이를 관측하지 않았다
|
||||
|
||||
<!-- body:end -->
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
---
|
||||
kind: CONCEPT
|
||||
slug: redis-admission-stages
|
||||
title: 명령 카탈로그와 admission 아홉 단계
|
||||
topic: redis-command-admission
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: concept:redis-admission-stages
|
||||
evidenceCapturedOn: 2026-09-01
|
||||
assets:
|
||||
- key: redis-admission-stages
|
||||
file: ../../../final/evidence/rendered/redis-admission-stages.svg
|
||||
- key: redis-admission-stages-diagram
|
||||
file: ../../../final/assets/diagrams/redis-admission-stages.svg
|
||||
evidence:
|
||||
- ../../../final/evidence/raw/redis-admission-stages.txt
|
||||
source:
|
||||
- 원본 분석 절은 final/document.md#4-4 · analysis/10 §64 이다.
|
||||
---
|
||||
|
||||
# 명령 카탈로그와 admission 아홉 단계
|
||||
|
||||
모든 Redis 명령이 하나의 승인 지점을 지나고, 그 지점은 정해진 순서로 검사한다. 순서의 기준은 비용이다. 명백히 거부될 명령은 무엇도 인코딩되거나 전송되기 전에 거부된다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **의미 어댑터 다섯이 gateway를 직접 불러 admission 아홉 단계를 건너뛴다**
|
||||
이 단일 지점이 실제로 단일인지에 대한 사례다.
|
||||
- **단일 admission point는 우회 경로를 세어야 성립한다**
|
||||
이 개념에서 끌어낸 규칙이다.
|
||||
- **분류되지 않은 명령은 fail-closed로 거부한다**
|
||||
카탈로그가 답하지 못하는 명령을 어떻게 다룰지 정한 결정이다.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
이 SDK가 명령 하나를 내보내기 전에 지나는 단계의 설명이다 — 카탈로그 분류(BLOCKED·R3·R4 거부) · capability/최소 버전 확인 · permit provenance 검증 · 네임스페이스 검사 · Cluster 동일 슬롯 검사 · 요청 예산 · 정책 기반 레인·타임아웃 유도 · 실패 번역 · 관측.
|
||||
|
||||
## 명령 입장의 단일 지점
|
||||
|
||||
:::evidence key="redis-admission-stages-diagram" alt="CommandPolicyGuard 에서 카탈로그를 통과하면 실행이고 미분류나 BLOCKED 이면 거절인 두 갈래가 나온다" caption="명령 입장의 단일 지점" zoom="false"
|
||||
:::
|
||||
|
||||
그 위에 얹힌 계약은 gateway가 "everything routed through it has already passed `CommandPolicyGuard`"를 전제한다는 것이다 — 그래서 정책·permit·예산·타임아웃·관측을 자기 관심사로 두지 않는다.
|
||||
|
||||
## CommandPolicyGuard 참조 위치
|
||||
|
||||
:::evidence key="redis-admission-stages" alt="코드베이스에서 CommandPolicyGuard 를 검색한 출력 26줄. 이 기록이 세는 참조가 그 출력에 그대로 보인다." caption="CommandPolicyGuard 코드베이스 검색 — 26줄 · exit 0" zoom="true"
|
||||
:::
|
||||
|
||||
## 이 구조의 바닥
|
||||
|
||||
카탈로그가 미분류 명령을 fail-closed로 거부하는 것이다.
|
||||
|
||||
:::note
|
||||
|
||||
토폴로지 레인 미실행 — 실제 서버에서 각 단계가 도는지 관측하지 않았다
|
||||
|
||||
:::
|
||||
|
||||
## 순서가 비용순이다
|
||||
|
||||
```java
|
||||
/**
|
||||
* The single admission point every command passes through.
|
||||
*
|
||||
* <p>Validation order is fixed and each step is cheaper than the one after it, so an obviously
|
||||
* inadmissible command is refused before anything is encoded or sent:
|
||||
*
|
||||
* <pre>{@code
|
||||
* capability -> risk and permit provenance -> namespace -> slot -> request budget
|
||||
* -> connection lane -> timeout/retry -> invocation -> reply budget -> translation -> telemetry
|
||||
* }</pre>
|
||||
*/
|
||||
```
|
||||
|
||||
앞쪽 단계는 메모리 안에서 끝난다. 능력 확인과 허가 출처 확인과 네임스페이스 검사가 그렇다.
|
||||
|
||||
슬롯 계산과 요청 예산이 그다음이고, 그다음이 커넥션 레인 선택이다. 실제 호출은 그 뒤에 있다.
|
||||
|
||||
이 순서가 아니면 거부될 명령이 인코딩되고 전송된 뒤에 거부된다.
|
||||
|
||||
## 허가를 제시하는 것으로 충분하지 않다
|
||||
|
||||
```java
|
||||
/**
|
||||
* <p>Presenting a permit is never sufficient. The guard verifies who issued it and which policy it
|
||||
* was issued for, so an application that implements the permit interface itself gets rejected with
|
||||
* the same message as one that presents no permit at all.
|
||||
*/
|
||||
```
|
||||
|
||||
허가 인터페이스를 애플리케이션이 직접 구현하면 허가를 만들어 낼 수 있다. 그래서 가드가 발급자와 발급 정책을 확인한다.
|
||||
|
||||
거절 메시지가 같다는 것도 설계의 일부다. 자기가 만든 허가와 허가 없음이 구별되지 않으므로 우회 시도가 정보를 얻지 못한다.
|
||||
|
||||
## 가드가 들고 있는 협력자
|
||||
|
||||
```java
|
||||
private final RedisCommandCatalog catalog;
|
||||
private final RedisPermitVerifier permitVerifier;
|
||||
private final RedisCapabilities capabilities;
|
||||
private final RedisNamespace namespace;
|
||||
private final RedisKeyRenderer keyRenderer;
|
||||
private final ToIntFunction<String> slotCalculator;
|
||||
```
|
||||
|
||||
카탈로그는 명령이 무엇인지 안다. 허가 검증기는 누가 발급했는지 안다. 능력은 이 배포가 무엇을 할 수 있는지 안다. 네임스페이스와 키 렌더러는 키가 어디에 속하는지 정한다. 슬롯 계산기는 클러스터 배치를 안다.
|
||||
|
||||
## 카탈로그의 지위
|
||||
|
||||
카탈로그는 명령의 정의를 담는다. 그 정의의 출처는 서버 메타데이터다. 정책 파일은 그 위에서 허용 범위를 정한다.
|
||||
|
||||
둘을 뒤집으면 정책 파일이 명령의 정의가 되고, 서버가 실제로 하는 일과 어긋날 수 있다.
|
||||
|
||||
:::tip
|
||||
|
||||
단일 승인 지점은 그것이 실제로 단일일 때만 의미가 있다. 우회 경로가 있으면 이 아홉 단계는 우회하지 않은 호출에만 적용되는 규칙이 된다.
|
||||
|
||||
:::
|
||||
|
||||
<!-- body:end -->
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
---
|
||||
kind: PROJECT_DECISION
|
||||
slug: unclassified-commands-are-refused
|
||||
title: 분류되지 않은 명령은 fail-closed로 거부한다
|
||||
topic: redis-command-admission
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: decision:unclassified-commands-are-refused
|
||||
decisionStatus: ADOPTED
|
||||
decidedOn: 2026-08-30
|
||||
source:
|
||||
- src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/RedisCommandCatalog.java
|
||||
- src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/CommandPolicyGuard.java
|
||||
- analysis/10-adapter-outbound-cache-redis.md
|
||||
---
|
||||
|
||||
# 분류되지 않은 명령은 fail-closed로 거부한다
|
||||
|
||||
## 결정문
|
||||
|
||||
명령 카탈로그가 분류하지 못하는 명령은 통과시키지 않고 거부한다.
|
||||
|
||||
## 판단 이유
|
||||
|
||||
승인 아홉 단계는 명령이 무엇인지 아는 것을 전제한다. 위험 등급도 키 스펙도 슬롯 계산도 카탈로그의 정의에서 나온다.
|
||||
|
||||
분류되지 않은 명령을 통과시키면 그 단계들이 적용되지 않은 채 실행된다. 위험 등급을 모르므로 허가 요구도 걸 수 없고, 키 스펙을 모르므로 네임스페이스 검사도 슬롯 계산도 할 수 없다.
|
||||
|
||||
즉 통과는 검사를 건너뛰는 것과 같다. 그리고 그 사실이 호출자에게 보이지 않는다.
|
||||
|
||||
거부는 시끄럽다. 새 명령을 쓰려면 카탈로그에 먼저 넣어야 한다. 그 마찰이 이 결정의 목적이다.
|
||||
|
||||
카탈로그의 정의가 서버 메타데이터에서 온다는 것과 함께 보면 구조가 완성된다. 서버가 아는 명령만 카탈로그에 있고, 카탈로그에 있는 명령만 실행된다.
|
||||
|
||||
## 영향
|
||||
|
||||
감수하는 것
|
||||
|
||||
새 Redis 명령을 쓰려면 카탈로그 갱신이 선행되어야 한다. 서버가 지원해도 바로 쓸 수 없다.
|
||||
|
||||
카탈로그가 뒤처지면 정상적인 명령이 거부된다. 그래서 드리프트 검사가 필요하고, 그 검사가 현재 빌드에 없다.
|
||||
|
||||
우회 경로가 있으면 이 결정이 그 경로에 적용되지 않는다. 다섯 어댑터가 게이트웨이를 직접 부르는 경로가 그렇다.
|
||||
|
||||
얻는 것
|
||||
|
||||
정책이 적용되지 않은 명령이 실행되지 않는다.
|
||||
|
||||
새 명령의 도입이 명시적 행위가 된다.
|
||||
|
||||
## 근거
|
||||
|
||||
- **서버 메타데이터가 명령의 정의이고 정책 파일은 허용 범위다**
|
||||
이 결정이 기대는 관계다.
|
||||
- **명령 카탈로그와 admission 아홉 단계**
|
||||
카탈로그가 없으면 적용될 수 없는 단계들이다.
|
||||
- **의미 어댑터 다섯이 gateway를 직접 불러 admission 아홉 단계를 건너뛴다**
|
||||
이 결정이 적용되지 않는 경로다.
|
||||
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
---
|
||||
kind: QUESTION
|
||||
slug: redis-topology-lane-not-executed
|
||||
title: Redis 토폴로지 레인이 실행되지 않아 key spec 드리프트가 확인되지 않았다
|
||||
topic: redis-command-admission
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: open-question:redis-topology-lane-not-executed
|
||||
questionStatus: OPEN
|
||||
verifiedOn: # 이 기록은 이번 회차에 실행 확인을 하지 않았다
|
||||
---
|
||||
|
||||
# Redis 토폴로지 레인이 실행되지 않아 key spec 드리프트가 확인되지 않았다
|
||||
|
||||
명령 카탈로그가 전제하는 키 스펙이 실제 서버와 맞는지는 서버에 붙어야 알 수 있다. 그 레인을 이 리비전에서 돌리지 않았다.
|
||||
|
||||
## 사실
|
||||
|
||||
명령 메타데이터 드리프트를 검사하는 코드가 있다.
|
||||
|
||||
그 검사를 실행하는 Gradle 태스크도 CI 단계도 없다.
|
||||
|
||||
이 컨테이너에 Docker 가 있고 cache-redis 의 기본 test 레인은 435 테스트가 통과했다.
|
||||
|
||||
기동 탐침은 프로덕션에서 실행되지 않는다.
|
||||
|
||||
## 가정
|
||||
|
||||
기본 test 레인이 통과했으므로 카탈로그가 맞다고 전제하고 싶어지지만, 그 레인은 서버 메타데이터와 대조하지 않는다. 카탈로그가 스스로 일관적인지만 확인한다.
|
||||
|
||||
## 미지수
|
||||
|
||||
카탈로그의 키 스펙이 대상 Redis 버전의 실제 메타데이터와 일치하는가.
|
||||
|
||||
클러스터 구성에서 슬롯 계산이 실제 배치와 맞는가.
|
||||
|
||||
드리프트 검사를 지금 돌리면 무엇이 나오는가.
|
||||
|
||||
## 제약
|
||||
|
||||
컨테이너가 필요하다.
|
||||
|
||||
버전마다 결과가 다를 수 있으므로 대상 버전을 정해야 한다.
|
||||
|
||||
애플리케이션 소스를 수정하지 않는다.
|
||||
|
||||
## 선택지
|
||||
|
||||
드리프트 검사를 수동으로 돌린다
|
||||
가장 직접적이다. 현재 드리프트 유무가 바로 나온다.
|
||||
|
||||
토폴로지 레인 전체를 돌린다
|
||||
슬롯 계산과 커넥션 레인까지 함께 확인되지만 시간이 더 든다.
|
||||
|
||||
## 다음 검증
|
||||
|
||||
Redis 컨테이너를 띄우고 명령 메타데이터를 조회해 카탈로그와 대조한다. 그 결과를 드리프트 검사의 출력으로 남긴다.
|
||||
|
||||
드리프트가 없으면 이 질문을 닫고, 그 검사를 빌드에 넣는 것을 별도 항목으로 남긴다.
|
||||
|
||||
드리프트가 있으면 그것이 새 Case 가 되고, 어느 명령의 어느 속성이 어긋났는지 기록한다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **build gate라 불리는 catalog drift 검사가 어디에서도 실행되지 않는다**
|
||||
이 질문이 확인하려는 검사다.
|
||||
- **서버 메타데이터가 명령의 정의이고 정책 파일은 허용 범위다**
|
||||
이 질문이 검증하려는 관계다.
|
||||
- **컨테이너가 필요한 특수 레인의 실제 결과를 실행으로 확인하지 않았다**
|
||||
이 질문의 상위 항목이다.
|
||||
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
---
|
||||
kind: REFERENCE
|
||||
slug: a-single-admission-point-must-count-its-bypasses
|
||||
title: 단일 admission point는 우회 경로를 세어야 성립한다
|
||||
topic: redis-command-admission
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: reference:a-single-admission-point-must-count-its-bypasses
|
||||
verifiedOn: # 이 기록은 이번 회차에 실행 확인을 하지 않았다
|
||||
---
|
||||
|
||||
# 단일 admission point는 우회 경로를 세어야 성립한다
|
||||
|
||||
## 목적
|
||||
|
||||
단일 승인 지점이라는 선언을 그 지점이 실제로 유일하다는 증거로 읽는 것을 막는다.
|
||||
|
||||
## 규칙
|
||||
|
||||
1. 선언은 두 가지를 함께 주장한다
|
||||
지나는 것이 전부 검사된다는 것과 모든 것이 지난다는 것이다. 코드가 보장하는 것은 대개 첫 번째뿐이다.
|
||||
|
||||
2. 하위 계층 타입을 직접 참조하는 곳을 센다
|
||||
승인 지점이 감싸고 있는 타입을 상위 코드가 직접 부르면 그것이 우회다.
|
||||
|
||||
3. 임포트 목록이 빠른 지표다
|
||||
어떤 패키지에서 무엇을 가져오는지 집계하면 우회 여부가 드러난다.
|
||||
|
||||
4. 우회가 있으면 선언을 좁히거나 경로를 막는다
|
||||
둘 중 하나를 하지 않으면 다음 사람이 같은 오해를 한다.
|
||||
|
||||
5. 컴파일 시점에 막을 수 있으면 그렇게 한다
|
||||
하위 타입을 패키지 밖에서 볼 수 없게 하면 우회 경로가 생기지 않는다.
|
||||
|
||||
## 적용 조건
|
||||
|
||||
단일 진입점이나 단일 승인 지점을 표방하는 모든 계층
|
||||
|
||||
정책과 실행이 분리된 구조
|
||||
|
||||
## 예외
|
||||
|
||||
성능이나 특수 목적으로 의도적으로 우회를 허용하는 경로가 있을 수 있다. 그 경우 어떤 검사가 생략되는지가 그 자리에 적혀 있어야 한다.
|
||||
|
||||
## 예시
|
||||
|
||||
명령 정책 가드가 자기를 모든 명령이 지나는 단일 승인 지점이라고 적는다. 의미 어댑터 다섯이 가드도 실행기도 타입 API 도 참조하지 않고 게이트웨이를 30 회 직접 부른다.
|
||||
|
||||
허가 출처 확인이 그 우회로 함께 건너뛰어진다. 가드는 애플리케이션이 허가 인터페이스를 직접 구현하는 경우까지 막도록 설계되어 있다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **의미 어댑터 다섯이 gateway를 직접 불러 admission 아홉 단계를 건너뛴다**
|
||||
이 규칙을 만든 사례다.
|
||||
- **명령 카탈로그와 admission 아홉 단계**
|
||||
우회되는 대상이다.
|
||||
- **중복 장치를 찾으면 어느 쪽이 조립됐는지 먼저 확인한다**
|
||||
같은 계열의 확인 규칙이다.
|
||||
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
---
|
||||
kind: REFERENCE
|
||||
slug: server-metadata-defines-the-command
|
||||
title: 서버 메타데이터가 명령의 정의이고 정책 파일은 허용 범위다
|
||||
topic: redis-command-admission
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: reference:server-metadata-defines-the-command
|
||||
verifiedOn: # 이 기록은 이번 회차에 실행 확인을 하지 않았다
|
||||
---
|
||||
|
||||
# 서버 메타데이터가 명령의 정의이고 정책 파일은 허용 범위다
|
||||
|
||||
## 목적
|
||||
|
||||
정책 파일이 명령의 정의 노릇을 해서, 서버가 실제로 하는 일과 어긋난 채 허용 판정이 내려지는 것을 막는다.
|
||||
|
||||
## 규칙
|
||||
|
||||
1. 정의는 서버에서 온다
|
||||
명령의 키 스펙과 플래그와 인자 구조는 서버 메타데이터가 정본이다.
|
||||
|
||||
2. 정책 파일은 그 위에서 범위만 정한다
|
||||
무엇이 허용되고 무엇이 위험한지를 적는다. 명령이 무엇인지를 다시 적지 않는다.
|
||||
|
||||
3. 두 쪽의 드리프트를 검사한다
|
||||
서버 버전이 올라가면 정의가 바뀔 수 있다. 정책 파일이 옛 정의 위에 서 있는지 확인하는 검사가 필요하다.
|
||||
|
||||
4. 그 검사를 실제로 돌린다
|
||||
게이트라고 부르는 것과 빌드에 있는 것은 다르다.
|
||||
|
||||
5. 분류되지 않은 명령은 거부한다
|
||||
카탈로그가 답하지 못하는 명령을 통과시키면 정책이 적용되지 않은 명령이 실행된다.
|
||||
|
||||
## 적용 조건
|
||||
|
||||
명령 단위로 허용 여부를 판정하는 모든 데이터 저장소 클라이언트
|
||||
|
||||
서버 버전에 따라 명령 정의가 달라지는 환경
|
||||
|
||||
## 예외
|
||||
|
||||
서버 메타데이터를 조회할 수 없는 구성에서는 정의를 고정할 수밖에 없다. 그 경우 고정한 버전을 명시하고, 다른 버전에 붙었을 때의 동작을 정해 둔다.
|
||||
|
||||
## 예시
|
||||
|
||||
명령 메타데이터 드리프트를 검사하는 코드가 있고 정책 파일 머리 주석이 그것을 빌드 게이트라고 부르지만, 그 검사를 실행하는 태스크도 CI 단계도 없다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **명령 카탈로그와 admission 아홉 단계**
|
||||
카탈로그가 승인에서 하는 역할이다.
|
||||
- **build gate라 불리는 catalog drift 검사가 어디에서도 실행되지 않는다**
|
||||
네 번째 규칙이 필요한 사례다.
|
||||
- **분류되지 않은 명령은 fail-closed로 거부한다**
|
||||
다섯 번째 규칙을 채택한 결정이다.
|
||||
|
||||
Reference in New Issue
Block a user