본문으로 바로가기

24

ACID 트랜잭션

돈이 사라지지 않게 하는 네 가지 약속

먼저 보면 좋은 강의: 웹 요청의 해부, 데이터베이스 복제

단계별로 보기

스페이스바로 재생/정지, 좌우 화살표로 단계 이동, R로 처음으로 돌아갑니다.

대기 중1 / 4 단계 — 원자성 없이 — 5만 원이 사라진다

철수 잔액에서 5만 원을 뺐습니다. 이제 영희에게 넣어야 하는데, 바로 그 순간 서버가 죽습니다. 철수 돈은 없어졌고 영희는 못 받았습니다. 은행 장부의 총합이 5만 원 줄었습니다. 아무도 훔치지 않았는데 돈이 증발했습니다.

이 강의를 마치면

  • 여러 단계로 이루어진 작업이 중간에 끊길 때 무슨 일이 벌어지는지 설명한다.
  • 원자성·일관성·격리성·지속성이 각각 어떤 사고를 막는 약속인지 구분한다.
  • 트랜잭션이 공짜가 아니며 성능과 맞바꾼 것임을 설명한다.
  • 데이터가 여러 곳에 흩어지면 이 약속이 왜 지켜지기 어려워지는지 판단한다.

5만 원이 사라진 밤

철수가 영희에게 5만 원을 보냅니다. 시스템이 하는 일은 두 가지입니다.

1단계: 철수 잔액 -50,000
2단계: 영희 잔액 +50,000

1단계가 끝나고 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번

이 강의 본문만 근거로 답합니다. 강의에 없는 내용은 지어내지 않습니다.