글감 1,001개 중 제1부(§3~§11) 앵커를 하나라도 가진 것은 112개뿐이었다. 나머지 889개는
제2부 모듈 분석 65편의 절 제목에서 나온 것이고, 그것이 재판정이 필요했던 이유다.
주제 44 → 16 (43개가 독자 질문 없이 있었다. 지금은 전부 있다)
글감 1,001 → 123 (제1부 앵커 112 + 제1부가 채택했는데 비어 있던 자리 11)
후보 965 → 1,088 · PENDING 905 → 0
error 3,042 → 0
내려온 889개는 후보 대장에 KEEP_IN_SSOT 로 남는다 — 버린 것이 아니라 분석에 남기고 독립
기록으로 만들지 않기로 한 것이다. 그 글감을 받치던 기록 파일 828개는 지웠다. 계약이 정본이고,
파일이 남아 있다는 이유로 계약에서 뺀 주제가 되살아나면 안 된다. 이력에는 그대로 있다 —
git checkout a0ca2bb -- <경로>.
제1부가 채택했는데 글감이 없던 자리 열하나를 채웠다: mongo high-water mark 가 재전달 이벤트를
삼킨 P1, admin plane 이 가드만 켜고 서비스는 켜지 않은 것과 그 짝인 결정, 실패 어휘 세 층과
SQLState 매트릭스 병합 규칙, 부하 아래에서만 새는 admission 경계, 발행 증거와 완료 판정의
분리, keyset·JSONB 결정 둘.
Concept 17개에 basis-version 을 채우고, 계약 제목과 기록 제목이 갈라져 있던 23건을 기록 쪽에
맞췄다. candidateScope 에 excludedAnchorPattern 을 적어 제2부 앵커만 가진 글감이 다시 올라올
수 없게 한다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
130 lines
8.7 KiB
Markdown
130 lines
8.7 KiB
Markdown
---
|
|
kind: CASE
|
|
slug: a-certifying-lane-that-compared-nothing
|
|
title: "certified"라 불리던 레인이 threshold를 하나도 비교하지 않고 있었다
|
|
topic: what-a-gate-does-not-prove
|
|
project: clean-architecture-backend-template
|
|
status: 게시 전
|
|
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
|
rootTreeNode: case:a-certifying-lane-that-compared-nothing
|
|
evidenceCapturedOn: 2026-09-02
|
|
assets:
|
|
- key: a-certifying-lane-that-compared-nothing
|
|
file: ../../../final/evidence/rendered/a-certifying-lane-that-compared-nothing.svg
|
|
evidence:
|
|
- ../../../final/evidence/raw/a-certifying-lane-that-compared-nothing.txt
|
|
source:
|
|
- 원본 분석 절은 final/document.md#a05 §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. 야간 워크플로가 이 레인을 새 이름으로 부르는지 확인한다.
|
|
|
|
## 본문
|
|
|
|
<!-- body:start -->
|
|
|
|
레인의 이름은 `jpaPlatformPerformanceTest` 였고 설명은 풀과 `REQUIRES_NEW` 압력을 인증한다고 적었다. 그 레인은 불리언 하나 뒤에 있었고, 그 불리언은 나타나는 모든 곳에서 기본값이 꺼짐이었다 — 빌드 파일에서도, 그것을 명시적으로 꺼짐으로 설정한 야간 워크플로에서도.
|
|
|
|
그래서 릴리스 게이트가 의존한 레인의 유일한 임계값 단언은 **임계값이 단언되지 않고 있다** 는 것이었다.
|
|
|
|
## 저장소가 그 이력을 주석에 남겼다
|
|
|
|
레인 등록부 위의 주석이 이 사건 전체를 기록한다. "certified" 가 가리킨 것은 어떤 지연도 처리량 한계도 무엇과도 비교되지 않은 실행이었다.
|
|
|
|
주석은 이유까지 적는다 — 프로퍼티 이름을 여기 다시 적지 않는 것은, 주석에 있는 이름이 누군가 다음에 설정해 보려 하는 대상이 되기 때문이다.
|
|
|
|
## 이름이 바뀌었고 플래그가 빠졌다
|
|
|
|
지금 태스크 이름은 `jpaPlatformPoolContractTest` 이고, 설명은 Hikari 풀과 `REQUIRES_NEW` 커넥션 동작을 검증한다고 적는다. 등록부에는 `failOnNoDiscoveredTests = true` 와 `outputs.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 컨테이너가 있어야 한다.
|
|
|
|
주석과 야간 워크플로가 광고하는 세 성질 중 어느 것에 실제 단언이 있는지는 원본 분석의 판정을 옮긴 것이고, 이 회차에 테스트 본문을 직접 읽어 확인하지는 않았다.
|
|
|
|
<!-- body:end -->
|