24강
ACID 트랜잭션
돈이 사라지지 않게 하는 네 가지 약속
단계별로 보기
스페이스바로 재생/정지, 좌우 화살표로 단계 이동, R로 처음으로 돌아갑니다.
대기 중1 / 4 단계 — 원자성 없이 — 5만 원이 사라진다
철수 잔액에서 5만 원을 뺐습니다. 이제 영희에게 넣어야 하는데, 바로 그 순간 서버가 죽습니다. 철수 돈은 없어졌고 영희는 못 받았습니다. 은행 장부의 총합이 5만 원 줄었습니다. 아무도 훔치지 않았는데 돈이 증발했습니다.
이 강의를 마치면
- 여러 단계로 이루어진 작업이 중간에 끊길 때 무슨 일이 벌어지는지 설명한다.
- 원자성·일관성·격리성·지속성이 각각 어떤 사고를 막는 약속인지 구분한다.
- 트랜잭션이 공짜가 아니며 성능과 맞바꾼 것임을 설명한다.
- 데이터가 여러 곳에 흩어지면 이 약속이 왜 지켜지기 어려워지는지 판단한다.
5만 원이 사라진 밤
철수가 영희에게 5만 원을 보냅니다. 시스템이 하는 일은 두 가지입니다.
1단계: 철수 잔액 -50,000
2단계: 영희 잔액 +50,0001단계가 끝나고 2단계 직전에 서버가 죽었습니다.
결과는 이렇습니다. 철수의 5만 원은 없어졌고, 영희는 받은 게 없습니다. 돈이 세상에서 증발했습니다. 은행 장부의 총합이 5만 원 줄었습니다.
이건 상상이 아닙니다. 아무 대비 없이 이런 코드를 짜면 반드시 일어납니다. 서버는 언제든 죽고, 하필 가장 나쁜 순간에 죽습니다.
그래서 묶는다
트랜잭션은 이렇게 말하는 것입니다.
"이 두 단계는 하나의 덩어리다. 둘 다 되든지, 둘 다 안 되든지. 중간은 없다."
1단계만 끝나고 죽었다면? 데이터베이스가 되살아나면서 1단계를 취소합니다. 철수 잔액이 원래대로 돌아옵니다. 이체는 실패했지만 돈은 그대로입니다.
실패해도 괜찮습니다. 철수가 다시 보내면 되니까요. 돈이 사라지는 게 문제입니다.
네 가지 약속 — ACID
이 보장을 네 개로 나눠 부릅니다. 각각이 어떤 사고를 막는지로 이해하면 쉽습니다.
A — 원자성 (Atomicity): "전부 아니면 전무"
방금 본 그것입니다. 덩어리 안의 모든 단계가 성공해야 반영되고, 하나라도 실패하면 전부 없던 일이 됩니다. 반쯤 된 상태는 존재할 수 없습니다.
C — 일관성 (Consistency): "규칙을 어기는 상태로 끝나지 않는다"
"잔액은 음수가 될 수 없다" 같은 규칙을 정해 두면, 트랜잭션이 끝난 뒤에도 그 규칙이 지켜져 있어야 합니다. 잔액 1만 원인 철수가 5만 원을 보내려 하면 거래 자체가 거부됩니다. 이체하고 나서 수습하는 게 아니라, 애초에 통과시키지 않습니다.
I — 격리성 (Isolation): "동시에 해도 따로 한 것처럼"
이게 가장 어렵고 가장 중요합니다. 잔액 1만 원인 철수가 동시에 두 창구에서 1만 원씩 출금을 시도합니다.
| 창구 A | 창구 B | |
|---|---|---|
| 잔액 확인 | 1만 원 ✓ | 1만 원 ✓ |
| 출금 실행 | -1만 원 | -1만 원 |
| 결과 | 잔액 -1만 원 |
둘 다 "잔액 충분함"을 보고 통과시켰습니다. 1만 원으로 2만 원을 뽑았습니다. 각 거래는 규칙을 지켰는데 합쳐 놓으니 사고가 났습니다.
격리성은 이 둘을 줄 세워서 한 번에 하나씩 처리한 것과 같은 결과를 보장합니다. B는 A가 끝나기를 기다렸다가 잔액 0원을 보고 거부됩니다.
D — 지속성 (Durability): "됐다고 했으면 진짜 된 것"
"이체 완료" 화면이 뜬 0.1초 뒤에 정전이 나도, 전기가 들어오면 그 이체는 거기 있어야 합니다. 완료라고 말하기 전에 데이터베이스는 그 내용을 전원이 꺼져도 남는 곳에 확실히 적어 둡니다.
공짜가 아니다
이제 대가를 봅시다. 이 네 약속은 전부 속도를 내주고 얻은 것입니다.
- 격리성은 줄을 세웁니다. 줄을 세운다는 건 기다린다는 뜻입니다.
같은 데이터를 건드리는 거래가 많아질수록 대기가 길어집니다.
- 지속성은 디스크에 확실히 쓸 때까지 응답을 미룹니다. 메모리에만
적으면 100배 빠르지만, 그건 약속을 깨는 겁니다.
그래서 판단이 필요합니다.
| 트랜잭션 | 이유 | |
|---|---|---|
| 계좌 이체, 결제, 재고 차감 | 반드시 | 틀리면 돈이 사라진다 |
| 조회수, 좋아요 수 | 대개 안 씀 | 하나쯤 틀려도 아무도 안 죽는다 |
| 로그, 접속 기록 | 안 씀 | 속도가 정확성보다 중요하다 |
"무조건 안전하게"가 아니라 "이 데이터가 틀리면 무슨 일이 생기는가"를 묻는 것이 옳은 접근입니다.
그리고 이 약속이 깨지는 곳
ACID는 데이터가 한 곳에 있을 때 지킬 수 있는 약속입니다. 한 데이터베이스 안에서는 줄을 세우고 취소하는 게 가능합니다.
그런데 앞 강의에서 우리는 데이터를 여러 샤드에 나눴습니다. 철수는 1번 샤드, 영희는 3번 샤드에 있다면? 두 데이터베이스에 걸친 이체입니다. 1번에서 빼는 건 성공했는데 3번에 넣다가 실패하면, 누가 1번을 되돌려 줍니까?
아무도 안 해줍니다. 그래서 샤딩하는 순간 트랜잭션이라는 안전장치를 잃습니다. 이것이 샤딩을 마지막 카드로 미루는 진짜 이유입니다. 분산 트랜잭션이라는 기술이 있지만, 느리고 복잡하고 여전히 어렵습니다.
이 강의가 단순화한 것
교육용 모형은 언제나 무언가를 생략합니다. 무엇을 생략했는지 아는 것도 학습의 일부입니다.
전제한 것
- 이 강의의 시뮬레이터에는 트랜잭션이라는 개념이 아예 없다. 장면들은 저자가 손으로 짠 각본이며 엔진이 실제로 롤백이나 잠금을 수행하는 것이 아니다. 이 강의는 개념을 눈으로 보여주는 것이 목적이고, 캔버스의 숫자는 설명을 위해 고른 값이다.
- 격리 수준을 '줄을 세운다'는 하나의 그림으로 단순화한다. 실제 데이터베이스는 여러 격리 수준을 제공하며, 대부분의 서비스는 기본값으로 가장 엄격한 수준을 쓰지 않는다. 즉 현실의 기본 설정은 이 강의가 약속한 것보다 느슨하다.
- 일관성(C)은 나머지 셋과 성격이 다르다. A·I·D는 데이터베이스가 제공하는 기능이지만, C는 개발자가 규칙을 선언해야 데이터베이스가 지켜줄 수 있는 것이다.
- 지속성을 '전원이 꺼져도 남는다'로 단순화한다. 실제로는 디스크 캐시, 파일시스템, 하드웨어 각 층위마다 '정말 썼는가'가 달라지며 여기서 데이터를 잃는 사고가 흔하다.
다루지 않은 것
- 격리 수준(READ COMMITTED, REPEATABLE READ, SERIALIZABLE)과 각각이 허용하는 이상 현상
- 잠금(lock)과 다중 버전 동시성 제어(MVCC)의 실제 동작
- WAL(선행 기록 로그)과 체크포인트
- 2단계 커밋과 분산 트랜잭션의 실제 절차
더 읽을거리: PostgreSQL Documentation — Transaction Isolation · A Critique of ANSI SQL Isolation Levels (Berenson et al., 1995)
이제 직접 만들어 보세요
읽어서 아는 것과 만들어서 아는 것은 다릅니다. 컴포넌트를 배치하고 트래픽을 흘려서 실제로 동작하는지 확인해 보세요.
이 강의에 물어보기
하루 5번이 강의 본문만 근거로 답합니다. 강의에 없는 내용은 지어내지 않습니다.