Skip to content

test(db): 락 상호배제를 방언마다 잰다 — 이 사고를 잡을 자리가 없었다 - #10

Merged
mack-erel merged 1 commit into
mainfrom
test/lock-dialect-contract
Aug 26, 2026
Merged

test(db): 락 상호배제를 방언마다 잰다 — 이 사고를 잡을 자리가 없었다#10
mack-erel merged 1 commit into
mainfrom
test/lock-dialect-contract

Conversation

@mack-erel

Copy link
Copy Markdown
Contributor

무엇이 비어 있었나

2026-08-25 에 MySQL 의 insertIgnore 가 바뀌면서 중복이 changes = 1 을 돌려줬고,
maildrop-lock.tsacquire() 가 그걸 "빈 자리를 잡았다" 로 읽어 살아 있는 남의
락을 자기 것으로 착각했다.
MySQL 배포에서만 두 세션이 같은 maildrop 을 동시에 열었다.

packages/store/test/maildrop-lock.test.ts 는 이걸 못 잡는다 — SQLite 인메모리로만
돌아 MySQL 경로를 한 번도 안 타기 때문이다.
잡아낸 것은 어댑터의 changes 계약
테스트였고, 그 테스트는 "락이 깨진다" 는 말을 하지 않는다. 숫자가 하나 다르다고만 한다.

상호배제 자체를 방언마다 재는 자리가 없었다.

어디에 넣었나

apps/server/test/dialect-contract.test.ts — 네 드라이버(SQLite/PG/MySQL/D1)에 같은
계약을 실연결로 거는 자리다. 락 스위트를 방언마다 복제하는 것보다 여기가 맞다.

계약 내용
insertIgnore 승인 분기 중복이면 changes = 0. 방언마다 문장이 다른데(INSERT IGNORE · ON CONFLICT DO NOTHING · INSERT OR IGNORE) 셋이 같은 값을 돌려주는 것이 계약
maildrop 락 상호배제 같은 시각의 두 번째 acquire 가 지는가 · 만료 전 탈취가 막히는가 · 놓아 주면 다음 세션이 잡는가

두 번째의 첫 단언이 2026-08-25 에 MySQL 에서 true 였던 자리다.

검증 (로컬 · 실물 PostgreSQL 17 · MySQL 8)

① 고쳐진 코드   sqlite ✔  postgres ✔  mysql ✔    3 pass / 0 fail
② 버그 되살림   mysql ✖  종료코드 1              ← 실제로 잡는다

②가 요점이다. 잡지 못하는 회귀 테스트는 없는 것과 같으므로, mysql.ts 를 사고 당시
형태(ON DUPLICATE KEY UPDATE)로 되돌려 빨개지는 것을 확인하고 원복했다. 이 PR 은
mysql.ts 를 건드리지 않는다 — 바뀐 파일은 테스트 하나뿐이다.

lint · typecheck 통과.

🤖 Generated with Claude Code

2026-08-25 에 MySQL 의 `insertIgnore` 가 `INSERT IGNORE` 에서
`ON DUPLICATE KEY UPDATE` 로 바뀌면서 중복이 `changes = 1` 을 돌려줬다.
`maildrop-lock.ts` 의 `acquire()` 는 그 값을 "빈 자리를 잡았다" 로 읽으므로
**살아 있는 남의 락을 자기 것으로 착각했다** — MySQL 배포에서만 두 세션이 같은
maildrop 을 동시에 열었다.

`packages/store/test/maildrop-lock.test.ts` 는 그걸 못 잡는다. SQLite 인메모리로만
돌아 MySQL 경로를 한 번도 안 타기 때문이다. 잡아낸 것은 어댑터의 `changes` 계약
테스트였고, 그 테스트는 "락이 깨진다" 는 말을 하지 않는다 — 숫자가 하나 다르다고만
말한다. **상호배제 자체를 방언마다 재는 자리가 없었다.**

방언 계약 테스트에 둘을 넣는다. 여기는 네 드라이버에 같은 계약을 실연결로 거는
자리이므로, 락 스위트를 방언마다 복제하는 것보다 여기가 맞다.

  ① insertIgnore 승인 분기 — 중복이면 changes 가 0. 방언마다 문장이 다른데
     (`INSERT IGNORE` · `ON CONFLICT DO NOTHING` · `INSERT OR IGNORE`) 셋이 같은 값을
     돌려주는 것이 계약이다. 표현이 다른데 의미가 같아야 하는 자리라 실연결로만 잰다.
  ② maildrop 락 상호배제 — 같은 시각의 두 번째 acquire 가 지는가, 만료 전 탈취가
     막히는가, 놓아 주면 다음 세션이 잡는가(영구 잠금이 아닌가).

②의 첫 단언이 2026-08-25 에 MySQL 에서 true 였던 자리다.

검증(로컬, 실물 PG 17 · MySQL 8):

  고쳐진 코드   sqlite ✔ postgres ✔ mysql ✔   (3 pass / 0 fail)
  버그 되살림   mysql ✖ 종료코드 1            ← 실제로 잡는다

두 번째가 요점이다. 잡지 못하는 회귀 테스트는 없는 것과 같으므로, `mysql.ts` 를 사고
당시 형태로 되돌려 빨개지는 것을 확인하고 원복했다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@mack-erel
mack-erel merged commit 724820c into main Aug 26, 2026
9 checks passed
@mack-erel
mack-erel deleted the test/lock-dialect-contract branch August 26, 2026 07:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant