[Database] ACID
트랜잭션은 데이터베이스 작업의 단위로, 여러 쿼리의 집합으로 생각하면 편하다.
가장 쉽게 설명하는 예시로는 계좌이체가 있는데.. 계좌이체 예시와 트랜잭션을 안전하게 처리하는 주요 특성인 ACID에 대해 살펴보자.
RDB는 ACID 속성을 엄격하게 준수하고, 락 기반으로 동시성을 제어하지만 NoSQL 기반 데이터베이스는 종류에 따라 ACID 속성을 사용하지 않고 확장성과 성능을 향상시키는 경우도 있다.
Atomicity
트랜잭션 안에 있는 모든 쿼리는 모두 성공하거나 실패해야한다는 속성이다.
계좌이체는 출금과 입금으로 이루어지는데.. 출금 쿼리만 성공하고 DB서버가 죽어버리면 DB서버가 다시 시작할 때 트랜잭션을 롤백한다. (clean up)
낙관적 트랜잭션 처리를 사용하는 데이터베이스에서는 트랜잭션이 실행되는 동안 데이터에 락을 걸지 않고, 트랜잭션이 커밋되는 시점에 데이터 충돌 여부를 확인한다.
트랜잭션이 데이터를 읽을 때 스냅샷을 만들고 커밋되려고 할 때 다른 트랜잭션이 해당 데이터를 변경했는지를 검사해 변경이 없다면 커밋, 변경이 있다면 실패로 간주한다.
충돌이 적은 경우 높은 성능을 보장할 수 있다.
비관적 트랜잭션 처리를 사용하는 데이터베이스에서는 충돌 가능성을 높게 보고 트랜잭션 실행 중 관련 데이터에 락을 걸어 다른 트랜잭션의 접근을 제한한다.
데이터의 충돌을 방지할 수 있지만 데드락이 발생할 수 있다.
Write Ahead Logging은 데이터베이스에서 사용하는 로깅 방식으로, 데이터를 디스크에 저장하기 전 로그에 변경 사항을 기록하는 방식이다.
데이터베이스의 clean up 작업에 사용되는 로그를 기록해 시스템 장애에 대응할 수 있도록 한다.
트랜잭션 처리 방식과 WAL은 모두 트랜잭션의 Atomicity를 보장하기 위한 매커니즘이다.
Isolation
여러 트랜잭션이 동시에 실행될 때 각 트랜잭션이 다른 트랜잭셔의 연산과는 독립적으로 실행돼야 함을 나타내는 속성이다.
데이터베이스는 4가지 격리 수준을 제공한다.
Read Uncommitted : 가장 낮은 격리수준으로 각 트랜잭션은 다른 트랜잭션에서 커밋되지 않은 데이터를 읽을 수 있다.
Read Committed : 각 트랜잭션은 다른 트랜잭션에서 커밋된 데이터만 읽을 수 있다.
Repeatable Read : 트랜잭션이 시작됐을 때 읽은 데이터를 보장한다.
Serializable : 모든 트랜잭션이 순차적으로 실행돼 동시성 이슈는 없지만 성능이 떨어진다.
한 트랜잭션에서 아직 커밋되지 않은 변경사항을 다른 트랜잭션이 읽는 경우를 Dirty Read라고 부른다.
트랜잭션2에서 상품을 Update 후 롤백해 트랜잭션1은 존재하지 않는 데이터를 읽게 된다.
격리 수준을 Read Committed 이상으로 설정하면 방지할 수 있다.
같은 데이터를 다루는 트랜잭션 두 개를 동시에 시작하고 두 번째 트랜잭션을 커밋한 후 첫 번째 트랜잭션에서 SELECT 시 첫 SELECT와 다른 결과를 발생하는 경우를 Non-repeatable read 라고 부르고, Repeatable Read 수준부터는 방지할 수 있다.
Non-repeatable read에서 UPDATE를 INSERT로 바꾸면 Phantom read가 발생한다.
PostgreSQL은 Multi Version Concurrency Control을 사용해 각 트랜잭션에 데이터의 스냅샷을 제공한다.
데이터의 각 행에는 생성되거나 변경된 시점을 나타내는 TIMESTAMP가 있고, 해당 행이 어떤 트랜잭션에 해당하는지 알아낼 때 사용된다.
MySQL의 InnoDB도 MVCC를 사용하지만, 버전 관리 대신 undo 로그를 사용한다.
InonDB는 변경된 데이터에 대한 이전 버전을 undo 로그에 저장하고 트랜잭션이 롤백되거나 다른 트랜잭션이 과거 데이터를 읽어야 할 때 사용한다.
PostreSQL에서 Repeatable Read 격리 수준은 트랜잭션이 시작될 때의 데이터 스냅샷을 유지하기에 다른 트랜잭션에서 데이터를 INSERT 하더라도 현재 트랜잭션에는 영향을 미치지 않아 Phantom read가 발생하지 않는다.
반면 다른 RDB에서는 Repeatable Read 격리 수준에서도 MVCC 매커니즘의 차이로 Phantom Read가 발생할 수 있다.
이번엔 UPDATE가 겹치는 경우 발생하는 Lost updates이다.
예시에서는 트랜잭션1 에서의 UPDATE가 덮어써지고 트랜잭션2의 변경사항만 반영되는 상황이다.
역시 Repeatable Read 수준부터 방지할 수 있다.
Row Level Lock, Table Lock, Page Lock으로 Lost update를 방지할 수 있다.
데이터베이스에서 트랜잭션간의 격리 수준을 구현하기 위해 다양한 락 매커니즘을 사용하는데, 락 상태를 기억해야하니 비용이 많이 소모된다.
일반적으로 트랜잭션의 격리 수준에 따라 필요한 락을 자동으로 적용하는데, 일부 사오항에서는 LOCK TABLE, SELECT FOR UPDATE 구문으로 락을 명시적으로 지정할 수 있다.
Consistency
데이터베이스가 일관된 상태를 유지하도록 보장하는 개념으로, 세 가지로 나눠서 생각하면 편하다.
Read Consistency : 트랜잭션이 실행되는 동안 해당 트랜잭션이 읽는 데이터는 항상 일관된 상태를 유지한다.
Eventual COnsistency : 데이터베이스가 Master와 Replica로 분리되는 경우 모든 복제본이 최종적으로 일관된 상태에 도달함을 의미한다.
Data Consistency : PK, FK, UNIQUE, NOT NULL, CASCADING 등 데이터베이스의 데이터가 정의된 규칙과 제약 조건을 준수함을 의미한다.
Master/Replica 로 구성되는 데이터베이스 시스템은 데이터베이스의 설정 파일을 조작해 비동기 복제 / 동기 복제를 지정할 수 있고, 내부 동작은 데이터베이스 시스템마다 다른데.. 보통 파일 기반 복제나 쿼리 기반 복제 방식을 사용한다.
Durability
커밋된 트랜잭션이 영구적으로 데이터베이스에 반영되어야 함을 의미한다.
앞서 언급한 WAL으로 데이터베이스의 변경 사항은 먼저 로그 파일에 기록하고, 로그를 통해 언제든 데이터를 복구할 수 있고, 정기적으로 체크포인트를 설정해 현재 상태를 디스크에 저장한다.
일반적으로 데이터베이스 시스템은 성능 최적화를 위해 데이터를 운영체제의 캐시에 임시로 저장하고 추후 디스크에 영구적으로 기록하는데, 캐시에만 존재하고 아직 디스크에 기록되지 않은 데이터는 DB가 죽었을 때 손실될 수 있다.
따라서 데이터베이스를 주기적으로 디스크에 플러시해 Durability를 보장해야 한다.
운영체제에서 fsync() 호출로 파일 시스템에 있는 변경사항을 디스크에 즉시 기록할 수 있지만.. 디스크 IO 작업을 증가시키니 성능이 떨어진다.
일반적으로 데이터베이스 시스템에서는 트랜잭션을 명시적으로 시작하지 않더라도 자동으로 트랜잭션을 시작하는 자동 커밋 모드를 지원한다.
자동 커밋 모드에서 각 트랜잭션에는 하나의 쿼리가 포함되니 복잡한 작업이 필요한 경우 트랜잭션을 명시적으로 설정하자.
스프링에서는 @Transactional 애너테이션을 사용해 해당 애너테이션이 붙은 메서드의 실행 범위 내에서 수행되는 모든 쿼리를 하나의 트랜잭션으로 묶는다.
ACID는 트랜잭션을 보조하는 속성이다.
SELECT에도 트랜잭션을 설정할지, 아니면 UPDATE DELETE INSERT 에만 트랜잭션을 설정할지는 상황에 따라 ACID 속성을 참고해서 결정하자.
'Database > Database' 카테고리의 다른 글
[Database] 동시성 처리 (0) | 2024.04.13 |
---|---|
[Database] Sharding (0) | 2024.04.08 |
[Database] Partitioning (0) | 2024.04.04 |
[Database] B+Tree 자료구조 (0) | 2024.03.30 |
[Database] 내부 저장 구조 (0) | 2024.01.29 |
댓글
이 글 공유하기
다른 글
-
[Database] Sharding
[Database] Sharding
2024.04.08 -
[Database] Partitioning
[Database] Partitioning
2024.04.04 -
[Database] B+Tree 자료구조
[Database] B+Tree 자료구조
2024.03.30 -
[Database] 내부 저장 구조
[Database] 내부 저장 구조
2024.01.29