Write Skew
Write Skew 는 두 트랜잭션이 같은 **판단 (읽기)**을 하고 각자 다른 레코드를 갱신해서 결과적으로 비즈니스 규칙이 깨지는 현상이다.
예: 두 의사가 동시에 당직 해제 판단 → 최종적으로 아무도 남지 않는 상황. Snapshot Isolation 은 읽기는 스냅샷에서 빠르게 처리하고 쓰기 충돌 (같은 행을 동시에 쓰는 경우) 만 검사하기 때문에 이런 상황을 허용할 수 있다.
예방은 DB 수준 (Serializable/SSI, predicate/next-key locks) 이나 애플리케이션 수준 (guard row, 명시적 락, 집계 테이블) 으로 수행하며, 운영적으로는 직렬화 오류율을 모니터링하고 재시도 정책을 설계해야 한다.
핵심 개념
Write Skew 는 두 개 이상의 트랜잭션이 같은 ’ 판단 근거 ‘(예: 현재 참가자 수) 를 보고 각자 다른 행을 수정해, 최종적으로 시스템 규칙 (예: 최대 인원) 이 깨지는 경우를 말한다.
SI(스냅샷 격리) 는 읽기 성능이 좋아 실무에서 자주 쓰이지만 이런 상황을 막지 못한다.
문제를 막으려면
- 핵심 트랜잭션엔 직렬 가능성 (Serializable) 또는 SSI 사용
- 범위/프레디케이트 락 (예: SELECT FOR UPDATE, next-key lock) 적용
- 애플리케이션 레벨 제약 (버전 체크·재시도)
을 조합해 보완한다.
| 핵심 개념 (한글·약어) | 정의 | 무슨 문제 해결? | 실무 적용 방식 |
|---|---|---|---|
| 다중버전 동시성 제어 (MVCC) | 스냅샷 기반 읽기, 각 트랜잭션은 고유 버전 사용 | 읽기 성능 향상, 락 경합 감소 | PostgreSQL/InnoDB 등 기본 메커니즘 활용 |
| 스냅샷 격리 (SI) | WW 충돌을 거부, 스냅샷에서 읽음 | 대부분의 쓰기 충돌 방지 (그러나 RW 허용) | 기본 격리로 많이 사용 (성능 우수) |
| 쓰기 스큐 (Write Skew) | 서로 다른 행을 수정해 전역 제약 위반 | SI 환경에서 발생 가능한 논리 위반 | Serializable/범위락/애플리케이션 제약으로 보완 |
| 직렬 가능성 (Serializable) | 어떤 순차 실행과 동일한 결과 보장 | 모든 동시성 이상 제거 | 성능 비용 수용 시 적용 |
| Serializable SI (SSI) | SI 위에 위험 구조 탐지 추가 | SI 장점 유지하면서 직렬성 수준 안전화 | PostgreSQL SSI 등에서 지원 |
| 2 단계 잠금 (2PL) / 프레디케이트 락 | 획득/해제 규칙으로 락 관리, 범위 락 포함 | 팬텀·범위 삽입 방지 | SELECT FOR UPDATE, next-key lock 등 |
쓰기 스큐는 SI 환경의 대표적 논리 이상이며, MVCC/SI 가 읽기 성능을 확보하는 동안 남는 빈틈을 SSI·Serializable·범위 락·애플리케이션 제약으로 막는다. 실무선택은 무결성 중요도와 성능 요구에 따라 달라진다.
개념 관계와 방지 메커니즘
| 출발 (원인) | → 작용 대상 | 목적 (왜) | 결과 (어떤 영향) |
|---|---|---|---|
| MVCC | → SI 동작 기반 제공 | 읽기 동시성 확보 | 스냅샷 읽기 (읽기 경합 감소) |
| SI | → 트랜잭션 간 상호작용 | WW 충돌 거부, RW 허용 | Write Skew 가능성 발생 |
| Write Skew 발생 | → 시스템 무결성 위험 | 비즈니스 규칙 위반 | 필요시 Serializable 로 보완 |
| SSI | → SI 에 검출 층 추가 | RW 사이클 탐지·차단 | 직렬성 보장 (성능 - 무결성 균형) |
| 2PL/Predicate Lock | → 범위/조건 보호 | 팬텀/삽입 차단 | 락 경합·교착 가능성 증가 |
관계의 핵심은 " 어떤 메커니즘이 어떤 이상을 막고, 그 대가로 어떤 비용 (성능/복잡도) 을 초래하는가 " 를 명확히 하고 적절히 조합하는 것이다.
Write Skew 실무 대책 매핑표
| 개념 | 실무에서 무엇을 적용 (무엇) | 어떻게 적용 (방법) | 왜 적용 (비즈니스 이유) |
|---|---|---|---|
| SI (스냅샷 격리) | 기본 격리로 사용 | DB 기본값 또는 설정 | 읽기 성능 우선, 대부분 충분 |
| Write Skew | 위험 거래에 대해 보호 | 해당 트랜잭션을 Serializable 로 전환 또는 범위락 적용 | 무결성·규정 준수 확보 |
| SSI | 핵심 트랜잭션에 권장 | PostgreSQL 의 SSI 사용 (재시도 처리) | SI 장점 유지 + 직렬성 보장 |
| 범위 락 (next-key/gap) | 예약·유니크 제약에 적용 | SELECT FOR UPDATE / 인덱스 설계 병행 | 과예약·삽입 충돌 차단 |
| 애플리케이션 제약 (버전) | UI·API 경계에서 검증 | version 컬럼 + UPDATE WHERE version=? | 사용자 think-time 시 실무적 보완 |
| 모니터링/검증 | 충돌률·직렬화 실패 측정 | Prometheus/Grafana 지표, Jepsen 스타일 테스트 | 정책 결정 근거 제공 |
실무에서는 SI 를 기본으로 두되, 비즈니스 영향이 큰 트랜잭션에 대해서만 Serializable/SSI 또는 범위 락을 적용하는 ’ 선택적 엄격화 ’ 전략이 현실적이다. 애플리케이션 레벨 방어와 운영 모니터링은 필수 보완책이다.
기초 조사 및 개념 정립
Write Skew 이해와 실무적 대응
두 사람이 같은 규칙을 기준으로 동시에 판단해서 서로 다른 레코드를 바꾸면, 합쳐진 결과가 규칙을 깨는 경우가 있다.
예를 들어 " 항상 두 명의 담당자가 있어야 한다 " 는 규칙에서, A 와 B 가 동시에 서로를 대신할 수 있다고 판단해 각자 업무를 내려놓으면 결과적으로 둘 다 없어져 규칙을 위반하게 된다. 이런 현상이 바로 Write Skew 다.
방지하려면 DB 의 직렬화 격리 (Serializable/SSI) 를 사용하거나, 애플리케이션에서 명시적 잠금 (SELECT … FOR UPDATE) 또는 불변식 검증을 원자적으로 수행해야 한다.
왜 발생하는가
- MVCC/Snapshot Isolation 에서는 각 트랜잭션이 자신만의 읽기 스냅샷을 가짐.
- T1 과 T2 가 같은 프레디킷 P 를 읽어 " 조건 충족 " 으로 판단 → 각각 서로 다른 행 R1, R2 를 업데이트.
- 서로의 업데이트를 보지 못했기 때문에 둘 다 커밋 가능 → 합쳐진 결과 (두 행의 상태 조합) 가 시스템 불변식을 위반할 수 있음.
간단한 재현 예제 (의사 SQL, 단계별 주석)
| |
- 위는 Snapshot Isolation 환경에서 전형적으로 발생하는 Write Skew 사례다.
방지 방법
Serializable / SSI 사용: DB 가 의존성 (읽기→쓰기 간 의존) 을 탐지해 충돌 시 하나를 Abort → 재시도 유도. 근본적 해결이나 성능·재시도 비용 존재.
명시적 락: 문제 구간에서
SELECT … FOR UPDATE등으로 관련 행 또는 범위를 잠금 → 다른 트랜잭션의 동시 판단 차단.원자적 불변식 검사·갱신: 애플리케이션이 불변식 검사와 갱신을 DB 내부 (예: 저장 프로시저) 에서 원자적으로 수행.
데이터 모델/제약 강화: DB 제약 (예: 체크 제약, 트리거, 고유성) 으로 불변식을 강제할 수 있으면 애플리케이션 레벨 위험 감소.
혼합 전략: 핵심 경로는 직렬화, 나머지는 SI + 검증으로 성능과 일관성 균형.
탐지·운영 관점
- 탐지 신호: 도메인 규칙 위반 알림, 애플리케이션 레벨 검증 실패, 재시도 패턴 이상.
- 진단 도구: 트랜잭션 로그, 직렬화 실패 (예: SQLSTATE 40001) 로그, APM 으로 긴 트랜잭션/동시성 패턴 분석.
- 런북 (즉시 대처): 문제 트랜잭션 롤백/재시도 유도, 임시로 중요 경로의 격리 수준 올리기, 해당 기능의 트래픽 셰이딩.
등장 배경 및 발전 과정
무엇이 문제인가?
두 트랜잭션이 같은 데이터를 읽고 서로 다른 행을 _ 갱신 _ 하면, 충돌 (같은 행을 쓰는 경우) 이 없어 보이지만 결과적으로 데이터 제약이 깨질 수 있다. 이 현상이 write-skew다.왜 SI 에서 발생하나?
SI 는 각 트랜잭션에 시작 시점 스냅샷을 보여주고, 서로 다른 행을 갱신할 때 쓰기 - 쓰기 충돌이 발생하지 않으면 커밋을 허용한다. 그 결과 스냅샷 기반으로 보았을 때 정합성 제약을 위반할 수 있다.어떤 기술이 해결했나?
전통적 락 (2PL) 로 막을 수 있지만 비용이 크다. SI 의 장점은 살리되 write-skew 를 차단하려고 SSI 같은 런타임 충돌 검출 기법이 도입되었다.
등장 배경
대규모 OLTP 와 MVCC 기반 DB 의 확산으로 읽기 성능을 중시하는 설계가 보편화되었다.
이 과정에서 SI 는 읽기 성능과 일관성의 좋은 절충을 제공했지만, 현실 비즈니스 제약 (예: " 적어도 한 명은 on-call 이어야 한다 “) 에서 SI 가 허용하는 특정 병렬 패턴이 제약을 깨뜨리는 사례가 발견되었다.
이 문제 (동시성 패턴으로 인한 제약 위반) 가 바로 write-skew이며, 이로 인해 SI 의 한계를 보완하는 연구·기술 (SSI 등) 이 촉발되었다.
발전 과정
| 시기 (개념 순서) | 기술/개념 | 등장 이유 (문제) | 개선/효과 |
|---|---|---|---|
| 1 | 2PL / 전통적 락 | 동시 쓰기 충돌·정합성 보장 | 직렬성 보장, 락 경합·교착 발생 |
| 2 | MVCC | 읽기 경합·성능 문제 완화 | 읽기 스냅샷 제공, 읽기 성능 향상 |
| 3 | Snapshot Isolation (SI) | MVCC 기반 읽기 일관성 강화 | 스냅샷 읽기 제공, 동시성↑ (하지만 write-skew 가능) |
| 4 | Write-Skew 문제 인식 | SI 하에서 비직렬화 사례 보고 | SI 의 한계 (비즈니스 제약 훼손) 드러남 |
| 5 | Serializable SI (SSI) | SI 의 취약점 보완 필요 | 런타임 충돌 검출로 직렬성 보장 (일부 트랜잭션 abort) |
| 6 | 애플리케이션/설계 보완 | 모든 트랜잭션 강제 직렬화 비용 회피 | 하이브리드 설계 (CQRS, 제약, 샤딩) 적용 |
timeline
title Write-Skew 등장과 대응 진화
"2PL / 락 중심" : 1
"MVCC 도입" : 2
"Snapshot Isolation (SI)" : 3
"Write-Skew 문제 인식" : 4
"Serializable SI / SSI" : 5
"애플리케이션/설계 보완" : 6
- 전통적 락 (2PL) 은 write-skew 같은 문제를 막지만 읽기 성능 저하라는 비용이 따라왔다. MVCC 와 SI 는 읽기 성능을 대폭 개선했으나 SI 는 write-skew 를 허용할 수 있어 비즈니스 제약을 손상시킬 소지가 드러났다. 이를 보완하기 위해 **SSI(런타임 충돌 검출)**가 등장했고, 실제 시스템에서는 비용과 효과를 고려해 **DB 수준의 SSI + 애플리케이션 설계 보완 (제약·검증·샤딩)**을 혼용하는 방식이 널리 채택된다. 또한 같은 이름의 격리 수준이라도 DB 별 구현 차이를 반드시 검증해야 한다.
Write Skew 문제·목적·해결
Write Skew 는 서로 다른 행을 동시에 업데이트한 두 트랜잭션이 합쳐져 전체 규칙 (불변식) 을 깨는 문제로, 주로 Snapshot Isolation 같은 격리 환경에서 발생한다.
간단 예시 (의료 온콜):
- T1: 읽기—의사 A on_call? yes; T2: 읽기—의사 B on_call? yes
- T1: A.on_call = false; 커밋
- T2: B.on_call = false; 커밋 → 결과: 병동에 on_call 담당자 0 명 (불변식 위반).
해결 아이디어 (직관): 두 트랜잭션이 서로의 판단에 영향을 주지 못하도록 _ 공유 자원에 대한 동시성 제어 _ 를 적용하거나 (락/직렬화), 애플리케이션 단에서 집계를 단일화해 원자적으로 갱신한다.
Write Skew 가 유발하는 문제들
| 문제 유형 | 구체 증상 | 비즈니스 영향 |
|---|---|---|
| 도메인 불변식 위반 | 서로 다른 행 동시 변경 → 전체 제약 (예: 최소 1 명 유지) 위반 | 규정 위반, 서비스 중단, 안전사고 가능 |
| 상태 불일치 | 트랜잭션 간 상태 판단 불일치 (스냅샷에 의존) | 예측 불가능한 동작, 버그 발생 |
| 탐지 어려움 | 각 트랜잭션 자체론 정상 → 문제는 합쳐졌을 때 발생 | 디버깅·감사 곤란 |
- Write Skew 는 ’ 부분적으로 보면 정상 ’ 이지만 전체 규칙엔 치명적인 오류**를 만드는 동시성 결함이다. 특히 안전·규제·금융 같은 도메인에서 즉시 문제로 이어질 수 있어 주의 깊은 설계가 필요하다.
Write Skew 해결의 핵심 목적
| 목적 | 구체 목표 | 기대 효과 |
|---|---|---|
| 도메인 불변식 보장 | 시스템 상태가 불변식 항상 만족하게 함 | 안전성·무결성 확보 |
| 합성 동작 신뢰도 향상 | 다중 트랜잭션 합성 결과 예측 가능 | 오류·사후 처리 비용 감소 |
| 운영·감사 용이성 | 이상상황 희소화로 디버깅 용이 | 규정 준수·감사 준비성 강화 |
- 핵심: 목적은 단순히 오류를 줄이는 게 아니라 시스템 레벨에서 규칙 (불변식) 을 항상 지키게 만들어 비즈니스 신뢰성을 확보하는 것이다.
문제와 목적의 연계 (Write Skew)
| 문제 (증상) | 대응 목적 | 연계 방식 |
|---|---|---|
| 도메인 불변식 위반 | 도메인 불변식 보장 | 트랜잭션 수준 충돌 차단 또는 불변식 단일화 |
| 상태 불일치 | 합성 동작 신뢰도 향상 | 읽기/쓰기 가시성 조정 (격리 수준/락) |
| 탐지 어려움 | 운영·감사 용이성 | 실패 시 재시도·로그·경보 체계 구축 |
- 핵심 연결: 각 문제는 불변식 보장·가시성 제어·운영 지원이라는 목적에 직접적으로 매핑된다. 해결책은 문제 유형에 맞춘 기술 (격리·락·애플리케이션 설계) 과 운영 정책 (재시도·모니터링) 을 조합하는 것이다.
Write Skew 발생요건과 확인 체크리스트
무슨 문제인가?
두 트랜잭션이 같은 _ 읽기 결과 _ 를 기반으로 서로 다른 행을 수정하면, 최종 상태가 시스템의 규칙 (불변식) 을 깨는 현상이 생길 수 있다. 이 현상이 바로 Write Skew다.언제 발생하나? (체크리스트)
- DB 가 MVCC/Snapshot 기반 (스냅샷 읽기) 을 사용한다.
- 불변식이 여러 행/범위에 걸쳐 정의되어 있다.
- 동시 트랜잭션들이 같은 읽기 집합을 읽고 서로 다른 행을 쓴다 (WW 충돌 없음).
- 격리 수준이
SERIALIZABLE이 아니거나 SI 만 적용되어 있는 경우.
가장 쉬운 예: 두 의사가 각각 다른 레코드를 지워서 결국 아무도 남지 않는 상황 (Doctors on-call 예).
단기·실무적 대처: 애플리케이션 수준에서 검증 및 재시도,
SELECT FOR UPDATE또는 의도적 업데이트 (충돌 물질화) 사용. 장기적으로는SERIALIZABLE/SSI 도입 검토.
Write Skew 발생 전제·요구조건 체크표
| 항목 | 요구사항 (명세) | 왜 필요한가 (핵심 이유) | 검증 방법 / 지표 |
|---|---|---|---|
| 실행 모델 | MVCC / Snapshot Isolation 또는 비잠금 읽기 환경 | 스냅샷 읽기로 다른 트랜잭션의 중간 쓰기를 보지 못함. | DB 문서 확인 (SHOW transaction_isolation 등). |
| 불변식 형태 | 불변식이 여러 행/범위에 걸쳐 표현됨 (조합적 제약) | 조합적 제약은 단일 행 잠금으로는 보장 불가. | 비즈니스 규칙 분석 (제약 SQL/유즈케이스 확인). |
| 동시성 패턴 | 여러 트랜잭션이 동시에 동일한 읽기 집합을 읽음 | 읽기 기반 의사결정이 서로를 인지하지 못하는 상황 생성. | 부하/동시성 로그, 트랜잭션 타임라인. |
| 쓰기 충돌 | 각 트랜잭션의 쓰기 집합이 서로 서로 다른 행(WW 충돌 없음) | WW 충돌이 없으면 DB 가 둘 다 커밋시키고 write-skew 발생 가능. | 쓰기 대상 분석, EXPLAIN/트랜잭션 로그. |
| 격리 수준 | 격리 수준이 SERIALIZABLE 보다 약함 (예: SI/REPEATABLE READ 등) | SERIALIZABLE 이면 이 클래스의 이상을 방지하거나 탐지 가능. | 격리설정 확인 및 SSI 여부 점검. |
Write Skew 가 발생하려면
- MVCC/Snapshot 기반 읽기 모델이면서
- 불변식이 여러 행/범위에 걸쳐 정의되어 있고
- 동시 트랜잭션들이 같은 읽기 집합을 읽은 뒤 서로 다른 행을 수정 (즉, WW 충돌이 없을 때) 해야 하며,
- 격리 수준이 완전한 직렬성을 보장하지 않을 때 발생 가능성이 높다.
위 항목을 하나하나 점검하면 write-skew 취약성을 판단할 수 있다.
Write Skew: 원인·특징·실무 대응
Write Skew 는 서로 다른 행을 동시에 바꾸는 두 트랜잭션이 함께 실행될 때, 각각은 정당해도 합치면 전체 규칙 (예: 최소 인원 유지) 을 깨는 문제다.
스냅샷 격리 (SI) 는 이런 상황에서 종종 탐지하지 못한다. 해결하려면 직렬성 보장 (Serializable/SSI) 을 쓰거나, 중요한 읽기 집합을 잠그거나, 커밋 전에 규칙을 다시 확인해 실패 시 재시도하는 패턴을 적용한다.
Write Skew 특징·근거·차별표
| 핵심 특징 | 기술적 근거 | 다른 동시성 이상과의 차별점 | 실무적 의미 (대응 포인트) |
|---|---|---|---|
| 발생 조건: 다른 행 동시 갱신 | MVCC 스냅샷 + WW 검사만 수행 | Lost Update/Dirty Write 는 동일 행 충돌 | 중요 불변식 관련 트랜잭션 식별 |
| 직렬성 위반 (비가시적) | 의존성 그래프에서 read–write 로 사이클 생성 | Phantom 은 범위 변화 연계, Write Skew 는 불변식 초점 | 자동화된 동시성 테스트 필요 |
| 탐지 어려움 | WW 가 없어 SI 검사 누락 | 버전비교/WW 기반 검사로는 탐지 불가 | Serializable/SSI 또는 읽은 집합 잠금 권장 |
| 대응의 비용 - 효과 트레이드오프 | SSI(오버헤드), FOR UPDATE(락 경합), 제약 (단순화) | 다른 이상보다 복합적 대응 요구 | 우선순위: DB 제약 → 잠금 → 격리상향 순 적용 |
Write Skew 는 SI 환경에서 특히 주의해야 할 동시성 이상으로, 탐지·예방이 단순 충돌 검사로는 어렵다. 실무에서는 불변식 관련 트랜잭션을 식별해 우선적으로 DB 제약이나 읽은 집합 잠금으로 보호하되, 필요한 경우 Serializable/SSI 전환을 검토해 전체 무결성을 확보한다. 각 선택은 성능·운영 비용을 동반하므로 테스트로 트레이드오프를 확인해야 한다.
Phase 2: 핵심 원리 및 이론적 기반
Write Skew 대응 원칙과 설계 전략
Write Skew 는 트랜잭션 A 와 B 가 같은 판단 근거 (읽기 결과) 를 보고 각자 다른 레코드를 수정했을 때 발생하는 일관성 위반 현상이다.
SI 는 읽기는 스냅샷에서 빠르게 처리하고, 같은 행을 동시에 쓸 때만 충돌을 막기 때문에 이런 시나리오가 가능하다.
해결책은
- DB 가 직렬성 보장 (SSI/Serializable) 하도록 설정하거나
- 중요한 판단 구간에 **명시적 잠금 (SELECT FOR UPDATE)**을 거는 것
- 스키마/애플리케이션 레벨로 제약을 물질화 (guard row/집계 테이블) 하거나
- 재시도·모니터링 정책을 운영하는 것이다.
실무에서는 비용·성능을 고려해 핵심 트랜잭션에만 강한 제어를 적용하는 전략이 일반적이다.
Write Skew 핵심 원칙 요약표
| 핵심 원칙 | 설명 | 목적 | 왜 필요한가 |
|---|---|---|---|
| SI: 스냅샷 읽기 + WW 검사 | 트랜잭션은 시작 스냅샷을 읽고, 동일 튜플 동시 쓰기만 충돌로 처리. | 읽기 성능 극대화 | 높은 동시성 환경에서 락 경합 최소화 필요 |
| Write Skew 조건 | 동일 상태 읽기 → 서로 다른 객체 쓰기 → 전체 불변식 위배. | 이상 현상 식별 | SI 의 WW 전제 때문에 발생 가능 |
| 직렬화 (Serializable/SSI) | 반의존성 추적 후 직렬성 위반 시 abort | 근본적 예방 | SI 로는 잡히지 않는 패턴 제거 |
| 충돌 물질화 (guard row) | 논리 제약을 실제 쓰기로 표현 | DB 충돌 검사 활용 | SI 의 한계 보완, 단순·효과적 |
핵심 원칙은 **SI 의 설계 (스냅샷 +WW 검사)**와 그로 인해 생기는 write-skew 조건을 이해하는 데 초점이 있다. 예방은 DB 수준 (직렬성 보장), 스키마 보강 (충돌 물질화), 애플리케이션 수준 (명시적 락/재시도) 으로 나뉘며, 실무는 성능 비용을 고려해 선택적으로 적용한다.
Write Skew 대응 설계 철학 요약
| 설계 철학 | 핵심 아이디어 | 적용 목적 | 고려사항 |
|---|---|---|---|
| 선택적 강화 | 핵심 트랜잭션만 Serializable 적용 | 비용 절감 + 정합성 보장 | 분류 정확성·테스트 필요 |
| 충돌 물질화 | 가드 행/집계 업데이트로 충돌 유도 | SI 환경에서 안전성 확보 | 정규화 위배·운영 복잡성 |
| 애플리케이션 락 & 재시도 | FOR UPDATE/분산 락·재시도 정책 | 외부 사이드이펙트 포함 작업 보호 | 재시도 안전성 (idempotency) 확보 필요 |
설계 철학은 " 전부를 포기하지 않되, 중요한 것만 강하게 지킨다 " 는 실무적 균형을 표현한다. 각 방식은 장단이 뚜렷하므로 워크로드·제약·운영문화에 맞춰 조합 적용해야 최적의 결과를 얻는다.
Write Skew 작동원리와 방지전략
Write Skew 는 두 사용자가 같은 기준으로 " 괜찮다 " 고 판단한 뒤 각각 다른 데이터를 바꿔서, 최종적으로 전체 규칙이 깨지는 상황이다. 예: 병동에 on_call 담당자가 1 명인데 A 와 B 가 각각 자신을 off 로 바꾸면 결과적으로 0 명이 된다. Snapshot Isolation 은 보통 이 상황을 막지 못하니, 중요한 규칙이 있다면 범위 잠금이나 직렬화 (Serializable/SSI) 를 적용하거나 애플리케이션에서 버전 검증·재시도를 구현해야 한다.
쓰기스큐 동작 원리와 메커니즘
핵심 메커니즘 (순서 기준)
- 트랜잭션 A/B 가 동일 조건의 쿼리 (SELECT) 를 각자 스냅샷에서 수행해 같은 판정 (예: 참가자 수 = 1) 을 얻음.
- 각 트랜잭션은 서로 다른 row(또는 객체) 를 업데이트 (또는 삭제) 함—WW 충돌은 없음.
- 트랜잭션들이 독립적으로 커밋되면, 시스템 전체에선 전역 제약 (invariant) 이 깨지게 됨 (예: 온콜 담당자 수 = 0).
- 발생 이유는 SI/Repeatable Read 에서 RW 반의존성(read→other write) 경로가 허용되어 직렬성 그래프에 RW 엣지들이 사이클을 형성할 수 있기 때문.
방지 옵션 (요약)
- 직렬 가능성 확보 (Serializable): 모든 이상을 차단하지만 비용 큼.
- SSI (Serializable Snapshot Isolation): SI 기반에서 위험 구조를 런타임 탐지·차단해 재시도 유도.
- 범위 락 (Next-Key / Predicate / SELECT FOR UPDATE): 조건 단위를 잠가 삽입/삭제로 인한 변화 방지.
- 애플리케이션 레벨 검증 (버전·제약): 사용자 think-time 포함 시 실무적 보완.
Write Skew 동작요약 표
| 단계 | 동작 | 핵심 기술적 포인트 | 왜 문제가 되는가 |
|---|---|---|---|
| 1 | 동일 조건 SELECT | 스냅샷 (SI/MVCC) 기반 읽기 | 각 트랜잭션이 같은 판단을 함 |
| 2 | 서로 다른 row 에 WRITE | WW 는 거부되지만 RW 는 허용될 수 있음 | 서로 다른 행 쓰기여도 전역 규칙 위반 가능 |
| 3 | 동시 COMMIT | 커밋 시점에 최종 상태 확정 | 최종 상태가 불변식 위반으로 귀결 |
| 4 | 결과 | 전역 비즈니스 규칙 (제약) 위반 | 심각한 비즈니스 오류 초래 |
쓰기 스큐는 " 같은 판단 근거 " 를 읽고 서로 다른 객체를 변경하는 패턴에서 발생한다. 핵심은 WW 충돌만 차단하는 SI 가 RW 에 의한 논리적 위반을 허용할 수 있다는 점이며, 이를 막으려면 조건 단위를 보호하거나 직렬화를 보장해야 한다.
쓰기스큐 흐름도
flowchart TD
Start([시작]) --> T1_BEGIN["T1: BEGIN (SI)"]
Start --> T2_BEGIN["T2: BEGIN (SI)"]
T1_BEGIN --> T1_SELECT[T1: SELECT count WHERE predicate -> v]
T2_BEGIN --> T2_SELECT[T2: SELECT count WHERE predicate -> v]
T1_SELECT --> T1_DECIDE["T1: 판단 (조건 충족?) -> 예"]
T2_SELECT --> T2_DECIDE["T2: 판단 (조건 충족?) -> 예"]
T1_DECIDE --> T1_WRITE[T1: UPDATE/DELETE 다른 row]
T2_DECIDE --> T2_WRITE[T2: UPDATE/DELETE 다른 row]
T1_WRITE --> DB_OK1["DB: 쓰기 OK (WW 없음)"]
T2_WRITE --> DB_OK2["DB: 쓰기 OK (WW 없음)"]
DB_OK1 --> T1_COMMIT[T1: COMMIT]
DB_OK2 --> T2_COMMIT[T2: COMMIT]
T1_COMMIT --> FinalCheck[DB: 최종제약 검증]
T2_COMMIT --> FinalCheck
FinalCheck --> Violation{제약 위반?}
Violation -- Yes --> InvariantBroken["결과: 불변식 위반 (Write Skew 발생)"]
Violation -- No --> OK[결과: 무결성 유지]
%% 방지 지점(검사/대응)
subgraph Mitigations [방지/완화 지점]
M1["선택지: SELECT … FOR UPDATE(범위락)"]
M2[선택지: SERIALIZABLE / SSI]
M3[선택지: 애플리케이션 버전검사 + UPDATE WHERE version=?]
end
T1_SELECT -.-> M1
T2_SELECT -.-> M1
T1_SELECT -.-> M3
T2_SELECT -.-> M3
T1_COMMIT -.-> M2
T2_COMMIT -.-> M2
위 흐름도는 쓰기 스큐의 전형적 시나리오를 단계별로 보여준다.
핵심은 트랜잭션이 같은 predicate 를 읽고 (스냅샷 기반) 서로 다른 대상에 쓰기를 한 뒤 동시 커밋하면 전역 불변식이 깨지는 지점이다.
방지 옵션은 세 가지 주요 지점에서 개입한다:
- SELECT 직후 범위락 (
SELECT … FOR UPDATE) 로 조건을 보호 - 커밋 단계에서 Serializable/SSI 로 위험 구조를 검출해 재시도 유도
- 애플리케이션 레벨에서 버전검사 (UPDATE WHERE version=?) 로 충돌을 탐지하고 재시도한다.
실무에서는 이들을 혼합해 ’ 성능 - 무결성 ’ 의 균형을 맞춘다. 또한 운영상 충돌률·재시도 통계를 수집해 정책 (예: 일시적 격리 상향, 샤드 리밸런싱) 을 동적으로 적용하는 것이 권장된다.
Write Skew 흐름·생명주기·탐지
트랜잭션 A 와 B 가 같은 규칙을 보고 각각 다른 레코드를 수정하면, 서로의 변경을 보지 못한 채 둘 다 커밋해 합쳐진 결과가 규칙을 깨는 경우가 생긴다.
핵심은 _ 읽기 시점의 스냅샷 가시성 _ 과 _ 커밋 시 검사 범위 _ 가 불일치한다는 점이다.
이를 막으려면 DB 의 직렬화 또는 명시적 잠금, 또는 불변식을 원자적으로 검사·갱신하는 설계가 필요하다.
Write Skew 생명주기 흐름
트랜잭션 시작 (Start)
- 트랜잭션이 시작되고, MVCC 환경이면 읽기 스냅샷이 고정된다.
- 타이밍: 스냅샷은 일반적으로 트랜잭션 시작 시점 (또는 첫 읽기 시점) 에 결정된다.
읽기·검증 (Read & Validate)
- 동일한 Predicate(예:
currentParticipants < maxParticipants) 을 읽음. - 판단은 스냅샷 기준으로 이루어짐 (다른 트랜잭션의 미커밋/커밋된 변경을 보지 못할 수 있음).
- 동일한 Predicate(예:
업데이트 (Update)
- T1 은 행 R1 을, T2 는 행 R2 를 업데이트 (서로 다른 행).
- 변경 내용은 각자의 쓰기 버퍼에 저장 (커밋 전까지 다른 트랜잭션에 보이지 않음).
커밋 (Commit)
- DB 는 보통 WW 충돌 (동일 행에 대한 동시 쓰기) 만 검사하거나 격리 정책에 따라 추가 검사.
- 두 트랜잭션이 서로 다른 행을 썼다면 둘 다 커밋될 가능성이 높음.
합성 상태 (Composite State) 노출
- 커밋 후의 전체 상태가 비즈니스 불변식을 위반하면 Write Skew 발생.
- 문제는 단일 트랜잭션의 관점에서는 정당하지만 전체 관점에서 불일치가 발생하는 것.
Write Skew 단계별 제어·데이터 흐름표
| 단계 | 동작 (데이터 흐름) | 제어 포인트 (검증·잠금) | 발생 위험 | 운영적 체크 |
|---|---|---|---|---|
| Start | 트랜잭션 시작 → 스냅샷 고정 | 트랜잭션 시작 시점 | 스냅샷 시점 불일치 | 트랜잭션 시작 로그 |
| Read & Validate | Predicate 기준 읽기 (스냅샷) | 애플리케이션 불변식 검사 (스냅샷 기준) | 잘못된 ‘OK’ 판단 | 동시 요청 패턴 모니터링 |
| Update | 서로 다른 행 업데이트 (쓰기 버퍼) | (보통 잠금 없음) | 서로의 변경 미가시성 | 쓰기 패턴·대상 분포 분석 |
| Commit | DB 가 충돌 검사 (주로 WW) → 커밋 | 격리 정책별 충돌 검증 | 합성 불변식 위반 노출 | 직렬화 실패/불변식 알람 |
| Post-Commit | 합성 상태로 불변식 위반 확인 | 애플리케이션·감시 시스템 | 비즈니스 규칙 위반 | 자동 롤백 불가 → 수동 대응/패치 |
핵심: **스냅샷 시점 (읽기 기준)**과 커밋 시 충돌 검사 범위가 달라 발생한다. 탐지 포인트는 동시 읽기 패턴, 특정 API 의 동시 호출, 커밋 시점의 불변식 위반 로그다. 운영에서는 트랜잭션 길이·동시성 패턴·직렬화 실패 및 애플리케이션 레벨 불일치 알람을 주의 깊게 모니터링해야 한다.
Write Skew 기본 흐름도
sequenceDiagram
participant T1 as 트랜잭션 T1
participant T2 as 트랜잭션 T2
participant DB as 데이터베이스
T1->>DB: BEGIN (스냅샷 고정)
T2->>DB: BEGIN (스냅샷 고정)
T1->>DB: SELECT predicate (스냅샷 기준) -> OK
T2->>DB: SELECT predicate (스냅샷 기준) -> OK
T1->>DB: UPDATE row R1 (쓰기 버퍼)
T2->>DB: UPDATE row R2 (쓰기 버퍼)
T1->>DB: COMMIT
DB-->>T1: COMMIT 성공
T2->>DB: COMMIT
DB-->>T2: COMMIT 성공
DB-->>All: 결과 상태 불변식 위반 -> Write Skew 발생
위 순서도는 MVCC/Snapshot 환경에서 전형적인 Write Skew 발생 경로를 보여준다. 핵심은 두 트랜잭션이 동일한 판단 (스냅샷 기준) 을 내리고 서로 다른 행을 수정한 뒤 둘 다 커밋되어 합쳐진 결과가 규칙을 깨는 것이다. 흐름도 개선 시점은 스냅샷 확정 타이밍, 격리 수준 분기 (Abort 가능성), 그리고 명시적 락 도입 지점이다.
Write Skew 생명주기 상태도
stateDiagram-v2
[*] --> Started: 트랜잭션 시작\n(스냅샷 고정)
Started --> ReadValidate: Predicate 읽기/검증
ReadValidate --> Update: 행 업데이트
Update --> CommitAttempt: 커밋 시도
CommitAttempt --> Committed: 커밋 성공
CommitAttempt --> Aborted: 충돌 탐지 → Abort (Serializable/SSI)
Committed --> PostState: 합성 상태 노출
PostState --> [*]: 정합성 확인 / 운영 알람
생명주기 다이어그램은 트랜잭션 상태 전이를 중심으로 Write Skew 의 가능 지점을 보여준다. 핵심은 CommitAttempt 에서의 분기 (Committed vs Aborted) 이며, Abort 는 직렬화 보장 메커니즘에서 발생해 재시도 로직을 요구한다. PostState 에서의 운영 감시와 회복 루프는 실제 서비스 안정성 확보에 필수적이다.
특성 분석 및 평가
Write-Skew 방지: 이점·현실적 선택
핵심 요지:
동시성을 높이면 성능이 좋아지지만 일부 상황에서는write-skew같은 보이지 않는 오류가 생긴다.무엇을 얻나:
MVCC/SI 같은 기법으로 읽기 성능과 동시성을 크게 올릴 수 있다.무엇을 더해야 안전한가:
SSI(런타임 검출), 범위락, DB 제약, 애플리케이션 락 같은 보완으로 중요한 비즈니스 규칙을 지켜야 한다.실무 규칙 (간단):
성능 우선이면 낙관적 접근 (SI) + 모니터링, 정합성 우선이면 강한 격리 (SSI/2PL/제약).
Write-Skew 대응 기법의 장점 한눈표
| 장점 | 기술 근거 (Why) | 실무 효과 (So what) | 적용 조건 / 예시 |
|---|---|---|---|
| 동시성 향상 (MVCC/SI) | 스냅샷 읽기로 읽기 - 쓰기 경합 회피 | 읽기 많은 서비스에서 처리량·응답성 개선 | 읽기 우선 시스템, 통계·로그 집계 |
| 성능 최적화 (낮은 격리) | 락 최소화로 짧은 트랜잭션 유지 | P99·TPS 개선, 인프라 효율성 | 낮은 정합성 요구, 캐주얼 데이터 |
| 개발 유연성 | 격리 수준/명시적 락 선택으로 정책화 가능 | 빠른 기능 출시·도메인별 처리 | 다양한 도메인 요구 공존 |
| SSI (직렬성 보장) | 런타임 의존성 그래프 검출로 위험 패턴 차단 | SI 성능 확보 + 정합성 보장 (일부 abort) | 핵심 금융 트랜잭션 (부분 적용 권장) |
| 2PL / 범위락 | predicate/next-key lock 으로 범위 변경 차단 | 팬텀/Write-Skew 예방, 예측 가능성↑ | 고정 규칙·작업 범위가 명확한 도메인 |
| 스키마 제약 · 집계 행 | DB 가 불변식 원자적으로 보장 | 애플리케이션 오류 감소, 무결성 강화 | 불변식이 DB 수준으로 표현 가능한 경우 |
| 애플리케이션 락 | 도메인 키 기반 락 (Advisory) | 핫스팟만 보호·점진적 도입 가능 | 분산·마이크로서비스 환경 |
요약: 성능 향상 (동시성) 은 MVCC/SI 가 주로 담당하고, 정합성 확보는 SSI·범위락·DB 제약·애플리케이션 락으로 보완한다.
실무 권장 패턴: 대부분 시스템은 혼합 전략(일반 작업은 SI/낙관적, 핵심 트랜잭션은 SSI/범위락 또는 DB 제약) 으로 운영한다. 적용 전에는 충돌률·핫스팟·비즈니스 민감도를 계량화해 정책을 정하라.
Write Skew 의 한계와 실무적 완화책
Write Skew 를 막으려면 _ 일관성 확보 수단 _ 을 도입해야 하는데, 그 수단들은 보통 성능 저하·운영 복잡성·확장성 제약을 함께 가져온다.
강한 일관성 (Serializable/범위 락) 은 안전하지만 느리고, 느슨한 일관성 (SI/낙관적) 은 빠르지만 write skew 가 생길 수 있다. 실무에서는 * 비용 (성능)* 과 * 리스크 (무결성)* 를 저울질해 부분 적용 또는 애플리케이션 레벨 보완을 사용한다.
Write Skew 의 주요 단점 표
| 단점 | 설명 | 원인 | 실무 영향 | 완화/해결 방안 | 대안 기술 |
|---|---|---|---|---|---|
| 재시도 비용 (SSI) | 충돌 시 트랜잭션 롤백 (예: SQLSTATE 40001) | 직렬화/충돌 탐지 메커니즘 | 지연·재시도 폭주·자원 낭비 | 지수 백오프, 멱등 설계, 재시도 한계 | 2PL, 애플리케이션 집계 |
| 락 경합 (2PL) | 강한 락으로 동시성 저하·데드락 | 장기간 락 보유, 넓은 범위 잠금 | Throughput 저하·타임아웃 | 인덱스 최적화, 트랜잭션 단축, 락 순서 규약 | SSI(부분 보완) |
| 제약 복잡성 | 복잡 불변식으로 운영·유지보수 부담 | 복합 도메인 규칙 | 코드·DB 동기화 문제, 버그 증가 | 불변식 단순화, 설계 가이드, 테스트 강화 | SSI(자동 보호 일부) |
| 분산 확장성 한계 | 샤딩된 환경에서 전역 불변식 유지 어려움 | 데이터 분산·파티션 | 전역 트랜잭션 필요, 높은 지연 | 파티션 재설계, Sagas, 합의 스토어 | Spanner/Calvin, 이벤트 소싱 |
- 핵심: 단점은 _ 일관성을 위해 투입한 비용 _ 이 곧 발생하는 문제들 (지연·대기·복잡도) 이다. 완화는 기술적 (백오프, 인덱스, 재설계)·운영적 (재시도 정책, 테스트) 조합으로 수행해야 효과적이다.
환경 기반 제약사항 표
| 제약사항 | 설명 | 원인 | 영향 | 완화/해결 방안 | 대안 기술 |
|---|---|---|---|---|---|
| ORM 기본 설정 | Lazy load·비잠금 읽기 기본값 노출 | 프레임워크 편의성 | Write Skew 노출, 트랜잭션 오용 | 트랜잭션 경계 명확화, 락 힌트, 명시적 SQL | 명시 SQL/Stored Proc |
| 인덱스 부재 | 범위 잠금 비효율·스캔 증가 | 설계 미비 | 잠금 범위 확대·성능 저하 | 복합 인덱스, 통계 갱신 | SSI(일부 보완) |
| 격리 수준 선택 한계 | Serializable 은 성능 저하 유발 | 성능·일관성 트레이드오프 | 처리량 감소·재시도 증가 | 선택적 직렬화, 하이브리드 전략 | 분산 일관성 DB |
| 샤딩/마이크로서비스 | 전역 불변식 구현 난해 | 데이터 분산 | 전역 락/2PC 필요, 지연 | 파티션 재설계, Sagas | 글로벌 일관성 DB |
- 핵심: 제약사항은 환경 (도구·구조) 이 제공하는 한계로 인해 특정 해법 적용이 어렵게 만든다. 이를 해결하려면 **설계 (인덱스·파티셔닝) 와 운영 (트랜잭션 경계·ORM 설정)**을 함께 고쳐야 한다.
Write Skew 트레이드오프 & 하이브리드 전략
문제 맥락: 데이터베이스에서 ’ 정확한 상태 ’ 를 보장하려면 트랜잭션 격리 수준을 높여야 하지만, 격리 수준을 높이면 동시에 처리할 수 있는 작업 수가 줄어들어 성능이 떨어진다. Write Skew 는 이런 트레이드오프가 실제로 문제를 일으키는 대표 사례다.
핵심 쟁점 (짧게):
- SI: 빠르지만 일부 복합 제약 위반 허용 → Write Skew 가능.
- SERIALIZABLE/SSI: 안전하지만 재시도·오버헤드 발생.
- 실무: 모든 트랜잭션을 직렬화하기보다 중요 트랜잭션 식별 → 고격리 적용, 또는 애플리케이션 레벨 해결을 조합해 사용.
SI Vs Serializable 비교표
| 비교 축 | Snapshot Isolation (SI) | Serializable / SSI |
|---|---|---|
| 핵심 장점 | 높은 동시성·읽기 성능 | 완전한 직렬성·정확성 |
| 핵심 단점 | write-skew 등 이상 허용 | 충돌·abort 로 처리량 저하 |
| 성능 영향 | 낮은 지연, 높은 처리량 | 재시도·락으로 처리량 감소 |
| 구현·운영 복잡도 | 낮음 (일반 MVCC) | 높음 (충돌 탐지·모니터링) |
| 적합 사례 | 웹 트래픽, 읽기많은 서비스 | 금융, 핵심 비즈니스 트랜잭션 |
| 실무 대안 | 애플리케이션 검증·재시도 | 선택적 직렬화, 모니터링 |
성능 (처리량/지연) 과 일관성 (데이터 무결성) 은 반비례 경향이 있다. SI 는 처리량을, Serializable 은 무결성을 우선한다. 실무에서는 워크로드별로 어느 쪽을 우선할지 기준을 세우고 (예: 금전·법적 영향이 큰 트랜잭션은 무결성 우선), 하이브리드로 보완하는 것이 현실적 해법이다.
Write Skew 하이브리드 대안 비교표
| 방법 | 구성 요소 | 적용 목적 | 장점 | 고려사항/단점 |
|---|---|---|---|---|
| 선택적 직렬화 (Selective) | 트랜잭션 분류 (critical vs normal), 격리 설정 정책 | 핵심 불변식만 강제해 전체 성능 보전 | 중요한 경로 무결성 확보·전체 성능 저하 제한 | 트랜잭션 분류 실수 위험, 복잡한 정책 관리. |
| SELECT FOR UPDATE / 명시잠금 | 읽기 시점 행 잠금, 잠금 해제 시점 관리 | 읽은 정보로 결정하는 트랜잭션 간 충돌 방지 | 확실한 충돌 차단 (애플리케이션 제어) | 잠금 경합·데드락·지연 유발. |
| Sentinel / Materialize Conflict | sentinel 행/집계 같이 업데이트 | 충돌을 강제하여 DB 가 하나를 abort 하게 유도 | DB 수준 충돌 감지 활용, 간단 구현 사례 많음 | 추가 업데이트 오버헤드, 설계·정합성 유지 필요. |
| 낙관적 버전 + 재시도 | 버전 칼럼/ETag, 재시도 로직 (백오프) | 충돌 드문 환경에서 효율 유지 | 높은 처리량 유지, 단순 구현 가능 | 재시도 비용·사용자 영향·복잡성 존재. |
| 엔진 수준 개선 (SSI 등) | SSI 알고리즘·충돌 추적·인덱스 최적화 | MVCC 장점 유지하면서 직렬성 보장 | 읽기·쓰기 분리 이점 유지, 일관성 보장 | 엔진 복잡도·운영 오버헤드·abort 가능. |
요약: 하이브리드 방법은 **정확성 향상 (또는 충돌 회피)**과 성능 유지 사이에서 서로 다른 지점을 선택한다. 운영에서는 트랜잭션 특성 (충돌 가능성, 응답성 요구, 실패 복구 용이성) 에 따라 적절한 하이브리드 조합을 설계해야 한다. 예를 들어 빈번한 충돌이 예상되는 경로에선 SELECT FOR UPDATE 나 직렬화 적용, 충돌이 드문 대량 처리 경로에선 SI+ 재시도 전략을 택하는 식이다.
Write Skew: 적용 적합성·운영 가이드
Write Skew 대응은 한마디로 위험도 기반 선택이다. 읽기 많은 서비스에선 Snapshot Isolation 을 써서 성능을 확보하고, 돈·안전처럼 핵심 규칙이 깨지면 안 되는 영역만 격리 수준을 올리거나 잠금/애플리케이션 검증을 더해 보호한다. 적용 전에는 어떤 쿼리와 불변식이 위험한지 찾아내고 (분석), 변경은 단계적으로 적용해 (설계) 모니터링 (운영) 으로 검증해야 한다.
Write Skew 적용 적합성 평가와 운영 전략
- 읽기 중심이면서 불변식 위험이 낮고 핫스팟이 크지 않은 서비스: Snapshot Isolation(혹은 SSI 로 성능 - 정합 균형) 을 1 차 선택.
- 핵심 무결성이 최우선인 서비스 (금융·의료·결제 등): Serializable(혹은 SSI + 추가 잠금/재검증) 적용 권장.
- 대규모 혼합 워크로드: Hybrid(선별적 직렬화 + 애플리케이션 레벨 검증 + 파티셔닝) 전략 권장.
설계 관점 (Design)
- 데이터 경계 정의: 불변식이 적용되는 도메인 경계를 명확히 나눠, 핵심 도메인만 강격리 적용.
- 모델 선택: 충돌 가능 영역은 집계 행 (single-row counters) 혹은 CQRS 로 분리해 충돌률을 낮춘다.
- 락 전략 설계: 범위·읽은 집합을 잠그는 전략 (선택적
SELECT … FOR UPDATE) 을 설계해 불변식 보호.
분석 관점 (Analysis)
- 리스크 평가: 불변식 위반 시 비즈니스 영향 (금전·안전·법적 리스크) 을 정량화해 격리 레벨 결정 근거로 사용.
- 성능 트레이드오프 분석: SSI/Serializable 적용 전후로 벤치마크 (throughput, latency, abort rate) 비교.
- 데이터 분포 분석: 핫 - 키, 파티션 스키마, 쓰기 집중 구간을 분석해 파티셔닝/샤딩 필요 여부 판단.
운영 관점 (Operations)
- 모니터링·알림: 재시도율, serialization failures, deadlocks, 비즈니스 무결성 체크 실패 등을 실시간 모니터링.
- 운영 플레이북: 무결성 위반 발생 시 우선격리 상향, 트래픽 셰이핑, 롤백·수정 절차 등 대응 시나리오 준비.
- 점진 적용·테스트: Canary/배치별로 격리 설정 변경 후 관측 (사전 자동 동시성 테스트 권장).
Write Skew 적용 적합성 판단표
| 시나리오 유형 | 권장 기본 전략 | 보완 조치 (필요 시) | 고려 포인트 (설계·운영) |
|---|---|---|---|
| 읽기 - 중심 대규모 서비스 (예: 콘텐츠, 피드) | Snapshot Isolation (SI) | 모니터링, 불변식 테스트 | 핫스팟 여부, 낮은 불변식 민감도 |
| 사용자 참여 많은 실시간 서비스 (예: 예약, 이벤트) | SI 우선 + 핫 - 스팟 차단 (파티셔닝) | 특정 경합 영역에 FOR UPDATE | 동시 쓰기 패턴, 배치 삽입 시간대 |
| 핵심 금융/의료/결제 | Serializable 또는 SSI | 읽은 집합 잠금, DB 제약, 애플리케이션 재검증 | 비즈니스 리스크·감사요구 |
| 혼합 워크로드 (읽기 + 쓰기 모두 높음) | Hybrid(선별적 격리 상향) | CQRS/집계 분리, 파티셔닝 | 도메인 경계 정의·테스트 계획 |
| 배치/ETL 중심 (대량 삽입) | SI + 배치 격리 처리 (창구화) | 일괄처리 창구·동시성 제어 | 배치 시간대·리소스 스케줄링 |
원칙: 성능 우선인가, 무결성 우선인가를 먼저 결정하고 그에 따라 격리 전략을 선택하라.
실무흐름: 위험 식별 → 기본 전략 (SI 권장) 적용 → 핵심 도메인에 보완 (잠금/Serializable) → 성능·무결성 테스트 → 운영 모니터링.
운영 팁: 격리 상향은 전체 시스템에 적용하기보다 도메인 단위로 점진 적용하고, 재시도·abort 상황을 자동화·관측해야 안전하다.
실무 적용 및 사례
실습 예제 및 코드 구현
실습 예제: 병동 온콜 불변식 (최소 1 명)
목적
- SI 에서 Write Skew 발생을 재현하고, SERIALIZABLE 과 잠금 기반 해결을 체득.
사전 요구사항
- PostgreSQL 14+ (로컬), psql
단계별 구현
스키마 및 시드
Write Skew 재현 (REPEATABLE READ≈SI)
세션 1:
세션 2:
세션 1:
해결 1: SERIALIZABLE(SSI)
초기화 후 재실행, 차이만 표시:
1 2 3 4 5 6 7 8 9 10 11 12 13TRUNCATE doctors RESTART IDENTITY; INSERT INTO doctors(ward, name, on_call) VALUES ('A','Alice',TRUE),('A','Bob',TRUE); -- 세션1 BEGIN ISOLATION LEVEL SERIALIZABLE; SELECT count(*) FROM doctors WHERE ward='A' AND on_call=TRUE; -- 2 UPDATE doctors SET on_call=FALSE WHERE name='Alice'; -- 세션2 BEGIN ISOLATION LEVEL SERIALIZABLE; SELECT count(*) FROM doctors WHERE ward='A' AND on_call=TRUE; -- 2 UPDATE doctors SET on_call=FALSE WHERE name='Bob'; COMMIT; -- 둘 중 한 세션이 여기 또는 다음 COMMIT에서 40001로 롤백됨 -- 세션1 COMMIT; -- 40001 Serialization failure 발생 가능 → 재시도 필요
해결 2: 가드 행 + 명시적 잠금
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27-- 가드 테이블: 병동별 on_call 수 카운트 CREATE TABLE ward_guard ( ward TEXT PRIMARY KEY, on_call_count INT NOT NULL ); INSERT INTO ward_guard VALUES ('A', 2); -- 트리거로 유지(단순화 예시) CREATE OR REPLACE FUNCTION upd_guard() RETURNS trigger AS $$ BEGIN IF NEW.on_call <> OLD.on_call THEN UPDATE ward_guard SET on_call_count = on_call_count + (CASE WHEN NEW.on_call THEN 1 ELSE -1 END) WHERE ward = NEW.ward; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql; DROP TRIGGER IF EXISTS trg_doctors_guard ON doctors; CREATE TRIGGER trg_doctors_guard AFTER UPDATE ON doctors FOR EACH ROW EXECUTE FUNCTION upd_guard(); -- 세션1 BEGIN; SELECT on_call_count FROM ward_guard WHERE ward='A' FOR UPDATE; -- 가드 행 잠금 UPDATE doctors SET on_call=FALSE WHERE name='Alice'; -- 세션2는 같은 가드 행에서 대기 또는 타임아웃
실행 결과
- REPEATABLE READ: Write Skew 재현.
- SERIALIZABLE: 한 트랜잭션이 **Serialization Failure(40001)** 로 중단 → 재시도 시 불변식 유지.
- 가드 행: 두 세션이 동일 행에 WW 충돌 → 순차화.
추가 실험
SELECT … FOR UPDATE로 프레디킷을 인덱스 범위로 잠그는 설계 비교.- 충돌 빈도에 따른 재시도 백오프 파라미터 실험.
Phase 6: 운영 및 최적화
Write Skew 관측·모니터링 실무 가이드
Write Skew 등의 직렬성 이상을 잡으려면 직렬화 실패 (40001), 데드락 (40P01), 락 대기 시간, 애플리케이션 재시도 빈도, 그리고 핫 로우 (키별 집중) 같은 지표를 모니터링해야 한다.
DB 내 pg_locks·pg_stat_activity 를 정기 조회하고, 에러 로그와 exporter(예: postgres_exporter) 를 통해 메트릭을 Prometheus/Grafana 나 SIEM 으로 집계하면 실시간 탐지·알람·포렌식이 가능하다.
임계값 (알람) 은 워크로드·SLA 에 맞춰 실험적으로 정하라.
Write Skew 관측 카테고리
시그널 메트릭 (에러·지연 중심)
serialization_failure_rate(SQLSTATE 40001), deadlock_rate(40P01), lock_wait_ms(avg/95p), retry_count.
이 지표들은 직렬성 위반·락 경합·재시도 과부하를 조기에 감지한다. 알람 예: 5 분간 serialization_failure_rate > 0.5% → P1 알람.
| 지표 | 왜 보는가 | 구현 예 |
|---|---|---|
| serialization_failure_rate | 직렬성 위반 (직접 신호) | DB 로그/애플리케이션 에러 → Prometheus counter |
| deadlock_rate | 락 정책 문제·교착 | DB 로그 (40P01) 파싱 → SIEM 이벤트 |
| lock_wait_ms | 성능저하 전조 | pg_locks/pg_stat_activity → exporter metric |
| retry_count | 재시도 과부하 | 앱 레이어 로그 → central metrics |
- 시그널 메트릭은 Write Skew 가능성 뿐 아니라 운영성 문제 (지연·재시도 과부하) 를 빠르게 알려주는 역할을 한다. 직렬화 오류 (40001) 는 반드시 수집·집계해 재시도 정책과 연동하라.
락·세션 관측 (상태·그래프 중심)
pg_locks, pg_stat_activity 정기 스냅샷 및 deadlock 그래프 캡처.
블로킹/대기 세션 트리거 시 자동 스냅샷 (locks + queries) 저장.
| 항목 | 왜 보는가 | 구현 예 |
|---|---|---|
| pg_locks 스냅샷 | 누가 무엇을 잠그는지 파악 | 주기 cron job 또는 트리거 시 수집 |
| pg_stat_activity | 세션별 현재 쿼리 상태 | 대기 중인 쿼리·트랜잭션 길이 확인 |
| deadlock graph | 교착 원인 분석 | deadlock 발생 시 자동 수집 스크립트 |
- 락·세션 관측은 문제의 근본 원인 (누가, 어떤 쿼리가 락을 잡고 있는지) 을 파악하는 데 필수적이다. deadlock 발생 시 즉시 그래프를 캡처해 원인분석에 사용하라.
애플리케이션 레벨 지표 (재시도·비즈니스 실패)
애플리케이션에서 발생한 retry_count, 재시도 후 성공률, idempotency 실패 로그, 비즈니스 규칙 위반 알람 등.
재시도 로직의 폭주 여부를 감지해 자동 백오프·큐잉 유도.
| 항목 | 왜 보는가 | 구현 예 |
|---|---|---|
| retry_count | 재시도 과부하 감지 | 미들웨어/라이브러리에서 카운트 노출 |
| retry_success_rate | 재시도 효과성 평가 | 성공/총 재시도 비율 계산 |
| business_violation_events | 규칙 위반 감지 | 애플리케이션 레벨 감사 이벤트 |
- 애플리케이션 지표는 DB 알람과 결합해 전체 워크플로의 건강도를 판단하게 해준다. 재시도 증가와 직렬화 실패가 함께 나타나면 자동화 대책을 검토하자.
핫스팟/스케일 지표 (키·노드 중심)
특정 키·인덱스에 집중된 요청 (QPS), 노드별 리소스·latch contention, 샤드별 트래픽 편중 등. 핫스팟이 확인되면 샤딩·파티셔닝·레디스 캐시 등 아키텍처적 대응을 검토.
| 항목 | 왜 보는가 | 구현 예 |
|---|---|---|
| key QPS 분포 | 특정 키 집중 확인 | pg_stat_statements + 로그 파싱 |
| node contention | 분산 DB 노드 문제 | 벤더 콘솔/노드 메트릭 |
| index contention | 인덱스 락 집중 | index-level statistics 수집 |
- 핫스팟은 시스템 전반 성능·정합성에 악영향을 준다. 조기 탐지로 샤드·캐시·데이터 모델 변경을 통해 충돌을 국지화하라.
Write Skew 관측 통합 대시보드 항목
| 카테고리 | 핵심 메트릭/로그 | 목적 | 우선순위 |
|---|---|---|---|
| 시그널 메트릭 | serialization_failure_rate(40001), deadlock_rate(40P01), lock_wait_ms, retry_count | 이상 탐지·알람 | 높음 |
| 락·세션 관측 | pg_locks, pg_stat_activity, deadlock graph | 원인 분석 (누가,무엇을) | 높음 |
| 애플리케이션 지표 | retry_count, retry_success_rate, business_violation_events | 재시도/비즈니스 영향 파악 | 높음 |
| 핫스팟 지표 | key QPS 분포, index contention, node resource | 확장성·샤딩 판단 근거 | 중~높 |
이 네 카테고리를 통합한 대시보드는 Write Skew·직렬성 이상을 조기에 탐지하고, 원인 분석→자동화 대응→아키텍처 개선으로 이어지는 운영 루프를 완성한다. 먼저 serialization_failure_rate 와 pg_locks 기반 알람을 구성하고, 이후 애플리케이션 재시도·핫스팟 분석을 추가하라.
6.2 보안 및 컴플라이언스
Write Skew 는 동시 트랜잭션이 같은 판단 근거로 행동해 전역 규칙을 깨는 현상이다.
보안·컴플라이언스 관점에서는 단순 성능 조치로 해결되지 않으므로, 권한 통제, 트랜잭션 복구 정책, 멱등성/아웃박스 패턴, 불변의 감사 로그, 데이터 암호화, 그리고 모니터링을 조합해 리스크를 관리해야 한다.
규제 업종은 특히 증적 보관·무결성·접근 통제가 엄격하다.
Write Skew 보안·컴플라이언스 카테고리
접근 제어 & 거버넌스
접근 제어는 누가 어떤 트랜잭션을 실행할 수 있는지를 제한해, 권한 남용이나 오작동으로 인한 불변식 위반을 방지한다. 실무적 적용으로는 RBAC, 최소 권한, 서비스 계정 분리, 운영자 액션 감사 (log of admin actions) 를 둔다. 규제 요건 (감사·접근 통제) 을 매핑해 권한 부여·검토 주기를 정한다.
트랜잭션 안전성 & 롤백
트랜잭션 안전성은 실패 시 데이터 일관성을 보장하는 메커니즘과 복구 절차를 포함한다. 자동 재시도는 멱등성 전제하에 적용하고, 복구 불가능한 실패엔 보상 (Saga) 또는 수동 복구 절차를 문서화한다. 또한 트랜잭션 실패에 대한 알림·수정 프로세스를 운영에 포함한다.
멱등성 & 아웃박스
멱등성은 동일 요청을 여러 번 적용해도 결과가 동일하도록 설계하는 것 (요청 토큰, idempotency key). 아웃박스 패턴은 DB 트랜잭션과 이벤트 발행을 원자적으로 묶어 분산 불일치 위험을 줄인다. 두 패턴은 재시도·비동기 통합에서 필수적이다.
감사 추적 & 증적 보존
트랜잭션 로그·변경 이력·트랜잭션 ID·버전 정보를 불변 (append-only) 으로 보관하고, 보관 기간·무결성 (암호화·서명) 을 규정에 맞춰 설정한다. 포렌식·감사 요청 시 재현 가능하도록 메타데이터를 포함해 저장한다.
데이터 보호 (암호화·키관리)
민감 데이터는 전송·저장에서 암호화하고, 키 관리는 중앙화 (KMS/HSM) 하여 접근·사용을 통제한다. 데이터 최소화·익명화 정책도 병행해 규제 위험을 축소한다.
모니터링·경보·운영 정책
충돌률, 재시도 실패율, 직렬화 실패 (HTTP 40001 등), lock-wait 등을 지표로 수집하고, 임계치 초과 시 자동 정책 (일시적 격리 상향, 트래픽 제한) 실행한다. Jepsen 스타일 검증을 주기적으로 실행해 실환경 가설 검증을 권장한다.
쓰기스큐 보안 종합 실행표
| 영역 | 주요 조치 | 목적 | 운영 지표 (예시) |
|---|---|---|---|
| 접근 통제 | RBAC, 세분화된 DB 권한 | 권한 오남용 방지 | 관리자 액션 로그 수, 권한 변경 빈도 |
| 트랜잭션 복구 | 멱등 재시도, Saga, 보상 | 실패 시 무결성 유지 | 재시도율, 보상 성공률 |
| 아웃박스·멱등성 | Outbox 테이블, idempotency key | 분산 이벤트 일관성 | Outbox 미전송 건수, 소비자 중복률 |
| 감사·증적 | 변경 이력, 불변 로그, 서명 | 규제 증빙·포렌식 | 감사로그 보관완료율, 검색 응답시간 |
| 데이터 보호 | TLS/TDE, KMS 관리 | 데이터 유출 방지 | 암호화 적용률, 키 회전 주기 |
| 모니터링 | 충돌률, serial failures, lock-wait | 조기 경보·자동 조치 | 충돌률 (%), 직렬화 실패 건수 |
Write Skew 성능·확장 최적화 전략
Write Skew 같은 동시성 문제는 일관성 강화를 위해 모든 트랜잭션을 무조건 직렬화하면 성능이 급격히 떨어진다.
그래서 실무에서는 짧은 트랜잭션 유지·인덱스 튜닝으로 락 범위를 줄이고, 핵심 경로만 높은 격리 적용, 지수 백오프 재시도와 같이 성능과 일관성의 균형을 맞춘다. 대규모일수록 샤딩·캐시·비동기 처리를 통해 충돌 표면을 분할·완화하는 것이 핵심이다.
Write Skew 성능·확장 카테고리
재시도·백오프 정책
내용: 충돌 (직렬화 실패·낙관적 잠금 충돌) 시 재시도 로직을 지수 백오프로 구현하고 재시도 횟수 제한과 랜덤 지터를 추가한다. 재시도 시 멱등성(idempotency key) 확보가 필수.
구현 팁: base=100ms, 재시도 n 일 때 delay = base * 2^n ± rand(0, base); 최대 재시도 3~5 회; 실패 시 사용자 피드백 또는 작업 큐로 이관.
| 항목 | 목적 | 구현 예 | 주의점 |
|---|---|---|---|
| 지수 백오프 + 지터 | 재시도 폭주 방지 | base*2^n ± jitter | 멱등성 미보장 시 중복 문제 |
| 최대 재시도 제한 | 자원 낭비 방지 | maxRetries=3~5 | 사용자 경험 고려 |
| 멱등키 | 중복 처리 방지 | request_id 토큰 | 토큰 저장소 필요 (짧은 TTL) |
- 요약: 재시도는 필수지만 제어하지 않으면 시스템을 망가뜨린다. 지수 백오프·지터·멱등성 조합으로 안전하게.
트랜잭션·락 설계
내용: 트랜잭션을 짧게 유지하고, 불가피한 경우 핵심 행만
SELECT … FOR UPDATE로 잠그거나 핵심 경로만 Serializable 로 설정한다. 인덱스를 통해 락 범위를 좁힌다.구현 팁: EXPLAIN 으로 범위 스캔 확인, 트랜잭션 내 외부 호출 제거, 필요 시 저장 프로시저로 원자화.
| 항목 | 목적 | 구현 예 | 주의점 |
|---|---|---|---|
| 트랜잭션 단축 | 락 보유 시간 최소화 | 읽기 후 계산, 쓰기만 트랜잭션 | 외부 I/O 제거 필요 |
| 행 수준 잠금 | 충돌 방지 (핫스팟) | SELECT … FOR UPDATE | 동시성 저하 가능 |
| 혼합 격리 | 부분적 직렬화 적용 | 핵심 API 만 Serializable | 운영 복잡성 증가 |
- 요약: 락·격리 수준을 좁게·선택적으로 적용해 성능을 지켜라.
데이터 접근 패턴 (인덱스·캐시)
내용: 인덱스를 통한 범위 축소로 락 효과를 높이고, 읽기 부담은 캐시로 오프로드한다. 쓰기 직후의 일관성 보장은 세션 강제 마스터 읽기나 캐시 무효화 전략으로 보완.
구현 팁: EXPLAIN → 복합 인덱스 추가, 캐시에는 쓰기 후 즉시 무효화 또는 이벤트 기반 동기화.
| 항목 | 목적 | 구현 예 | 주의점 |
|---|---|---|---|
| 인덱스 튜닝 | 락 범위 축소 | 복합 인덱스, 커버링 인덱스 | 인덱스 비용 (쓰기 비용) |
| 캐싱 | 읽기 부하 완화 | Redis, TTL, pub/sub 무효화 | 캐시 일관성 관리 필요 |
| 마스터 강제 읽기 | 스테일 리드 방지 | 세션 레벨 설정 | 리플리카 부하 고려 |
- 요약: 선제적 인덱스와 캐시 전략으로 DB 충돌 표면을 줄여라.
아키텍처적 확장 (샤딩·분산락·CQRS)
내용: 도메인 분할 (샤딩) 으로 충돌 범위를 줄이고, 글로벌 불변식은 분산 락이나 이벤트 방식으로 처리. 쓰기·읽기 분리, CQRS 로 확장성 확보.
구현 팁: 샤드 키는 균등 분포 목표, 분산 락은 TTL·리더 선출로 안전성 확보, CQRS 시 보상 트랜잭션 설계.
| 항목 | 목적 | 구현 예 | 주의점 |
|---|---|---|---|
| 샤딩 | 충돌 도메인 분리 | user_id % N 샤드 | 크로스샤드 트랜잭션 비용 |
| 분산 락 | 글로벌 불변식 보장 | etcd / Redis RedLock | 네트워크 지연·가용성 고려 |
| CQRS / 이벤트 | 확장성·비동기 처리 | Command queue + read model | 복잡성·운영 부담 증가 |
- 요약: 샤딩·분산락·CQRS 는 강력하지만 설계·운영 비용이 크다—꼭 필요한 곳에만 적용하라.
운영·모니터링
내용: 재시도율, 직렬화 실패율, 락 대기 p95/p99, 트랜잭션 길이 등의 지표로 성능·충돌을 관측하고 자동화된 런북으로 1 차 대응을 실행.
구현 팁: Prometheus + Grafana, OpenTelemetry, 알람 임계값 (예: 직렬화 실패 >0.5% 경고).
| 지표 | 목적 | 임계값 예시 | 대응 |
|---|---|---|---|
| 재시도율 | 충돌 빈도 감지 | >2% 경고 | 백오프 조정, 패치 |
| 직렬화 실패율 | SSI/Serializable 문제 신호 | >0.5% 심각 | 핵심 경로 점검 |
| 락 대기 p95 | 락 경합 탐지 | p95 > 2000ms | 트랜잭션 분해 |
- 요약: 계측 (모니터링) 은 모든 설계의 필수 전제—지표 기반으로 조치하라.
Write Skew 대응 종합 전략표
| 카테고리 | 핵심 기법 | 목적 | 구현 포인트 |
|---|---|---|---|
| 재시도 · 백오프 | 지수 백오프 + 지터, 멱등키, 재시도 한도 | 재시도 폭주/리소스 낭비 방지 | base delay, maxRetries, 멱등성 확보 |
| 트랜잭션·락 설계 | 트랜잭션 단축, 행 잠금, 혼합 격리 | 락 보유시간·경합 최소화 | EXPLAIN, FOR UPDATE, 핵심만 Serializable |
| 데이터 접근 | 인덱스 튜닝, 캐시, 마스터 강제 읽기 | 락 범위 축소·읽기 부하 완화 | 복합 인덱스, 캐시 무효화 전략 |
| 아키텍처 확장 | 샤딩, 분산 락, CQRS | 샤드 단위 충돌 축소·확장성 확보 | 샤드키 설계, TTL 락, 이벤트소싱 |
| 운영·관측 | 재시도율·직렬화 실패·락 p95 모니터링 | 조기 경보·자동화 대응 | Prometheus/Grafana, Runbook |
6.4 트러블슈팅 및 문제 해결
Write-Skew 트러블슈팅 종합 가이드
- 증상: 간헐적으로 집계·제약이 깨져 결과가 틀림.
- 원인: 동시 트랜잭션이 같은 스냅샷을 보고 서로 다른 행을 바꿔도 DB 가 이를 막지 못하는 상황 (=write-skew).
- 당장 해야 할 일: 문제 트랜잭션 찾아 재현 → 임시로 해당 경로에 락 적용 또는 격리 상향 → 근본적으로는 버전검사·원자적 업데이트·DB 제약으로 해결.
- 중요: 프로덕션 격리 변경은 비용 (성능/가용성) 을 수반하므로 단계적으로 적용하고 모니터링해야 한다.
왜 발생하는가 (근본 원인)
- 동시 트랜잭션이 같은 시점의 스냅샷을 읽고 서로 다른 행을 갱신할 때, 쓰기 - 쓰기 충돌이 명시적으로 없으면 SI 등 낮은 격리에서 커밋을 허용하여 비즈니스 불변식을 위배한다 (=write-skew).
- 또한 핫 파티션 (동일 키/범위에 집중된 트래픽), 잘못된 인덱스 또는 조건 기반 업데이트가 원인을 악화시킨다.
무엇으로 어떻게 해결하는가 (구체적 수단)
단기 완화 (빠른 대응)
- 문제 트랜잭션을 식별해 임시로 해당 경로에
SELECT … FOR UPDATE같은 비관적 잠금 적용. - 문제 범위가 좁으면 격리 수준을
Serializable(또는 DB 의 SSI 활성화) 로 높여 보수적으로 처리 (단, 카나리아/제한 적용). - 임시로 해당 키에 애플리케이션 락 (Advisory lock) 적용.
- 문제 트랜잭션을 식별해 임시로 해당 경로에
근본 해결 (설계/코드 변경)
- 원자적 SQL로 표현 가능한 연산은 DB 쪽으로 위임 (
UPDATE … WHERE version = x또는UPDATE accounts SET bal = bal +? WHERE id=?). - 낙관적 락 (버전 컬럼) + 재시도 로직 도입 (지수 백오프 + 지터).
- 비즈니스 불변식은 DB 제약 (CHECK, EXCLUSION, UNIQUE) 또는 가드 행 (집계 행) 으로 모델링.
- 핵심 트랜잭션은 SSI/Serializable 로 처리하고 일반 트랜잭션은 SI 로 운영하는 하이브리드 전략 도입.
- 원자적 SQL로 표현 가능한 연산은 DB 쪽으로 위임 (
운영 보완
- 충돌·재시도·abort 지표화 (모니터링→경보).
- 정기적 동시성 스트레스 테스트 및 재현 시나리오 자동화.
- 감사 로그·CDC 기반 포렌식과 복구 절차 준비.
Write-Skew 트러블슈팅
탐지·탐색 (Detection & Triage)
증상 식별과 우선순위 분류 단계.
- 핵심 활동:
- 사용자 리포트·경고 수집 (에러/무결성 불일치)
- 관련 트랜잭션 ID·타임스탬프·쿼리 패턴 확보
- 초기 영향 범위 결정 (테이블/파티션/서비스)
- 도구/명령 예:
- PostgreSQL: pg_stat_activity, pg_locks, pg_stat_wal
- MySQL: SHOW PROCESSLIST, SHOW ENGINE INNODB STATUS
- 애플리케이션 로그, APM(트레이스)
| 항목 | 목적 | 도구/증거 |
|---|---|---|
| 에러/리포트 수집 | 문제 인지 | Sentry/로그/유저 리포트 |
| 트랜잭션 식별 | 원인 트래픽 타겟 | tx id, timestamp |
| 초기 영향 범위 | 긴급도 판정 | 테이블/파티션 목록 |
- 핵심: 문제를 빠르게 탐지하고 영향 범위를 좁혀 우선순위를 정하라.
재현·분석 (Reproduction & Root Cause)
원인 재현과 근본 원인 분석 단계.
- 핵심 활동:
- 스테이징에서 동시성 재현 (다중 세션, sleep 타이밍)
- EXPLAIN ANALYZE 로 실행계획 확인
- 락/대기 로그, deadlock 로그 분석
- WAL/Redo 또는 CDC 로 커밋 시퀀스 검증
- 도구/명령 예:
- SQL 스크립트 (동시 트랜잭션), EXPLAIN, SHOW ENGINE INNODB STATUS, pg_waldump
| 항목 | 목적 | 도구/증거 |
|---|---|---|
| 동시성 재현 | 문제 원인 확인 | 동시 스크립트/트랜잭션 시뮬레이터 |
| 락·대기 분석 | 잠금 원인 규명 | pg_locks / innodb status |
| 로그 타임라인 | 커밋 시퀀스 검증 | WAL / binlog / CDC |
- 핵심: 재현 가능한 시나리오를 만들고 로그로 타임라인을 검증하면 근본 원인을 정확히 파악할 수 있다.
단기 완화 (임시 대책) (Immediate Mitigation)
프로덕션 영향 최소화를 위한 신속한 조치.
- 핵심 활동:
- 문제 트랜잭션에 대한
SELECT … FOR UPDATE적용 (범위 제한) - 키·범위에 advisory lock(앱 레벨) 적용
- 제한적 격리 상향 (카나리아/특정 서비스에만)
- 트래픽 셰이딩 (핫파티션 임시 분리)
- 문제 트랜잭션에 대한
- 도구/명령 예:
- SQL 변경 (롤링 적용), 앱 레벨 락 코드, 트래픽 라우팅
| 항목 | 목적 | 실행 예 |
|---|---|---|
| FOR UPDATE 적용 | 즉시 일관성 확보 | 특정 쿼리 변경 |
| Advisory Lock | 분산 락 적용 | Redis/DB advisory lock |
| 격리 상향 (카나리아) | 근본 변경 전 임시 보호 | SERIALIZABLE on subset |
- 핵심: 임시완화는 최소범위·단기간 적용하고 영향 (성능) 을 면밀히 모니터링하라.
근본 해결 (설계/코드) (Permanent Fixes)
재발 방지 위한 설계·코드 변경.
- 핵심 활동:
- 원자적 SQL 또는 version 기반 낙관적 락 구현
- DB 제약 (CHECK/EXCLUSION/UNIQUE) 추가
- 핵심 트랜잭션을 SSI/Serializable 로 전환 (선별 적용)
- CQRS/파티셔닝/leader-per-shard 등 아키텍처 변경
- 도구/명령 예:
- DB 마이그레이션 (제약 추가), 코드 배포, 테스트 자동화
| 항목 | 목적 | 구현 예 |
|---|---|---|
| 원자적 SQL | 네트워크/중간상태 제거 | UPDATE … WHERE version =? |
| DB 제약 추가 | 무결성 강제 | CHECK / UNIQUE / EXCLUSION |
| 하이브리드 격리 | 비용 - 효율적 정합성 | SSI for critical |
- 핵심: 설계 변경은 충분한 테스트·롤아웃 계획 (카나리아) 과 모니터링을 동반해야 안전하다.
운영·모니터링 (Monitoring & Prevention)
지표 기반 탐지와 예방적 운영.
- 핵심 활동:
- 충돌률 (abort rate), 재시도 횟수, lock wait, P99 레이턴시 지표 수집
- 경보 임계치 설정 및 자동화 대응 (서킷브레이커)
- 정기 동시성 스트레스 테스트 및 회귀 테스트
- 도구/명령 예:
- Prometheus/Grafana, APM, Synthetic Tests
| 항목 | 목적 | 구현 예 |
|---|---|---|
| 충돌률 지표 | 이상탐지 | prometheus metric |
| 자동 경보 | 신속 알림 | PagerDuty |
| 정기 테스트 | 회귀 방지 | CI 스트레스 테스트 |
- 핵심: 측정 가능한 지표를 정하고 자동 경보와 주기적 테스트로 사전 예방하라.
회복·감사 (복구/포렌식) (Recovery & Forensics)
사건의 복구와 원인 증거 확보.
- 핵심 활동:
- WAL/binlog/CDC 기반 타임라인 복원 및 증거 수집
- 무결성 검증 (체크섬), 복구 시나리오 실행 (restore drill)
- 사후보고 (RCA) 와 교훈 문서화
- 도구/명령 예:
- pg_waldump, mysqlbinlog, Debezium CDC, WORM 보관소
| 항목 | 목적 | 도구 |
|---|---|---|
| 로그 기반 복구 | 시계열 재구성 | WAL / binlog / CDC |
| 복구 연습 | 복구 능력 검증 | 복원 시나리오 |
| 사후보고 | 프로세스 개선 | RCA 문서 |
- 핵심: 사건 증거를 안전히 보관하고 정기적으로 복구 능력을 검증하라.
Write-Skew 문제해결 요약표
| 카테고리 | 핵심 목표 | 대표 조치 (단기 → 중기 → 장기) | 대표 도구/지표 |
|---|---|---|---|
| 탐지·탐색 | 빠르게 문제 인지·영향파악 | 로그 수집 → tx id 확보 → 영향도 분류 | Sentry, pg_stat_activity, SHOW ENGINE |
| 재현·분석 | 근본 원인 규명 | 스테이징 재현 → EXPLAIN/락 로그 분석 | EXPLAIN, pg_locks, innodb status |
| 단기 완화 | 프로덕션 영향 최소화 | FOR UPDATE / advisory lock / 카나리아 격리 | SQL 변경, App lock |
| 근본 해결 | 재발 방지 설계 적용 | version lock, DB 제약, SSI 적용 | 마이그레이션, 코드배포 |
| 운영·모니터링 | 사전 탐지·자동화 대응 | 지표 채택 → 경보 → 정기 테스트 | Prometheus, Grafana, APM |
| 회복·감사 | 복구·증거 확보 | WAL/binlog 복구 → RCA → 문서화 | pg_waldump, mysqlbinlog, CDC |
핵심 흐름: 탐지 → 재현·분석 → 임시완화 → 근본해결 → 모니터링 → 회복 (사후분석).
운영 규칙: 프로덕션 변경은 항상 제한적 (카나리아)·모니터링 기반으로 수행하고, 근본 해결은 충분한 재현·테스트 후 단계적으로 롤아웃하라.
최종 정리 및 학습 가이드
내용 종합
Write Skew 는 실무에서 특히 위험한 동시성 버그다. 두 트랜잭션이 동일한 조건을 읽고 (예: " 이 병동에 on_call 이 있나?”) 서로 다른 행을 변경하면, 각각의 로컬 판단은 옳지만 합쳐진 결과가 도메인 규칙을 위반할 수 있다. 예컨대 의사 A 와 B 가 각자 " 나만 off 처리하면 다른 사람이 남아있다 " 는 판단으로 동시에 off 를 처리하면 실제로는 아무도 남지 않는다.
기술적으로 SI(스냅샷 격리) 는 각 트랜잭션에 안정된 읽기 스냅샷을 제공하지만, 서로의 쓰기를 보지 못하므로 전역 불변식을 보장하지 못한다. 따라서 실무에서는 다음 중 하나 혹은 조합을 택한다.
- 직렬화 (Serializable): DB 가 논리적 직렬화를 보장하지만 충돌 시 트랜잭션을 롤백하므로 재시도 비용 발생.
- 비관적 락: 관련 리소스에 락을 걸어 문제를 예방하나 동시성·스루풋 손실과 데드락 위험 존재.
- 애플리케이션 설계 변경: 불변식 경계를 좁혀 단일 행으로 묶거나 원자적 연산으로 처리하면 비용을 줄일 수 있다.
- 분산 시스템 대책: 샤딩 경계 재설계나 Sagas 와 같은 보상 패턴을 통해 전역 일관성 요구를 낮추는 방법도 있다.
결론적으로 현실적 운영에서는 SSI/2PL/스키마 개선/앱 락의 혼합 전략으로 안전성과 성능을 균형 맞춘다.
실무 적용 가이드
| 단계 | 작업 항목 | 대상 (레벨) | 구현 예시 | 장점 | 주의사항 |
|---|---|---|---|---|---|
| 1 | 불변식 식별 및 우선순위화 | 비즈니스/도메인 | 불변식 목록, 영향도 점수 (금전·안전성) | 보호 우선순위 명확화 | 식별 누락 위험—리뷰 필요 |
| 2 | 인덱스·범위 설계 | DB 쿼리/스키마 | 인덱스 추가·쿼리 리팩토링, EXPLAIN | 범위 노출 축소 → 위험 감소 | 인덱스 과다로 쓰기 비용 증가 |
| 3 | 방어전략 선택 (권장 조합) | DB / App / Infra | DB: SERIALIZABLE/SSIApp: SELECT FOR UPDATE, sentinel rowInfra: Redis 분산락 | 불변식 보호 (여러 층의 보강) | 성능 영향, 데드락·가용성 고려 |
| 4 | 재시도·멱등성 | 애플리케이션 | 지수 백오프 + 지터, 멱등 토큰, max 3~5 회 | 충돌 시 자동 복구·사용자 영향 최소화 | 재시도 비용·UX 지연 |
| 5 | 관측·알람·튜닝 | 운영/모니터링 | 지표: tx_latency, abort_rate, long_tx_count, undo_size | 조기탐지·원인분석 용이 | 임계치 설정과 노이즈 관리 필요 |
| 6 | 테스트·검증 | QA/스테이징 | 재현 시나리오 (동시 트랜잭션 스크립트), 격리수준별 A/B 테스트 | 실제 영향 확인·정책 검증 | 스테이징이 프로덕션과 다를 수 있음 |
| 7 | 운영 룰북 작성 | 운영팀/개발팀 | deadlock victim 정책, 자동 rollback 기준 문서화 | 신속 대응·책임소재 명확화 | 룰 유지보수 필요 |
학습 로드맵
| 단계 | 주제 범위 | 학습 목표 | 실습 (구체) | 평가 지표 | 권장 도구 |
|---|---|---|---|---|---|
| 1 기초 | ACID, Read Committed / Repeatable Read / Serializable, MVCC 개념 | 격리 수준과 이상 종류 이해 | 두 세션 동시 UPDATE 재현 (Lost Update) | 데이터 일관성 위반 여부 | Postgres, MySQL |
| 2 핵심 | Serialization Graph, Write Skew, Phantom | 읽기 - 쓰기 의존성 이해 및 재현 | On-call Write Skew 시나리오 실행 | 무결성 위반 재현 여부 | Postgres(SI) |
| 3 응용 | SELECT FOR UPDATE, 가드 행, 트리거·제약 | 불변식 보호 패턴 적용 능력 | FOR UPDATE 적용 후 재검증, 집계 행 모델 적용 | abort 률, P99, 무결성 유지 | pgbench, EXPLAIN |
| 4 고급 | SSI, Predicate Lock, Index Range Lock, 분산 트랜잭션 | 고급 직렬성, 분산 불변식 설계 | SSI 켜고 성능 비교, 분산 트랜잭션 (2PC) 시뮬 | throughput, abort rate, 운영 복잡도 | OpenTelemetry, Jaeger, 2PC 툴 |
학습 항목 정리
| 단계 | 항목 | 목표 | 실습/과제 | 실무 연관성 | 비고 |
|---|---|---|---|---|---|
| 기초 | ACID/트랜잭션 시작/종료 | 트랜잭션 흐름 파악 | BEGIN/COMMIT/ROLLBACK 실습 | 모든 서비스 필수 | 문서 숙지 |
| 기초 | 격리 수준 차이 | 이상별 차이 재현 | Dirty/Lost/Non-repeatable 실습 | 디버깅용 | EXPLAIN 병행 |
| 핵심 | Write Skew 재현 | SI 한계 직접 체감 | 의사 on-call 예제 (2TX) | 예약/의료 | SQL 스크립트 제공 |
| 핵심 | Serialization Graph | 사이클 탐지 이해 | 그래프 작성 연습 | 설계 검토 도구 | 수기 그래프/자동화 스크립트 |
| 응용 | SELECT FOR UPDATE | 읽은 집합 잠금 적용 | 적용 후 성능/무결성 비교 | 트랜잭션 보호 | 록 웨이트 관찰 |
| 응용 | 가드 행/집계 행 | 충돌 영역 축소 | 모델 변경 후 테스트 | 이벤트·카운터 시스템 | CQRS 연계 |
| 고급 | SSI 내부 | 충돌 검출 원리 학습 | SSI 활성화로 비교 실험 | 고신뢰 시스템 | Postgres SSI |
| 고급 | Predicate/Index Range Lock | 범위 잠금 이해 | INDEX 기반 갭락 실험 | 범위 쿼리 많은 시스템 | 인덱스 설계 병행 |
| 고급 | 분산 불변식 설계 | 전역 불변식 유지 전략 | 2PC/사건 소싱 시나리오 | 샤드된 시스템 | conflict resolution 설계 |
용어 정리
| 카테고리 | 용어 (한글)—(영어 풀네임, 약어) | 정의 (간략) | 관련 개념 | 실무 활용 |
|---|---|---|---|---|
| 핵심 | 쓰기 스큐—(Write Skew) | 동일 판단 기반의 분산 쓰기로 비즈니스 제약 위반 | SI, RW 사이클 | 핵심 트랜잭션 직렬화/락 적용 |
| 핵심 | 팬텀—(Phantom) | 범위 쿼리 결과 집합이 다른 트랜잭션으로 바뀜 | Predicate/Range Lock, Gap Lock | 범위 락/인덱스 설계, 트랜잭션 검증 |
| 핵심 | 스냅샷 격리—(Snapshot Isolation, SI) | 시작 시점 스냅샷 읽기, WW 만 충돌 검사 | MVCC, Write Skew 허용 가능 | 읽기 중심 워크로드 성능 최적화 |
| 구현 | 명시적 잠금—(Explicit Lock / SELECT FOR UPDATE) | 쿼리 수준의 선점 락 제어 | Lock granularity, Deadlock | 민감 판단구간 락 적용 |
| 구현 | 2 단계 잠금—(Two-Phase Locking, 2PL) | 락 확장/축소로 직렬성 보장 | Predicate Lock, Phantom 방지 | 강한 일관성 트랜잭션에 적용 |
| 구현 | 갭/넥스트키 락—(Gap / Next-Key Lock) | 인덱스 gap 잠금으로 범위 삽입 차단 | Phantom 방지, 인덱스 의존 | 인덱스 기반 범위 보호 (InnoDB) |
| 고급 | SSI—(Serializable Snapshot Isolation, SSI) | SI 에 직렬성 위반 탐지 추가 | Serializable, abort/retry | 고성능과 정확성 균형 적용 |
| 운영 | 멱등성—(Idempotency) | 동일 요청 반복시 결과 불변성 보장 | 재시도 정책, 외부 사이드이펙트 | 재시도 안전화, API 설계 |
| 운영 | 직렬성 격리—(Serializable Isolation) | 모든 트랜잭션이 직렬 실행 결과와 동일 | 2PL, MVCC+SSI | 금융·원장 등 최고 수준 보장 |
참고 및 출처
- PostgreSQL Documentation — Transaction Isolation
- MySQL :: MySQL Documentation
- Microsoft SQL Documentation - SQL Server | Microsoft Learn
- 트랜잭션 격리 수준 완벽 가이드: 실무에서 만나는 문제와 해결법
- [MySQL] Lost Update와 Write Skew
- 데이터 중심 애플리케이션 설계 Ch 7. 트랜잭션 | BLOG
- concurrency control 3 : transaction isolation level
- 트랜잭션의 동시성 문제를 알아보자
- JPA의 낙관적 락과 비관적 락을 통해 엔티티에 대한 동시성 제어에 대해 알아보자
- (Spring/JPA/Transaction) 쓰기 스큐, 팬텀. Write skew, Phantom
- 트랜잭션 격리 수준과 실무에서의 활용
- InnoDB의 REPEATABLE READ가 PHANTOM READ를 방지하는 원리
- Spring JPA Transactional과 Transaction Isolation Level 격리수준 실습
- Phantom Read 부정합문제 해결방안 In PostgreSQL, MSSQL Server