Isolation Level Lock Concurrency

3줄 요약

  • Isolation Level은 동시에 실행되는 트랜잭션이 서로의 변경을 얼마나 보거나 막을지 정하는 규칙이다.
  • Lock은 정합성을 지키는 도구지만, 잘못 잡으면 API 지연, 데드락, 커넥션 고갈, 장애 전파의 출발점이 된다.
  • 실무 동시성 설계는 격리 수준만 높이는 것이 아니라 Unique 제약, 원자적 UPDATE, 낙관적 락, 비관적 락, 재시도, 큐잉을 조합하는 일이다.

핵심 정리

  • Dirty read, non-repeatable read, phantom read는 격리 수준을 이해하기 위한 대표 이상 현상이다. DB마다 기본 격리 수준과 구현 방식은 다르다.
  • PostgreSQL은 MVCC 기반으로 읽기와 쓰기를 많이 분리하지만, 업데이트 충돌과 명시적 락은 여전히 중요하다.
  • MySQL InnoDB는 Repeatable Read에서 next-key lock, gap lock이 등장할 수 있어 범위 조건 업데이트와 삭제가 예상보다 넓게 막힐 수 있다.
  • SELECT ... FOR UPDATE는 읽은 행을 이후 변경할 의도가 있을 때 사용한다. 단순 조회에 남발하면 동시성을 크게 낮춘다.
  • Deadlock은 버그라기보다 동시성 시스템에서 가능한 실패 모드다. 중요한 것은 탐지, 롤백, 재시도, 락 획득 순서 표준화다.
  • 재고 차감, 포인트 적립, 좋아요 중복, 카운터 증가 같은 기능은 동시성 테스트와 DB 제약을 함께 설계해야 한다.
  • 격리 수준을 높이는 것은 마지막 버튼이 아니다. 먼저 원자적 update, Unique 제약, lock 범위 축소, 접근 순서 통일, retry/idempotency로 비즈니스 불변식을 표현할 수 있는지 본다.
  • lock 문제는 DB 내부 지표만으로 끝나지 않는다. 백엔드의 트랜잭션 길이, SQL 조건과 인덱스, batch 시간대, 외부 API 호출이 lock wait와 deadlock을 만든다.

헷갈리는 지점

  • 격리 수준을 가장 높이면 항상 좋은 설계라고 생각하기 쉽다. 강한 격리가 안전해 보이기 때문이다.
    • 핵심은 강한 격리가 동시성과 지연 시간을 희생할 수 있다는 점이다.
    • 비즈니스 불변식에 필요한 수준만 선택하고 재시도와 제약으로 보완한다.
  • 락은 DB 내부 문제라 백엔드 개발자가 몰라도 된다고 생각하기 쉽다. 쿼리만 작성하면 DB가 알아서 처리한다고 느끼기 때문이다.
    • 핵심은 백엔드 코드의 트랜잭션 경계와 쿼리 순서가 락 보유 시간과 데드락 가능성을 만든다는 점이다.
    • 어떤 API가 어떤 행을 어떤 순서로 잠그는지 설명할 수 있어야 한다.
  • Deadlock은 반드시 제거해야 하는 치명적 결함이라고 오해하기 쉽다. 로그에 에러로 남기 때문이다.
    • 핵심은 데드락 가능성을 줄이고, 발생 시 한 트랜잭션이 롤백되며, 애플리케이션이 안전하게 재시도할 수 있어야 한다는 점이다.
    • 빈번한 데드락은 설계나 인덱스 문제의 신호다.
  • FOR UPDATE를 붙이면 동시성 버그가 모두 해결된다고 생각하기 쉽다. 직접 row를 잠근다는 표현이 강해 보이기 때문이다.
    • 핵심은 어떤 조건과 인덱스로 어떤 row 또는 range를 잠그는지에 따라 효과와 피해가 달라진다는 점이다.
    • 잠금은 짧고 좁게 잡고, 가능하면 조건부 update와 Unique 제약처럼 더 단순한 DB 원자성을 먼저 검토한다.

확인 질문

  • SELECT ... FOR UPDATE를 써야 하는 대표 상황은 무엇인가?
    • 읽은 값을 바탕으로 같은 행을 곧 변경해야 하고, 다른 트랜잭션이 동시에 변경하면 안 되는 경우다.
  • Deadlock을 줄이는 백엔드 설계 원칙은 무엇인가?
    • 여러 테이블과 행을 항상 같은 순서로 접근하고, 트랜잭션을 짧게 유지하며, 필요한 조건에 맞는 인덱스로 잠금 범위를 줄이는 것이다.
  • 동시성 버그를 코드 검증만으로 막기 어려운 이유는 무엇인가?
    • 여러 요청이 동시에 들어오는 타이밍, DB 락, 제약 조건, 격리 수준이 함께 작동하므로 단일 요청 로직만 보면 문제가 드러나지 않기 때문이다.
  • lock wait가 발생했을 때 백엔드 개발자가 가져가야 할 정보는 무엇인가?
    • 문제 시간대, endpoint, 실제 SQL과 bind 값, 트랜잭션 경계, lock을 잡는 코드 경로, 최근 배포/batch, pool 지표, DB 세션 대기 정보를 함께 가져가야 한다.