티스토리 뷰

카테고리 없음

DDIA 5주차

changha. 2026. 7. 3. 23:47

이번주차는 7장이다. 

트랜잭션 부분.. 정합성 측면에서 정말 중요하다고 느낀다. 

최근 회사에서도 트랜잭션 관련하여 오류 하나가 있었는데 더욱 중요하다고 느껴졌다.

 

이 장을 통해 다양한 트랜잭션이 막을 수 있는 문제들이 무엇이 있고 어떻게 해결하는지 알게되었다.

 

완화된 격리 수준 

1. Read Committed

가장 기본적인 트랜잭션 격리 수준. 두 가지를 보장한다.

  • 커밋된 데이터만 읽을 수 있다 (더티 읽기 방지)
  • 커밋된 데이터만 덮어쓸 수 있다 (더티 쓰기 방지)

2. Snapshot Isolation(MVCC)

MySQL 은 MVCC를 격리 수준마다 어떻게 사용하는지

- 각 행에 숨은 컬럼: DB_TRX_ID(이 행 마지막 수정한 txid), DB_ROLL_PTR(undo log 포인터 = 옛 버전 체인).

- 수정 시 옛 버전을 undo log에 쌓음 → 버전 체인.
- Read View(스냅샷): "지금 어떤 txid까지 커밋된 걸로 볼지" 스냅샷. 이게 가시성 판단 기준.
- 규칙: Read View보다 나중 txid가 만든 버전은 안 보이고, undo 체인 따라 보이는 옛 버전으로 내려감.

- RC: 문장 하나 끝나고 다른 트랜잭션이 커밋하면, 다음 SELECT는 그 값 봄 → 같은 걸 두 번 읽어도 값 바뀔 수 있음.
- RR: 첫 SELECT 순간 스냅샷 박제 → 트랜잭션 동안 남이 뭘 커밋하든 안 흔들림.

SI로도 못막는 것

1. Lost Update

Lost Update는 무엇인가?

- 두 트랜잭션이 같은 행을 읽고 -> 계산 -> 쓰기 하는데, 한 쪽 수정이 덮여 사라지는 것. 스냅샷 격리로도 안 먹힌다. 

Lost Update는 어떻게 막을까?

2. 쓰기 스큐 & 팬텀

쓰기 스큐란

- 읽어서 조건 검사 -> 그 결과 믿고 씀. 근데 검사~쓰기 사이 다른 트랜잭션이 전제를 깸.

- 스냅샷 격리로도 못 막음.

더보기

스냅샷 격리로도 막지 못하는 이유 

 

스냅샷 격리로는 서로 다른 행에 대한 동시 쓰기는 막지 못함. 

- 쓰기 스큐는 Alice→Alice행, Bob→Bob행. 쓰는 행이 겹치지 않음 → SI 입장에선 "충돌 없음" → 둘 다 정상 커밋.

갱신 손실 vs 쓰기 스큐 

- 갱신 손실: 두 트랜잭션이 같은 행 수정 -> DB가 충돌 감지 기능.

- 쓰기 스큐: 같은 조건 읽고 다른 행에 씀 -> 충돌 없음.

 

의사 당직 vs 회의실 예약 - 같은 쓰기 스큐지만 차이가 있음 

둘 다 쓰기 스큐지만 막는 방법이 다르다.

팬텀개념이 들어감. 

- 팬텀: 한 트랜잭션 쓰기가 다른 트랜잭션 WHERE 결과를 바꿈

 


1. 의사 당직 — (잠글 행이 있다)
- 검사하는 대상: on_call = true인 행들 = Alice, Bob. 이미 테이블에 존재함.
- 그래서 잠글 게 있다.

SELECT * FROM doctors WHERE on_call = true FOR UPDATE;


- Alice가 먼저 이 락을 잡으면, Bob은 같은 행을 잠그려다 대기한다.
- Alice 커밋 후 Bob이 다시 조회 → 이제 당직 1명 → "빠지면 안 되겠다" 판단.
- 즉, 읽는 순간 존재하는 행을 잠가버리니 다른 트랜잭션이 끼어들 틈이 없다. FOR UPDATE 한 줄로 해결.

 

2. 회의실 예약 — (잠글 행이 없다)
- 검사하는 대상: "그 시간대에 예약이 없는지". 즉 행의 부재를 확인한다.

SELECT count(*) FROM bookings
WHERE room_id = 5 AND time_range OVERLAPS ('12:00','13:00');  -- 0
-- "비었네" → INSERT


- 검사 결과가 0개. 없는 행은 잠글 수 없다. FOR UPDATE를 걸어도 잠글 대상 자체가 없음.
- 두 트랜잭션이 동시에 "비었네"를 확인 → 각자 INSERT → 이중 예약.
- 여기가 락으로 못 막는 순수 팬텀 지점이다.

 

