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:
DongHyeonka
2026-09-04 22:51:59 +09:00
co-authored by Claude Opus 5
parent 43bccd08a8
commit b2963105a8
5017 changed files with 372751 additions and 4943 deletions
@@ -0,0 +1,60 @@
---
kind: QUESTION
slug: a02-f001-lro
title: response/LRO invariant enforcement boundary
topic: multitenancy-isolation
project: clean-architecture-backend-template
status: 게시 전
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
rootTreeNode: open-question:a02-f001-lro
questionStatus: OPEN
verifiedOn: # 이 기록은 이번 회차에 실행 확인을 하지 않았다
---
# response/LRO invariant enforcement boundary
응답 봉투 계열 타입의 유효한 형태가 팩토리 테스트에는 고정되어 있고 공개 정규 생성자에서는 강제되지 않는다. 생성자를 확장 표면으로 둘 것인지 결정이 필요하다.
## 사실
봉투와 벌크 봉투와 연산과 페이지 메타의 유효한 형태가 팩토리 테스트에 고정되어 있다.
공개 정규 생성자는 그 불변식을 강제하지 않는다.
## 가정
정규 생성자가 외부에서 호출되지 않을 것이라고 암묵적으로 전제하고 있다. 그 전제가 확인되지 않았다.
## 미지수
원시 생성자가 의도된 확장 표면인가.
유효하지 않은 상태를 생성자에서 차단해야 하는가.
## 제약
이 타입들은 공유 계약 계층에 있으므로 변경이 여러 어댑터에 영향을 준다.
## 선택지
생성자에 불변식을 넣는다
팩토리와 같은 규칙이 적용되고 유효하지 않은 값이 존재할 수 없다. 확장 표면이 좁아진다.
확장 표면으로 유지하고 문서화한다
허용 범위와 실패 의미를 계약에 적는다. 유효하지 않은 값이 만들어질 수 있다는 것을 받아들인다.
## 다음 검증
프로덕션에서 원시 생성자를 호출하는 곳을 전수 확인한다. 그리고 유효하지 않은 형태의 생성자 테스트를 추가해 현재 허용 표면을 고정한다.
원시 생성자가 외부 확장 계약이면 허용 범위와 실패 의미를 문서화한다.
그렇지 않으면 생성자 수준 불변식을 추가하고 팩토리와 같은 규칙을 검증한다.
## 관계
- **위험한 조합은 정책이 아니라 생성자가 거부하게 만든다**
이 질문이 선택할 수 있는 한쪽 방향의 규칙이다.
- **모르는 것은 성공도 실패도 아닌 세 번째 결과여야 한다**
같은 계약 계층의 상태 어휘 원칙이다.
@@ -0,0 +1,60 @@
---
kind: QUESTION
slug: a02-f002-domaincontextkey
title: DomainContextKey same-name different-type collision
topic: multitenancy-isolation
project: clean-architecture-backend-template
status: 게시 전
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
rootTreeNode: open-question:a02-f002-domaincontextkey
questionStatus: OPEN
verifiedOn: # 이 기록은 이번 회차에 실행 확인을 하지 않았다
---
# DomainContextKey same-name different-type collision
컨텍스트 키의 신원이 이름뿐인데 조회는 요청된 타입으로 캐스팅한다. 같은 이름의 다른 타입 키를 선언하는 것이 금지인지 허용인지 정해져 있지 않다.
## 사실
키의 신원은 이름만으로 정해진다.
조회는 요청된 타입으로 캐스팅한다.
## 가정
같은 이름의 키가 하나뿐일 것이라고 전제하고 있다. 그 전제를 강제하는 것이 없다.
## 미지수
같은 이름의 다른 타입 키 선언이 금지된 계약인가.
허용이라면 캐스팅 실패의 의미가 무엇인가.
## 제약
키 선언이 여러 모듈에 흩어져 있으면 생성 시점 충돌 탐지가 전역 레지스트리를 요구한다.
## 선택지
생성이나 등록 단계에서 충돌을 거부한다
같은 이름이 두 번 선언되면 실패한다. 전역 레지스트리가 필요하다.
허용하고 실패 의미를 문서화한다
캐스팅 실패가 언제 어떤 형태로 나타나는지 계약에 적는다.
## 다음 검증
같은 이름에 다른 타입을 갖는 키 픽스처를 만들고 생성과 조회의 실패를 고정한다. 그다음 레지스트리의 실제 키 선언을 전수 대조한다.
같은 이름 다른 타입이 금지라면 생성이나 등록 단계에서 충돌을 거부한다.
허용이라면 캐스팅 실패 의미와 사용 조건을 계약에 명시한다.
## 관계
- **이름은 값이 아니라 registry key다**
이 질문이 다루는 이름의 성질이다.
- **위험한 조합은 정책이 아니라 생성자가 거부하게 만든다**
한쪽 방향의 구현 규칙이다.
@@ -0,0 +1,64 @@
---
kind: QUESTION
slug: a05-f003-capabilitysupport-constraints
title: CapabilitySupport.constraints 의 경계가 타입에 없다
topic: multitenancy-isolation
project: clean-architecture-backend-template
status: 게시 전
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
rootTreeNode: open-question:a05-f003-capabilitysupport-constraints
questionStatus: OPEN
verifiedOn: # 이 기록은 이번 회차에 실행 확인을 하지 않았다
---
# CapabilitySupport.constraints 의 경계가 타입에 없다
능력 선언의 제약 목록이 경계가 있다고 서술되지만 십만 자 문자열도 받아들인다. 그 목록은 액추에이터 리포트 모델에 포함된다.
## 사실
십만 자 제약 문자열이 받아들여진다.
능력 목록은 플랫폼 리포트를 통해 액추에이터 엔드포인트 모델에 포함된다.
현재 출하 조립은 짧은 정적 리터럴만 만든다.
## 가정
외부나 동적 생산자가 임의 제약을 넣지 않을 것이라고 전제하고 있다. 그 전제를 확인하지 않았다.
## 미지수
외부나 포크나 동적 생산자가 공개 API 에 임의 제약을 넣는 실제 경로가 있는가.
경계를 타입이 소유해야 하는가 리포트 투영이 소유해야 하는가.
## 제약
현재 조립에서는 사고가 아니다. 공개 API 불변식의 공백이며, 포크나 동적 조립에서는 경계가 호출자 규율에 의존한다.
## 선택지
타입 수준 최대 길이나 어휘를 강제한다
공개 API 를 쓰는 모든 생산자에 적용된다.
리포트 투영에서 경계를 건다
공개되는 지점에서만 자른다. 값 자체는 자유롭다.
## 다음 검증
공개 API 의 외부 생산자와 포크 확장점을 추적하고, 과대 제약에 대한 생성자와 리포트 경계 테스트를 추가한다.
동적이나 외부 생산자가 확인되면 경계를 타입이나 리포트 투영에서 강제하고 이 항목을 Case 로 승격한다.
없다면 공개 불변식과 문서를 현재 조립의 보장 수준으로 좁힌다.
## 관계
- **진단 리포트가 살아 있는 리소스를 담지 않도록 값 타입을 좁혔다**
같은 값 타입의 다른 제약이다.
- **관측을 위해 수집한 데이터가 관측 대상보다 위험할 수 있다**
리포트로 공개되는 값의 성질이다.
- **카디널리티 경계를 타입으로 표현하기**
경계를 타입에 두는 같은 계열의 개념이다.
@@ -0,0 +1,62 @@
---
kind: QUESTION
slug: analysis-finding-a02-f003
title: bounded operational record identifiers
topic: multitenancy-isolation
project: clean-architecture-backend-template
status: 게시 전
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
rootTreeNode: open-question:analysis-finding-a02-f003
questionStatus: OPEN
verifiedOn: # 이 기록은 이번 회차에 실행 확인을 하지 않았다
---
# bounded operational record identifiers
운영 레코드의 네임스페이스와 키가 경계가 있다고 문서에 적혀 있는데, 생성자는 공백 여부만 확인한다. 그 경계를 공유 계약이 소유할지 어댑터가 소유할지 정해지지 않았다.
## 사실
운영 레코드 문서가 네임스페이스와 키를 경계가 있는 값으로 설명한다.
생성자는 공백 여부만 확인한다.
## 가정
각 어댑터가 자기 제공자의 제한을 알아서 지킬 것이라고 전제하고 있다. 그 전제를 확인하지 않았다.
## 미지수
제공자별 키 크기와 문자 집합 제한을 공유 계약이 소유해야 하는가 어댑터가 소유해야 하는가.
여러 제공자가 공통으로 요구하는 최소 경계가 있는가.
## 제약
공유 계약이 특정 제공자의 제한을 담으면 그 계약이 제공자에 묶인다.
어댑터가 각자 검증하면 같은 검사가 여러 곳에 생긴다.
## 선택지
공유 값 객체에서 공통 최소 경계를 강제한다
여러 제공자가 공통 최소 경계를 요구하면 이쪽이 맞다.
공유 문서를 좁히고 어댑터 경계에서 검증한다
제한이 제공자별이면 공유 계약은 경계를 주장하지 않는 편이 정확하다.
## 다음 검증
실제 제공자와 어댑터의 식별자 제한을 조사하고 프로덕션 생성 지점과 대조한다. 경계값 픽스처를 추가한다.
여러 제공자가 공통 최소 경계를 요구하면 공유 값 객체에서 강제한다.
제공자별이면 공유 문서의 표현을 좁히고 어댑터 경계에서 검증한다.
## 관계
- **카디널리티 경계를 타입으로 표현하기**
경계를 타입으로 표현하는 같은 계열의 개념이다.
- **위험한 조합은 정책이 아니라 생성자가 거부하게 만든다**
값 타입에서 강제하는 방향의 규칙이다.
@@ -0,0 +1,58 @@
---
kind: QUESTION
slug: analysis-finding-a02-f004
title: permission component grammar
topic: multitenancy-isolation
project: clean-architecture-backend-template
status: 게시 전
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
rootTreeNode: open-question:analysis-finding-a02-f004
questionStatus: OPEN
verifiedOn: # 이 기록은 이번 회차에 실행 확인을 하지 않았다
---
# permission component grammar
권한 값은 콜론 구분 세그먼트 수와 공백과 정규화를 강제하지만 세그먼트의 문자 문법은 제한하지 않는다. 레지스트리가 더 좁은 문법을 실제 정본으로 쓰는지 확인되지 않았다.
## 사실
권한 값 객체가 강제하는 것은 세 가지다. 콜론 세그먼트 수와 공백 여부와 정규화다.
세그먼트의 문자 문법은 제한하지 않는다.
## 가정
레지스트리의 권한 리터럴이 공유 값 객체가 허용하는 범위 안에 있을 것이라고 전제하고 있다. 반대 방향은 확인하지 않았다.
## 미지수
레지스트리 정본이 공유 값 객체보다 좁은 문자 문법을 계약으로 요구하는가.
## 제약
공유 값 객체를 좁히면 기존 권한 리터럴 중 일부가 무효가 될 수 있다.
## 선택지
공유 값 객체가 레지스트리와 같은 문법을 강제한다
레지스트리가 더 좁은 문법을 실제 정본으로 쓰면 이쪽이 맞다. 패리티 테스트로 두 쪽을 고정한다.
현재의 넓은 문법이 의도임을 문서화한다
레지스트리가 관행으로만 좁게 쓰는 것이면 이쪽이다.
## 다음 검증
레지스트리의 모든 권한 리터럴을 수집해 허용 문자 집합을 만들고, 공유 파서와 패리티 테스트로 대조한다.
레지스트리가 더 좁은 문법을 실제 정본으로 쓰면 공유 값 객체가 같은 문법을 강제한다.
아니면 현재의 넓은 문법이 의도임을 문서화한다.
## 관계
- **이름은 값이 아니라 registry key다**
권한 값이 등록된 것인지 자유 문자열인지의 문제다.
- **문서와 상수가 서로 일치하는 것으로는 아무것도 증명되지 않는다**
패리티 테스트가 필요한 이유다.
@@ -0,0 +1,58 @@
---
kind: QUESTION
slug: analysis-finding-a02-f005
title: messaging schema qualification boundary
topic: multitenancy-isolation
project: clean-architecture-backend-template
status: 게시 전
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
rootTreeNode: open-question:analysis-finding-a02-f005
questionStatus: OPEN
verifiedOn: # 이 기록은 이번 회차에 실행 확인을 하지 않았다
---
# messaging schema qualification boundary
현재 검사는 JDK 만으로 자원과 다이제스트와 선택된 의미 벡터를 검증한다. 실제 JSON 스키마 검증기와의 호환성은 그 검사만으로 주장할 수 없다.
## 사실
현재 테스트는 JDK 만 쓴다. 정확한 자원과 다이제스트와 선택된 의미 벡터를 검증한다.
그 테스트는 통과한다.
## 가정
JDK 만으로 만든 의미 벡터가 실제 검증기의 해석과 같을 것이라고 전제하고 있다. 그 전제가 확인되지 않았다.
## 미지수
채택할 검증기가 커밋된 스키마와 긍정 부정 벡터를 동일하게 해석하는가.
## 제약
검증기를 테스트 클래스패스에 추가하면 이 리프의 의존이 늘어난다. 프레임워크 없는 계약 리프를 유지하려는 방침과 충돌할 수 있다.
## 선택지
별도 소스셋이나 레인에서 실제 검증기로 실행한다
리프의 기본 의존을 늘리지 않으면서 증거를 만든다.
현재 상태를 유지하고 표현을 좁힌다
JDK 만으로 확인한 것이 무엇인지 문서에 적고 호환성 주장을 하지 않는다.
## 다음 검증
채택할 검증기로 커밋된 스키마와 긍정 부정 벡터를 실행하고 결과를 증거로 남긴다.
실제 검증기가 동일한 의미 벡터를 통과해야 호환성을 주장한다.
실패하면 스키마나 지원 범위를 수정하고 JDK 전용 게이트의 표현을 좁힌다.
## 관계
- **문서와 상수가 서로 일치하는 것으로는 아무것도 증명되지 않는다**
이 질문이 다루는 증거의 성질이다.
- **능력 등급은 코드가 아니라 실행된 증거에서 파생한다**
호환성 주장에 증거가 필요한 이유다.
@@ -0,0 +1,62 @@
---
kind: QUESTION
slug: analysis-finding-a03-f002
title: notification derived idempotency key가 32-bit hash
topic: multitenancy-isolation
project: clean-architecture-backend-template
status: 게시 전
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
rootTreeNode: open-question:analysis-finding-a03-f002
questionStatus: OPEN
verifiedOn: # 이 기록은 이번 회차에 실행 확인을 하지 않았다
---
# notification derived idempotency key가 32-bit hash
멱등성 키가 주어지지 않았을 때 쓰는 대체 키가 32비트 해시다. 서로 다른 요청의 충돌을 배제할 수 없어 문서의 강한 유일성 표현과 실제 보장이 맞지 않는다.
## 사실
대체 키는 표준 라이브러리 해시를 16진수 문자열로 만든 값이다. 32비트다.
정규 지문 비교가 별도로 존재한다.
## 가정
32비트 공간에서 실제 요청들이 충돌하지 않을 것이라고 전제하고 있다. 그 전제를 확인하지 않았다.
## 미지수
서로 다른 정규 요청이 같은 대체 키를 만들 수 있는가.
만들 수 있다면 하류가 잘못된 충돌로 끝나는가.
이 위험을 허용할 것인가.
## 제약
정규 지문 비교가 있으므로 조용한 수렴보다는 잘못된 충돌 쪽이 중심 위험이다. 즉 서로 다른 두 요청이 같은 것으로 합쳐지는 것이 아니라, 한쪽이 중복으로 거절될 수 있다.
## 선택지
정규 계획에 대한 암호학적 다이제스트로 교체한다
충돌 확률이 실질적으로 사라진다. 키 길이가 늘어난다.
현재 값을 유지하고 문서를 좁힌다
유일성 표현을 실제 보장 수준으로 낮춘다.
## 다음 검증
알려진 해시 충돌 픽스처나 속성 탐색으로 서로 다른 정규 요청이 같은 대체 키를 만드는 사례를 찾고, 그때 하류의 충돌 동작을 고정한다.
서로 다른 정규 요청의 충돌이 재현되면 암호학적 다이제스트로 교체한다.
재현하지 못해도 유일성 문서 표현은 실제 보장 수준으로 좁힌다.
## 관계
- **digest는 길이 프레이밍하고 버전을 붙인다**
다이제스트로 교체할 때 따라야 할 규칙이다.
- **모르는 것은 성공도 실패도 아닌 세 번째 결과여야 한다**
잘못된 충돌이 어떤 결과로 보고되는지의 문제다.
@@ -0,0 +1,64 @@
---
kind: QUESTION
slug: analysis-finding-a03-f004
title: isolation vocabulary와 legacy routing capability의 시차
topic: multitenancy-isolation
project: clean-architecture-backend-template
status: 게시 전
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
rootTreeNode: open-question:analysis-finding-a03-f004
questionStatus: OPEN
verifiedOn: # 이 기록은 이번 회차에 실행 확인을 하지 않았다
---
# isolation vocabulary와 legacy routing capability의 시차
공개 격리 수준 어휘에는 더 엄격한 값들이 있는데, 옛 트랜잭션 포트의 템플릿은 하나로 고정되어 있다. 어휘만 보면 이미 지원되는 능력으로 오해할 수 있다.
## 사실
공개 격리 수준 타입은 더 엄격한 값들을 표현한다.
옛 트랜잭션 포트의 템플릿은 읽기 커밋으로 고정되어 있다.
테스트도 더 엄격한 라우팅을 계획됨으로 적는다.
## 가정
어휘에 있는 값이 언젠가 라우팅될 것이라고 전제하고 있다. 그 시점과 소유자가 정해지지 않았다.
## 미지수
더 엄격한 격리를 옛 포트까지 확장할 것인가.
아니면 정규 정책 트랜잭션 경로에서만 제공할 것인가.
## 제약
라우팅 소유자가 둘이면 같은 유스케이스 정책이 어느 경로로 들어오느냐에 따라 다른 격리를 받는다.
## 선택지
옛 포트까지 확장한다
기존 호출자가 그대로 더 엄격한 격리를 쓸 수 있다. 옛 포트의 표면이 넓어진다.
정규 경로에서만 제공한다
소유자가 하나가 된다. 옛 포트 호출자는 이전해야 한다.
## 다음 검증
현재 프로덕션 유스케이스의 격리 요구와 옛 트랜잭션 포트 호출자를 대조한다. 그리고 라우팅 소유자를 하나로 정하는 설계와 계약 테스트를 만든다.
선택한 소유자에서 유스케이스 정책과 어댑터 트랜잭션 정의가 일대일로 검증되어야 한다.
지원하지 않는 경로는 능력으로 노출하지 않는다.
## 관계
- **트랜잭션 템플릿은 모드별로 미리 만들어 둔다**
템플릿이 고정되어 있는 구조다.
- **Bean 애너테이션이 있다는 것은 조립 증거가 아니다**
어휘의 존재가 능력의 증거가 아니라는 같은 계열이다.
- **등급은 네 단계로 나누고 관측보다 높게 적지 않는다**
능력을 실제보다 높게 노출하지 않는 규칙이다.
@@ -0,0 +1,60 @@
---
kind: QUESTION
slug: analysis-finding-a04-f006
title: cache-redis 와 httpclient 의 support 간선이 죽었는지 확정되지 않았다
topic: multitenancy-isolation
project: clean-architecture-backend-template
status: 게시 전
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
rootTreeNode: open-question:analysis-finding-a04-f006
questionStatus: OPEN
verifiedOn: # 이 기록은 이번 회차에 실행 확인을 하지 않았다
---
# cache-redis 와 httpclient 의 support 간선이 죽었는지 확정되지 않았다
두 리프가 지원 모듈에 대한 Gradle 의존을 갖는데 현재 프로덕션 자바 참조가 0 이고 지원 자원도 쓰지 않는다. 죽은 간선인지 아직 확정할 수 없다.
## 사실
두 리프 모두 지원 모듈에 대한 Gradle 의존이 선언되어 있다.
현재 프로덕션 자바 참조는 0 이다.
지원 모듈의 자원도 쓰지 않는다.
## 가정
빌드나 테스트나 리플렉션 경로에서 그 의존이 필요하지 않을 것이라고 전제하고 있다. 확인하지 않았다.
## 미지수
빌드나 테스트나 리플렉션이나 후속 범위에서 이 간선이 필요한가.
## 제약
각 리프의 전수 분석이 끝나기 전에는 죽은 간선으로 단정할 수 없다.
## 선택지
간선을 제거하고 레인을 돌린다
불필요한 의존이 사라지고 클래스패스와 아키텍처 서술이 좁아진다. 소비자가 있으면 실패로 드러난다.
유지하고 소유자를 문서화한다
소비자가 발견되면 그 이유를 적는다.
## 다음 검증
각 하류 리프의 전수 분석 결과를 확인한 뒤, 지원 의존을 제거한 상태로 집중 테스트와 애플리케이션 조립 테스트를 실행한다.
프로덕션과 빌드와 테스트와 런타임 소비자가 0 이고 의존 제거 후 관련 레인이 통과하면 간선을 제거한다.
소비자가 발견되면 그 소유자와 이유를 문서화한다.
## 관계
- **Bean 애너테이션이 있다는 것은 조립 증거가 아니다**
선언과 사용을 구별하는 같은 계열이다.
- **legacy compatibility surface의 제거 조건을 세 가지로 고정한다**
제거 후보를 판정하는 같은 계열의 규칙이다.
@@ -0,0 +1,72 @@
---
kind: QUESTION
slug: performance-and-capacity-unmeasured
title: 실제 성능·용량 특성을 어떤 모듈에서도 측정하지 않았다
topic: multitenancy-isolation
project: clean-architecture-backend-template
status: 게시 전
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
rootTreeNode: open-question:performance-and-capacity-unmeasured
questionStatus: OPEN
verifiedOn: # 이 기록은 이번 회차에 실행 확인을 하지 않았다
---
# 실제 성능·용량 특성을 어떤 모듈에서도 측정하지 않았다
이 분석은 성능을 측정하지 않았다. 문서에 남은 성능과 용량 관련 서술은 전부 코드가 선언한 상한과 그 강제 여부에 대한 것이다.
## 사실
성능 레인은 릴리스 게이트에서 의도적으로 빠져 있다. 기계 의존적인 측정을 게이트 판정의 입력으로 삼지 않는다는 결정이다.
풀 사이징 수식은 설정 파일 주석에 있고 부하 테스트로 조정하라고 적혀 있다.
테넌트 풀 예산은 두 상한을 갖지만 그 값이 어떤 부하에서 적절한지에 대한 근거가 없다.
httpclient 의 성능 레인과 벤치마크는 기계 의존적이라는 이유로 기본 검사에서 빠져 있다.
GraphQL 리프의 등급표는 실부하와 장애를 미달성으로 적고, 증거가 없으면 릴리스 게이트가 거부한다고 적는다.
## 가정
선언된 상한들이 합리적인 범위에 있다고 전제하고 있다. 그 전제의 근거는 문서에 적힌 시작점 수식이며 측정이 아니다.
## 미지수
각 상한이 실제 부하에서 적절한가.
테넌트 풀 예산의 두 상한이 어떤 테넌트 수와 부하에서 유효한가.
성능 회귀가 발생하면 무엇이 그것을 알아채는가.
## 제약
실부하 증거는 실제 부하 인프라를 요구한다. 리프 안에서 생성할 수 없다.
기계 의존적 측정을 게이트에 넣지 않는다는 결정이 이미 있다.
## 선택지
전용 성능 인프라를 만든다
러너와 워밍업과 표본 수와 기록된 기준선이 필요하다. 그것이 생기면 별도 레인으로 만든다.
측정하지 않은 상태를 명시적으로 유지한다
등급표가 이미 그렇게 하고 있다. 릴리스 게이트가 증거 없이는 거부한다.
## 다음 검증
성능 레인을 실행할 인프라를 정하고, 어떤 지표를 어떤 조건에서 잴지 먼저 문서로 고정한다. 그다음 기준선을 기록한다.
기준선이 기록되면 이 질문을 닫고, 이후의 측정은 그 기준선과의 비교가 된다.
인프라를 만들지 않기로 하면 그 결정과 그때 포기하는 것을 기록한다.
## 관계
- **성능 측정은 릴리스 게이트에 넣지 않는다**
이 질문이 전제하는 결정이다.
- **tenant별 풀이 개별적으로 합리적이고 그 합이 서버 상한을 넘긴다**
측정되지 않은 용량 판단의 사례다.
- **능력 등급은 코드가 아니라 실행된 증거에서 파생한다**
실부하 증거가 최상위 등급의 조건인 이유다.