전부 성공 아니면 전부 취소 — 트랜잭션과 ACID
포인트 이체에서 차감만 되고 적립이 실패하면 데이터가 깨진다. "절반만 반영된 상태"가 아예 존재할 수 없게 만드는 트랜잭션과 ACID를, FTP로 파일 절반만 올라가던 그 아찔함에 빗대 정리한다.
서버에서 DB를 다루다 보면 여러 쓰기 작업이 반드시 함께 성공해야 하는 순간이 와요.
포인트 이체가 대표적이에요.
A에서 차감, B에 적립 — 둘 중 하나만 실행되면 데이터가 깨져요.
퍼블리싱 시절, FTP로 파일 여러 개를 올리다 절반만 올라가서 사이트가 깨져 보이던 그 아찔함, 기억나시죠.
트랜잭션은 바로 그 “절반만 반영된 상태”가 아예 존재할 수 없게 만드는 장치예요.
작업들을 하나의 묶음으로 묶어서, 전부 성공하면 반영(커밋), 하나라도 실패하면 전부 취소(롤백)해요.
// Next.js Server Action + Prisma
'use server';
export async function transferPoints(fromId: string, toId: string, amount: number) {
await prisma.$transaction(async (tx) => {
await tx.user.update({ where: { id: fromId }, data: { points: { decrement: amount } } });
await tx.user.update({ where: { id: toId }, data: { points: { increment: amount } } });
}); // 안에서 하나라도 throw되면 둘 다 롤백
}
만약 $transaction 없이 update를 그냥 두 번 실행하면, 각 update가 독립적으로 즉시 커밋돼요.
두 번째에서 에러가 나도 첫 번째 차감은 이미 반영된 뒤라 되돌릴 수 없고요.
”저장은 됐는데 데이터가 반쯤 이상하다”는 버그의 상당수가 여기서 나와요.
이 성질을 정리한 말이 ACID예요.
| 글자 | 성질 | 뜻 |
|---|---|---|
| A | 원자성(Atomicity) | 전부 아니면 전무 |
| C | 일관성(Consistency) | 규칙을 위반한 상태는 만들지 않음 |
| I | 격리성(Isolation) | 진행 중인 중간 상태를 다른 요청이 못 봄 |
| D | 지속성(Durability) | 커밋된 건 장애가 나도 남음 |
특히 격리성이 실무에서 헷갈려요.
”차감은 됐는데 적립은 아직 안 된” 중간 상태를, 동시에 들어온 다른 요청이 읽지 못하게 막는 성질이에요.
이체 도중에 잔액을 조회한 사람이 이상한 값을 보지 않도록 하는 거죠.
Server Action이나 API 라우트에서 두 테이블 이상을 함께 수정하는 순간 트랜잭션이 필요해요.
DB 인덱스가 읽기의 기본기라면, 트랜잭션은 쓰기의 기본기고요.
여러 쓰기를 함께 해야 할 때 물어보세요.
하나만 성공하면 데이터가 깨지나.
그렇다면 트랜잭션으로 묶어서, 절반만 반영된 상태가 애초에 불가능하게 만들면 돼요.
댓글