데이터베이스를 공부하다 보면 흔히 ACID 라는 말을 자주 듣곤 합니다.
아시드..? 애시드?? 이 아이들은 뭐길래 항상 꼭 나올까요??
ACID(원자성, 일관성, 고립성, 지속성)는 데이터베이스 트랜잭션이 안전하게 수행된다는 것을 보장하기 위한 성질을 가리키는 약어이다
위키에 따르면 ACID는 각각 트랜잭션의 수행은 보장해주는 성질들의 약어 입니다.
흐음...ACID를 알기 전에 우선 트랜잭션이 무엇인지 부터 알아야 겠군요
간단히 말해서 트랜잭션은 DB에서 여러 작업의 묶음입니다!
보통 어플리케이션이 DB에 정보를 변경하거나 조회할 때, 보통 여러 개의 쿼리를 날립니다.
계좌에서 인출을 하려고 시도하면
간단한 출금에도 적어도 2개의 쿼리가 나가야 하죠
두 개의 쿼리가 따로 따로 나가면 어떻게 될까요???
물론 정상적으로 잘 작동하면 좋겠지만... 현실은 그렇지 않을 가능성도 있죠
그렇기 때문에 두 개의 작업을 하나의 트랜잭션으로 묶고, 한 번에 작동하도록 하는것입니다!
2개의 쿼리는 모두 반영되거나, 모두 반영되지 않죠!
원자성이란 트랜잭션과 관련된 작업들이 부분적으로 실행되다가 중단되지 않는 것을 보장하는 능력이다.
쉽게말해 원자성이란 하나의 트랜잭션은 완전하게 수행되거나 아예 수행되지 말아야 하는 특성입니다. 트랜잭션이 부분적으로 실행되면 안된다는 말입니다. 왜 그럴까요??
트랜잭션의 원자성이 깨지면 안되는 이유를 가장 대중적이면서 간단한 예제로 알아보도록 하죠!!
바로 계좌 이체 예제입니다
여기 친구 사이인 A와 B가 있습니다. 둘의 계좌에는 각각 2500원씩 있군요.
이때 B가 A에게 1500원을 빌리려고 합니다.
그러면 흔히 우리는 다음과 같이 생각하게 됩니다
위 그림과 같이 A의 계좌에서 B의 계좌로 돈이 이동한다고 생각하게 되죠
하지만 데이터베이스 관점에서 보면 단계가 추가되어야 합니다.
SQL 문을 DB에 날린다고 생각해 봤을때 총 2단계를 거쳐야 합니다.
정리하면 A가 B에게 1500원을 송금하는 트랜잭션 안에는
2개의 SQL 문이 들어있게 됩니다.
여기서 만약 여러 가지 이유(서버, 클라이언트, 사람의 실수 등등)으로 위의 과정에서 1만 DB에 반영되고 2는 반영되지 않았다고 생각해봅시다.
A의 계좌에는 1500원이 인출되어 1000원만 남아있지만 B의 계좌에는 잔액의 변동이 없습니다!!
서로 매우 당황스러워 보이죠 A의 돈이 말 그대로 증발했으니까요
이와 같은 이유 때문에 하나의 트랜잭션 안의 작업들은 전부 다 DB에 반영되거나 아예 반영이 되지 말아야 합니다.
일관성은 트랜잭션이 실행을 성공적으로 완료하면 언제나 일관성 있는 데이터베이스 상태로 유지하는 것을 의미한다. 무결성 제약이 모든 계좌는 잔고가 있어야 한다면 이를 위반하는 트랜잭션은 중단된다.
쉽게 말해 일관성은 DB의 제약조건에 위반되는 작업은 반영될 수 없다는 것을 의미합니다.
아래의 DDL로 생성된 테이블 account 에서 위의 4가지 SQL이 두 개의 트랜잭션(1, 2 / 3, 4)으로 들어왔을 때, 과연 문제가 생길까요??
CREAT TABLE `account`(
`id` bigint auto increment primary key,
`nickname` varchar(10) unique,
`balance` int
);
바로 4번째 SQL 문을 수행할 때 문제가 생깁니다! 이미 A라는 이름을 가진 계좌가 존재하는데 'A' 라는 이름으로 계좌를 추가로 생성하려고 해서 문제가 생깁니다.
제약조건은 위반하면 안되므로 4번째 SQL은 commit 되지 못하고 결국 두 번째 트랜잭션 안의 모든 작업 모두 롤백 됩니다.
독립성은 트랜잭션을 수행 시 다른 트랜잭션의 연산 작업이 끼어들지 못하도록 보장하는 것을 의미한다. 이것은 트랜잭션 밖에 있는 어떤 연산도 중간 단계의 데이터를 볼 수 없음을 의미한다
하나의 트랜잭션이 진행 중이면 다른 트랜잭션은 이를 침투할 수 없다는 특성입니다.
역시 계좌 예제를 보면서 설명을 해보죠
친구인 A, B, C가 오랜만에 만나서 고기를 먹었습니다.
저녁 식사로 총 63,000원이 나왔고 1/N로 나누어서 계산하기로 합니다.
계산은 3명이 각각 하는게 귀찮아서 A가 대표로 계산을 했습니다.
그러면 이제 B, C가 A에게 각각 21,000원씩 보내줘야 하죠
그림을 보면 다음과 같습니다.
앗, 그런데 이때 독립성이 보장되지 않으면 A의 계좌에는 총 42,000원이 아니라 21000원만 입금될 수 있습니다!!
어떻게 된 일일까요???
각각의 트랜잭션이 독립성이 보장되지 않으면 위와 같은 문제가 생길 수 있습니다. B, C의 송금 트랜잭션이 각각 동시에 A의 계좌에 접근을 했고, 동시에 값을 변경시켰으므로 A의 계좌에는 결과적으로 21000원만 입금되게 된 것입니다!!!
위와 같은 동시성 문제를 제어하기 위해서는 대표적으로 Lock이 사용됩니다.
Lock이란 잠금으로 DB에 동시에 여러 개의 트랜잭션이 접근하는 것을 막는 기법입니다.
위에서는 트랜잭션이 하나씩 접근하도록 막으면 문제가 해결됩니다.
다만 하나의 트랜잭션이 Critical Section을 점유하고 다른 트랜잭션은 배척되므로 이에 따라 성능 손실이 발생하게 됩니다.
지속성은 성공적으로 수행된 트랜잭션은 영원히 반영되어야 함을 의미한다. 시스템 문제, DB 일관성 체크 등을 하더라도 유지되어야 함을 의미한다. 전형적으로 모든 트랜잭션은 로그로 남고 시스템 장애 발생 전 상태로 되돌릴 수 있다. 트랜잭션은 로그에 모든 것이 저장된 후에만 commit 상태로 간주될 수 있다.
너무 당연한 소리죠??
한 번 반영된 트랜잭션의 내용은 DB에 영원히 반영되어야 한다.
DB가 멀쩡히 있는 이상 데이터베이스의 내용은 트랜잭션에 따라서 내용이 변경은 되고, 이는 영원하다는 의미입니다.
하지만 트랜잭션이 반영된 데이터가 모종의 이유로 영원히 반영될 수도 있겠죠?? 그래서 로그 등을 남겨서 추후에 DB를 복구할 수 있게 합니다
아니면 가용성 및 내구성 둥을 위해서 DB Replication 을 사용하기도 합니다.
MySQL 기준 Replica 서버를 만드는 타입 중 바이너리 로그 포맷 기반이 위키에서 설명한 로그를 사용합니다.
다른 방식(변경된 값 자체를 저장)과는 다르게 실행된 로그를 저장하는 방식으로 Replica 서버를 생성합니다.