Files
document-haness/docs/clean-architecture-backend-template/tech-log-studio/commit-ambiguity-as-a-result/case/case-commit-ambiguity-is-not-only-sqlstate-08.md
T
DongHyeonkaandClaude Fable 5.1 b25357c48a docs(clean-architecture-backend-template): fold analysis into final and re-select one topic
- analysis/·source-index·state.json 을 final/document.md 제2부·제3부로 접었다. SSOT 는 하나다
- 파일럿 — commit-ambiguity-as-a-result 를 새 기준으로 재선별. 후보 14 → 글감 5
  (PROMOTE 5 · MERGE_INTO 3 · KEEP_IN_SSOT 4 · 보류 2). 기록 5건을 다시 썼고 그림 1개를
  techviz 로 만들었다
- 재선별이 잡은 것: 제1부 §6.2·§11.1 이 자기 §13.2 와 어긋나 있었다(레인을 안 돌렸다 vs
  돌렸다) — 정정. 이미 답이 나와 있던 Question 을 HEAD 재실행 질문으로 다시 세웠다.
  Concept 이 인용한 코드가 SSOT 에 없어 뺐다
- candidateScope·sourceRepository 기록. 나머지 43개 주제는 재선별 대기(PENDING 905)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 12:39:20 +09:00

7.5 KiB

id, kind, slug, title, topic, topicName, project, status, studio, lastVerifiedOn, source, sourceRevision, evidence
id kind slug title topic topicName project status studio lastVerifiedOn source sourceRevision evidence
CASE commit-ambiguity-is-not-only-sqlstate-08 pg_terminate_backend 가 57P01 로 도착하고 커밋 레코드는 이미 WAL 에 있었다 commit-ambiguity-as-a-result 커밋 모호성 — 「모른다」를 결과로 유지하기 clean-architecture-backend-template 게시 전 2026-08-29
final/document.md#4-1
final/document.md#a05 §136
final/document.md#a05 §139
21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
../../../final/evidence/raw/050-jpa-commit-ambiguity-probe.txt
../../../final/evidence/raw/115-integration-lane-original-verification.txt

pg_terminate_backend 가 57P01 로 도착하고 커밋 레코드는 이미 WAL 에 있었다

커밋 모호성을 SQLSTATE class 08 로만 정의한 규칙이, 서버가 자기 종료를 알리는 57P01 앞에서 성립하지 않았다. 그 코드가 도착하는 시점에 커밋 레코드는 이미 WAL 에 있을 수 있다.

관계

  • 트랜잭션 결과 대수 — 다섯 변형이 각각 답하는 질문 이 사례의 결론이 어느 결과 변형으로 들어가는지를 정의한다.
  • 모르는 것은 성공도 실패도 아닌 세 번째 결과여야 한다 이 사례가 그 세 번째 결과를 필요로 하는 이유다.
  • completion-unknown 은 자동으로도 수동으로도 재시도하지 않는다 이 분류가 만들어 내는 예외를 어떻게 다룰지 정한 결정이다.
  • 커밋 모호성 레인이 HEAD 에서도 통과하는지는 실행이 아니라 드리프트 0 으로 답했다 이 사례를 고정하는 계약 레인에 남은 질문이다.

문제

설계는 커밋 모호성을 SQLSTATE class 08, 즉 연결 예외로 정의했다. 그 정의는 클라이언트가 자기 연결에서 무슨 일을 겪었는지를 기준으로 삼는다.

서버가 스스로 종료를 알리는 경우가 그 정의 밖으로 나간다. 커밋이 진행 중인 백엔드를 pg_terminate_backend 로 끊으면 클라이언트는 57P01 을 받는다. 이 코드는 class 08 이 아니다. 클라이언트의 연결 시도가 아니라 서버가 자기 종료를 알린 것이어서 계열이 다르다.

커밋 입장에서 결과는 같고 오히려 더 나쁘다. 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 은 지금 접속할 수 없음이며, 서버가 진행 중인 커밋을 어떻게 처리했든 클라이언트가 그것을 알지 못했다는 점에서 셋이 같다.

넓어진 규칙이 다시 좁아지지 못하도록 CommitAmbiguityContractTest 가 SQLSTATE 를 직접 단언한다.

반대 방향의 비용도 같은 자료에 적혀 있다. 커밋 단계의 모든 연결 오류를 모호성으로 표시하면 평범한 풀 고갈과 서버 재시작이 조정 큐로 밀려든다. 그러면 운영자는 그 큐를 읽지 않고 비우는 습관을 배우고, 중요했던 한 건이 나머지와 같이 지워진다. 규칙은 넓혀도 좁혀도 해롭고, 그래서 커밋 단계와 드라이버의 침묵이 동시에 성립할 때로 조건이 좁혀져 있다.

