02 동시성 문제
02 동시성 문제
1. Note
- 1개 세션에 여러개 커넥션을 가질 수 있음
- 세션(Session)은 사용자 또는 애플리케이션과 DB 사이의 “논리적인 대화 단위”
- 커넥션(Connection): 실제 DB 서버와 맺는 “물리적인 연결”
- 1개의 세션이 여러개의 커넥션을 가질 수 있음
- 스프링에서는 커넥션풀이 “한개의 세션작업에서는 한개의 커넥션으로 쓰게 만듬”
- 단, 해당 작업에 한정해서 한개의 커넥션을 쓰게함!
- 그리고 다른 작업에서는 새로운 세션이 열리고, 기존에 끝난 커넥션을 가질 수 있음!
- 동시성은 여러가지를 고려해야함
- 극단적으로 보면 새벽에 배치만 돌리고, 대민사업으로 리드만 하는 경우라면 굳이 동시성 문제 고려 X
- 어떤 상황에 어떤 수준으로 어떤걸 포기해야하는지에 대한 생각에 대한 문제
- 시스템에서 문제가 있는 부분과 문제가 없는 부분은 무엇인가?
2. 동시성 문제
1. 동시성
- 여러 스레드(또는 프로세스)가 동시에 같은 데이터를 읽고/수정할 때 “실행 순서에 따라 결과가 달라지는 문제”
2. 상황
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1. 은행 계좌 잔액이 10,000원이라고 가정할때,
- 스레드 A: 5,000원 출금
- 스레드 B: 5,000원 출금
2. 정상 적인 흐름
- A → 5,000원
- B → 0원
3. 동시 실행 상황
- A가 잔액 10,000 읽음
- B도 잔액 10,000 읽음
- A가 5,000으로 수정
- B도 5,000으로 수정
4.최종 예상
- 5,000원이 남아야 하는데, 10,000 → 5,000으로 덮어씀
- 즉, 한 번의 출금이 “사라진 것처럼” 보임
3. 원인
- 동시성 문제는 여러 스레드나 트랜잭션이 같은 공유 자원을 동시에 사용할 때 발생
- 하나의 작업이 읽기, 수정, 쓰기로 나뉘어 실행되는 동안 실행 순서가 섞이면서 문제가 생김
- 같은 데이터를 누군가는 쓰고 / 누군가는 읽고 / 누군가는 수정
- 그 결과 한 작업의 변경이 다른 작업에 의해 덮이거나, 중간 값을 기준으로 잘못 계산될 수 있음
- 원자성이 보장되지 않으면 이런 문제가 더 쉽게 발생
3. ACID
1. Atomicity (원자성)
- 개념
- 트랜잭션 안에서 실행되는 여러 작업은 하나의 묶음처럼 처리되어야 하며,
- 모든 작업이 성공하면 커밋되고 하나라도 실패하면 전체가 롤백되어야함
- 중간 단계가 남게 되면 돈이 사라지거나 중복되는 심각한 데이터 오류가 발생할 수 있기 때문에 반드시 보장되어야함
- 예시
- 은행 송금에서는 A 계좌에서 돈을 출금하고 B 계좌에 입금하는 두 작업이 함께 수행됨
- 이때 중간에 오류가 발생하면 출금과 입금이 모두 취소되어야 데이터가 맞게 유지됨
- 문제 유형
- Partial Update (부분 업데이트) : 하나의 작업이 여러 단계로 나뉘어 있는데, 그중 일부만 DB에 반영된 상태
- Inconsistent State (중간 상태) : DB 전체 관점에서 데이터 정합성이 깨진 상태
2. Consistency (일관성)
- 개념
- 트랜잭션이 시작되기 전과 끝난 후 모두 데이터베이스의 규칙과 제약 조건이 항상 유지되어야 한다는 성질
- 데이터가 비정상적인 상태로 저장되는 것을 막아 전체 시스템의 신뢰성을 유지함
- 예시
- 잔액은 0보다 작을 수 없고, 주문 데이터는 반드시 존재하는 상품 ID를 참조해야 함
- 이러한 규칙은 트랜잭션이 끝난 이후에도 반드시 지켜져야 함
- 문제 유형 이름
- Constraint Violation (제약조건 위반) : DB에 정의된 “규칙(제약조건)”을 어긴 상태
- Data Corruption (논리적 불일치) : 데이터 자체는 저장되지만, 비즈니스 의미가 깨진 상태
3. Isolation (격리성)
- 개념
- 여러 트랜잭션이 동시에 실행되더라도 서로의 작업 결과에 영향을 주지 않고 독립적으로 실행되는 것처럼 동작해야함
- 동시성 환경에서 데이터 충돌을 방지하고, 예측 가능한 결과를 보장하기 위해 필요함
- 예시
- 두 사용자가 동시에 동일한 재고 1개 상품을 구매하는 상황에서,
- 각각이 재고가 있다고 판단하고 동시에 구매를 진행하면 재고가 음수가 되는 문제가 발생할 수 있음
- 문제 유형
- Dirty Read
- Non-Repeatable Read
- Phantom Read
- Lost Update
4. Durability (지속성)
- 개념
- 트랜잭션이 성공적으로 커밋된 이후에는 시스템 장애나 서버 다운이 발생하더라도 해당 결과는 영구적으로 저장되어야 함
- 시스템 장애 상황에서도 중요한 데이터가 유실되지 않도록 보장하여 신뢰성을 유지함
- 예시
- 결제가 완료된 직후 서버가 다운되더라도, 결제 데이터는 DB에 안전하게 남아 있음
- 문제유형
- Data Loss (데이터 유실) : 트랜잭션이 “커밋됐다고 생각했는데” 데이터가 사라지는 문제
- Crash Recovery Failure : 시스템이 장애 후 복구 과정에서 데이터 상태를 제대로 복원하지 못하는 문제
3. AICD - 격리성 레벨
1. READ UNCOMMITTED (Lv0)
- 개념
- 다른 트랜잭션이 아직 커밋하지 않은 데이터까지 읽을 수 있는 가장 낮은 격리 수준
- 확정되지 않은 중간 데이터까지 그대로 보이게 됨
- 데이터 정합성보다 성능과 동시성을 최우선으로 하기 때문에 락을 거의 사용하지 않음
- 그 결과 Dirty Read(커밋되지 않은 데이터 읽기)가 발생할 수 있음
- 동작 방식
- 트랜잭션이 다른 트랜잭션의 “미커밋 데이터”까지 그대로 읽음
- 락을 거의 사용하지 않거나 매우 약하게 사용
- 쓰기 중인 데이터도 읽을 수 있어도 막지 않음
- 장/단점
- 장점
- 락을 최소화하기 때문에 성능이 가장 빠르고,
- 대량 조회나 분석처럼 정확도가 크게 중요하지 않은 경우에 유리함
- 단점
- 아직 확정되지 않은 데이터를 읽을 수 있어서 실제 DB 상태와 다른 결과가 나올 수 있음
- 서비스 로직에서는 거의 사용되지 않음
- 장점
2. READ COMMITTED (Lv1)
- 개념
- 커밋이 완료된 데이터만 읽을 수 있는 격리 수준
- 대부분의 DB와 Spring에서 기본으로 사용하는 수준
- Dirty Read는 방지되지만, 같은 데이터를 같은 트랜잭션 안에서 여러 번 조회하면 결과가 달라질 수 있음
- 읽는 시점에 따라 값이 바뀔 수 있음
- 동작 방식
- SELECT 시점에 “커밋된 데이터만” 읽음
- 쓰기 중인 데이터는 읽지 못하게 잠깐 막음
- 하지만 읽을 때마다 최신 커밋 데이터를 다시 가져옴
- 장/단점
- 성능과 안정성의 균형이 가장 좋아서 일반적인 웹 서비스에서 가장 널리 사용됨
- 조회 시점마다 결과가 달라질 수 있어, 완전히 고정된 데이터를 기대하면 문제가 될 수 있음
3. REPEATABLE READ (Lv2)
- 개념
- 한 트랜잭션이 시작된 이후에는 같은 데이터를 여러 번 읽어도 항상 동일한 결과를 보장하는 격리 수준
- 트랜잭션 시작 시점의 데이터를 기준으로 읽기 때문에 중간에 다른 트랜잭션이 데이터를 변경해도 영향을 받지 않음
- Non-repeatable Read는 방지되지만, 새로운 데이터가 “끼어드는” Phantom Read는 DB에 따라 발생할 수 있음
- 동작 방식
- 트랜잭션 시작 시점의 “스냅샷 데이터”를 기준으로 읽음
- 이후 다른 트랜잭션이 데이터를 바꿔도 내 트랜잭션에서는 안 보임
- 보통 MVCC(버전 관리) 방식으로 구현
- 장단점
- 읽기 일관성이 매우 높아서, 보고서 생성이나 정합성이 중요한 조회 작업에 적합
- 동시성이 줄어들고, 내부적으로 락이나 MVCC 비용이 증가하여 성능 부담이 생길 수 있음
4. SERIALIZABLE (Lv3)
- 개념
- 가장 강한 격리 수준으로, 모든 트랜잭션이 마치 하나씩 순서대로 실행된 것처럼 처리됨
- 동시에 실행되더라도 결과적으로는 완전히 순차 실행된 것처럼 보이게 만들기 때문에
- Dirty Read, Non-repeatable Read, Phantom Read까지 모두 방지
- 동작 방식
- 모든 트랜잭션을 “완전히 순서대로 실행된 것처럼” 처리
- 읽기/쓰기 모두 강하게 락을 검
- 범위까지 잠그는 경우가 많아 Phantom Read도 막음
- 장단점
- 데이터 정확성과 일관성이 가장 높아서 금융처럼 절대 오류가 나면 안 되는 시스템에서 사용
- 동시 처리가 거의 불가능해지고 성능이 크게 떨어지며, 트랜잭션 충돌이 자주 발생할 수 있음
4. AICD - 격리성 문제
1. 전체 분류
| 격리 수준 | Dirty Read | Non-Repeatable Read | Phantom Read | Lost Update |
|---|---|---|---|---|
| READ UNCOMMITTED | O | O | O | O |
| READ COMMITTED | X | O | O | O |
| REPEATABLE READ | X | X | O* | O |
| SERIALIZABLE | X | X | X | X |
2. Dirty Read (더티 리드)
- 개념
- 아직 확정되지 않은 데이터를 읽어버려 잘못된 판단을 하게 되는 문제
- Dirty Read는 다른 트랜잭션이 아직 커밋하지 않은 데이터를 읽는 현상
- 즉, 확정되지 않은 “중간 상태 데이터”를 읽어버리는 문제
- 예시
1 2 3 4 5 6 7
금액 조회에서 트랜잭션 A: 사용자 잔액을 10,000원 → 5,000원으로 변경 (아직 커밋 전) 트랜잭션 B: 해당 사용자의 잔액을 조회 → 5,000원으로 읽음 이후 트랜잭션 A가 실패하여 롤백됨 (실제 잔액은 10,000원 유지) -> 트랜잭션 B는 존재하지 않는 5,000원이라는 데이터를 읽게 됨
- 해결
- READ COMMITTED 이상을 사용하여 Dirty Read를 방지 가능
- 이 수준부터는 커밋이 완료된 데이터만 읽을 수 있기 때문에,
- 아직 커밋되지 않은 중간 상태 데이터는 조회되지 않음
3. Non-Repeatable Read (반복 불가능 읽기)
- 개념
- Non-Repeatable Read는 같은 트랜잭션 안에서 같은 데이터를 다시 조회했을 때 값이 달라지는 현상
- 같은 트랜잭션 안에서 같은 데이터를 다시 조회했을 때 값이 달라지는 문제
- 예시
1 2 3 4 5 6 7
주문 시스템에서 트랜잭션 A: 주문 상태를 조회 → “결제 대기” 트랜잭션 B: 해당 주문 상태를 “결제 완료”로 변경 후 커밋 트랜잭션 A: 같은 주문을 다시 조회 → “결제 완료” -> 같은 트랜잭션 안에서 조회 결과가 달라지게 됨
- 해결
- REPEATABLE READ 이상을 사용하여 Non-Repeatable Read를 방지
- 트랜잭션 시작 시점의 데이터를 기준으로 조회되기 때문에,
- 다른 트랜잭션이 값을 변경하고 커밋하더라도 현재 트랜잭션에서는 변경된 값이 보이지 않음
4. Phantom Read (팬텀 리드)
- 개념
- 같은 조건으로 데이터를 다시 조회했을 때 결과 행의 개수가 달라지는 현상
- 기존 데이터가 바뀌는 것이 아니라 새로운 데이터가 추가되거나 삭제되어 조회 결과 자체가 달라지는 문제
- 예시
1 2 3 4 5 6 7
주문 시스템에서 트랜잭션 A: “결제 금액 10,000원 이상 주문” 조회 → 3건 트랜잭션 B: 새로운 주문(15,000원) 추가 후 커밋 트랜잭션 A: 같은 조건으로 다시 조회 → 4건 -> 같은 조건인데 조회 결과 행 수가 달라지게 됨
- 해결
- SERIALIZABLE 수준을 사용하여 Phantom Read를 방지
- 범위 자체를 잠그거나 완전히 직렬 실행처럼 처리하기 때문에,
- 다른 트랜잭션이 새로운 데이터를 추가하더라도 현재 트랜잭션의 조회 결과에는 영향을 주지 않음
This post is licensed under CC BY 4.0 by the author.