병행성 제어
1. 개요
1. 개요
병행성 제어는 여러 개의 트랜잭션이 동시에 실행될 때, 데이터베이스의 일관성을 유지하면서 가능한 많은 트랜잭션을 동시에 처리할 수 있도록 하는 기술이다. 이는 분산 시스템이나 다중 사용자 데이터베이스 관리 시스템 환경에서 데이터의 정확성과 시스템의 전체 처리량을 보장하기 위한 핵심 메커니즘이다.
주요 목적은 데이터베이스의 상태를 정확하게 유지하는 일관성 보존과, 자원을 효율적으로 활용하여 동시 실행 트랜잭션의 처리량을 최대화하는 것이다. 이를 위해 각 트랜잭션의 실행 결과가 마치 순차적으로 실행된 것과 동일한 효과를 내는 직렬 가능성을 보장하는 것을 궁극적인 목표로 삼는다.
병행 실행을 제어하지 않을 경우 발생하는 주요 문제점으로는 갱신 분실, 더티 리드(현황 파악 오류), 반복 불가능 읽기, 팬텀 읽기 등이 있다. 이러한 문제들은 데이터의 불일치를 초래하여 시스템의 신뢰성을 떨어뜨린다.
이를 해결하기 위한 주요 기법으로는 로킹, 타임스탬프 순서, 낙관적 병행 제어, 다중 버전 병행 제어 등이 널리 사용된다. 각 기법은 교착 상태 처리, 성능, 구현 복잡도 등의 측면에서 다른 특성을 가지며, 응용 프로그램의 요구사항에 따라 적절한 격리 수준과 함께 적용된다.
2. 병행성 제어의 필요성
2. 병행성 제어의 필요성
병행성 제어는 여러 개의 트랜잭션이 동시에 실행될 때 데이터베이스의 일관성을 유지하면서도 시스템의 처리량을 최대화하기 위해 필수적인 기술이다. 단일 사용자 환경에서는 순차적으로 트랜잭션을 처리하면 되지만, 다중 사용자 데이터베이스 관리 시스템 환경에서는 여러 트랜잭션이 동시에 같은 데이터에 접근하고 변경하려고 시도한다. 이러한 병행 실행을 제어하지 않으면 데이터의 정확성과 신뢰성이 심각하게 훼손될 수 있다.
병행성 제어의 핵심 목적은 동시 실행되는 트랜잭션들의 결과가 마치 순차적으로 실행된 것과 동일한 효과, 즉 직렬 가능성을 보장하는 것이다. 이를 통해 사용자는 항상 논리적으로 정확한 데이터를 접근할 수 있게 된다. 동시에, 가능한 많은 트랜잭션을 병렬로 처리하여 시스템 자원의 활용도를 높이고 전체적인 처리 성능을 향상시키는 것도 중요한 목표이다.
병행 실행이 제어되지 않을 경우 발생하는 주요 문제점으로는 갱신 분실, 더티 리드, 반복 불가능 읽기, 팬텀 읽기 등이 있다. 예를 들어, 갱신 분실은 두 트랜잭션이 같은 데이터를 읽고 순차적으로 갱신할 때, 먼저 수행된 갱신 결과가 나중에 수행된 갱신에 의해 덮어써져 사라지는 현상을 말한다. 이러한 문제들은 데이터의 불일치를 초래하여 비즈니스 로직에 치명적인 오류를 일으킬 수 있다.
따라서 병행성 제어는 로킹, 타임스탬프 순서, 낙관적 병행 제어, 다중 버전 병행 제어 같은 다양한 기법을 통해 이러한 문제들을 방지하고, 데이터의 무결성과 시스템의 효율성을 동시에 달성하는 데 기여한다. 이는 현대 데이터베이스 시스템이 신뢰성과 고성능을 유지하는 데 있어 가장 기본적이고 중요한 메커니즘 중 하나이다.
3. 병행 실행의 문제점
3. 병행 실행의 문제점
3.1. 갱신 분실
3.1. 갱신 분실
갱신 분실은 병행성 제어에서 발생하는 주요 문제점 중 하나이다. 이는 두 개 이상의 트랜잭션이 동시에 같은 데이터 항목을 읽고 갱신할 때, 하나의 트랜잭션이 수행한 갱신 결과가 다른 트랜잭션에 의해 덮어쓰여져 사라지는 현상을 의미한다. 이로 인해 데이터베이스의 일관성이 훼손될 수 있다.
갱신 분실의 전형적인 시나리오는 다음과 같다. 두 트랜잭션 T1과 T2가 동시에 데이터 항목 A의 값을 읽는다. T1은 읽은 값에 기반하여 A를 갱신하고 데이터베이스에 기록한다. 그러나 T2도 자신이 읽은 오래된 값에 기반하여 A를 갱신하고 기록하면, T1이 수행한 갱신 내용은 완전히 분실되고 T2의 갱신만 최종적으로 남게 된다. 이는 마치 여러 사람이 동시에 같은 문서를 편집할 때 한 사람의 수정 사항이 저장되지 않고 지워지는 것과 유사하다.
이 문제를 방지하기 위해서는 적절한 병행성 제어 기법이 필요하다. 로킹 기법을 사용할 경우, 트랜잭션이 데이터를 갱신하기 전에 해당 데이터에 대한 배타적 락을 획득하도록 함으로써 다른 트랜잭션이 동시에 갱신하는 것을 막을 수 있다. 또는 타임스탬프 순서 기법이나 다중 버전 병행성 제어와 같은 다른 기법들을 적용하여 갱신 분실을 근본적으로 차단할 수 있다.
3.2. 모순된 분석
3.2. 모순된 분석
모순된 분석은 트랜잭션이 동시에 실행될 때 발생할 수 있는 일관성 문제 중 하나로, 하나의 트랜잭션이 다른 트랜잭션에 의해 아직 확정되지 않은 중간 결과를 읽어 잘못된 분석을 수행하는 현상을 말한다. 이는 현황 파악 오류라고도 불린다. 예를 들어, 트랜잭션 A가 특정 데이터를 수정한 후 아직 커밋하지 않은 상태에서, 트랜잭션 B가 그 수정된 데이터를 읽어 연산을 수행하고 그 결과를 기반으로 결정을 내린다면, 이후 트랜잭션 A가 롤백되면 트랜잭션 B는 존재하지도 않았던 데이터를 기반으로 잘못된 작업을 수행한 셈이 된다.
이 문제는 데이터베이스의 일관성을 심각하게 훼손한다. 트랜잭션 B의 잘못된 분석 결과는 다른 데이터를 더럽히거나(더티 라이트), 잘못된 보고서를 생성하거나, 오류를 포함한 추가 연산을 유발할 수 있다. 병행성 제어의 주요 목적이 데이터베이스 상태의 정확성을 보장하는 것이므로, 모순된 분석을 방지하는 것은 필수적이다.
로킹 기법을 사용할 경우, 쓰기 로크를 설정한 트랜잭션이 완료될 때까지 다른 트랜잭션이 해당 데이터에 대한 읽기 로크를 획득하지 못하도록 함으로써 이 문제를 해결할 수 있다. 또한 격리 수준을 조정하여 방지할 수도 있는데, READ COMMITTED 이상의 격리 수준은 커밋된 데이터만 읽도록 보장함으로써 모순된 분석이 발생하지 않도록 한다.
3.3. 갱신 불가 분석
3.3. 갱신 불가 분석
갱신 불가 분석은 여러 개의 트랜잭션이 동시에 실행될 때 발생할 수 있는 문제점 중 하나이다. 이 문제는 하나의 트랜잭션이 특정 데이터를 읽은 후, 다른 트랜잭션이 그 데이터를 갱신하고 커밋했음에도 불구하고, 첫 번째 트랜잭션이 같은 데이터를 다시 읽을 수 없는 상황을 의미한다. 이는 트랜잭션의 반복 읽기 일관성을 해치는 현상으로, 직렬 가능성을 보장하지 못하는 비직렬화된 스케줄에서 나타난다.
구체적으로, 트랜잭션 T1이 데이터 항목 A의 값을 읽은 후, 트랜잭션 T2가 A의 값을 수정하고 그 변경 사항을 데이터베이스에 확정(커밋)한다. 이후 T1이 동일한 작업 내에서 다시 A를 읽으려고 하면, 처음 읽었던 값과는 다른 T2가 갱신한 새로운 값을 보게 된다. 이로 인해 T1 내에서 단일한 논리적 작업을 수행하는 도중에 외부의 변경으로 인해 일관된 뷰를 유지할 수 없게 된다. 이러한 현상은 격리 수준이 낮은 경우, 특히 '반복 읽기'가 보장되지 않는 수준에서 발생할 수 있다.
갱신 불가 분석 문제는 데이터의 정확성에 의존하는 애플리케이션, 예를 들어 잔액을 두 번 확인하는 금융 시스템이나 통계 집계를 수행하는 데이터 분석 작업에서 중대한 오류를 초래할 수 있다. 따라서 병행성 제어 기법은 이러한 문제를 방지하여 각 트랜잭션이 마치 순차적으로 실행된 것과 동일한 결과, 즉 직렬 가능한 결과를 보장해야 한다. 이를 해결하기 위한 일반적인 방법에는 로킹 기법을 사용하여 트랜잭션이 데이터를 읽는 동안 다른 트랜잭션이 해당 데이터를 갱신하지 못하도록 하는 공유 락을 설정하거나, 더 높은 격리 수준을 사용하는 것이 포함된다.
4. 병행성 제어 기법
4. 병행성 제어 기법
4.1. 로킹 기법
4.1. 로킹 기법
로킹 기법은 병행성 제어를 구현하는 가장 일반적인 방법 중 하나이다. 이 기법은 트랜잭션이 데이터베이스의 특정 데이터 항목(예: 레코드, 페이지, 테이블)을 사용하기 전에 해당 항목에 대한 잠금(락)을 요청하고 획득하는 방식으로 동작한다. 잠금은 다른 트랜잭션의 접근을 제어하는 권한으로, 기본적으로 공유 잠금(Shared Lock)과 배타적 잠금(Exclusive Lock) 두 종류가 있다. 공유 잠금은 데이터를 읽기만 할 때 사용되며, 여러 트랜잭션이 동시에 같은 데이터에 대한 공유 잠금을 가질 수 있다. 반면 배타적 잠금은 데이터를 쓰거나 수정할 때 필요하며, 한 트랜잭션이 배타적 잠금을 보유하고 있으면 다른 트랜잭션은 공유 잠금이나 배타적 잠금을 모두 획득할 수 없다.
로킹 기법의 핵심은 트랜잭션 간의 간섭을 방지하여 직렬 가능성을 보장하는 데 있다. 이를 위해 트랜잭션이 잠금을 요청하고 해제하는 시점을 규정하는 로킹 프로토콜이 사용된다. 가장 기본적인 프로토콜은 2단계 로킹 프로토콜로, 모든 트랜잭션이 잠금 확장 단계와 잠금 수축 단계를 거치도록 강제한다. 이 프로토콜은 직렬 가능한 스케줄을 생성할 수 있도록 보장하지만, 교착 상태가 발생할 가능성을 내포하고 있다.
로킹 기법은 직관적이고 구현이 비교적 간단하다는 장점이 있지만, 잠금 관리에 따른 오버헤드와 교착 상태 처리라는 과제를 안고 있다. 또한, 잠금의 단위(락 그레뉼)를 어떻게 설정하느냐에 따라 동시성과 제어 오버헤드 사이에 트레이드오프가 발생한다. 이러한 문제들을 해결하기 위해 엄격한 2단계 로킹 프로토콜이나 강한 2단계 로킹 프로토콜과 같은 변형 프로토콜들이 개발되었다.
4.2. 타임스탬프 순서 기법
4.2. 타임스탬프 순서 기법
타임스탬프 순서 기법은 트랜잭션이 시작될 때 시스템이 부여하는 고유한 타임스탬프 순서에 따라 트랜잭션의 실행을 제어하는 방법이다. 이 기법은 로킹을 사용하지 않으며, 각 데이터 항목에 대해 가장 최근에 읽거나 쓴 트랜잭션의 타임스탬프를 기록하여 관리한다. 트랜잭션의 타임스탬프는 트랜잭션의 시작 시간을 기준으로 생성되며, 일반적으로 트랜잭션의 순서를 결정하는 기준이 된다.
이 기법의 핵심 규칙은 모든 읽기와 쓰기 연산이 타임스탬프 순서에 따라 실행되어야 한다는 것이다. 트랜잭션이 어떤 데이터를 읽으려 할 때, 해당 데이터에 기록된 최종 쓰기 타임스탬프가 트랜잭션 자신의 타임스탬프보다 이후라면 그 읽기 연산은 거부된다. 마찬가지로, 쓰기 연산을 시도할 때 해당 데이터의 최종 읽기 타임스탬프나 최종 쓰기 타임스탬프가 자신의 타임스탬프보다 이후라면 그 쓰기 연산은 취소된다. 연산이 거부되면 해당 트랜잭션은 중단되고 새로운 타임스탬프로 재시작된다.
타임스탬프 순서 기법의 주요 장점은 교착 상태가 발생하지 않는다는 점이다. 로킹 기법에서는 상호 대기 조건으로 인해 교착 상태가 발생할 수 있지만, 타임스탬프 기법은 순서에 따라 연산을 허용하거나 거부하기 때문에 대기 상황 자체를 만들지 않는다. 또한 직렬 가능성을 보장하며, 구현이 비교적 단순하다는 장점이 있다.
반면, 단점으로는 트랜잭션 중단과 재시작이 빈번하게 발생할 수 있다는 점이 있다. 늦게 도착한 트랜잭션이 이전 타임스탬프를 가진 트랜잭션의 연산과 충돌할 경우 쉽게 중단되므로, 시스템의 처리 효율이 떨어질 수 있다. 또한 갱신 분실이나 모순된 분석 같은 문제를 방지하기 위해서는 데이터에 대한 타임스탬프 기록을 정확하게 유지 관리해야 하는 부담이 있다.
4.3. 낙관적 병행성 제어 기법
4.3. 낙관적 병행성 제어 기법
낙관적 병행성 제어 기법은 트랜잭션 간 충돌이 드물게 발생할 것이라고 낙관적으로 가정하는 접근 방식이다. 이 기법은 트랜잭션 실행 과정에서 로킹과 같은 제약을 두지 않고, 트랜잭션이 자유롭게 데이터를 읽고 수정하도록 허용한다. 대신, 트랜잭션이 변경 내용을 데이터베이스에 최종 반영하려는 시점, 즉 커밋 단계에서 해당 트랜잭션이 실행되는 동안 다른 트랜잭션에 의해 자신이 읽었던 데이터가 변경되었는지 검증 작업을 수행한다. 이 검증을 통과해야만 트랜잭션의 결과가 확정된다.
이 기법은 일반적으로 읽기 작업이 매우 빈번하고 쓰기 충돌이 적은 환경에서 유리하다. 낙관적 병행성 제어의 대표적인 구현 방식은 타임스탬프나 버전 번호를 사용하는 것이다. 각 데이터 항목은 버전 번호를 가지며, 트랜잭션이 데이터를 읽을 때의 버전 번호를 기억한다. 이후 트랜잭션이 커밋을 시도할 때, 자신이 수정하려는 데이터의 현재 버전 번호가 기억했던 읽기 시점의 버전 번호와 동일한지 확인한다. 만약 다르다면 그 사이에 다른 트랜잭션이 해당 데이터를 갱신했다는 의미이므로, 현재 트랜잭션은 검증에 실패하여 롤백된다.
낙관적 병행성 제어의 주요 장점은 교착 상태가 발생하지 않으며, 트랜잭션 실행 중에 대기 시간이 거의 없어 처리 성능이 높을 수 있다는 점이다. 그러나 단점은 트랜잭션이 장시간 실행되거나, 갱신이 집중되는 데이터에 대해 경쟁이 심한 환경에서는 검증 실패로 인한 롤백이 빈번히 발생할 수 있다는 것이다. 이 경우 트랜잭션을 재시작하는 오버헤드가 성능 저하를 초래할 수 있다. 따라서 이 기법의 효과는 작업 부하의 특성에 크게 의존한다.
4.4. 다중 버전 병행성 제어 기법
4.4. 다중 버전 병행성 제어 기법
다중 버전 병행성 제어 기법은 데이터베이스에서 동시성을 관리하는 방법 중 하나로, 데이터 항목에 대한 여러 버전을 유지함으로써 읽기와 쓰기 작업 간의 충돌을 줄이는 것을 핵심으로 한다. 이 기법은 트랜잭션이 데이터를 읽을 때, 해당 트랜잭션이 시작된 시점에 존재했던 데이터의 일관된 스냅샷을 제공한다. 이를 통해 읽기 작업은 다른 트랜잭션의 쓰기 작업을 기다리지 않고도 진행될 수 있어, 동시성과 처리량을 크게 향상시킬 수 있다.
이 기법의 작동 방식은 각 데이터 항목에 대해 여러 버전의 값을 저장하고, 각 버전에 생성된 타임스탬프나 트랜잭션 ID를 태그하는 것이다. 읽기 트랜잭션은 자신보다 먼저 커밋된 버전 중 가장 최신의 버전을 읽게 된다. 쓰기 트랜잭션은 새로운 버전을 생성하며, 이는 다른 트랜잭션이 읽고 있는 기존 버전을 덮어쓰지 않는다. 따라서 갱신 분실이나 반복 불가능 읽기 같은 문제가 발생하지 않는다.
다중 버전 병행성 제어 기법의 주요 장점은 높은 동시성을 제공하면서도 격리 수준 중 스냅샷 격리나 직렬화 가능한 수준을 보장할 수 있다는 점이다. 특히 읽기 작업이 많은 환경에서 성능 이점이 두드러진다. 그러나 단점으로는 여러 버전의 데이터를 저장해야 하므로 저장 공간 오버헤드가 발생하며, 오래된 버전을 정리하는 버전 관리 메커니즘이 필요하다는 점을 들 수 있다.
이 기법은 현대의 많은 관계형 데이터베이스 관리 시스템에서 널리 채택되고 있다. PostgreSQL과 Oracle 같은 시스템은 핵심 동시성 제어 메커니즘으로 다중 버전 병행성 제어를 사용하여 높은 성능과 데이터 일관성을 동시에 달성한다.
5. 로킹 프로토콜
5. 로킹 프로토콜
5.1. 2단계 로킹 프로토콜
5.1. 2단계 로킹 프로토콜
2단계 로킹 프로토콜은 로킹 기법을 사용하는 병행성 제어의 기본적인 규칙으로, 트랜잭션이 락을 획득하고 해제하는 시점에 관한 두 가지 단계를 정의한다. 이 프로토콜은 직렬 가능성을 보장하기 위한 충분 조건을 제공한다. 첫 번째 단계는 성장 단계로, 트랜잭션이 필요한 모든 락을 요청하고 획득하는 단계이다. 이 단계에서는 이미 획득한 락을 해제할 수 없다. 두 번째 단계는 축소 단계로, 트랜잭션이 모든 락을 점진적으로 해제하는 단계이다. 이 단계에서는 새로운 락을 요청할 수 없다.
이 프로토콜의 핵심은 락의 획득과 해제가 엄격히 분리된다는 점이다. 트랜잭션이 한 번이라도 락을 해제하기 시작하면, 그 시점부터는 더 이상 새로운 락을 요청하지 않는다. 이 규칙을 준수함으로써 교착 상태는 방지할 수 없지만, 직렬화 불가능한 스케줄이 생성되는 것을 막을 수 있다. 그러나 2단계 로킹 프로토콜만으로는 캐스케이딩 롤백을 완전히 방지하지는 못한다는 한계가 있다.
단계 | 명칭 | 허용되는 작업 | 금지되는 작업 |
|---|---|---|---|
1단계 | 성장 단계(Growing Phase) | 새로운 락 요청 및 획득 | 획득한 락 해제 |
2단계 | 축소 단계(Shrinking Phase) | 획득한 락 해제 | 새로운 락 요청 |
이러한 기본 프로토콜의 한계를 보완하기 위해 더 엄격한 규칙을 적용한 변형 프로토콜들이 개발되었다. 대표적으로 엄격한 2단계 로킹 프로토콜은 트랜잭션이 모든 락을 커밋 또는 롤백 작업이 완료될 때까지 유지하도록 하여 캐스케이딩 롤백 문제를 해결한다. 또한 강한 2단계 로킹 프로토콜은 트랜잭션이 모든 락을 트랜잭션 종료 시점에 한꺼번에 해제하도록 하여 직렬 가능성을 보장하면서도 구현을 단순화한다.
5.2. 엄격한 2단계 로킹 프로토콜
5.2. 엄격한 2단계 로킹 프로토콜
엄격한 2단계 로킹 프로토콜은 2단계 로킹 프로토콜의 한 종류로, 교착 상태를 방지하지는 않지만 트랜잭션의 회복을 보다 단순하고 효율적으로 만드는 것을 목표로 한다. 이 프로토콜의 핵심 규칙은 모든 배타적 로크를 트랜잭션이 종료(커밋 또는 롤백)될 때까지 유지하는 것이다. 즉, 쓰기 작업에 사용된 락은 트랜잭션이 완료된 후에야 해제된다.
이 방식은 직렬 가능성은 보장하지만, 동시성 수준은 기본 2단계 로킹 프로토콜보다 더 제한적이다. 다른 트랜잭션이 특정 데이터 항목을 읽거나 쓰기 위해서는 해당 데이터에 대한 배타적 락을 보유한 트랜잭션이 완전히 끝날 때까지 대기해야 한다. 이는 갱신 분실이나 현황 파악 오류 같은 문제를 방지하는 강력한 격리를 제공한다.
엄격한 2단계 로킹의 주요 장점은 회복 관리가 간단해진다는 점이다. 트랜잭션이 커밋되기 전까지는 다른 트랜잭션이 그 트랜잭션이 수정한 데이터를 읽지도, 쓰지도 못하기 때문에, 시스템이 장애가 발생했을 때 롤백해야 할 트랜잭션이 수정한 데이터를 다른 트랜잭션이 참조한 경우를 고려할 필요가 없어진다. 이는 UNDO와 REDO 연산을 단순화시킨다.
따라서 이 프로토콜은 높은 데이터 일관성이 요구되고, 비교적 낮은 동시성 수준이 허용되는 환경에서 주로 사용된다. 격리 수준 측면에서 보면, 이는 READ COMMITTED보다 강한 격리를 제공하며, ANSI/ISO SQL 표준의 격리 수준 중 하나와 유사한 특성을 가진다.
5.3. 강한 2단계 로킹 프로토콜
5.3. 강한 2단계 로킹 프로토콜
강한 2단계 로킹 프로토콜은 2단계 로킹 프로토콜의 한 종류로, 엄격한 2단계 로킹 프로토콜보다 더 엄격한 제약을 가진다. 이 프로토콜은 트랜잭션이 직렬 가능성을 보장받으면서도 회복이 용이하도록 설계되었다. 핵심 규칙은 모든 락을 트랜잭션이 종료될 때까지 해제하지 않는 것이 아니라, 모든 쓰기 락을 트랜잭션 종료 시점까지 유지하는 것이다.
구체적으로, 강한 2단계 로킹 프로토콜은 두 가지 조건을 만족시켜야 한다. 첫째, 트랜잭션은 락을 걸고 해제하는 시점에 따라 확장 단계와 수축 단계로 구분되는 2단계 로킹 프로토콜의 기본 규칙을 따라야 한다. 둘째, 트랜잭션이 획득한 모든 쓰기 락은 해당 트랜잭션이 커밋되거나 롤백되는 시점, 즉 트랜잭션이 완전히 종료될 때까지 해제되어서는 안 된다. 반면, 읽기 락은 더 이상 필요하지 않다고 판단되는 시점에 해제할 수 있다.
이러한 방식은 엄격한 2단계 로킹 프로토콜이 모든 락(읽기 락과 쓰기 락 모두)을 트랜잭션 종료 시까지 유지하는 것에 비해 더 유연하다. 읽기 락을 일찍 해제함으로써 다른 트랜잭션의 동시성을 높일 수 있다. 그러나 모든 쓰기 락을 종료 시까지 유지하기 때문에, 갱신 분실이나 더티 리드 같은 문제는 발생하지 않으며, 트랜잭션 회복 시 캐스케이딩 롤백을 방지할 수 있다. 다만, 락을 오래 유지하는 특성상 교착 상태가 발생할 가능성은 여전히 존재한다.
6. 교착 상태
6. 교착 상태
6.1. 교착 상태 발생 조건
6.1. 교착 상태 발생 조건
교착 상태는 병행성 제어 기법, 특히 로킹 기법을 사용할 때 발생할 수 있는 주요 문제점이다. 이는 두 개 이상의 트랜잭션이 서로 상대방이 점유하고 있는 자원을 무한정 기다리며 진행을 멈춘 상태를 의미한다.
교착 상태가 발생하기 위해서는 네 가지 조건이 동시에 성립해야 한다. 첫째는 상호 배제(Mutual Exclusion) 조건으로, 한 번에 하나의 트랜잭션만이 특정 자원(예: 데이터베이스의 특정 데이터 항목)을 사용할 수 있어야 한다. 둘째는 점유와 대기(Hold and Wait) 조건으로, 트랜잭션이 이미 어떤 자원을 보유한 상태에서 다른 트랜잭션이 보유한 추가 자원을 기다리는 상태여야 한다. 셋째는 비선점(No Preemption) 조건으로, 다른 트랜잭션이 점유한 자원을 강제로 빼앗을 수 없어야 한다. 마지막으로 순환 대기(Circular Wait) 조건이 필요하며, 이는 대기 중인 트랜잭션들이 순환 형태로 서로의 자원을 기다리는 관계를 형성해야 함을 의미한다.
이 네 가지 조건은 교착 상태를 정의하는 이론적 모델을 제공하며, 실제 시스템에서 교착 상태를 방지하거나 탐지하기 위한 방법의 기초가 된다. 예를 들어, 2단계 로킹 프로토콜은 직렬 가능성을 보장하지만, 점유와 대기 조건 및 순환 대기 조건이 성립될 여지를 남겨 교착 상태를 초래할 수 있다. 따라서 대부분의 데이터베이스 관리 시스템(DBMS)은 교착 상태를 처리하기 위한 별도의 매커니즘을 구현하여 운영한다.
6.2. 교착 상태 처리 방법
6.2. 교착 상태 처리 방법
교착 상태 처리 방법은 크게 예방, 회피, 탐지 및 회복의 세 가지 범주로 나뉜다.
교착 상태 예방 방법은 교착 상태의 네 가지 필요 조건 중 하나 이상이 성립하지 않도록 시스템을 설계하는 것이다. 예를 들어, 모든 트랜잭션이 시작 시 필요한 모든 자원을 한꺼번에 요청하도록 하여 점유와 대기 조건을 제거하는 방법이 있다. 또는 자원에 우선순위를 부여하고, 높은 우선순위의 트랜잭션이 낮은 우선순위의 트랜잭션이 점유한 자원을 요청할 경우 선점하도록 하여 비선점 조건을 제거할 수도 있다. 이러한 방법들은 교착 상태가 발생하지 않도록 보장하지만, 자원 활용도를 저하시키거나 트랜잭션 실행에 제약을 많이 가하여 시스템 처리량을 감소시킬 수 있다.
교착 상태 회피 방법은 트랜잭션이 자원을 요청할 때마다 시스템의 안전 상태 여부를 동적으로 검사하여 교착 상태 가능성을 피하는 것이다. 대표적인 알고리즘으로 은행원 알고리즘이 있다. 이 방법은 각 트랜잭션의 최대 자원 요구량을 미리 알고 있어야 하며, 안전 상태 검사를 위한 추가적인 오버헤드가 발생한다. 따라서 실제 데이터베이스 관리 시스템에서는 널리 사용되지 않는다.
가장 일반적으로 사용되는 접근법은 교착 상태 탐지 및 회복이다. 시스템은 주기적으로 자원 할당 그래프 등을 검사하여 교착 상태가 발생했는지 탐지한다. 교착 상태가 탐지되면, 이를 해결하기 위해 하나 이상의 트랜잭션을 희생시켜 롤백한다. 희생자 선택 기준은 트랜잭션의 진행 정도, 사용한 자원의 양, 롤백 비용 등을 고려하여 최소 비용으로 교착 상태를 해소할 수 있는 트랜잭션을 선택한다. 회복된 트랜잭션은 나중에 재시작될 수 있다. 이 방법은 교착 상태가 실제로 발생하기 전까지는 아무런 제약을 가하지 않아 시스템 효율성이 높지만, 주기적인 탐지 오버헤드와 희생된 트랜잭션의 롤백 및 재시작 비용이 발생한다.
7. 격리 수준
7. 격리 수준
격리 수준은 여러 트랜잭션이 동시에 실행될 때, 각 트랜잭션이 다른 트랜잭션의 작업으로부터 얼마나 격리되어 보이는지를 정의하는 기준이다. 데이터베이스 시스템은 트랜잭션의 직렬 가능성을 완벽히 보장하는 대신, 성능과 동시성을 높이기 위해 다양한 격리 수준을 제공한다. 각 수준은 특정한 동시성 제어 문제의 발생을 허용하거나 방지함으로써 일관성과 처리량 사이의 트레이드오프를 관리한다.
주요 격리 수준은 낮은 격리성에서 높은 격리성 순으로, READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, SERIALIZABLE이 있다. READ UNCOMMITTED 수준에서는 한 트랜잭션이 다른 트랜잭션이 아직 커밋하지 않은, 즉 더티 리드가 가능한 데이터를 읽을 수 있다. READ COMMITTED 수준에서는 커밋된 데이터만 읽을 수 있어 더티 리드는 방지되지만, 같은 트랜잭션 내에서 동일한 데이터를 두 번 읽었을 때 값이 달라지는 반복 불가능 읽기 문제가 발생할 수 있다.
REPEATABLE READ 수준은 한 트랜잭션이 읽은 데이터에 대해서는 다른 트랜잭션이 갱신 또는 삭제하는 것을 방지하여 반복 불가능 읽기를 해결한다. 그러나 이 수준에서는 새로운 데이터 행이 추가되는 팬텀 읽기 현상이 여전히 발생할 수 있다. 가장 높은 격리 수준인 SERIALIZABLE은 모든 트랜잭션을 완전히 순차적으로 실행한 것과 동일한 결과를 보장하며, 팬텀 읽기를 포함한 모든 동시성 문제를 방지한다. 그러나 로킹 범위가 넓어져 동시성과 성능이 가장 저하되는 단점이 있다.
응용 프로그램의 요구사항에 따라 적절한 격리 수준을 선택하는 것이 중요하다. 높은 데이터 정확성이 요구되는 금융 거래 시스템에서는 SERIALIZABLE 수준이 필요할 수 있지만, 대부분의 온라인 트랜잭션 처리 시스템은 성능을 위해 READ COMMITTED나 REPEATABLE READ 수준을 사용한다. 데이터베이스별로 이러한 표준 격리 수준의 구현 방식과 세부 동작에는 차이가 있을 수 있다.
8. 관련 개념
8. 관련 개념
8.1. 트랜잭션
8.1. 트랜잭션
트랜잭션은 데이터베이스 시스템에서 논리적인 작업의 단위를 의미한다. 이는 하나의 프로그램이나 사용자의 요청을 구성하는 여러 데이터베이스 연산(읽기, 쓰기, 갱신, 삭제 등)을 하나의 묶음으로 처리하는 개념이다. 트랜잭션의 핵심 속성은 ACID로 요약되는데, 이는 원자성(Atomicity), 일관성(Consistency), 격리성(Isolation), 지속성(Durability)을 보장한다는 것을 뜻한다. 병행성 제어는 특히 이 네 가지 속성 중 격리성을 실현하는 핵심 메커니즘이 된다.
병행성 제어의 목적은 여러 트랜잭션이 동시에 실행되더라도 각 트랜잭션이 마치 순차적으로 실행된 것과 같은 결과, 즉 직렬 가능성을 보장하는 데 있다. 이를 통해 데이터베이스의 일관성을 유지하면서도 시스템의 처리량과 자원 활용률을 최대화할 수 있다. 만약 적절한 병행성 제어가 없다면, 동시 실행되는 트랜잭션 간의 간섭으로 인해 갱신 분실, 더티 리드, 반복 불가능 읽기, 팬텀 읽기 등의 문제가 발생하여 데이터의 정확성과 신뢰성이 크게 훼손될 수 있다.
따라서 데이터베이스 관리 시스템(DBMS)은 로킹, 타임스탬프 순서, 낙관적 병행 제어, 다중 버전 병행 제어(MVCC)와 같은 다양한 병행성 제어 기법을 구현하여 트랜잭션의 안전한 동시 실행을 관리한다. 이러한 기법들은 트랜잭션의 격리 수준 설정에 따라 그 엄격함과 성능이 조절되며, 때로는 교착 상태와 같은 새로운 문제를 야기하기도 한다. 결국 트랜잭션 개념은 데이터의 신뢰성을 보장하는 데이터베이스 운영의 기본 단위이며, 병행성 제어는 이를 동시 실행 환경에서 실현하기 위한 필수적인 기술이다.
8.2. 직렬 가능성
8.2. 직렬 가능성
직렬 가능성은 여러 트랜잭션을 병행 실행했을 때의 결과가, 이 트랜잭션들을 어떤 순서로 한 번에 하나씩 차례대로 실행한 결과와 동일함을 보장하는 성질이다. 이는 병행성 제어의 핵심 목표 중 하나로, 시스템이 아무리 많은 트랜잭션을 동시에 처리하더라도 최종적인 데이터베이스 상태가 마치 순차적으로 실행된 것처럼 정확해야 함을 의미한다. 직렬 가능성을 보장받지 못하는 병행 실행은 데이터 무결성을 훼손하고 데이터 일관성 오류를 초래할 수 있다.
직렬 가능성을 판단하는 이론적 기준으로는 충돌 직렬 가능성과 뷰 직렬 가능성이 있다. 일반적으로 데이터베이스 관리 시스템에서 실용적으로 다루는 것은 충돌 직렬 가능성이다. 두 트랜잭션의 연산 순서가 교환 가능한지 여부는 동일한 데이터 항목에 대한 쓰기 연산과 읽기 연산이 서로 충돌하는지에 따라 결정되며, 이러한 충돌 관계를 그래프로 표현하여 사이클 존재 여부로 직렬 가능성을 검사할 수 있다.
대부분의 병행성 제어 기법은 직렬 가능한 스케줄을 생성하는 것을 목표로 설계된다. 예를 들어, 로킹 기법을 사용하는 2단계 로킹 프로토콜은 직렬 가능성을 보장하는 대표적인 프로토콜이다. 또한 타임스탬프 순서 기법은 각 트랜잭션에 고유한 시간 값을 부여하여 그 순서에 따라 연산을 처리함으로써 직렬 가능성을 달성한다.
8.3. 회복
8.3. 회복
회복은 데이터베이스 관리 시스템에서 트랜잭션 실행 중 발생할 수 있는 다양한 장애 상황에도 불구하고 데이터베이스를 일관된 상태로 복원하는 메커니즘이다. 병행성 제어 기법이 여러 트랜잭션이 동시에 실행되는 과정에서의 정확성을 보장한다면, 회복 기법은 시스템 크래시, 하드웨어 고장, 소프트웨어 오류와 같은 예기치 못한 사건 이후 데이터의 무결성과 지속성을 보장하는 역할을 한다. 이는 ACID 특성 중 내구성(Durability)을 실현하는 핵심 기술이다.
회복의 기본 원리는 로그 기반의 기록과 데이터 덤프 또는 체크포인트를 활용한 복원이다. 가장 널리 사용되는 로그 기반 회복 기법으로는 즉시 갱신 기법과 지연 갱신 기법이 있다. 즉시 갱신 기법에서는 트랜잭션이 데이터를 변경할 때마다 로그에 변경 사항을 먼저 기록한 후 실제 데이터베이스를 갱신한다. 반면, 지연 갱신 기법에서는 모든 변경 사항을 로그에만 기록해 두었다가 트랜잭션이 성공적으로 완료되는 시점에 한꺼번에 데이터베이스에 반영한다. 이를 통해 장애 발생 시, 로그를 분석해 재실행 또는 취소 작업을 수행함으로써 데이터베이스를 정확한 상태로 되돌릴 수 있다.
회복 관리자는 롤백과 롤포워드라는 두 가지 주요 연산을 수행한다. 롤백은 장애가 발생한 트랜잭션이 데이터베이스에 남긴 부분적인 결과를 모두 취소하여 트랜잭션이 시작되기 전의 상태로 되돌리는 작업이다. 롤포워드는 완료된 트랜잭션의 결과가 장애로 인해 손실되었을 경우, 로그에 저장된 기록을 바탕으로 해당 변경 사항을 데이터베이스에 다시 적용하는 작업이다. 이러한 회복 기법은 은행 시스템이나 항공권 예매 시스템과 같이 데이터의 정확성이 극히 중요한 분야에서 필수적으로 적용된다.
9. 여담
9. 여담
병행성 제어는 데이터베이스 관리 시스템의 핵심 기능 중 하나로, 온라인 거래 처리 시스템의 성능과 신뢰성을 결정짓는 중요한 요소이다. 이 기술의 발전은 클라우드 컴퓨팅 환경과 분산 데이터베이스의 확산으로 더욱 중요성을 더하고 있으며, 대규모 사용자를 지원하는 웹 애플리케이션과 핀테크 서비스의 기반이 된다.
실제 데이터베이스 제품들은 다양한 병행성 제어 기법을 조합하거나 변형하여 적용한다. 예를 들어, PostgreSQL은 주로 다중 버전 병행성 제어를 사용하여 읽기와 쓰기의 충돌을 최소화하는 반면, MySQL의 InnoDB 스토리지 엔진은 MVCC에 로킹 기법을 결합한다. 이러한 구현 방식의 차이는 각 시스템의 설계 철학과 목표하는 워크로드 특성에 따라 달라진다.
병행성 제어의 이론은 트랜잭션 처리 분야를 넘어 동시성 컴퓨팅 전반에 영향을 미쳤다. 운영체제의 프로세스 동기화, 프로그래밍 언어의 멀티스레드 프로그래밍 모델, 그리고 분산 시스템의 합의 알고리즘 설계에도 유사한 개념과 문제점이 나타난다. 따라서 데이터베이스의 병행성 제어를 이해하는 것은 현대 컴퓨팅 시스템의 동작 원리를 파악하는 데 필수적인 지식이 된다.
