고립성이란?

공부용·2025년 8월 26일

여러 트랜잭션이 동시에 실행될 때, 각 트랜잭션이 다른 트랜잭션의 작업에 영향을 받지 않고 독립적으로 실행되는 것처럼 보이게 하는 성질이다. 즉, 진행 중인 트랜잭션이 다른 트랜잭션의 중간 변경 내용을 볼 수 있는지 여부를 제어한다.


고립성이 없을 때 발생하는 문제들 (Read Phenomena)

고립성이 제대로 지켜지지 않으면 데이터의 일관성이 깨지는 여러 문제들이 발생할 수 있다.

  • 더티 리드 (Dirty Reads): 아직 커밋(확정)되지 않은 트랜잭션의 변경 내용을 읽는 현상이다. 만약 다른 트랜잭션이 롤백되면, 나는 존재하지 않는 '더러운' 데이터를 읽는 셈이 된다.
  • 반복 불가능한 읽기 (Non-repeatable Reads): 한 트랜잭션 안에서 같은 행(row)을 두 번 읽었는데, 그 사이에 다른 트랜잭셔닝 해당 행의 값을 수정하고 커밋하는 바람에 두 번의 읽기 결과가 다르게 나타나는 현상이다.
  • 팬텀 리드 (Phantom Reads): 한 트랜잭션에서 일정 범위의 데이터를 두 번 읽었는데, 첫 번째 읽기에서는 없었던 새로운 행이 두 번째 읽이에서 나타나는 현상이다. 이는 다른 트랜잭셔닝 새로운 행을 추가하고 커밋했기 때문에 발생하며, 마치 유령(Phantom) 처럼 없던 데이터가 나타났다고 해서 붙여진 이름이다.
  • 유실된 업데이트 (Lost Updates): 두 개 이상의 트랜잭션이 같은 데이터를 동시에 수정할 때, 나중에 실행된 트랜잭션의 수정 내용이 먼저 실행된 트랜잭션의 수정 내용을 덮어쓰면서 먼저 실행된 변경 내용이 사라지는(유실되는) 현상이다.

고립 수준 (Isolation Levels)

고립 수준설명더티 리드반복 불가능한 읽기팬텀 리드
Read Uncommitted기장 낮은 수준. 다른 트랜잭션이 커밋하지 않은 변경 내용도 볼 수 있다.발생발생발생
Read Committed커밋된 변경 내용만 읽을 수 있다. 대부분의 데이터베이스에서 기본값으로 사용된다.방지발생발생
Repeatable Read트랜잭션이 시작된 이후 다른 트랜잭션에 의해 수정된 내용은 보이지 않아, 반복해서 읽어도 항상 같은 결과를 얻는다.방지방지발생
Serializable가장 높은 수준. 트랜잭션들을 마치 순서대로 하나씩 실행하는 것처럼 동작하도록 하여 동시성 문제를 완벽히 방지한다.방지방지방지

각 DBMS는 이러한 구립 수준을 잠금(Locking)을 이용한 비관적 동시성 제어 또는 버전 관리를 이용한 낙관적 동시성 제어 등의 방식으로 다르게 구현할 수 있다.

비관적 동시성 제어 (Pessimistic Concurrency Control)

트랜잭션 간 충돌이 자주 발생할 것이라고 가정하는 '비관적인' 접근법이다. 따라서 데이터 무결성을 지키기 위해, 한 트랜잭션이 데이터에 접근하는 순간부터 잠금(Lock)을 걸어 다른 트랜잭션의 접근을 막는다.

  • 작동 방식:

    1. 트랜잭션이 데이터를 읽거나 수정하려고 할 때 해당 데이터에 잠금을 요청한다. (예: 행 잠금, 테이블 잠금)
    2. 잠금을 획득하면 작업을 수행한다.
    3. 트랜잭션이 완료(커밋 또는 롤백)되면 잠금을 해제한다.
    4. 잠금이 걸려있는 동안 다른 트랜잭션은 해당 데이터에 접근할 수 없고 기다려야 한다.
  • 장점:

    • 잠금을 통해 충돌을 원척적으로 방지하므로 데이터 무결성을 확실하게 보장할 수 있다.
    • 충돌이 잦은 환경에서 롤백 및 재시도 비용이 없어 오히려 효율적일 수 있다.
  • 단점:

    • 잠금 때문에 다른 트랜잭션들이 대기해야 하므로 전체적인 시스템 동시성(성능)이 저하될 수 있다.
    • 잠금을 잘못 사용하면 교착 상태(Deadlock)에 빠질 위험이 있다.
  • 주요 사용 사례: 충돌 발생 시 손실이 큰 금융 거래, 좌석 예약, 재고 관리 등 데이터 정합성이 매우 중요한 시스템에 적합하다.

