2 Phase Lock & mysql -> MVCC
아래와 같은 로직은 serializable하지 않아 실행 순서에 따라 결과가 다르다(x=100; y=200에서 시작)
Serializable은 트랜잭션 격리 수준(iso-level) 중 가장 높은 수준으로, 실행 순서에 상관없이 동일한 결과를 보장합니다.
Serializable은 트랜잭션들이 서로 겹치지 않도록 순차적으로 실행되는 것처럼 보이도록 보장합니다. 즉, 동시에 실행되는 여러 트랜잭션이 서로 간섭하지 않도록 하여, 트랜잭션이 직렬화된 것처럼 처리됩니다.
- 결과의 일관성 보장: 여러 트랜잭션이 동시에 실행되더라도, 실행 순서에 관계없이 동일한 결과를 보장합니다. 즉, 트랜잭션 간에 발생할 수 있는 경쟁 조건이나 읽기-쓰기에 의한 문제(예: 더티 리드, 비반영 읽기, 팬텀 리드 등)를 방지합니다.
- 트랜잭션 순차성: 데이터베이스는 트랜잭션들이 마치 순차적으로 실행된 것처럼 처리되도록 합니다. 이는 데이터베이스가 내부적으로 잠금 또는 스케줄링을 관리하여 발생할 수 있는 충돌을 막습니다.
- 동시성 감소: 여러 트랜잭션이 동시에 실행되면, 그들이 서로 잠금을 요구하거나 기다리는 상태가 발생할 수 있습니다. 이로 인해 성능 저하가 있을 수 있습니다.
이를 보장하기 위해선?
2단계 잠금(2-Phase Locking, 2PL)은 데이터베이스에서 트랜잭션의 일관성과 동시성을 유지하기 위한 잠금 프로토콜입니다. 2단계 잠금 규칙을 따르면 데이터베이스의 ACID 특성을 유지하면서 다중 트랜잭션이 동시에 실행될 때도 무결성을 보장할 수 있습니다.
2단계 잠금(2-Phase Locking)의 원리
: 모든 잠금 작업이 첫 번째 잠금 해제 작업보다 반드시 먼저 이루어지는 것입니다.
- 확장 단계(Growing Phase):
- 트랜잭션은 필요한 모든 잠금을 획득하는 단계입니다.
- 잠금 해제는 허용되지 않으며 오로지 잠금 획득만 할 수 있습니다.
- 트랜잭션이 접근하는 데이터에 대해 읽기 잠금 또는 쓰기 잠금을 설정합니다.
- 축소 단계(Shrinking Phase):
- 트랜잭션이 모든 잠금을 해제하는 단계입니다.
- 이 단계에서는 더 이상 잠금을 획득할 수 없습니다.
- 트랜잭션이 모든 작업을 완료하고 나면, 잠금을 해제하여 다른 트랜잭션이 접근할 수 있도록 합니다.
2단계 잠금이 일관성을 보장하는 이유
2PL을 따르는 경우, 트랜잭션 간의 교착 상태(Deadlock)나 무결성 문제를 예방할 수 있습니다. 트랜잭션이 모든 잠금을 획득할 때까지 축소를 시작하지 않기 때문에, 중간에 변경되는 데이터를 읽어 일관성이 깨지는 상황을 방지할 수 있습니다.
2단계 잠금의 단점
- 교착 상태 발생 가능성: 여러 트랜잭션이 서로의 잠금을 기다리다가 교착 상태가 발생할 수 있습니다.
- 성능 저하: 트랜잭션이 길어질수록 잠금을 오랫동안 유지해야 하므로 다른 트랜잭션의 병렬 실행을 방해할 수 있습니다.
종류
- 2PL
- 모든 잠금 작업이 첫 번째 잠금 해제 작업보다 반드시 먼저 이루어지는 것
- 락은 트랜잭션이 끝날 때까지 지속되는 것이 아니라, 쓰기 작업이 완료되는 시점에 해제
- C2PL
- 트랜잭션이 시작되기 전에 필요한 모든 잠금을 미리 획득
- 모든 리소스를 잠근 후, 트랜잭션을 시작하고 잠금 해제는 트랜젝션 처리하자마자
- S2PL
- 트랜잭션 시작 시점에 모든 잠금을 미리 걸지는 않음. 대신, 잠금을 필요로 할 때마다 걸고, 획득한 모든 쓰기 잠금을 트랜잭션이 완료될 때까지 유지
- write lock의 unlock이 커밋 이후
- SS2PL
- 각 데이터에 접근할 때 해당 데이터에 대한 잠금을 획득하며, 획득한 모든 잠금은 트랜잭션이 완료될 때까지 유지
- read/write lock의 unlock이 커밋 이후
MySQL의 2PL 사용 방식
MySQL은 InnoDB 스토리지 엔진을 사용할 때, 트랜잭션 격리 수준에 따라 락킹을 관리합니다. InnoDB는 기본적으로 Strict 2-Phase Locking (S2PL)을 사용하며, 이는 일반적인 2PL과 달리 트랜잭션이 종료될 때까지 쓰기 잠금을 유지합니다. 이 방식은 Repeatable Read 및 Serializable 격리 수준에서 특히 활용됩니다.
- Repeatable Read 격리 수준: InnoDB의 기본 격리 수준이며, InnoDB는 기본적으로 S2PL을 사용하여 트랜잭션 중에 읽은 데이터가 변경되지 않도록 보장합니다. 추가로 MySQL에서는 멀티버전 동시성 제어(MVCC)를 사용해 읽기 작업에 대한 잠금 경합을 줄입니다.
- Serializable 격리 수준: 이 수준에서는 MySQL이 트랜잭션 충돌을 방지하기 위해 더 강한 잠금을 사용합니다. 결과적으로 트랜잭션을 직렬화된 순서로 수행하려는 경향이 있으며, SS2PL과 유사한 동작을 제공합니다.
JPA와 데이터베이스 락킹의 관계
JPA에서 특정 락킹을 요청할 때(예: @Lock(LockModeType.PESSIMISTIC_WRITE)), 이 요청은 데이터베이스로 전달되어 MySQL의 2PL 방식에 따라 적용됩니다. 결국, JPA를 통해 락 모드를 설정해도 최종적인 락킹 동작은 데이터베이스의 격리 수준과 락킹 방식에 의해 결정됩니다.
요약
- MySQL + JPA 조합에서는 InnoDB의 2PL 구현(S2PL)에 의해 락킹이 수행됩니다.
- 트랜잭션 격리 수준 설정에 따라 2PL의 엄격성이나 일관성 수준이 달라집니다.
- JPA는 데이터베이스에 락 모드를 요청할 수 있지만, 락킹 방식은 데이터베이스의 구현에 의존합니다.
2PL의 문제점
2PL은 데이터에 공유 락(읽기용) 또는 배타적 락(쓰기용)을 걸어서 트랜잭션이 안전하게 처리되도록 합니다. 하지만 몇 가지 문제점이 있습니다:
- 데드락 발생 가능성:
- 여러 트랜잭션이 서로의 락을 기다리는 상태에 빠져서 교착 상태가 발생할 수 있습니다.
- 높은 락 대기 시간:
- 트랜잭션 간의 상호 락으로 인해 읽기와 쓰기가 겹칠 때마다 대기 시간이 길어질 수 있으며, 대기 상태에서 성능 저하가 발생합니다.
- 성능 저하:
- 특히 읽기 작업이 많은 시스템에서 성능이 크게 저하됩니다. 모든 트랜잭션이 락을 걸어야 하므로, 높은 동시성 요구를 충족하기 어렵습니다.
MVCC (Multi-Version Concurrency Control)는 다중 버전 동시성 제어라고 불리는 방식으로, 데이터베이스에서 동시성 제어를 위해 여러 데이터 버전을 관리하여 성능을 향상시키는 기법입니다. 특히 읽기 작업이 많은 환경에서 락을 걸지 않고도 동시성을 보장할 수 있도록 설계되었습니다.
MVCC의 주요 개념
- 데이터 버전 관리: MVCC에서는 데이터베이스의 각 행(row)에 대해 여러 버전을 저장합니다. 새로운 트랜잭션이 변경을 가할 때마다, 기존 버전을 덮어쓰지 않고 새로운 버전을 생성합니다. 과거의 데이터는 그대로 유지됩니다.
- 트랜잭션 격리: MVCC는 트랜잭션이 시작될 때의 스냅샷(시점) 기준으로 데이터를 읽도록 하여, 다른 트랜잭션이 데이터를 변경하는 중에도 해당 스냅샷 기준 데이터를 읽게 됩니다. 이를 통해 락 없이도 일관된 읽기 작업을 제공합니다.
- 삭제 지연: 과거의 버전 데이터는 특정 조건에서 삭제되며, 데이터베이스가 자동으로 불필요한 버전을 제거하는 가비지 컬렉션 작업을 수행합니다.
MVCC의 작동 방식
- 트랜잭션 시작 시점의 스냅샷 사용: 트랜잭션이 시작되면 해당 시점의 데이터 스냅샷을 이용하여 데이터 조회를 수행합니다. 이렇게 하면 다른 트랜잭션에서 데이터가 변경되더라도 현재 트랜잭션은 일관성 있는 데이터를 확인할 수 있습니다.
- 데이터 버전 관리: 데이터에 대한 수정이 발생할 때 기존 데이터는 그대로 유지하고 새로운 버전을 생성합니다. 예를 들어, A 트랜잭션이 테이블의 특정 행을 수정하면, 원래 데이터를 덮어쓰지 않고 새로운 데이터 버전을 추가하는 방식입니다.
- 커밋 후 가시성: 트랜잭션이 완료되면 변경된 데이터 버전이 다른 트랜잭션에서도 보이게 됩니다. 아직 완료되지 않은 트랜잭션에서의 변경은 다른 트랜잭션에 영향을 미치지 않습니다.
- 가비지 컬렉션: 시간이 지나면서 더 이상 참조되지 않는 오래된 데이터 버전은 데이터베이스에서 주기적으로 삭제하여 공간을 확보합니다.
MVCC의 장점
- 락을 사용하지 않고도 일관된 읽기를 보장하므로, 읽기 성능이 우수합니다.
- 트랜잭션이 많아도 충돌이 적어, 데드락 발생 가능성이 줄어듭니다.
- 동시 읽기 및 쓰기 작업을 효율적으로 처리하여 높은 동시성을 제공합니다.
MVCC의 단점
- 모든 데이터의 버전을 유지해야 하므로, 저장 공간이 더 많이 필요할 수 있습니다.
- 가비지 컬렉션 작업이 필요하여, 오래된 버전을 제거하는 데 추가적인 관리 비용이 발생할 수 있습니다.
적용 사례
MVCC는 PostgreSQL, MySQL의 InnoDB 엔진, Oracle 등 여러 데이터베이스 시스템에서 사용되며, 특히 트랜잭션 격리 수준을 높이면서도 성능을 유지해야 하는 환경에서 널리 활용됩니다.