Files
2nd/10_Wiki/Topic_Programming/Coding/DB_Lock_Analysis.md
T
Antigravity Agent 9148c358d0 docs(10_Wiki): 위키 전체 재구성 — Topic_* 폴더를 4개 카테고리로 통합 + 대규모 중복 제거
Topic_Agent/Topic_Blog/Topics/Topics_Biz/Topics_Meeting/Topics_Rag의 마크다운 지식 문서를
Topic_General/Topic_Programming/Topic_Graphic/Topic_Business 4개 카테고리로 재분류.

- 중복 제거: frontmatter의 status:duplicate/merged + duplicate_of/redirect_to 필드로
  자기 자신을 중복으로 선언한 리다이렉트 stub 1032개 제거, 완전 동일 내용 파일 472개 제거,
  동일 파일명·다른 내용 충돌 시 더 큰(완전한) 버전만 유지(162개 제거) — 총 1639개 중복 제거.
- 분류: 폴더 단위로 명확한 항목(AI_and_ML/Coding/Architecture 등 → Programming,
  Comfyui/Visual_Effects → Graphic, Topics_Biz/Topics_Meeting/사업 등 → Business,
  Poetic_Blog_Writing/창의성/Game_Design 등 → General)은 폴더 우선순위로,
  나머지 혼재 폴더(Topic_Agent/Topic_Blog/Topics 루트/Thinking & Reasoning/Other/UI_UX_Assets)는
  title/tags 키워드 스코어링으로 파일 단위 분류(불명확한 경우 General로 폴백).
  원본 폴더명은 "From_*" 서브폴더로 보존해 추적 가능성 유지.
- 최종 배치: Programming 2784 / General 1608 / Graphic 285 / Business 249 = 4926개 문서.
- 에이전트 운영 상태(.astra/.agent/.obsidian/sessions/memory/_company/docs/lessons/_shared/src)는
  지식 콘텐츠가 아니므로 재분류 대상에서 제외하고 원위치 유지.
- Topics/Topic_email(상위 보호 폴더 Topic_email과 파일명 100% 중복) 삭제 — 보호 폴더 자체는 미변경.
- 완전히 비게 된 Topic_Agent/Topic_Blog/Topics_Biz/Topics_Rag 폴더 제거.
2026-07-05 00:33:48 +09:00

6.8 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-lock-analysis Postgres Lock 분석 — Deadlock / Wait / 진단 Coding draft B conceptual 2026-05-09 2026-05-09
database
postgres
lock
vibe-coding
language applicable_to
SQL / Postgres
Backend
pg lock
deadlock
lock_timeout
statement_timeout
blocked queries
FOR UPDATE

Postgres Lock 분석

"느린 query" 가 사실 lock wait. pg_stat_activity + pg_locks 로 누가 누구를 차단. lock_timeout / statement_timeout 으로 무한 hang 방지.

📖 핵심 개념

  • Row-level lock: SELECT FOR UPDATE / DELETE / UPDATE.
  • Table-level lock: ALTER / DROP / VACUUM FULL.
  • Advisory lock: 직접 잡음.
  • Deadlock: 양방 lock — Postgres 가 1개 abort.

💻 코드 패턴

Locking SELECT

-- Row lock (다른 transaction 의 같은 row UPDATE 차단)
SELECT * FROM accounts WHERE id = $1 FOR UPDATE;

-- Skip locked (큐 패턴)
SELECT * FROM jobs WHERE status = 'pending' ORDER BY id LIMIT 1
FOR UPDATE SKIP LOCKED;

-- No wait (즉시 fail)
SELECT * FROM accounts WHERE id = $1 FOR UPDATE NOWAIT;

-- Share (read 차단 X but write 차단)
SELECT * FROM accounts WHERE id = $1 FOR SHARE;

Lock conflicts

SELECT       : ACCESS SHARE
SELECT FOR UPDATE / SHARE : ROW SHARE → ROW EXCLUSIVE
INSERT/UPDATE/DELETE : ROW EXCLUSIVE
CREATE INDEX : SHARE
CREATE INDEX CONCURRENTLY : SHARE UPDATE EXCLUSIVE (덜 차단)
ALTER TABLE  : ACCESS EXCLUSIVE (모두 차단)

→ ALTER TABLE = 모든 query 차단. Concurrent 가능한 변경 사용.

Blocked queries 진단

SELECT
  blocked.pid AS blocked_pid,
  blocked.usename AS blocked_user,
  blocked.query AS blocked_query,
  blocking.pid AS blocking_pid,
  blocking.usename AS blocking_user,
  blocking.query AS blocking_query,
  age(NOW(), blocked.query_start) AS blocked_for
FROM pg_stat_activity blocked
JOIN pg_stat_activity blocking ON blocking.pid = ANY(pg_blocking_pids(blocked.pid))
WHERE NOT blocked.pid = blocking.pid;

→ "이 query 가 저 query 를 대기 중".

Lock timeout

SET lock_timeout = '5s';
-- 5초 안 못 잡으면 ERROR