낙관적 동시성 제어 (Optimistic Concurrency Control)

트랜잭션 간 충돌이 거의 발생하지 않을 것이라고 가정하는 '낙관적인' 접근법이다. 따라서 일단 잠금 없이 모든 트랜잭션의 접근을 허용하고, 트랜잭션이 작업을 마친 후 최종적으로 커밋하는 시점에 충돌이 있었는지 검사한다.

  • 작동 방식:
    1. 데이터를 읽을 때 잠금을 걸지 않고, 대신 데이터의 버전 정보(Version) 또는 타임스탬프(Timestamp)를 함께 읽어온다.
    2. 트랜잭션을 진행하고 데이터를 수정한다.
    3. 커밋 시점에, 처음에 읽었던 버전 정보와 현재 데이터의 버전 정보가 일치하는지 확인한다.
    4. 버전이 일치하면 다른 트랜잭션의 개입이 없었다는 의미이므로 커밋을 완료한다.
    5. 버전이 일치하지 않으면 충돌이 발생한 것이므로, 트랜잭션을 롤백하고 재시도하도록 처리한다.
  • 장점:
    • 잠금을 사용하지 않으므로 대기 시간이 없어 높은 동시성(성능)을 확보할 수 있다.
    • 읽기 작업이 대부분인 시스템에서 매우 효율적이다.
  • 단점:
    • 충돌이 자주 발생하면 롤백 및 재시도 비용이 커져 오히려 성능이 저하될 수 잇다.
    • 개발자가 직접 재시도 로직을 구현해야 하는 경우도 있다.
  • 주요 사용 사례: 게시판 글 수정, 상품 정보 조회, SNS와 같이 읽기 작업이 많고 쓰기 충돌 가능성이 낮은 서비스에 적합

어떤 DBMS를 사용해야할까?

실제 동작 방식

각 DBMS가 특정 고립 수준을 어떻게 구현했는지를 이해하고, 내 서비스의 요구사항과 맞는지를 판단하는 것이 중요하다. ANSI SQL 표준은 각 고립 수준이 '최소한' 보장해야 하는 상항을 정의할 뿐, 실제 DBMS들은 그보다 더 높은 수준의 고립성을 제공하는 경우가 많다.

DBMS마다, 심지어 같은 DBMS의 버전이 스토리지 엔진에 따라서도 고립 수준의 동작 방식이 다르다

  • MySQL (InnoDB): 기본 고립 수준이 REPEATABLE READ이며, MVCC(Multi-Version Concurrency Control) 덕분에 팬텀 리드까지 방지된다. 이는 데이터 일관성을 매우 중요하게 생각하는 설계 철학을 보여준다.
  • PostgreSQL: 기본 고립 수준은 READ COMMITTED이다. 하지만 REPEATABLE READ 수준으로 올리면 스냅샷 방식으로 동작하여 팬텀 리드를 방지한다. 이는 성능을 우선시하되, 필요시 높은 수준의 일관성을 제공하는 유연한 접근이다.
  • Oracle: 기본 수준인 READ COMMITTED조차 MVCC 기반으로 동작하여, 다른 DBMS라면 REPEATABLE READ 수준에서나 막을 수 있는 반복 불가능한 읽기(Non-repeatable Read) 현상가지 방지해준다.
  • MS SQL Server: 전통적으로 잠금(Locking) 기반으로 동작하지만, 최신 버전에서는 스냅샷 기반의 고립 수준을 옵션으로 제공하여 유연성을 높였다.

트레이드 오프

DBMS를 결정할 때 고립성의 구현 방식을 기준으로 성능이나 비요의 트레이드 오프를 따져 기준을 마련하려한다.

어떤 구현 방식을 선택하느냐에 따라 특정 작업(Workload)환경에서의 성능, 개발의 복잡성, 심지어 하드웨어 비용까지 직접적인 영향을 받기 때문이다.

잠금(Locking) vs 버전관리(MVCC)

고립성 구현 방식의 트레이드 오프는 두 가지 방식의 차이에서 발생한다.

  1. 잠금(Locking) 기반 제어 (비관적): 한 트랜잭션이 데이터에 접근할 때 잠금을 걸어 다른 트랜잭션의 접근을 막는 방식

  2. 버전 관리(MVCC) 기반 제어 (낙관적): 데이터를 수정할 때마다 새로운 버전을 만들어 각 트랜잭션이 특정 시점의 데이터 스냅샷을 보도록 하는 방식이다.

