- 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>
127 lines
5.4 KiB
Markdown
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 -->
|