9148c358d0
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 폴더 제거.
7.4 KiB
7.4 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 | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| cs-mvcc-concurrency | MVCC — Multi-Version Concurrency Control | Coding | draft | B | conceptual | 2026-05-09 | 2026-05-09 |
|
|
|
MVCC
Postgres / MySQL InnoDB / SQL Server 의 동시성. 각 transaction 가 자체 snapshot 봄. Read 가 write 안 차단. 단 vacuum / cleanup 필요.
📖 핵심 개념
- 매 row = 여러 version (xmin, xmax).
- Transaction 가 시작 시점 snapshot.
- Read = 자기 snapshot 만 봄.
- Write = 새 version 만들고 옛 version 은 dead tuple.
💻 코드 패턴
기본 동작 (Postgres)
-- T1 시작
BEGIN;
SELECT balance FROM accounts WHERE id = 1; -- 100
-- T2 (다른 connection)
BEGIN;
UPDATE accounts SET balance = 200 WHERE id = 1;
COMMIT;
-- T1 다시 read
SELECT balance FROM accounts WHERE id = 1; -- 100 (자기 snapshot)
COMMIT;
-- 새 connection
SELECT balance FROM accounts WHERE id = 1; -- 200
→ Read 가 write 안 차단. 일관 view.
Isolation level
-- Read committed (default Postgres)
BEGIN ISOLATION LEVEL READ COMMITTED;
-- 매 statement 가 fresh snapshot
-- Repeatable read
BEGIN ISOLATION LEVEL REPEATABLE READ;
-- Transaction 시작 시 snapshot — 같은 query = 같은 결과
-- Serializable
BEGIN ISOLATION LEVEL SERIALIZABLE;
-- 가장 강. Conflict 시 abort
xmin / xmax
SELECT xmin, xmax, * FROM accounts WHERE id = 1;
-- xmin = 만든 transaction id
-- xmax = 삭제 / 변경한 transaction id (0 = 활성)
-- Tuple visible 조건:
-- 1. xmin 가 commit 됨 + 자기 snapshot 보다 작거나 같음
-- 2. xmax 가 0 또는 (commit 안 됨 OR 자기 snapshot 보다 큼)
Dead tuple
-- UPDATE
-- 옛 row: xmax = 1234 (삭제됨)
-- 새 row: xmin = 1234
-- DELETE
-- xmax = 1234 (삭제됨)
-- Vacuum 가 dead tuple 정리.
Read consistency 예
-- T1: BEGIN; SELECT 1000개 row;
-- T2 (중간): UPDATE / DELETE 일부
-- T1: 같은 1000개 row 봄 (snapshot)
→ Backup / report 시 일관 view.
Write conflict (concurrent update)
-- T1: BEGIN; UPDATE x SET v = v + 1 WHERE id = 1;
-- T2 (동시): UPDATE x SET v = v + 1 WHERE id = 1;
-- T2 가 wait (lock)
-- T1 commit → T2 진행
-- 결과: v = 2 (둘 다 적용)
-- Serializable 시 T2 abort 가능 (write skew):
-- T1 read x = 5; UPDATE based on 5
-- T2 read x = 5; UPDATE based on 5 (다른 row)
-- → Inconsistent — Serializable 가 detect + abort
SELECT FOR UPDATE
BEGIN;
SELECT * FROM accounts WHERE id = 1 FOR UPDATE; -- row lock
-- 다른 transaction 의 같은 row write 차단
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
COMMIT;
Optimistic concurrency (version)
-- 옛 schema
ALTER TABLE accounts ADD COLUMN version INT NOT NULL DEFAULT 0;
-- App
async function transfer(id: string, amount: number, expectedVersion: number) {
const r = await db.execute(`
UPDATE accounts
SET balance = balance + $1, version = version + 1
WHERE id = $2 AND version = $3
`, [amount, id, expectedVersion]);
if (r.rowCount === 0) throw new ConcurrencyError();
}
→ Lock 없이 conflict 검출.
MVCC 의 장점
+ Read 가 write 차단 X
+ 일관 view (snapshot)
+ Reader / writer 동시
+ Backup / report 가 production 안 멈춤
단점
- Dead tuple 누적 (vacuum 필요)
- Write 가 새 row 만듦 (in-place X)
- Visibility 검사 overhead (작음)
- Long transaction 가 vacuum 차단
Bloat (MVCC 의 비용)
-- 자주 UPDATE = bloat
-- HOT update (index 컬럼 안 변경) = 같은 page 안 — bloat 적음
-- 측정
SELECT relname, n_dead_tup, n_live_tup,
round(100.0 * n_dead_tup / NULLIF(n_live_tup + n_dead_tup, 0), 2) AS dead_pct
FROM pg_stat_user_tables ORDER BY n_dead_tup DESC;
Long transaction = autovacuum 차단
T1: 30분 transaction (read-only OK).
T1 의 snapshot 가 옛 — 그 시점부터 dead tuple 정리 못 함.
→ 다른 table 도 bloat 자라남.
-- 검출
SELECT pid, age(NOW(), xact_start) FROM pg_stat_activity
WHERE state != 'idle' AND xact_start < NOW() - INTERVAL '10 minutes';
Snapshot isolation vs Serializable
Snapshot: Read consistent — but write skew 가능.
Serializable: Write skew 도 차단 — abort + retry.
Postgres SSI (Serializable Snapshot Isolation):
- Snapshot + serialize order detection
- Conflict 시 abort
Write skew 예
-- 두 의사 가 둘 다 on-call 가능.
-- 1명 이상 항상 on-call 보장.
-- T1: SELECT count(*) FROM doctors WHERE on_call = true; -- 2
-- T2: SELECT count(*) FROM doctors WHERE on_call = true; -- 2
-- T1: UPDATE doctors SET on_call = false WHERE id = 'A';
-- T2: UPDATE doctors SET on_call = false WHERE id = 'B';
-- 둘 다 commit
-- 결과: on_call 0명 — invariant 깨짐
→ Snapshot isolation 가 detect 못 함. Serializable 가 한 쪽 abort.
BEGIN ISOLATION LEVEL SERIALIZABLE;
-- T1, T2 둘 다 위 시도 → 한 명 abort + retry
COMMIT;
MySQL InnoDB
Repeatable read (default).
Phantom read 검출 X — 단 InnoDB 의 next-key lock 가 부분 방어.
CockroachDB
Serializable default.
Strong consistency — 분산.
MVCC 가 없는 DB
SQLite: rollback journal — write 가 read 차단.
Old MySQL MyISAM: table lock.
App 측 패턴
async function transfer(from: string, to: string, amount: number) {
return db.transaction(async (tx) => {
const sender = await tx.execute('SELECT balance FROM accounts WHERE id = $1 FOR UPDATE', [from]);
if (sender.balance < amount) throw new Error('insufficient');
await tx.execute('UPDATE accounts SET balance = balance - $1 WHERE id = $2', [amount, from]);
await tx.execute('UPDATE accounts SET balance = balance + $1 WHERE id = $2', [amount, to]);
});
}
→ FOR UPDATE = pessimistic. 또는 optimistic version.
Retry on serializable
async function withRetry<T>(fn: () => Promise<T>, max = 3): Promise<T> {
for (let i = 0; i < max; i++) {
try {
return await fn();
} catch (e) {
if (e.code === '40001' /* serialization_failure */) continue;
throw e;
}
}
throw new Error('max retries');
}
🤔 의사결정 기준
| 상황 | Isolation |
|---|---|
| 일반 OLTP | Read Committed (default) |
| Long report | Repeatable Read |
| Strong invariant | Serializable + retry |
| Counter (concurrent) | UPDATE + atomic |
| Transfer (atomic balance) | FOR UPDATE 또는 Serializable |
❌ 안티패턴
- Long transaction (10분+): vacuum 차단 + bloat.
- Read 결과 그대로 저장 + 변경 X: idempotent 깨짐.
- Concurrent UPDATE 무 lock: lost update.
- Serializable 가정 + retry 없음: random failure.
- Optimistic 가정 + version 없음: 검출 X.
- Vacuum off: dead tuple 폭발.
- Dead tuple 무관심: query 점차 느려짐.
🤖 LLM 활용 힌트
- Postgres / MySQL = MVCC (snapshot).
- Serializable + retry = 강한 안전.
- FOR UPDATE = pessimistic.
- Long transaction 피하기.