Files
document-haness/docs/clean-architecture-backend-template/tech-log-studio/owner-safe-state-machines/case/case-native-claim-did-not-bump-the-version.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

89 lines
4.1 KiB
Markdown

---
kind: CASE
slug: native-claim-did-not-bump-the-version
title: native claim이 @Version을 올리지 않아 충돌을 보고하지 않는 낙관적 잠금이 됐다
topic: owner-safe-state-machines
project: clean-architecture-backend-template
status: 게시 전
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
rootTreeNode: case:native-claim-did-not-bump-the-version
evidenceCapturedOn: 2026-09-01
assets:
- key: native-claim-did-not-bump-the-version
file: ../../../final/evidence/rendered/native-claim-did-not-bump-the-version.svg
evidence:
- ../../../final/evidence/raw/native-claim-did-not-bump-the-version.txt
source:
- 원본 분석 절은 final/document.md#4-1 · analysis/05 §11.2 이다.
---
# native claim이 @Version을 올리지 않아 충돌을 보고하지 않는 낙관적 잠금이 됐다
네이티브 SQL 로 청구하면서 JPA 버전 컬럼을 올리지 않았다. 청구 전에 로드된 관리 엔티티가 옛 버전을 그대로 들고 있어서, 그 플러시가 성공하면서 리스와 상태를 청구 이전 값으로 덮었다.
## 관계
- **CAS 튜플과 update count가 답이 되는 구조**
네이티브 청구가 쓰는 구조다.
- **Atomic 타입의 존재는 원자성의 증거가 아니다**
잠금 장치가 있다는 것과 그것이 충돌을 보고한다는 것이 다르다는 점에서 같은 계열이다.
## 문제
수신자 전달 청구는 네이티브 SQL 로 이뤄진다. 리스 소유자와 리스 만료와 펜스를 설정하고 상태를 전달 중으로 바꾼다.
같은 테이블에 JPA 엔티티가 매핑되어 있고 그 엔티티에는 버전 컬럼이 있다.
## 결론
네이티브 문장이 버전 컬럼을 함께 올리지 않으면 낙관적 잠금이 무력해진다.
문장 안의 주석이 그 이유를 정확히 적는다. 이것이 JPA 의 버전 컬럼이고 네이티브 쓰기가 그것을 전진시킨다는 것이다. 이것이 없으면 청구 전에 로드된 관리 엔티티가 여전히 옛 버전을 들고 있고, 그 플러시가 성공하면서 리스와 상태를 청구 이전 값으로 덮는다. 충돌이 없다고 보고하는 낙관적 잠금이 되는 것이다.
문제의 성격이 특이하다. 낙관적 잠금은 있고 동작하며 예외를 던지지 않는다. 던지지 않는 이유가 네이티브 문장이 버전이 바뀌었다고 알려 주지 않았기 때문이다.
그래서 증상은 잠금 실패가 아니라 조용한 덮어쓰기다. 청구가 성공했고 다른 트랜잭션의 플러시가 그것을 되돌린다.
수정은 네이티브 문장의 SET 절에 버전 증가를 넣는 것이다. 같은 문장이 펜스도 함께 올린다.
## 검증 환경
OpenJDK : 21.0.12
데이터베이스 : PostgreSQL
확인 방식 : 문장과 그 주석 확인
소스 수정 : x
## 재현 조건
1. 수신자 청구 SQL 의 SET 절을 읽는다.
2. 버전 컬럼 증가 줄과 그 위 주석을 확인한다.
3. 같은 문장이 펜스를 올리는지 확인한다.
4. 같은 테이블에 매핑된 엔티티의 버전 컬럼을 확인한다.
## 본문
<!-- body:start -->
claim이 native `UPDATE`인데 JPA `@Version` 컬럼을 올리지 않으면, claim 전에 로드된 managed 엔티티가 여전히 옛 version을 들고 있다.
## native UPDATE 와 managed 엔티티
:::evidence key="native-claim-did-not-bump-the-version" alt="분석 문서 final/document.md 에서 이 기록의 근거 절을 그대로 잘라낸 18줄. 코드베이스를 측정한 것이 아니라 원본 판정이 무엇을 적었는지를 보여 준다." caption="final/document.md 발췌 — 18줄" zoom="true"
:::
## 충돌을 보고하지 않는 낙관적 잠금이 된다
그 flush가 **성공하면서 lease와 state를 pre-claim 값으로 덮어쓴다.** native statement가 충돌이 있었다고 말해주지 않았기 때문이다.
## 수정은 한 줄이고 주석이 붙어 있다
statement에 `version = d.version + 1`을 넣는다.
## 확인하지 못한 것
청구 전에 로드한 엔티티를 플러시해 덮어쓰기를 재현하지 않았다. 이 기록은 문장과 그 주석에 근거한다.
컨테이너 레인 미실행
<!-- body:end -->