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>
103 lines
5.5 KiB
Markdown
103 lines
5.5 KiB
Markdown
---
|
|
kind: CASE
|
|
slug: commit-ambiguity-is-not-only-sqlstate-08
|
|
title: pg_terminate_backend가 57P01로 도착하고 커밋 레코드는 이미 WAL에 있었다
|
|
topic: commit-ambiguity-as-a-result
|
|
project: clean-architecture-backend-template
|
|
status: 게시 전
|
|
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
|
rootTreeNode: case:commit-ambiguity-is-not-only-sqlstate-08
|
|
evidenceCapturedOn: 2026-09-01
|
|
body: case-commit-ambiguity-is-not-only-sqlstate-08.body.md
|
|
assets:
|
|
- key: commit-ambiguity-is-not-only-sqlstate-08
|
|
file: ../../../final/evidence/rendered/commit-ambiguity-is-not-only-sqlstate-08.svg
|
|
evidence:
|
|
- ../../../final/evidence/raw/commit-ambiguity-is-not-only-sqlstate-08.txt
|
|
source:
|
|
- 원본 분석 절은 final/document.md#4-1 · analysis/05 §3.5 이다.
|
|
---
|
|
|
|
# pg_terminate_backend가 57P01로 도착하고 커밋 레코드는 이미 WAL에 있었다
|
|
|
|
커밋 모호성을 SQLSTATE class 08로만 정의했던 규칙이, 서버가 자기 종료를 알리는 57P01 앞에서 성립하지 않았다. 그 시점에 커밋 레코드는 이미 WAL에 있을 수 있다.
|
|
|
|
## 관계
|
|
|
|
- **모르는 것은 성공도 실패도 아닌 세 번째 결과여야 한다**
|
|
이 사례가 그 세 번째 결과를 필요로 하는 이유다.
|
|
- **커밋 모호성 판정은 넓혀도 좁혀도 해롭다**
|
|
이 사례가 규칙을 넓힌 쪽이고, 그 규칙의 반대편 비용을 함께 다룬다.
|
|
- **completion-unknown은 자동으로도 수동으로도 재시도하지 않는다**
|
|
이 분류가 만들어 내는 예외를 어떻게 다룰지 정한 결정이다.
|
|
- **커밋 모호성 계약 레인이 이 리비전에서 통과하는지 실행으로 확인되지 않았다**
|
|
이 사례의 회귀 방지 레인에 대한 미해결 질문이다.
|
|
|
|
## 문제
|
|
|
|
설계는 커밋 모호성을 SQLSTATE class 08, 즉 연결 예외로 정의했다. 그 정의는 클라이언트가 자기 연결에서 무슨 일이 일어났는지를 기준으로 삼는다.
|
|
|
|
문제는 서버가 스스로 종료를 알리는 경우다. in-flight 커밋 중인 백엔드를 pg_terminate_backend 로 끊으면 클라이언트는 57P01 을 받는다. 이것은 class 08 이 아니다. 클라이언트의 연결 시도에 대한 이야기가 아니라 서버가 자기 종료를 알린 것이기 때문이다.
|
|
|
|
그러나 커밋 입장에서 결과는 같고 오히려 더 나쁘다. 57P01 이 도착한 시점에 커밋 레코드가 이미 WAL 에 있을 수 있다. class 08 만 모호성으로 보는 규칙은 이 실패를 평범한 실패로 분류하고, 평범한 실패는 재시도된다. 커밋됐을 수도 있는 쓰기를 다시 실행하는 경로가 여기서 열린다.
|
|
|
|
## 결론
|
|
|
|
규칙이 57P01 과 57P02 와 57P03 까지 넓어졌다.
|
|
|
|
현재 CommitFailureClassifier 는 네 가지를 모호성으로 본다.
|
|
|
|
40003 : statement completion unknown
|
|
08 으로 시작하는 상태 : connection exception
|
|
57P01 57P02 57P03 : 서버가 자기 종료를 알린 상태
|
|
transport 수준 단절
|
|
|
|
세 상태를 함께 넣은 이유는 코드 주석에 남아 있다. 57P01 은 종료된 백엔드나 fast shutdown 이나 failover 가 보고하는 것이고, 그것이 도착할 때 커밋 레코드가 이미 WAL 에 있을 수 있다. 57P02 는 crash shutdown, 57P03 은 지금 접속할 수 없음이며, 서버가 in-flight 커밋을 어떻게 했든 클라이언트가 그것을 알지 못했다는 점에서 같다.
|
|
|
|
넓어진 규칙이 다시 좁아지지 못하도록 CommitAmbiguityContractTest 가 SQLSTATE 를 직접 assert 한다.
|
|
|
|
이 규칙에는 반대 방향의 비용도 함께 기록되어 있다. 커밋 단계의 모든 연결 오류를 모호성으로 표시하면 평범한 풀 고갈과 서버 재시작이 조정 큐로 밀려들고, 운영자는 그 큐를 읽지 않고 비우는 습관을 배운다. 그리고 정작 중요한 항목 하나가 나머지와 함께 지워진다.
|
|
|
|
## 검증 환경
|
|
|
|
OpenJDK : 21.0.12
|
|
Gradle : 9.0.0
|
|
Spring Boot : 4.0.8
|
|
데이터베이스 : PostgreSQL
|
|
관측 출처 : 저장소가 기록한 컨테이너 레인. 이 분석에서 재실행하지 않았다
|
|
|
|
## 재현 조건
|
|
|
|
원래 관측은 컨테이너 레인에서 나왔다. 절차는 다음과 같다.
|
|
|
|
1. 커밋이 진행 중인 트랜잭션을 만든다.
|
|
2. 그 백엔드를 pg_terminate_backend 로 끊는다.
|
|
3. 클라이언트가 받는 SQLSTATE 와 그 시점의 WAL 상태를 확인한다.
|
|
|
|
이번 사이클에서 확인한 것은 규칙의 현재 코드 형태와 그 근거 문장이다.
|
|
|
|
## 본문
|
|
|
|
<!-- body:start -->
|
|
|
|
설계가 "커밋 모호성은 SQLSTATE class 08뿐"이라고 적었다.
|
|
|
|
## 컨테이너 레인이 보인 것
|
|
|
|
in-flight 커밋 중인 백엔드를 `pg_terminate_backend`로 끊었을 때 `57P01`(admin_shutdown)이 도착하며, 그 시점에 커밋 레코드가 이미 WAL에 있을 수 있다.
|
|
|
|
## CommitAmbiguityContractTest 참조 위치
|
|
|
|
:::evidence key="commit-ambiguity-is-not-only-sqlstate-08" alt="코드베이스에서 CommitAmbiguityContractTest 를 검색한 출력 1줄. 이 기록이 세는 참조가 그 출력에 그대로 보인다." caption="CommitAmbiguityContractTest 코드베이스 검색 — 1줄 · exit 0" zoom="true"
|
|
:::
|
|
|
|
## 규칙이 넓어지고 다시 좁아지지 못하게 고정됐다
|
|
|
|
`57P01/57P02/57P03`까지 넓어졌고 `CommitAmbiguityContractTest`가 SQLSTATE를 직접 assert한다.
|
|
|
|
## 확인하지 못한 것
|
|
|
|
jpaPlatformFailureTest 를 이 리비전에서 실행하지 않았다. 57P01 관측이 PostgreSQL 16 과 17 과 18 전부에서 재현되는지도 확인하지 않았다. 관련 미해결 질문에 그 조건을 적어 두었다.
|
|
|
|
<!-- body:end -->
|