예시

  1. 쓰기 충돌이 잦은 워크로드 (High Contention)

    • 시나리오: 실시간 예매, 선착순 이벤트, 재고 관리 시스템처럼 여러 사용자가 동시에 같은 데이터를 수정하려는 경합이 심한 경우.

    • 잠금(Locking) 기반이 유리할 수 있는 이유:

      • 예측 가능한 비용: 충돌이 예상되므로 미리 잠금을 걸어 순서를 제어하는 것이 효율적이다. 다른 트랜잭션은 일단 대기하므로, 롤백하고 처음부터 다시 시도하는 낭비가 없다.

      • 개발 비용 감소: 충돌 시 재시도 로직을 애플리케이션에 복잡하게 구현할 필요가 줄어든다.

  2. MVCC 기반의 비용:

    • 높은 재시도 비용: 충돌이 잦으면 수많은 트랜잭션이 마지막 커밋 단계에서 실패하고 롤백된다. 이는 CPU 자원을 낭비하고, 애플리케이션의 재시도 로직을 복잡하게 만들어 개발 및 유지보수 비용을 증가시킨다.
  3. 읽기 위주의 워크로드 (Read-Heavy)

    • 시나리오: 블로그, 뉴스 사이트, 쇼핑몰의 상품 조회처럼 쓰기 작업보다 읽기 작업이 압도적으로 많은 경우.

    • MVCC 기반이 압도적으로 유리한 이유:

      • 최상의 성능: '읽기 작업은 쓰기 작업을 막지 않고, 쓰기 작업 또한 읽기 작업을 막지 않는다.' 이는 MVCC의 가장 큰 장점으로, 잠금으로 인한 대기 시간이 거의 없어 월등히 높은 동시 처리 성능을 제공한다.
    • 잠금(Locking) 기반의 비용:

      • 심각한 성능 저하: 데이터를 분석하기 위한 긴 읽기 트랜잭션 하나가 수많은 행을 잠가버리면, 해당 데이터를 수정하려는 다른 짧은 쓰기 트랜잭션들이 모두 대기해야 하는 병목 현상이 발생한다. 이는 시스템 전체의 반응성을 떨어뜨리는 치명적인 단점이 될 수 있다.

DBMS 선택 기준

  1. 내 서비스의 요구사항 정의: 내 애플리케이션이 어느 정도의 데이터 부정합을 허용할 수 있는지 먼저 정의해야 한다.
    • 예시: 금융 거래처럼 단 1원의 오차도 없어야 한다면, 팬텀 리드까지 완벽히 막아주는 SERIALIZABLE이나 MySQL의 REPEATABLE READ처럼 강력한 고립 수준을 제공하는 DBMS가 필요하다.
    • 예시: 단순 조회나 통계처럼 약간의 데이터 차이가 치명적이지 않다면, READ COMMITTED를 기본으로 하여 성능을 확보하는 PostgreSQL이나 Oracle 같은 DBMS가 더 유리할 수 있다.
  2. DBMS 공식 문서 확인: 관심 있는 DBMS가 각 고립 수준을 어떤 기술(MVCC, Locking)으로 구현했고, 표준 이론과 어떤 차이가 있는지 공식 문서를 통해 확인해야 한다.
  3. 직접 테스트: 간단한 테스트 스크립트를 만들어 내가 원하는 수준의 고립성이 실제로 보장되는지 검증하는 것이 좋다.

A 서비스에는 B라는 고립 수준이 필요하니 C라는 DBMS를 써야 한다는 공식은 없다. 대신, "내 서비스는 팬텀 리드를 막아야 하는데, 테스트해보니 MySQL의 REPEATABLE READ와 PostgreSQL의 SERIALIZABLE이 그 동작을 보장해주는구나와"와 같이 실제 동작을 기반으로 판단하는 것이 좋은 접근법이다.

기준Locking 기반 고려 (전통적 RDBMS)MVCC 기반 고려 (PostgreSQL, MySQL InnoDB 등)
주요 워크로드쓰기 경합이 매우 심하고 예측 가능성이 중요할 때읽기 작업이 대부분이며 높은 동시성이 중요할 때
성능읽기-쓰기 경합 시 성능 저하 가능성읽기 위주 환경에서 최고의 성능
개발/운영 비용교착상태(Deadlock) 해결 등 잠금 관리에 대한 운영 비용잦은 충돌 시 애플리케이션 재시도 로직 구현에 대한 개발 비용.
profile
공부 내용을 가볍게 적어놓는 블로그.

0개의 댓글