검증 환경

OpenJDK : 21 Gradle : 9.0.0 Spring Boot : 4.0.8 데이터베이스 : PostgreSQL, Testcontainers 로 기동 레인 리비전 : a24ece9cf797f7ea647e33bf846b115208ed1ba5 레인 실행 시각 : 2026-08-29T14:46:23+00:00

재현 조건

  1. 커밋이 진행 중인 트랜잭션을 만든다.
  2. 그 백엔드를 pg_terminate_backend 로 끊는다.
  3. 클라이언트가 받은 SQLSTATE 와 그 시점의 커밋 레코드 상태를 확인한다.
  4. CommitFailureClassifier 가 그 상태를 TransactionCompletionUnknownException 과 retryable=false 로 번역하는지 본다.

이 절차는 CommitAmbiguityContractTest 가 수행하고, 그 클래스는 jpaPlatformFailureTest 레인에 있다.

본문

규칙이 처음 적혀 있던 형태

설계 문서는 커밋 모호성을 한 문장으로 정의했다.

커밋 모호성은 SQLSTATE class 08 뿐이다.

class 08 은 connection exception 계열이고, 클라이언트가 연결을 잃었다는 사실을 말한다. 커밋 응답을 받지 못한 상황을 여기에 대응시키는 것은 처음에는 맞아 보인다.

레인이 보여 준 SQLSTATE

컨테이너 레인은 커밋이 진행 중인 백엔드를 pg_terminate_backend 로 끊는다. 클라이언트가 받은 것은 08006 이 아니라 57P01 이었다.

57P01admin_shutdown 이고, 서버가 자기 종료를 알린 것이다. PostgreSQL 은 이 상황을 클라이언트 연결 문제로 보고하지 않는데, 커밋을 요청한 쪽에서 보면 두 상황은 구별되지 않는다 — 응답이 오지 않았고, 서버가 무엇을 했는지 알 수 없다.

그리고 57P01 이 도착할 때 커밋 레코드는 이미 WAL(write-ahead log, 미리 쓰기 로그)에 기록돼 있을 수 있다. 종료 신호와 커밋 기록 사이에 순서 보장이 없으므로 둘 중 어느 쪽이 먼저였는지 클라이언트는 알지 못한다.

넓힌 규칙과 그것을 고정하는 단언

현재 규칙은 네 갈래다.

상태 무엇을 뜻하나
40003 statement completion unknown
08* connection exception
57P01 · 57P02 · 57P03 서버가 자기 종료를 알렸다
transport 단절 드라이버가 응답을 받지 못했다

CommitAmbiguityContractTest 는 이 목록을 문장으로 서술하지 않고 SQLSTATE 값을 직접 단언한다. 규칙을 좁히는 변경은 그 단언을 깨뜨려야만 들어올 수 있다.

프로브가 보여 준 경계

같은 사이클에서 돌린 프로브는 다른 것을 보여 준다. 가짜 PlatformTransactionManager 의 커밋이 08006 을 던지게 하고 현재의 벤더 번역기를 태우면 결과는 이렇다.

type=ConnectionUnavailableException
category=CONNECTION_UNAVAILABLE
completionUnknown=false
retryable=false

08006 이 그 자체로 completion-unknown 을 만들지 않는다. 판정에는 관측된 단계가 함께 필요하고, 이 프로브는 실제 소켓 유실을 흉내 내지 않으며 데이터베이스가 실제로 커밋했음을 증명하지도 않는다. 프로브 파일의 관측 경계에 그 한계가 적혀 있다.

확인하지 못한 것

레인은 a24ece9c 에서 돌았다. 다섯 태그 레인을 --rerun-tasks 로 실행해 51 클래스 244 tests 가 0 failures 로 끝났고 jpaPlatformFailureTest 는 그중 2 클래스 6 tests 였다. HEAD 인 21234e38 에서는 다시 돌리지 않았고, 두 리비전 사이에 persistence-jpa 경로의 변경 파일이 0 이라는 측정으로 대신했다.

57P01 관측이 PostgreSQL 16 과 17 과 18 에서 같게 나오는지도 재지 않았다. 레인의 매트릭스는 실행 한 번에 major 하나만 고르는데 이 사이클은 한 번만 돌렸다.