Files
document-haness/docs/clean-architecture-backend-template/tech-log-studio/what-a-gate-does-not-prove/case/case-a-certifying-lane-that-compared-nothing.md
T
DongHyeonkaandClaude Opus 5 b2963105a8 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>
2026-09-04 22:51:59 +09:00

8.7 KiB

kind, slug, title, topic, project, status, sourceRevision, rootTreeNode, evidenceCapturedOn, assets, evidence, source
kind slug title topic project status sourceRevision rootTreeNode evidenceCapturedOn assets evidence source
CASE a-certifying-lane-that-compared-nothing "certified"라 불리던 레인이 threshold를 하나도 비교하지 않고 있었다 what-a-gate-does-not-prove clean-architecture-backend-template 게시 전 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916 case:a-certifying-lane-that-compared-nothing 2026-09-02
key file
a-certifying-lane-that-compared-nothing ../../../final/evidence/rendered/a-certifying-lane-that-compared-nothing.svg
../../../final/evidence/raw/a-certifying-lane-that-compared-nothing.txt
원본 분석 절은 analysis/05 §123 이고, 남은 간격의 판정은 §124–§126 에 있다. `final/document.md#6-6` 이 같은 주석을 이 유형의 정본 수정으로 인용한다. 플래그와 소스셋 이름이 어디에 남아 있는지는 이 기록에 붙은 자산에 있다.

"certified"라 불리던 레인이 threshold를 하나도 비교하지 않고 있었다

성능 레인이 릴리스 게이트에 걸려 있었고, 그 레인의 유일한 임계값 단언은 임계값이 단언되지 않고 있다는 것이었다. 게이트는 기본값이 꺼짐인 불리언 뒤에 있었다.

관계

  • 빠뜨림이 통과가 되는 게이트는 게이트가 아니다 이 레인도 비교할 대상이 없을 때 실패하지 않고 통과로 끝난다.
  • 성능 측정은 릴리스 게이트에 넣지 않는다 이 사례에서 나온 결정이다.
  • strict test lane — 발견하지 못하면 실패하는 레인 발견하지 못하면 실패해야 한다는 레인 규약이다.

문제

레인의 이름은 성능 테스트였고, 설명은 풀과 REQUIRES_NEW 압력을 인증한다고 적혀 있었다.

그 레인은 불리언 하나 뒤에 있었고, 그 불리언은 나타나는 모든 곳에서 기본값이 꺼짐이었다. 빌드 파일에서도, 그것을 명시적으로 꺼짐으로 설정하는 야간 워크플로에서도 그랬다.

따라서 릴리스 게이트는 임계값 단언이 하나뿐인 레인에 의존하고 있었고, 그 하나의 단언은 임계값이 단언되지 않고 있다는 것이었다. 인증됨이라는 말은 어떤 지연이나 처리량 한계도 무엇과도 비교되지 않은 실행을 가리켰다.

결론

레인의 이름이 그것이 실제로 하는 일로 바뀌었다. 지금 이름은 풀 동작 계약 테스트다.

주석은 그 레인이 붙드는 것을 동작 계약 셋으로 적는다 — REQUIRES_NEW 깊이 1 이 동시 스레드당 커넥션 두 개를 요구한다는 것, 포화된 풀이 자기 대기 수를 보고한다는 것, 호출자가 커넥션 없이 진행하는 대신 기다린다는 것.

셋 중 둘에는 실제 단언이 있고 가운데 하나에는 없다. 원본 분석에서 그것은 따로 떼어져 Confirmed P2 로 올라 있다. 이름이 약속한 것과 레인이 관측하는 것의 간격이 완전히 닫히지는 않았다.

불리언 프로퍼티는 이 레인에서 제거됐다. 다만 같은 이름의 플래그가 저장소의 다른 두 성능 레인을 여전히 게이트한다.

검증 환경

OpenJDK : 21.0.12 Gradle : 9.0.0 확인 방식 : build.gradle 의 레인 주석과 등록부 확인, 플래그 이름과 옛 소스셋 이름 전수 검색 소스 수정 : x

재현 조건

  1. persistence-jpa 의 build.gradle 에서 레인 등록부 위의 주석을 읽는다. 옛 이름과 불리언의 기본값이 거기 적혀 있다.
  2. 그 레인이 릴리스 게이트 태스크에 포함되는지 확인한다.
  3. 현재 등록부에 failOnNoDiscoveredTests 와 upToDateWhen 이 설정되어 있는지 확인한다.
  4. performance.assertions.enabled 를 저장소 전체에서 검색해 이 레인 밖의 잔존 위치를 확인한다.
  5. 야간 워크플로가 이 레인을 새 이름으로 부르는지 확인한다.

본문

레인의 이름은 jpaPlatformPerformanceTest 였고 설명은 풀과 REQUIRES_NEW 압력을 인증한다고 적었다. 그 레인은 불리언 하나 뒤에 있었고, 그 불리언은 나타나는 모든 곳에서 기본값이 꺼짐이었다 — 빌드 파일에서도, 그것을 명시적으로 꺼짐으로 설정한 야간 워크플로에서도.

