7.3 KiB
7.3 KiB
id, title, category, status, source_trust_level, verification_status, created_at, updated_at, tags, tech_stack, applied_in, aliases
| id | title | category | status | source_trust_level | verification_status | created_at | updated_at | tags | tech_stack | applied_in | aliases | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| db-transaction-patterns-deep | DB Transaction Deep — isolation / SAVEPOINT / advisory lock | Coding | draft | B | conceptual | 2026-05-09 | 2026-05-09 |
|
|
|
DB Transaction Deep
ACID 의 정밀 control. Isolation, SAVEPOINT, advisory lock, FOR UPDATE.
📖 핵심 개념
- ACID: Atomic, Consistent, Isolated, Durable.
- Isolation level (4 가지).
- SAVEPOINT = nested transaction.
- Advisory lock = app-level.
💻 코드 패턴
Basic transaction
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
-- 또는 ROLLBACK;
Isolation levels
READ UNCOMMITTED: dirty read 가능 (Postgres 가 안 implement).
READ COMMITTED: dirty read 안 (Postgres default).
REPEATABLE READ: phantom 안 (range query OK).
SERIALIZABLE: 가장 strict (slow).
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN;
-- ...
COMMIT;
SAVEPOINT (nested)
BEGIN;
INSERT INTO orders (...) VALUES (...);
SAVEPOINT after_order;
INSERT INTO items (...) VALUES (...);
-- Error
ROLLBACK TO after_order;
-- 매 order 보존, items 만 rollback.
COMMIT;
→ Partial rollback.
Explicit lock (FOR UPDATE)
BEGIN;
SELECT * FROM accounts WHERE id = 1 FOR UPDATE;
-- Other transaction 가 wait.
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
COMMIT;
→ Exclusive row lock.
FOR UPDATE SKIP LOCKED (queue)
SELECT * FROM jobs
WHERE status = 'pending'
ORDER BY created_at
LIMIT 1
FOR UPDATE SKIP LOCKED;
-- → Lock 된 row 가 skip. Multi-worker queue.
FOR SHARE
SELECT * FROM users WHERE id = 1 FOR SHARE;
-- → Shared lock. Read 가 OK, write 가 wait.
Advisory lock (app-level)
-- Acquire
SELECT pg_advisory_lock(12345);
-- Critical section
-- ...
-- Release
SELECT pg_advisory_unlock(12345);
→ Cross-transaction. Cron / migration 친화.
Transaction-scoped advisory
SELECT pg_advisory_xact_lock(12345);
-- → Transaction 끝 시 자동 release.
Distributed lock (Postgres)
async function withDistributedLock<T>(key: number, fn: () => Promise<T>): Promise<T> {
const client = await pool.connect();
try {
await client.query('SELECT pg_advisory_lock($1)', [key]);
return await fn();
} finally {
await client.query('SELECT pg_advisory_unlock($1)', [key]);
client.release();
}
}
→ Single Postgres = 작은 system 의 distributed lock.
Optimistic locking (version)
-- Version column
UPDATE users
SET name = 'Bob', version = version + 1
WHERE id = 1 AND version = 5;
-- Affected rows = 0 → conflict.
→ Lock 없음. Retry on conflict.
Read-write split (transaction)
// Read replica
const reader = pool.replica;
const writer = pool.primary;
// Write transaction
await writer.transaction(async (tx) => {
await tx.query('UPDATE ...');
});
// Read after write 의 함정:
// Write tx commit → replica 에 lag.
// → Read 가 stale.
→ Sticky reads.
Long transaction 의 함정
BEGIN;
SELECT * FROM users LIMIT 1;
-- 1 hour 후
COMMIT;
→ Vacuum 가 freeze X. Bloat 누적.
-- Auto kill
SET statement_timeout = '30s';
SET idle_in_transaction_session_timeout = '60s';
Deadlock
Tx A: lock row 1, wait row 2.
Tx B: lock row 2, wait row 1.
→ Postgres detect + abort 1.
try {
await tx.query(...);
} catch (e) {
if (e.code === '40P01') {
// Deadlock — retry.
}
}
Deadlock 방지
- 모든 transaction 가 같은 order 로 lock.
- Short transaction.
- 최소 lock.
Two-phase commit (XA, 2PC)
PREPARE TRANSACTION 'tx-1';
-- 다른 system 도 PREPARE.
-- Coordinator 가 모두 OK.
COMMIT PREPARED 'tx-1';
-- 또는
ROLLBACK PREPARED 'tx-1';
→ Cross-DB. 매우 느린. 거의 안 사용.
→ Backend_Saga_Choreography_vs_Orchestration 가 modern.
Idempotent transaction
INSERT INTO transactions (id, ...) VALUES ($idempKey, ...)
ON CONFLICT (id) DO NOTHING;
-- Retry-safe.
Outbox pattern
BEGIN;
INSERT INTO orders (...);
INSERT INTO outbox (event_type, payload) VALUES ('OrderCreated', ...);
COMMIT;
-- Background process: outbox → publish.
→ DB write + event 가 atomic.
MVCC (Multi-Version Concurrency Control)
Postgres / MySQL InnoDB:
- 매 row 가 version 가 가짐.
- Read 가 lock X (snapshot).
- Write 가 새 version.
→ "Reader 가 writer 가 막지 않음".
Connection pool + transaction
// 매 transaction 가 dedicated connection.
const client = await pool.connect();
try {
await client.query('BEGIN');
await client.query('UPDATE ...');
await client.query('COMMIT');
} finally {
client.release();
}
// 또는 helper
await pool.transaction(async (tx) => {
await tx.query('UPDATE ...');
});
PgBouncer 함정 (transaction mode)
PgBouncer transaction mode:
- 매 transaction = 다른 connection.
- Session-bound feature 안 됨 (LISTEN, prepared, advisory lock).
→ Session mode 또는 raw query.
→ DB_Connection_Pooling_Patterns.
Phantom read (REPEATABLE READ 가 막음)
Tx A: SELECT count(*) FROM users WHERE age > 18 → 100.
Tx B: INSERT new user (age 20).
Tx A: SELECT count(*) → 101 (REPEATABLE READ X) / 100 (REPEATABLE READ).
Serializable conflict
-- Postgres detect + abort.
ERROR: could not serialize access due to concurrent update
SQLSTATE: 40001
→ Retry on 40001.
Performance
SERIALIZABLE: 가장 slow.
REPEATABLE READ: 약간 slow.
READ COMMITTED: default + 빠름.
→ Use case 의 적절.
Real-world tip
1. Default = READ COMMITTED.
2. Transfer / financial = SERIALIZABLE 또는 explicit lock.
3. Long read = SET TRANSACTION READ ONLY.
4. Idempotency key 매 critical write.
5. Optimistic lock + version (web app).
6. Advisory lock (distributed lock).
Distributed transaction (saga)
Cross-DB / cross-service:
- 2PC 안 사용 (slow, brittle).
- Saga + compensation.
- Idempotent step.
→ Backend_Saga_Choreography_vs_Orchestration.
함정
- Long transaction: bloat / lock contention.
- Implicit transaction: ORM 의 auto-commit 무시.
- Lock order 다름: deadlock.
- Retry 무한: 내려.
- 2PC: 느린, 거의 안 사용.
🤔 의사결정 기준
| 작업 | 추천 |
|---|---|
| Read | Default isolation |
| Critical write | SERIALIZABLE / FOR UPDATE |
| Queue | FOR UPDATE SKIP LOCKED |
| Web app | Optimistic + version |
| Distributed lock | pg_advisory_lock |
| Cross-service | Saga (NOT 2PC) |
| Idempotent | Idempotency key |
❌ 안티패턴
- Long transaction: bloat.
- 2PC: brittle.
- No retry on serialize fail: lost.
- 모든 거 SERIALIZABLE: slow.
- Lock order inconsistent: deadlock.
- No timeout: hang.
🤖 LLM 활용 힌트
- READ COMMITTED 가 default.
- FOR UPDATE SKIP LOCKED 가 queue 의 답.
- Advisory lock 가 app-level.
- Saga > 2PC.