test(db): 락 상호배제를 방언마다 잰다 — 이 사고를 잡을 자리가 없었다 - #10
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
무엇이 비어 있었나
2026-08-25 에 MySQL 의
insertIgnore가 바뀌면서 중복이changes = 1을 돌려줬고,maildrop-lock.ts의acquire()가 그걸 "빈 자리를 잡았다" 로 읽어 살아 있는 남의락을 자기 것으로 착각했다. MySQL 배포에서만 두 세션이 같은 maildrop 을 동시에 열었다.
packages/store/test/maildrop-lock.test.ts는 이걸 못 잡는다 — SQLite 인메모리로만돌아 MySQL 경로를 한 번도 안 타기 때문이다. 잡아낸 것은 어댑터의
changes계약테스트였고, 그 테스트는 "락이 깨진다" 는 말을 하지 않는다. 숫자가 하나 다르다고만 한다.
즉 상호배제 자체를 방언마다 재는 자리가 없었다.
어디에 넣었나
apps/server/test/dialect-contract.test.ts— 네 드라이버(SQLite/PG/MySQL/D1)에 같은계약을 실연결로 거는 자리다. 락 스위트를 방언마다 복제하는 것보다 여기가 맞다.
changes = 0. 방언마다 문장이 다른데(INSERT IGNORE·ON CONFLICT DO NOTHING·INSERT OR IGNORE) 셋이 같은 값을 돌려주는 것이 계약acquire가 지는가 · 만료 전 탈취가 막히는가 · 놓아 주면 다음 세션이 잡는가두 번째의 첫 단언이 2026-08-25 에 MySQL 에서
true였던 자리다.검증 (로컬 · 실물 PostgreSQL 17 · MySQL 8)
②가 요점이다. 잡지 못하는 회귀 테스트는 없는 것과 같으므로,
mysql.ts를 사고 당시형태(
ON DUPLICATE KEY UPDATE)로 되돌려 빨개지는 것을 확인하고 원복했다. 이 PR 은mysql.ts를 건드리지 않는다 — 바뀐 파일은 테스트 하나뿐이다.lint · typecheck 통과.
🤖 Generated with Claude Code