그래서 릴리스 게이트가 의존한 레인의 유일한 임계값 단언은 임계값이 단언되지 않고 있다 는 것이었다.

저장소가 그 이력을 주석에 남겼다

레인 등록부 위의 주석이 이 사건 전체를 기록한다. "certified" 가 가리킨 것은 어떤 지연도 처리량 한계도 무엇과도 비교되지 않은 실행이었다.

주석은 이유까지 적는다 — 프로퍼티 이름을 여기 다시 적지 않는 것은, 주석에 있는 이름이 누군가 다음에 설정해 보려 하는 대상이 되기 때문이다.

이름이 바뀌었고 플래그가 빠졌다

지금 태스크 이름은 jpaPlatformPoolContractTest 이고, 설명은 Hikari 풀과 REQUIRES_NEW 커넥션 동작을 검증한다고 적는다. 등록부에는 failOnNoDiscoveredTests = trueoutputs.upToDateWhen { false } 가 있다 — 발견 0 이 성공이 되지 않고, 이전 실행 결과를 다시 내놓지도 않는다.

릴리스 게이트 태스크가 이 레인을 부르고, 야간 워크플로도 새 이름으로 부른다.

같은 플래그가 다른 두 레인에는 남아 있다

:::evidence key="a-certifying-lane-that-compared-nothing" alt="코드베이스에서 performance.assertions.enabled 와 옛 소스셋 이름을 검색한 출력 20줄. 이 레인의 build.gradle 에서 매치가 0 이고, 같은 플래그가 httpclient 와 mongo 레인에는 남아 있으며, 옛 소스셋 이름이 아홉 곳에 남아 있다는 것이 그 출력에 그대로 보인다." caption="performance.assertions.enabled 잔존 위치 · 옛 소스셋 이름 — 20줄 · exit 0" zoom="true" :::

performance.assertions.enabled 는 이 레인의 build.gradle 에서 매치가 0 이다. 그런데 저장소 밖으로 나가면 그렇지 않다.

  • httpclient/build.gradle — 기본값 false
  • persistence-mongo/build.gradle — 기본값 true
  • httpclient-nightly.yml — 워크플로가 true 로 켠다

이 레인에서 사라진 것이지 저장소에서 사라진 것이 아니다. 같은 형태의 게이트가 두 곳에 더 있고, 그중 하나는 기본값이 꺼짐이다.

이름이 약속한 것과 레인이 관측하는 것의 간격은 아직 남아 있다

주석과 야간 워크플로가 이 레인의 검사 대상을 셋으로 적는다.

  1. REQUIRES_NEW 깊이 1 은 동시 스레드당 커넥션 두 개를 필요로 한다
  2. 포화된 풀은 자기 대기 수를 보고한다
  3. 호출자는 커넥션 없이 진행하는 대신 기다린다

원본 분석이 1 번과 3 번에는 실제 단언이 있고 2 번에는 없다 고 판정한다. pending()saturated() 를 단언하는 곳은 손으로 만든 record 하나뿐이고, 실제 풀에서 대기 수를 읽는 유일한 지점은 포화되지 않은 풀에서 읽은 뒤 대기 수에 대해서는 아무것도 단언하지 않는다.

이 기록의 주제가 "이름이 약속한 것을 아무도 측정하지 않았다" 인데, 새 이름이 약속하는 셋 중 하나도 아직 같은 상태다. 원본 분석은 그것을 별도의 Confirmed P2 로 둔다.

태스크 이름은 바뀌었고 소스셋 이름은 남아 있다

build.gradle 에서 옛 이름이 남은 곳은 아홉 줄이다 — 소스셋 선언 한 줄, 의존성 설정 네 줄, 이력 주석 한 줄, 레인 등록부 두 줄, 그리고 단위 레인이 이 디렉터리를 업투데이트 입력으로 선언하는 한 줄.

마지막 것이 기능적이다. test 태스크가 src/jpaPlatformPerformanceTest/java 를 자기 입력 디렉터리로 읽는다.

소스셋 이름을 유지한 것은 문서에 적힌 결정이다 — docs/jpa/repository-adaptation.md 가 소스셋이 원래 계획의 이름을 지킨다고 적는다. 주석이 다시 적지 않겠다고 한 이름은 프로퍼티 이름이고, 소스셋 이름은 그 대상이 아니다.

진짜 성능 게이트가 요구하는 것

전용 러너와 워밍업과 표본 수와 기록된 임계값이다. 그것이 생기면 별도 레인으로 만드는 것이 맞고, 이 레인의 불리언 뒤가 아니다.

확인하지 못한 것

그 시점 야간 워크플로가 무엇을 실행했는지는 확인 범위 밖이다. 현재 레인 쪽도 돌려 보지 못했다. 실 PostgreSQL 컨테이너가 있어야 한다.

주석과 야간 워크플로가 광고하는 세 성질 중 어느 것에 실제 단언이 있는지는 원본 분석의 판정을 옮긴 것이고, 이 회차에 테스트 본문을 직접 읽어 확인하지는 않았다.