Files
document-haness/docs/clean-architecture-backend-template/tech-log-studio/owner-safe-state-machines/concept/concept-cas-tuple-and-update-count.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

127 lines
5.4 KiB
Markdown

---
kind: CONCEPT
slug: cas-tuple-and-update-count
title: CAS 튜플과 update count가 답이 되는 구조
topic: owner-safe-state-machines
project: clean-architecture-backend-template
status: 게시 전
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
rootTreeNode: concept:cas-tuple-and-update-count
evidenceCapturedOn: 2026-09-01
assets:
- key: cas-tuple-and-update-count
file: ../../../final/evidence/rendered/cas-tuple-and-update-count.svg
- key: cas-tuple-and-update-count-diagram
file: ../../../final/assets/diagrams/cas-tuple-and-update-count.svg
evidence:
- ../../../final/evidence/raw/cas-tuple-and-update-count.txt
source:
- 원본 분석 절은 final/document.md#4-1 · final/document.md#a05 §10.1, §10.3 이다.
---
# CAS 튜플과 update count가 답이 되는 구조
상태를 전이시키는 모든 문장이 소유자 튜플 전체를 where 절에 반복한다. 그래서 갱신 건수가 곧 답이 된다. 한 건이면 이 소유자가 이 리비전에서 여전히 소유자였다는 뜻이고, 0 이면 다른 무언가가 레코드를 움직였다는 뜻이다.
## 관계
- **fenced lease — 만료 시각만으로는 부족한 이유**
이 구조가 강제하는 소유권 모델이다.
- **CAS 튜플을 where 절에 전부 반복하고 update count를 답으로 쓴다**
이 개념을 규칙으로 옮긴 것이다.
- **native claim이 Version을 올리지 않아 충돌을 보고하지 않는 낙관적 잠금이 됐다**
같은 구조가 JPA 버전 컬럼과 만나는 지점의 사례다.
## 본문
<!-- body:start -->
상태 전이를 "읽고 → 판단하고 → PK로 update"하면 그 사이에 takeover한 worker의 상태를 덮어쓴다. 이 저장소의 형태는 소유권 튜플 전체(scope · owner token · attempt · claim operation id · state revision)를 where 절에 반복하고 **update count 자체를 답으로 쓰는** 것이다.
## 판정이 되는 갱신 행 수
:::evidence key="cas-tuple-and-update-count-diagram" alt="소유권 튜플 조건부 UPDATE 에서 한 행 갱신과 영 행 갱신 두 갈래가 나온다" caption="판정이 되는 갱신 행 수" zoom="false"
:::
1이면 이 owner가 그 revision에서 여전히 owner였고, 0이면 다른 무언가가 record를 움직였으니 caller는 자기 view를 현재로 취급하면 안 된다.
## where 절에 반복되는 소유권 튜플
:::evidence key="cas-tuple-and-update-count" alt="분석 문서 final/document.md 에서 이 기록의 근거 절을 그대로 잘라낸 18줄. 코드베이스를 측정한 것이 아니라 원본 판정이 무엇을 적었는지를 보여 준다." caption="final/document.md 발췌 — 18줄" zoom="true"
:::
## polling delivery가 더 얹는 조건
authority `EXISTS` 서브쿼리를 더해 cutover를 가로지르지 못하게 한다.
:::note
컨테이너 레인 미실행 — 동시 claim에서 실제로 0행이 나오는지 관측하지 않았다
:::
## 문장이 답을 만든다
```java
/**
* The statements that advance a claim its owner already holds.
*
* <p>Every one repeats the complete owner tuple — scope, token, attempt, claim operation and state
* revision — in its {@code where} clause, so the update count <em>is</em> the answer: one row means
* this owner was still the owner at this revision, zero means something else moved the record and
* the caller must not treat its own view as current.
*/
```
튜플은 다섯이다. 스코프, 토큰, 시도 번호, 청구 연산, 상태 리비전.
## 읽고 나서 쓰면 안 되는 이유
```java
/**
* Reading the row and then updating on the scope
* alone would let a worker whose lease expired overwrite the state of the one that took over.
*/
```
읽기와 쓰기 사이에 다른 작업자가 들어올 수 있다. 스코프만으로 갱신하면 그 사이의 변화를 보지 못한다.
## 상태 리비전이 함께 오르는 이유
```sql
update idempotency_record
set status = 'EXECUTING',
state_revision = state_revision + 1,
last_transition_operation_id = ?,
last_transition_kind = 'START',
last_transition_result_digest = ?,
```
전이마다 리비전이 오른다. 그래서 같은 소유자라도 자기가 본 리비전이 아니면 갱신이 0 건이 된다. 소유권만으로는 부족하고 시점까지 맞아야 한다.
## 한 자리에 모으는 이유
```java
/**
* <p>Collected here rather than in the store because they are one family: same guard, same
* interpretation of the count, same reason a caller may not skip the guard. The store decides which
* of them a given outcome permits.
*/
```
같은 가드와 같은 해석을 공유하는 문장들을 한 타입에 둔다. 저장소는 어떤 결과에 어떤 전이가 허용되는지만 정한다.
이 분리가 하는 일은 가드를 건너뛰는 문장이 새로 생기지 않게 하는 것이다. 문장이 저장소에 흩어져 있으면 하나가 where 절을 짧게 쓰는 것을 막을 방법이 없다.
## 같은 구조가 다른 곳에도 있다
outbox 폴링 전달 어댑터의 완료 CAS 문장 셋이 같은 형태다. 최종 상태 쓰기가 자기 획득 토큰을 지목하고, 밀려난 작업자의 쓰기는 0 건이 된다.
:::tip
이 구조에서 예외는 실패를 뜻하지 않는다. 갱신 건수 0 은 정상적인 답이고, 그 답을 어떻게 해석할지는 호출자가 정한다. 그래서 경합이 예외 처리 경로가 아니라 정상 경로에 있다.
:::
<!-- body:end -->