-- Statement timeout
SET statement_timeout = '30s';
-- 30초 안 안 끝나면 cancel
// App 에서 — 매 connection
await pool.query("SET statement_timeout = '30s'");
await pool.query("SET lock_timeout = '5s'");

또는 connection string:

postgresql://...?options=-c%20statement_timeout=30s

Deadlock

Tx A: UPDATE a → wait for b (held by Tx B)
Tx B: UPDATE b → wait for a (held by Tx A)
→ deadlock — Postgres 가 1개 abort
-- log
log_lock_waits = on
deadlock_timeout = 1s  -- 1초 후 deadlock 검사

→ Deadlock 자주 = 코드에서 lock 순서 통일.

// ❌ 다른 순서
async function transferA(from, to) {
  await update(from, ...);  // lock from
  await update(to, ...);    // lock to
}
async function transferB(from, to) {
  await update(to, ...);    // lock to
  await update(from, ...);  // lock from — deadlock 가능
}

// ✅ 항상 ID 순
async function transfer(from, to) {
  const [first, second] = [from, to].sort();
  await update(first, ...);
  await update(second, ...);
}

Long-running transaction

-- 30분+ transaction 검출
SELECT pid, usename, state, query, age(NOW(), xact_start) AS tx_age
FROM pg_stat_activity
WHERE state != 'idle' AND age(NOW(), xact_start) > INTERVAL '30 minutes';
// 강제 cancel
await db.query('SELECT pg_cancel_backend($1)', [pid]);
// 또는 더 강력
await db.query('SELECT pg_terminate_backend($1)', [pid]);

Idle in transaction (큰 문제)

App 이 BEGIN → 외부 API call → 1분 hang → 다른 트랜잭션 차단
-- 검출
SELECT * FROM pg_stat_activity WHERE state = 'idle in transaction';

-- 한도
ALTER SYSTEM SET idle_in_transaction_session_timeout = '60s';
SELECT pg_reload_conf();

→ 60초 후 자동 cancel.

Migration safety

-- ❌ Big table 에 column 추가 (default = NULL OK, default value 면 lock)
ALTER TABLE orders ADD COLUMN x INT DEFAULT 0;
-- PG 11+ = fast (metadata only). 옛 PG = full rewrite.

-- ❌ NOT NULL 추가
ALTER TABLE orders ADD COLUMN x INT NOT NULL DEFAULT 0;
-- 점진적 migration

-- ✅ Safer pattern (아래 DB_Migration_Safety 참조)

Index — concurrently

-- ❌ Lock 모든 write
CREATE INDEX orders_user ON orders (user_id);

-- ✅ Lock 없이 (느리지만 안전)
CREATE INDEX CONCURRENTLY orders_user ON orders (user_id);

Advisory lock (app 레벨)

-- 단일 instance 가 작업
SELECT pg_advisory_lock(42);  -- bigint key
-- 작업
SELECT pg_advisory_unlock(42);

-- Transaction-scoped (자동 release)
SELECT pg_advisory_xact_lock(hashtext('process-orders'));
COMMIT;

→ Cron / single-leader 패턴.

Lock 통계

SELECT mode, count(*) FROM pg_locks GROUP BY mode;
-- 어떤 종류 lock 가 많은지

-- Wait 시간
SELECT
  query, wait_event_type, wait_event, count(*)
FROM pg_stat_activity
WHERE wait_event IS NOT NULL
GROUP BY 1, 2, 3 ORDER BY count DESC;

App 측 transaction 짧게

// ❌ 트랜잭션 안 외부 호출
await db.transaction(async (tx) => {
  const order = await tx.orders.find(id);
  const result = await fetch(externalApi);  // 1초+
  await tx.orders.update(...);
});  // 1초 lock

// ✅
const order = await db.orders.find(id);
const result = await fetch(externalApi);
await db.transaction(async (tx) => {
  await tx.orders.update(...);  // ms
});

Optimistic vs Pessimistic

-- Pessimistic — lock
SELECT * FROM orders WHERE id = $1 FOR UPDATE;
-- 작업

-- Optimistic — version check
UPDATE orders SET ..., version = version + 1
WHERE id = $1 AND version = $expected_version;
-- 0 row affected = 다른 process 가 변경 — retry

→ Lock contention 적음 = optimistic.

🤔 의사결정 기준

상황 추천
큰 query lock 차단 lock_timeout
일반 prod statement_timeout 30s
Idle in transaction idle_in_transaction_session_timeout
Cron leader Advisory lock
큰 table ALTER concurrent migration patterns
Update conflict Optimistic version

안티패턴

  • Lock timeout 없음: hang. 5-30s 항상.
  • Long transaction (30분+): autovacuum / 다른 query 차단.
  • Idle in transaction 무한: timeout 설정.
  • Lock 순서 다름: deadlock.
  • ALTER TABLE prod + 큰 table: 모두 차단.
  • CREATE INDEX (non-concurrent) prod: write 차단.
  • App 이 외부 API 후 commit: tx 길어짐.

🤖 LLM 활용 힌트

  • statement_timeout + lock_timeout 항상.
  • pg_blocking_pids 로 진단.
  • 트랜잭션 안 외부 호출 금지.

🔗 관련 문서