Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 62 additions & 2 deletions docs/decisions/r276-lock-root-cause-fix.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# #276 근본 처방 — 재시도가 아니라 «중복 하나가 파티션 끝을 잠그는» 자리를 없앤다

작성: 2026-09-24
상태: **ㄴ(RC) 채택 · 적용 (2026-09-24 사용자 confirm)** — ㄷ(자연키 PK) 비용 측정 착수는 미결정
상태: **ㄴ(RC) 채택 · 적용 (2026-09-24 사용자 confirm)** — ㄷ(자연키 PK) 비용 측정 **착수·설계 확정(2026-11-08)**, rig 미작성 — §5
근거: [`loadtest/results/r276-lock-trace-2026-09-24/`](../../loadtest/results/r276-lock-trace-2026-09-24/README.md)
관련: [#276](https://github.com/Shadowfit/init/issues/276) · [`r276-retry-followup.md`](./r276-retry-followup.md) · [`pose-batch-idempotency-vs-partition.md`](./pose-batch-idempotency-vs-partition.md) · [`online-ddl-vs-blocking-alter.md`](./online-ddl-vs-blocking-alter.md)

Expand Down Expand Up @@ -72,12 +72,72 @@ RR 에서 **중복 키 한 건**이 `PRIMARY` 의 파티션 끝(supremum)에 `X`
## 4. 미결정 (사용자 confirm 필요)

1. ~~ㄴ 을 적용할지~~ → ✅ 적용(아래 결정 로그)
2. ㄷ 의 비용 측정(§3 순서)을 착수할지
2. ~~ㄷ 의 비용 측정(§3 순서)을 착수할지~~ → ✅ 착수(2026-11-08 사용자 confirm). 설계는 §5, 그 안의 미결정은 §5-5
3. #276 에 이 판 결과를 코멘트로 남기고, 이슈 상태 블록을 갱신할지

## 5. ㄷ 비용 측정 설계 (2026-11-08 — 착수·세부 확정, rig 미작성)

### 5-1. 왜 재나

ㄴ(RC)을 고른 이유는 «ㄷ 이 더 근본적이지만 치르는 것 네 줄이 전부 미측정» 이었다(§2 ㄷ 표). 그 판단이
맞았는지는 **ㄷ 의 값을 재야** 말할 수 있다. 이 측정은 ㄴ 을 뒤집으려는 것이 아니라 «보류» 를 «근거 있는
선택» 으로 바꾸려는 것이다 — 결과가 «ㄷ 이 싸다» 로 나오면 ㄴ 을 다시 연다.

### 5-2. 이미 아는 것 — 출발점

- 🟢 **흩어진 삽입의 값은 이미 한 번 쟀다.** [uk-bufferpool AWS 08-23](../../loadtest/results/uk-bufferpool-aws-2026-08-23/README.md)
(`c7i.xlarge`) — **보조 인덱스**만 흩어져도 버퍼풀을 넘으면 유니크 −46%, 비유니크조차 append 대비 29배 느려졌다.
ㄷ 은 **행 전체(클러스터드 인덱스)** 가 흩어지는 경우라 그보다 클 수 있다 — 이것이 ① 의 가설이다
- 🟢 **PK 를 바꾸는 DDL 의 비교 기준이 있다.** [online-ddl AWS 08-12](../../loadtest/results/online-ddl-aws-2026-08-12/README.md)
(`m6i.xlarge`) — 1,000만 행 · 차단 ALTER 68~70초(전 구간 쓰기 정지) ↔ pt-osc 112~114초(최대 정지 30~364ms,
binlog 441MB). 단 그때 DDL 은 `PARTITION BY` 라 INPLACE 가 거절됐다. **PK 교체는 INPLACE 가 되는지부터 모른다**
- 흩어지는 정도 = 동시에 쓰는 운동 세션 수. 이 값은 이미 가정이 있다 —
피크 동시 = DAU × 0.0675([`ai-horizontal-scaling.md:35`](./ai-horizontal-scaling.md)) → DAU 1,000 이면 약 68, DAU 10,000 이면 약 675

### 5-3. 팔

| 팔 | 스키마 |
|---|---|
| **A** (지금) | PK `(id, created_at)` + `uk_pose_event` + `idx_session_timestamp` · RC |
| **B** (ㄷ) | PK `(session_id, rep_number, timestamp_sec, created_at)` + `KEY(id)`(AUTO_INCREMENT 유지) + `idx_session_timestamp` |
| **B′** | B 에서 `idx_session_timestamp` 를 뺀 것 — PK 앞부분(`session_id`)과 겹쳐 중복인지 본다 |

### 5-4. 단계 — §3 의 순서 그대로

**① 삽입 처리량** — 재고표에 없던 «클러스터드 인덱스가 흩어질 때» 를 잰다
- 체제 둘: 메모리 안(풀 2GB) · 버퍼풀 초과(풀을 줄인 전용 컨테이너 — uk-bufferpool rig 과 같은 방식)
- 동시 세션: 68 · 675(위 가정에서 나온 값) — 실제 페이로드처럼 세션마다 배치로
- 지표: 행/초 · 배치 p99 · `Innodb_buffer_pool_reads` · 페이지 분할(`information_schema.innodb_metrics` 의 `index_page_splits`)
- 판: 팔 A·B·B′ × 4블록(첫 블록 버림) 라틴 방격

**② PK 재구성** — 1,000만 행(online-ddl 08-12 와 같은 시더·규모라 직접 비교된다)
- 먼저 소형 복제본에 `ALTER TABLE … DROP PRIMARY KEY, ADD PRIMARY KEY(…), ALGORITHM=INPLACE, LOCK=NONE` 을
던져 **서버가 받는지** 확인한다(08-12 의 1845 거절과 같은 사전 확인)
- 팔: 받으면 INPLACE · 거절되면 COPY, 그리고 pt-osc — `uk_pose_event` 가 있어 pt-osc 가 청크를 나눌 키가 있다
- 지표: DDL 소요 · 최대 쓰기 정지 · binlog 증가 · 디스크 최대(08-12 와 같은 writer)

**③ 세션 단위 읽기** — `findFramesBySessionId` 형태
- A 는 `idx_session_timestamp` → PK 룩업, B 는 PK 범위 스캔. 버퍼풀을 비운 직후(차가움)와 데운 뒤 둘 다
- 덤: id 로 찾는 조회(`findJointCoordinatesById`)가 B 에서 보조 인덱스 + PK 룩업으로 바뀌는 값

### 5-5. 세부 — ✅ 네 건 모두 추천대로 확정 (2026-11-08 사용자 confirm)

1. **박스** — 추천: **① 은 `c7i.xlarge`, ② 는 `m6i.xlarge`** — 각각 비교 기준(08-23 · 08-12)과 같은 타입이라 값을
나란히 놓을 수 있다. ③ 은 ② 의 무대에 얹는다. 둘 다 4 vCPU 라 동시에 띄워도 8 — 계정 한도 32 를 다른 세션
라운드(binlog 재생 RTO)와 나눠 쓴다
2. **② 규모** — 추천: 1,000만 행(08-12 직접 비교). 1억 행은 시딩만 수 시간이라 ①③ 결과를 보고 판단
3. **③ 을 이번에 같이 할지** — 추천: 같이(② 의 무대를 그대로 쓰면 추가 시딩이 없다)
4. **B′ 를 넣을지** — 추천: 넣는다(중복 인덱스 질문이 §2 ㄷ 표에 «검토 안 함» 으로 남아 있다)

## 결정 로그

- 2026-09-24 — 문서 작성. 결정 없음
- 2026-09-24 — **ㄴ 채택 (사용자 confirm).** `PoseDataService.savePoseDataBatch` 에 `@Transactional(isolation = READ_COMMITTED)`.
회귀 가드 `PoseDataResendDeadlockRaceTest`(실 MySQL, 세션 8 × 재전송 20): **RR 로 되돌리면 데드락 63/160 으로 실패, RC 에서 0 으로 통과**를 둘 다 확인했다.
데드락 재시도(상한 5)는 그대로 둔다(§3)
- 2026-11-08 — **ㄷ 비용 측정 착수 (사용자 confirm).** 계기: 자소서에 «PK 재구성 비용을 재지 못해 보류» 라고 적었고,
«그럼 지금은?» 에 답할 값이 없다. 설계 §5 — 팔 A(지금)·B(자연키 PK)·B′(B − `idx_session_timestamp`),
① 삽입 처리량(메모리 안 · 버퍼풀 초과 × 동시 세션 68·675) ② PK 재구성(1,000만 행, INPLACE 가능 여부부터) ③ 세션 단위 읽기.
박스·규모·범위(§5-5 네 건)는 confirm 대기
- 2026-11-08 — **§5-5 네 건 추천대로 확정 (사용자 confirm).** ① `c7i.xlarge` · ②③ `m6i.xlarge` · ② 1,000만 행 ·
③ 같이 · B′ 넣음. 다음은 rig 작성 → 로컬 축소 스모크 → EC2 무인 라운드
Loading