ACID
ACID
개념
작업 묶음이 중간에 끊기거나 서로 겹쳐도 데이터가 망가지지 않도록 데이터베이스가 지키는 네 가지 성질입니다.
등장 배경
데이터를 고치는 일은 대개 한 줄로 끝나지 않습니다. 계좌이체 한 번에도 보내는 쪽 잔액을 줄이고, 받는 쪽 잔액을 늘리고, 거래 기록을 한 줄 남기는 세 가지 작업이 들어갑니다. 문제는 이 셋 사이에 짧은 틈이 있다는 것입니다. 첫 번째와 두 번째 사이에 서버가 죽으면 보낸 쪽 돈만 사라진 채로 남습니다.
같은 줄을 두 사람이 동시에 건드릴 때도 비슷한 일이 생깁니다. 남은 좌석이 하나인 상품을 두 사람이 같은 순간에 결제하면, 둘 다 "1개 남음" 을 읽고 둘 다 0으로 바꿔 적어 재고는 0인데 주문은 두 건이 됩니다. 경쟁 상태라고 부르는 상황입니다.
초기에는 이런 사고를 프로그램이 직접 막았습니다. 실패하면 앞서 한 것을 되돌리는 코드를 기능마다 손으로 짜 넣는 식이었습니다. 되돌리는 코드가 또 실패하면 어떻게 할지까지 생각해야 했고, 기능이 늘수록 이 방어 코드가 제각각이 되어 어디가 안전한지 아무도 말하지 못하게 됐습니다. 그래서 이 보장을 프로그램 밖으로 꺼내 데이터베이스가 책임지도록 정리했고, 그때 정리한 네 가지 약속에 붙은 이름이 ACID 입니다.
작동 방식
여러 작업을 트랜잭션 하나로 묶어 보내면 데이터베이스가 그 묶음에 네 가지를 보장합니다. 트랜잭션은 전부 성공하거나 전부 없던 일이 되어야 하는 작업 묶음이고, 개발자가 시작과 끝을 코드로 표시해 줍니다.
- 원자성 — 묶음 안의 작업이 전부 반영되거나 하나도 반영되지 않습니다. 이체 도중 전원이 나가면 보낸 쪽 잔액도 원래대로 돌아갑니다. 실제로는 데이터를 고치기 전에 무엇을 고칠지를 기록에 먼저 적어 두고, 중간에 끊기면 그 기록을 거꾸로 읽어 되돌립니다.
- 일관성 — 규칙을 어긴 상태로는 끝나지 않습니다. 잔액이 0보다 작아질 수 없다거나 없는 회원의 주문은 못 만든다는 규칙을 걸어 두면, 그 규칙을 깨는 묶음은 통째로 거절됩니다.
- 격리성 — 동시에 도는 묶음끼리 서로의 중간 상태를 보지 못합니다. 앞의 좌석 문제에서 먼저 들어온 묶음이 그 줄을 붙잡고 있는 동안 뒤의 묶음은 기다리고, 차례가 오면 이미 0으로 바뀐 값을 읽어 결제를 막습니다.
- 지속성 — 성공했다고 답한 뒤에는 그 결과가 남습니다. 답을 돌려주기 전에 디스크에 먼저 적기 때문에, 직후에 서버가 꺼져도 다시 켰을 때 그 내용이 그대로 있습니다.
넷은 서로 다른 사고를 막습니다. 원자성과 지속성은 중간에 끊기는 사고를, 일관성과 격리성은 다른 작업이나 규칙과 부딪히는 사고를 막습니다. 넷 중 성능과 가장 자주 부딪히는 것은 격리성입니다. 완전히 줄을 세우면 안전하지만 느려져서, 데이터베이스는 격리 수준을 몇 단계로 나눠 두고 기능마다 고르게 합니다. 돈이 걸린 기능은 엄격한 단계로, 조회 화면은 느슨한 단계로 둡니다.
쓰이는 곳
이 성질이 필요한지가 데이터베이스를 고르는 기준입니다. 관계형 데이터베이스는 행과 열이 있는 표에 담고 표끼리 값으로 이어 두는 계열이고, 이 네 가지를 기본 제공합니다. NoSQL은 표 모양을 미리 정하지 않는 대신 초기에는 이 보장을 기록 하나 단위로만 주는 제품이 많았습니다. 지금은 여러 기록을 묶어 주는 제품도 늘었지만 어디까지 보장하는지는 제품마다 달라 문서를 확인하고 골라야 합니다.
기능 설계에서도 기준입니다. 돈, 재고, 권한처럼 어긋나면 사람이 손으로 수습해야 하는 값은 한 트랜잭션에 묶습니다. 반대로 조회수 집계나 활동 로그처럼 한두 건 어긋나도 서비스가 무너지지 않는 값은 굳이 묶지 않습니다. 묶을수록 다른 요청이 기다리는 시간이 길어지기 때문입니다.
범위를 아는 것도 쓰임입니다. 보장은 데이터베이스 한 대 안에서만 성립합니다. 데이터를 여러 서버에 쪼개 담는 샤딩을 하거나 서비스를 나눠 각자 데이터베이스를 갖게 하면, 서버를 넘나드는 작업은 이 네 가지 밖으로 나갑니다. 그때는 실패한 부분을 보상하는 절차를 따로 짜고, 같은 요청을 여러 번 보내도 결과가 같도록 멱등성을 맞춥니다.
실무 용례
"결제 기록이랑 좌석 수 증가를 한 트랜잭션으로 묶어야죠. ACID 가 보장해 줍니다."
둘을 따로 처리하면 결제만 되고 좌석은 안 늘어나는 상태가 생길 수 있으니 묶자는 제안입니다.
"이 작업은 서비스 두 개에 걸쳐 있어서 ACID 로 안 됩니다."
한 데이터베이스 안에서 끝나지 않는 일이라 보장 범위 밖이고, 실패했을 때 되돌리는 절차를 직접 짜야 한다는 뜻입니다.
"격리 수준을 한 단계 낮춰도 되는 화면인지 먼저 확인해 주세요."
정확도를 조금 양보하고 속도를 얻어도 되는 자리인지 묻는 말입니다.
페이지덱에서 이 이야기가 나온 자리는 무료 팀이 유료로 넘어가는 결제 흐름이었습니다. 결제가 끝나면 요금제를 유료로 바꾸고, 좌석 수를 계약한 만큼 늘리고, 대기 중이던 멤버를 활성화하는 세 가지가 이어져야 했습니다. 초기 구현은 셋을 따로 돌렸고, 알림 발송에서 오류가 나 중간에 멈춘 팀이 몇 곳 생겼습니다.
증상은 제각각이었습니다. 어떤 팀은 요금이 빠져나갔는데 좌석이 그대로였고, 어떤 팀은 좌석은 늘었는데 대기 멤버가 들어오지 못했습니다. 태오가 세 작업을 한 트랜잭션으로 묶고 알림은 묶음 밖으로 빼면서 정리됐습니다. 알림은 실패해도 다시 보내면 그만이라 같은 묶음에 넣을 이유가 없었습니다.
흔한 오해
ACID 를 쓰면 데이터가 어긋날 일이 없다고 여기는 경우가 많습니다. 데이터베이스가 보장한다고 하니 거기까지 믿게 되는 것입니다. 보장의 단위는 한 데이터베이스 안에서 도는 한 트랜잭션입니다. 결제는 결제 대행사에 요청하고 좌석은 우리 쪽에 적는 작업처럼 경계를 넘나드는 일에는 이 보장이 닿지 않습니다. 이것을 모르고 설계하면 외부 결제는 성공했는데 내부 기록은 없는 건이 쌓이고, 그때부터는 매달 사람이 양쪽 목록을 맞춰 손으로 메워야 합니다.
네 글자를 모두 켜 두면 언제나 더 안전하다는 것도 오해입니다. 안전과 속도가 같은 방향이 아니라서 그렇습니다. 격리 수준을 가장 엄격하게 두면 같은 줄을 건드리는 요청이 줄을 서서 기다리고, 몰리는 시간대에는 대기가 길어져 응답이 밀립니다. 그래서 실제 설계는 기능마다 어느 값이 어긋나면 안 되는지를 정하고 그 값에만 엄격한 수준을 겁니다.
같이 보면 좋은 페이지
마지막 업데이트 2026-09-16