Files
2nd/10_Wiki/Topics/Coding/DB_Transaction_Patterns_Deep.md
T
2026-05-10 22:08:15 +09:00

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
database
transaction
vibe-coding
language applicable_to
SQL
Database
transaction
isolation
savepoint
advisory lock
nested transaction
explicit lock

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.

Backend_Outbox_Pattern.

MVCC (Multi-Version Concurrency Control)

Postgres / MySQL InnoDB:
- 매 row 가 version 가 가짐.
- Read 가 lock X (snapshot).
- Write 가 새 version.

→ "Reader 가 writer 가 막지 않음".

CS_MVCC_Concurrency.

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.

🔗 관련 문서