시작하기 전에...
동시성(Concurrency)이란 무엇인가?
논리적으로 동시에 실행되는 것처럼 보이게 만드는 개념 (동시 실행처럼 보이지만, 실제로는 아님!)
단일 코어 위에서 시간 분할을 통해 여러 스레드를 번갈아 가며 작업 수행
사용자의 입력을 기다리거나, 네트워크 요청, 파일 입출력 등 I/O 작업이라고 함
I/O 작업 시에는 CPU가 유휴 상태로 대기함. 입력/출력이 끝날 때까지 손 놓고 기다린다는 뜻!
이때 CPU의 효율성을 위해! 아무것도 안하는 대신에 컨텍스트 스위칭으로 다른 스레드 작업을 처리함
이 방법을 쓰면 여러 클라이언트 요청을 동시에 처리할 수 있음
여러 스레드를 사용하면서 Deadlock, Race Condition, Starvation 등의 문제가 발생할 수 있음
(해당 문제는 아래 본문에서 서술)
대립되는 개념, 병렬성(Parallelism)
물리적으로 동일한 시간에 여러 작업을 독립적으로 수행하는 것 (동시실행처럼 보이고, 실제로도 동시수행임!)
여러 개의 코어가 각각 독립된 스레드의 작업을 동시에 처리함
한 작업을 독립적인 하위 작업으로 나눠서 여러 코어에 분산함
작업 완료 시간을 최소화해 고성능 컴퓨팅에 이상적임
한 작업을 병렬 처리하면, 데이터나 리소스를 공유할 때 작업 간 동기화가 필요한 경우가 많음
동기화로 인해 상당한 오버헤드가 발생할 수 있음
동시성 제어
정의: 동시에 발생하는 트랜잭션의 상호간섭 작용 사이에서 DB를 보호하는 것
목적: 동시 실행 트랜잭션 수를 최대화하면서도 데이터의 무결성을 유지해야 함.
종류
1. 낙관적 동시성 제어(Optimistic Concurrency Control)
- 동시에 같은 데이터에서 트랜잭션이 발생하지 않을 것이라고 가정
- 데이터를 읽는 시점에 Lock을 걸지 않는 대신 수정 시점에 값이 변경됐는지 검사
- version 정보를 담는 컬럼을 추가하고, 수정 연산에서 row 버전을 확인해 동일하면 commit, 아니면 rollback
2. 비관적 동시성 제어(Pessimistic Concurrency Control)
- 동시에 같은 데이터에서 트랜잭션이 발생할 것이라고 가정
- 데이터를 읽는 시점에 Lock을 걸고, 트랜잭션이 완료될 때까지 기다림
각 동시성 제어 타입의 예시
Lock-Based Concurrency Control(비관적 동시성 제어의 일종)
정의: 잠금(Lock)을 사용해 데이터 접근의 동시성을 제어함.
문제
- 동시다발적으로 동일한 데이터에 접근할 경우 성능 저하가 발생할 수 있음. (Lock으로 접근 제한.)
- Deadlock(교착 상태)
잘못된 잠금 순서나 설계로 인해 서로의 잠금 해제를 계속 기다리며 진행이 되지 않는 상황.
두 개 이상의 트랜잭션이 서로 필요한 데이터의 락을 점유하고 있어 무한히 대기가 발생함.
트랜잭션 A,B가 있고 id가 1,2인 데이터가 있다
트랜잭션 A는 id 1번을 읽고, 2번을 변경한다. 트랜잭션 B는 id 2번을 읽고, 1번을 변경한다.
트랜잭션 A는 1번, B는 2번 데이터에 대해 공유 락을 획득한다.
공유락은 동시에 다른 트랜잭션이 '읽기 작업'을 위해 공유락을 획득할 수 있지만, 쓰기작업은 안됨.
트랜잭션 A는 2번 데이터에 대해 쓰기 작업이 필요함. 하지만 B가 2번 공유락을 가지고 있어서 획득 불가.
트랜잭션 B는 1번 데이터에 대해 읽기 작업이 필요함. 하지만 A가 1번 공유락을 가지고 있어서 획득 불가.
서로 무한히 기다리기만 함....
모든 트랜잭션이 id 1번->2번 순서로 락을 획득한다면 괜찮음.
트랜잭션 A가 id 1번의 공유락을 획득함.
트랜잭션 B는 id 1번의 배타락을 요청하지만, 트랜잭션 A가 쓰고 있어서 대기함
트랜잭션 A는 id 2번 데이터의 배타락을 요청함.
트랜잭션 B는 id 2번에 대해 공유락을 요청하지만, 트랜잭션 A가 배타락을 요청하고 있기 때문에 대기함.
트랜잭션 B는 트랜잭션 A가 데이터 1번 락을 해제하면, 1번부터 락을 얻을 것이고
트랜잭션 A가 데이터 2번 락을 해제하면, 그 후 2번 락을 얻어서 트랜잭션을 끝낼 것이다.
모든 트랜잭션에 이렇게 엄격한 락 획득 룰을 설계해서 서로의 자원을 기다리기만 하는 상황을 해결할 수 있음.
락 타임아웃을 설정해서 해결할수도 있음.
락을 일정 시간 안에 획득하지 못하면 자동적으로 롤백하거나 다른 처리를 하도록 설정함. - Starvation: 자원을 획득하기 위해 무기한 대기해야 하는 상황.
Lock 종류
- Shared Lock(공유락)/Read Lock(읽기락): 읽기 잠금. (동시에 다른 읽기 작업 가능. 쓰기 작업은 불가능)
공유락이 걸린 데이터에 대해 다른 트랜잭션에서도 '읽기 작업을 위한' 공유락을 획득할 수 있음.
공유락을 사용하면, 트랜잭션 내에서 조회한 데이터가 변경되지 않음을 보장함.
SELECT * FROM table_name WHERE id=1 FOR SHARE; - Exclusive Lock(배타락)/Write Lock(쓰기락): 쓰기 잠금 (동시에 읽기/쓰기 작업 모두 불가능.)
배타락이 걸린 데이터에 대해 다른 트랜잭션에서 공유락과 배타락 획득할 수 없음.
배타락을 획득한 트랜잭션은 데이터에 대한 독점권을 가짐.
SELECT * FROM table_name WHERE id=1 FOR UPDATE;
Lock Based Protocol (잠금 기반 프로토콜 종류)
- Simplistic Lock Protocol: 모든 쓰기 연산 전에 데이터를 잠금.
- Pre-Claiming Lock Protocol: 트랜잭션을 미리 평가해 필요한 잠금을 모두 획득 후 트랜잭션 시작.
- Two-phase Locking Protocol: 트랜잭션 잠금(해제 불가) Growhing Phase<->Shiringking Phase 해제(잠금 불가)
- Strict Two-Phase Locking Protocol: Cascade rollback 방지. 트랜잭션 커밋 or 중단까지 모든 독점 잠금 유지.
MVCC(Multi-Version Concurrency Control)(낙관적 동시성 제어의 일종)
정의: 데이터의 여러 버전을 유지해 트랜잭션이 동시에 데이터를 읽고 쓸 수 있도록 함.
장점: 읽기 작업 시 잠금을 사용하지 않아 높은 동시성을 보임(빠르다). 쓰기 작업과의 충돌이 줄어듦.
단점: 여러 버전의 데이터를 유지해야 함. (버전 관리, 저장 공간 문제)
rollback segment
- 각 트랜잭션은 시작 시점의 SnapShot을 기반으로 데이터를 읽어, 다른 트랜잭션의 변경사항에 영향을 안 받음.
- 트랜잭션 롤백: 트랜잭션 발생 전으로 다시 원래 값을 복원(undo)
- 트랜잭션 복구: 트랜잭션 수행 시 연산을 기록해서, 비정상적으로 종료된 경우 변경 사항을 재실행(redo)
- 읽기 일관성: 트랜잭션 수행 시 커밋하지 않은 변경 데이터를 보여줌. Isolation level(격리 수준)에 따라 적용.
Isolation level(격리 수준): 동시에 트랜잭션을 처리할 때 각 트랜잭션이 얼마나 고립되어 있는가
- read uncommitted: 각 트랜잭션에서 변경 내용이 commit 혹은 rollback 여부에 상관없이 값을 읽을 수 있음
- read committed: 트랜잭션에 의해 실행되는 쿼리는 쿼리 시작 시점에 커밋된 데이터만 읽음(PostgreSQL, Oracle)
- repeatable read: 각 트랜잭션마다 부여된 트랜잭션 ID보다 작은 트랜잭션 번호에서 변경한 것만 읽음(MySQL)
- serializable: 트랜잭션이 동시에 일어나지 않고 순차적으로 실행되는 것처럼 동작함
(+BE 250115 추가)
Gap Lock과 Next-key Lock으로 팬텀 리드를 방지함
- 팬텀 리드(Phantom Read)란 무엇인가?
- 트랜잭션이 동일한 조건의 쿼리를 반복 실행할 때 나타남
- 나중에 실행된 쿼리에서 처음에는 존재하지 않았던 새로운 행이 나타나는 현상
- 읽기 일관성(Read Consistency)를 유지하는 과정에서 발생 가능
- 데이터의 삽입이나 삭제가 다른 트랜잭션에 의해 이루어지는 경우 발생
- 트랜잭션 A 실행(조회)->트랜잭션 B 실행(삽입)->트랜잭션 A실행(조회, 결과 달라짐)
- 갭 락(Gap Lock)이란 무엇인가?
- 특정 index 값 사이의 공간을 잠가서 기존 레코드 사이의 간격을 보호. 새로운 레코드 삽입 방지.
- 범위 내에 특정 레코드가 존재하지 않을 때 적용함.
- 레코드와 레코드 사이 영역을 잠가서 삽입 방지.(갭락)+범위 사이에 걸린 레코드는 수정/삭제 방지(레코드락)
- SELECT * FROM table WHERE id BETWEEN 10 AND 20 FOR UPDATE
- (8, Sue), (10, Alice), (15, Bob), (20, Carol), (21, Dave)
- id가 10 이상과 20이하인 레코드(Alice, Bob, Carol, 수정/삭제 금지) 사이에 데이터를 삽입하는 것을 방지.
- 넥스트 키 락(Next-Key Lock)이란 무엇인가?
- 레코드 자체(레코드 락)와 그 레코드와 인접한 앞뒤 갭(갭락)을 동시에 잠금
- SELECT * FROM table WHERE id = 10 FOR UPDATE
- (8, Sue), (10, Alice), (15, Bob), (20, Carol), (21, Dave)
- id=10인 레코드(Alice, 수정/삭제 금지)와 그 앞 뒤 갭(Sue-Alice 사이, Alice-Bob 사이 삽입 금지)까지 잠금.
- 갭 락과 넥스트 키 락으로 팬텀 리드 방지
- 트랙잭션A가 특정 범위의 데이터를 조회할 때 갭락, 넥스트키락을 설정함
- 트랜잭션 B는 새로운 레코드를 삽입하거나 기존 레코드를 수정할 수 없음
- 트랜잭션 A가 다시 동일한 조건으로 조회해도 결과가 달라지지 않음(팬텀 리드 방지)
실제로는 둘을 절충하여 사용한다
- MySQL의 InnoDB(변경 사항 저장)+UNDO DB(원본 SnapShot 저장): MVCC+Lock-Based
- 읽기 트랜잭션: MVCC를 사용해 일관된 스냅샷을 기반으로 데이터 읽음. 잠금 최소화, 높은 동시성 유지.
- 쓰기 트랜잭션: 잠금을 사용해 데이터의 일관성과 무결성 유지. 데이터 충돌 방지.
개인적인 첨언
: 운영체제 동시성 제어 관련 챕터 내용이네요~ 전공 과목 들으셨던 분들은 그 부분을 같이 보시면 좋을 것 같아용
출처
1. 매일메일, 2025.01.14. 데이터베이스 시스템에서 동시성을 제어하는 방법. https://maeil-mail.kr
2. MVCC란? https://mangkyu.tistory.com/53
3. MVCC 알아보기 https://monday9pm.com/mvcc-multi-version-concurrency-control-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0-e4102cd97e59
4. 매일메일. 2025.01.15. 갭락과 넥스트키 락이란 무엇이며, 어떻게 Phantom Read를 방지하나요? 93번
5. 매일메일. 2025.02.04. 동시성과 병렬성에 대해 설명해주세요. 120번.
6. 매일메일. 2025.01.06. 공유락과 배타락에 대해 설명해주세요. 80번
'개발자 강화 > 백엔드' 카테고리의 다른 글
[매일메일] 캐싱 전략 (BE.250205) (0) | 2025.02.08 |
---|---|
[매일메일] ACID란? (BE.250207) (0) | 2025.02.08 |
[매일메일] CORS란? (BE.250116) + (FE.250205) (0) | 2025.02.05 |
[매일메일] 로드 밸런싱이란? (BE.250203) (0) | 2025.02.03 |
[매일메일] 다중 서버에서 세션 기반 인증을 사용했을 때 문제점? (BE.250131) (0) | 2025.01.31 |