동시성 제어 (Concurrency Control)
동시에 실행되는 여러 트랜잭션의 실행 순서를 제어하여 데이터의 일관성과 무결성을 보장하는 것.
특히 멀티스레드 환경에서는 공유 자원(예: 데이터베이스, 변수)에 대한 동시 접근을 조정해야 한다.
동시성 제어의 주요 목적
- 트랜잭션의 직렬성 보장 : 트랜잭션이 순차적으로 수행되는가?
- 공유도 최대화
- 응답시간 최소화
- 시스템 활동의 최대
동시성을 제어해야 하는 이유?
동시성을 제어하지 않을 경우, 아래와 같은 현상이 생길 수 있다.
- Race Condition (경쟁상태)
- 둘 이상의 프로세스나 스레드가 공유 자원에 동시에 접근하여 결과값에 예측할 수 없는 영향을 줄 수 있는 상태를 말한다. 이는 공유 데이터의 최종값을 보장할 수 없는 상황을 초래할 수 있다.
- Dead Lock (교착상태)
- 두 개 이상의 트랜잭션이 서로가 보유한 자원의 잠금(Lock)을 해제하기를 기다리며 진행하지 못하는 상태다. 이는 시스템에서 프로세스들이 서로의 자원을 기다리며 무한히 대기하는 상황을 초래한다.
- Data Corruption (데이터 손상)
- 동시성 제어가 제대로 이루어지지 않을 경우, 여러 트랜잭션이 동시에 같은 데이터에 접근하여 데이터의 일관성을 해치는 상황이 발생할 수 있다. 이는 데이터베이스의 무결성을 위협하는 심각한 문제다.
- Lost Update (데이터 손실)
- 하나의 트랜잭션이 갱신한 내용을 다른 트랜잭션이 덮어씀으로써 갱신이 무효화가 되는 것을 의미
- 두 개이상 트랜잭션이 한 개의 데이터를 동시에 갱신(Update)할 때 발생
- 현황파악오류 (Dirty Read)
- 모순성 (Inconsistency)
- 다른 트랜잭션들이 해당 항목 값을 갱신하는 동안 한 트랜잭션이 두 개의 항목 값 중 어떤 것은 갱신되기 전의 값을 읽고 다른 것은 갱신된 후의 값을 읽게 되어 데이터의 불일치가 발생하는 상황
- 연쇄복귀 (Cascading Rollback)
- 두 트랜잭션이 동일한 데이터 내용을 접근할 때 발생
- 한 트랜잭션이 데이터를 갱신한 다음 실패하여 Rollback 연산을 수행하는 과정에서 갱신과 Rollback 연산을 실행하고 있는 사이에 해당 데이터를 읽어서 사용할 때 발생할 수 있는 문제
동시성Concurrency
과 병렬성Parallelism
은 어떻게 다른 것인가?
동시성은 싱글 코어에서도 가능하지만, 병렬성은 멀티 코어에서만 가능하다.
동시성은 물리적으로 동시에 실행되는 것이 아니라 논리적으로 동시에 실행되는 것처럼 보이는 것이다.
동시성(Concurrency)
- 여러 작업(스레드, 프로세스)이 번갈아 실행되면서 동시에 실행되는 것처럼 보이는 성질을 의미한다.
- 싱글 코어 CPU에서도 동시성을 구현할 수 있다. → 운영체제(OS) 스케줄러가 빠르게 여러 스레드를 전환하면서 실행되므로, 마치 동시에 실행되는 것처럼 보인다.
멀티 스레드는 동시성을 구현하는 방법 중 하나일 뿐, 동시성 자체와 동일한 개념이 아니다.
예시:
- 싱글 스레드에서 코루틴(Coroutine) 을 사용하여 동시성을 만족할 수 있음 (예: Kotlin 코루틴) : 코루틴은 실제로 하나의 스레드에서 여러 개의 작업을 동시 실행하는 것처럼 보이게 만든다.
- Java의 ExecutorService 를 활용한 멀티스레딩 기반 동시 처리
- Node.js의 이벤트 루프 기반 비동기 처리 (싱글 스레드에서도 동시성을 제공)
병렬성(Parallelism)
- 여러 작업이 물리적으로 동시에 실행되는 것을 의미한다.
- 멀티 코어 CPU에서 여러 개의 작업을 실제로 동시에 실행할 수 있다.
즉, 병렬성은 동시성과 다르게, 실제로 여러 작업이 동시에 수행되는 것을 의미한다.
예시:
- 멀티 코어 CPU에서 각 코어가 서로 다른 스레드를 동시에 실행하는 경우
- GPU에서 여러 개의 연산을 병렬로 처리하는 경우
동시성 제어 기법들
Locking
- 데이터에 대한 접근을 물리적으로 제어
- 트랜잭션이 데이터에 잠금(lock)을 설정하면 다른 트랜잭션은 해당 데이터에 대해 잠금이 해제(unlock)될 때까지 접근/수정/삭제가 불가하다.
- 트랜잭션이 사용하는 자원에 대하여 상호 배제(Mutual Exclusive) 기능을 제공하는 기법
Timestamp
- 각 트랜잭션과 데이터 항목에 타임스탬프를 부여하여 논리적으로 접근을 제어
- 타임스탬프를 기반으로 트랜잭션의 실행 순서를 결정
Locking vs Timestamp
어떤 방식을 사용할지는 시스템의 특성(트랜잭션 충돌 빈도, 성능 요구사항 등)에 따라 결정해야 함!
비교 항목 | 락킹(Locking) | 타임스탬프(Timestamp) |
---|---|---|
접근 방식 | 데이터 접근을 물리적으로 제어하며, 트랜잭션이 접근 전 락을 획득하고 작업 후 해제 | 각 트랜잭션과 데이터 항목에 타임스탬프를 부여하여 논리적으로 접근 제어 |
충돌 처리 | 충돌 발생 시, 한 트랜잭션이 락을 획득할 때까지 대기 (교착 상태 발생 가능) | 충돌 발생 시, 타임스탬프를 비교하여 즉시 롤백 또는 작업 수행 (교착 상태 없음) |
동시성 수준 | 락 범위 및 유지 시간에 따라 동시성이 달라짐 (보수적인 락킹은 동시성 저하) | 일반적으로 락킹보다 높은 동시성을 제공, 병렬 실행 가능 |
구현 복잡성 | 구현이 상대적으로 간단하며, 2PL, 낙관적 락킹 등 다양한 기법 존재 | 구현이 복잡할 수 있으며, 시스템 전체의 일관된 시간 관리 필요 |
성능 특성 | 충돌이 빈번한 환경에서는 대기 시간이 길어질 수 있음 | 충돌이 적은 환경에서 효율적이지만, 롤백으로 인한 오버헤드 발생 가능 |
장점 | 데이터의 일관성을 직관적으로 보장 | 교착 상태가 발생하지 않으며, 동시성이 높음 |
단점 | 교착 상태 발생 가능, 동시성 저하 우려 | 롤백이 많으면 성능 저하 가능 |