해결책

- 충돌 구체화 

    - 잠글 행이 없기에 미리 만들어둔다.

    - 방x시간 슬롯 조합을 time_slots 테이블에 전부 insert 해놓고, 예약 시 그 슬롯 행을 FOR UPDATRE로 잠금.

SELECT * FROM time_slots
WHERE room_id=5 AND slot='12:00' FOR UPDATE;  -- 이제 잠글 실재 행 있음

    - 단점 : 슬롯 조합 미리 다 생성해야 됨. 동시성 제어가 데이터 모델로 새어나옴.

직렬성 - SSI 

SSI(Serializable Snapshot Isolation) - 직렬성 스냅샷 격리 

- 스냅샷 격리 성능은 그대로 두고, 직렬성까지 보장하는 방법.

- 낙관적 동시성 제어. Postgres SERIALIZABLE이 이걸 씀.

 

배경 — 기존 직렬성 구현의 문제

직렬성 보장 방법은 원래 셋:
- 실제 직렬 실행 — 싱글 스레드로 하나씩. 단순하지만 처리량 한계.
- 2단계 잠금(2PL) — 읽고 쓰는 것마다 락. 확실하지만 느림. 락 대기·데드락 많음.
- SSI — 위 둘의 단점 극복하려고 나옴.

 

비관 vs 낙관 (2PL vs SSI)

- 2PL(비관적): "충돌 날지 모르니 미리 잠그고 기다린다." 충돌 안 나도 락 비용 냄. 느림.

- SSI(낙관적): "일단 다 진행시킨다. 커밋할 때 진짜 충돌났는지 검사해서, 났으면 그 트랜잭션 취소하고 재시도."
- 충돌이 드문 워크로드면 SSI가 훨씬 빠름. 충돌 잦으면 재시도 폭증해서 손해.

 

어떻게 동작하나 (핵심 아이디어)


- 스냅샷 격리 위에서 돎 → 각 트랜잭션은 자기 시점 스냅샷을 봄 (읽기 락 없음, 빠름).
- 문제는 쓰기 스큐: "내가 읽은 전제가 커밋 시점엔 이미 낡았을 수 있다."
- SSI는 이걸 감지함:
  - 트랜잭션이 읽은 데이터를 다른 트랜잭션이 나중에 바꿨는지 추적.
  - 커밋 시점에 "내 판단 근거가 된 읽기가 무효화됐나?" 검사.
  - 무효화됐으면 = 직렬 실행이었다면 불가능한 결과 → abort.
- 즉 "낡은 전제 위에서 커밋하려는 트랜잭션"을 잡아 죽인다.

장단점

- 장점: 읽기에 락 안 검(스냅샷 격리 속도) + 직렬성 보장. 한 트랜잭션이 다른 걸 블록 안 함.
- 단점: 커밋 실패(abort) → 재시도 로직 필요. 충돌 잦으면 abort 비율↑ 성능↓.

 

회사에서 생긴 일 

이슈 - @Modifying(clearAutomatically=true) 영속성 컨텍스트 유실

 

문제 상황

// 1. 엔티티 저장 (영속성 컨텍스트에 올라감)
val classAttend = buildAttend(command, course, nowDate, isWaiting, 0)
classAttendProvider.save(classAttend)

// 2. 쿠폰 컬럼 set 후 2차 save (아직 DB 반영 전, 컨텍스트에만 dirty 상태)
if (command.couponSeq != null) {
    classAttend.couponSeq = command.couponSeq
    ...
    classAttendProvider.save(classAttend)
}

// 3. 벌크 UPDATE
@Modifying(clearAutomatically = true)
@Query("update LivePaymentJpaEntity e set e.attendSeq = :attendSeq where e.merchantUid = :orderId")
fun updateAttendSeqByMerchantUid(...)
→ 3번 실행 시 앞서 변경한 영속성 컨텍스트 내용이 날아감.


해결 

@Modifying(clearAutomatically = true, flushAutomatically = true)
- flushAutomatically = true → 벌크 실행 전에 대기 중인 변경을 DB로 flush.
- 순서 바뀜: flush(dirty→DB) → 벌크 UPDATE → clear. 유실 없음.

 

직접적으로 이번 장과 연관 있는 격리 문제는 아니지만 

데이터가 지금 어느 계층에서 어떤 상태인지 파악하는 것이 비슷해서 가져왔다. 

 

이번 장은 트랜잭션으로 막을 수 있는 어떤 문제들이 있는지 파악했고 특히 회의실 예약 예제에서 충돌 구체화는 평소 생각하지 못했던 거라 알게 되어 좋았다. 

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2026/07   »
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 28 29 30 31
글 보관함