database

Concurrency Control

동시성 제어는 다중 사용자 환경에서 데이터베이스나 공유 자원의 일관성과 무결성을 유지하기 위해 반드시 필요한 기술입니다. 동시성 제어를 통해 여러 사용자가 동시에 동일한 데이터에 접근할 경우 발생할 수 있는 데이터 손실이나 오류를 방지할 수 있습니다. 동시성 문제(Concurrency Issue)는 다양한 형태로 나타날 수 있으며, 주요 유형은 다음과 같습니다.


Lost Update

Lost Update 문제는 동시성 제어에서 발생하는 대표적인 데이터 무결성 문제 중 하나로, 여러 트랜잭션이 동시에 같은 데이터를 읽고 이를 수정한 뒤 저장할 때, 한 트랜잭션의 업데이트가 다른 트랜잭션에 의해 덮어씌워져 손실되는 상황을 의미합니다. 예를 들어, 두 사용자가 동일한 은행 계좌의 잔액을 동시에 조회하여 각자 출금을 시도하면, 한쪽의 출금 내역이 사라지는 결과를 초래할 수 있습니다. 이는 주로 데이터베이스나 공유 자원에 대한 동시 접근 시 적절한 동기화 메커니즘이 적용되지 않을 때 발생합니다. Lost Update 문제를 방지하려면 비관적 락(Pessimistic Lock)이나 낙관적 락(Optimistic Lock)과 같은 동시성 제어 기법을 도입하여 데이터의 무결성을 유지해야 합니다. 이러한 기술을 활용하면 트랜잭션 충돌을 감지하거나 차단하여 안정적인 데이터 처리가 가능해집니다.


계좌에 1000이 있습니다. 이 계좌를 A, B트랜잭션이 계좌의 잔액을 수정한다고 가정해보겠습니다.


  1. 트랜잭션 A가 1000을 읽고 200을 출금하려고 준비
  2. 트랜잭션 B가 1000을 읽고 300을 입금하려고 준비
  3. 트랜잭션 A가 800( 1000 - 200 )으로 업데이트
  4. 트랜잭션 B가 1300( 1000 + 300 )으로 업데이트

우리는 1100을 기대하지만, 실제 결과는 트랜잭션 A가 무시되고 1300이 되는 경우가 발생합니다. 이러한 경우에 아래와 같이 해결방안을 생각해 볼 수 있습니다.


  1. 비관적 락(Pessimistic Lock): 트랜잭션이 데이터를 수정하는 동안 다른 트랜잭션이 해당 데이터를 읽거나 수정하지 못하도록 락을 설정.
  2. 낙관적 락(Optimistic Lock): 데이터에 버전 번호를 부여하고, 업데이트 시 버전 번호가 변경되지 않았는지 검증.
  3. 트랜잭션 격리 수준: Repeatable Read 또는 Serializable 수준으로 설정하여 동시 접근을 제한.

이 예시는 Lost Update 문제의 핵심인 동시 접근으로 인한 업데이트 손실을 잘 보여줍니다. 이를 방지하려면 동시성 제어 기법을 적절히 적용해야 합니다.


Dirty Read

Dirty Read는 동시성 제어에서 발생할 수 있는 데이터 무결성 문제로, 한 트랜잭션이 아직 커밋되지 않은 다른 트랜잭션의 데이터를 읽는 상황을 말합니다. 이 문제는 트랜잭션 간 데이터 일관성을 위협하며, 특히 롤백이 발생할 경우 심각한 오류를 초래할 수 있습니다. 예를 들어, 한 트랜잭션이 계좌의 잔액을 임시로 변경하고 커밋하지 않은 상태에서 다른 트랜잭션이 이를 읽어 잘못된 데이터를 기반으로 작업을 수행하면, 이후 첫 번째 트랜잭션이 롤백되더라도 두 번째 트랜잭션의 결과는 잘못된 상태로 남게 됩니다. Dirty Read를 방지하려면 데이터베이스의 트랜잭션 격리 수준을 Read Committed 이상으로 설정하거나, 적절한 락 메커니즘을 통해 트랜잭션 간 간섭을 차단해야 합니다. 이를 통해 데이터 무결성을 보장하고 안정적인 시스템 운영이 가능합니다.


계좌에 1000이 있습니다. 이 계좌를 A, B트랜잭션이 계좌의 잔액을 수정한다고 가정해보겠습니다. 한 트랜잭션이 아직 커밋되지 않은 데이터를 읽어 잘못된 결과를 초래하는 예시입니다.


  1. 트랜잭션 A가 1000을 읽고 200을 출금 준비. 잔액은 800원으로 변경되었고 아직 커밋은 되지 않음
  2. 트랜잭션 B가 800을 읽고 300을 입금하려는 추가작업을 준비함
  3. 트랜잭션 A가 오류 발생이나 사용자 취소로 롤백하여 1000으로 복구
  4. 트랜잭션 B는 1100을 데이터베이스에 저장

A의 롤백으로 1300이 되어야 하는데, B의 Dirty Read로 인해 잘못된 데이터로 작업이 수행되었습니다. 이러한 경우에 아래와 같이 해결방안을 생각해 볼 수 있습니다.


  1. 트랜잭션 격리 수준 설정: Read Committed 이상으로 설정하여 커밋되지 않은 데이터를 읽지 않도록 제한
  2. 락 메커니즘 사용 : 트랜잭션 A가 잔액을 수정 중일 때 B가 데이터를 읽지 못하도록 공유 락(Shared Lock) 또는 배타 락(Exclusive Lock)을 사용
  3. 적절한 검증 로직 추가: 커밋된 데이터를 다시 검증하거나, 트랜잭션 완료 후 작업 수행

이 예시는 Dirty Read의 핵심 문제인 커밋되지 않은 데이터 사용이 시스템의 데이터 무결성을 어떻게 위협하는지를 잘 보여줍니다. 적절한 격리 수준과 동시성 제어 기법으로 이를 방지해야 합니다.


Non-Repeatable Read

Non-Repeatable Read는 동시성 제어에서 발생하는 문제로, 한 트랜잭션이 같은 데이터를 두 번 읽을 때 그 값이 달라지는 상황을 의미합니다. 이는 첫 번째 읽기와 두 번째 읽기 사이에 다른 트랜잭션이 해당 데이터를 수정하거나 삭제했을 때 발생합니다. 예를 들어, A 트랜잭션이 상품의 가격을 조회한 뒤 다시 조회했을 때, 중간에 B 트랜잭션이 가격을 수정하면 A 트랜잭션은 두 번의 조회 결과가 일치하지 않는 문제를 겪습니다. 이러한 문제는 데이터의 일관성이 요구되는 환경에서 치명적일 수 있습니다. Non-Repeatable Read를 방지하려면 데이터베이스의 트랜잭션 격리 수준을 Repeatable Read 이상으로 설정하거나, 읽기 작업 동안 데이터가 변경되지 않도록 공유 락(Shared Lock)을 적용해야 합니다. 이를 통해 데이터의 일관성을 유지하고 정확한 작업 처리가 가능합니다.


계좌에 1000이 있습니다. 이 계좌를 A, B트랜잭션이 계좌의 잔액을 수정한다고 가정해보겠습니다. 트랜잭션이 동일한 데이터를 두 번 읽을 때, 다른 트랜잭션의 수정으로 인해 값이 달라지는 상황을 기반으로 합니다.


  1. 트랜잭션 A가 1000을 조회
  2. 트랜잭션 B가 1000을 조회해서 200을 출금하고, 800을 데이터베이스에 저장
  3. 트랜잭션 A가 동일한 계좌를 다시 조회 하면 800이 조회됨

동일한 트랜잭션 내에서는 일관된 데이터를 읽어야합니다. 하지만 두 번째 조회 시 800이 도출 되면서 데이터의 일관성이 문제가 발생합니다. 이러한 경우에 아래와 같이 해결방안을 생각해 볼 수 있습니다.


  1. 트랜잭션 격리 수준 설정: Repeatable Read 이상으로 설정하여 트랜잭션 동안 데이터가 수정되지 않도록 보장
  2. 공유 락(Shared Lock) 사용: A 트랜잭션이 데이터를 읽을 때, B 트랜잭션이 해당 데이터를 수정하지 못하도록 락을 설정
  3. 데이터 스냅샷 사용: A 트랜잭션이 시작될 때 데이터의 스냅샷을 생성하고, 트랜잭션 내에서 동일한 데이터를 반환하도록 보장

이 예시는 Non-Repeatable Read의 핵심 문제인 트랜잭션 내 데이터 일관성 부족이 시스템 무결성에 어떤 영향을 미치는지를 보여줍니다. 적절한 격리 수준과 동시성 제어 기법을 통해 이를 방지해야 합니다.


Phantom Read

Phantom Read는 동시성 제어에서 발생하는 문제로, 한 트랜잭션이 같은 조건으로 데이터를 두 번 조회할 때, 첫 번째 조회와 두 번째 조회 결과가 일치하지 않는 상황을 의미합니다. 이는 다른 트랜잭션이 중간에 데이터를 삽입하거나 삭제하면서 발생합니다. 예를 들어, A 트랜잭션이 "가격이 100 이상인 상품"을 조회한 뒤 같은 조건으로 다시 조회했을 때, B 트랜잭션이 중간에 해당 조건에 맞는 새로운 상품을 추가하거나 기존 상품을 삭제하면, A 트랜잭션은 두 조회에서 서로 다른 결과를 얻게 됩니다. Phantom Read는 데이터 집합의 일관성이 중요한 환경에서 문제가 될 수 있습니다. 이를 방지하려면 데이터베이스의 트랜잭션 격리 수준을 Serializable로 설정하거나, 범위 락(Range Lock)과 같은 기법을 사용하여 트랜잭션 간 간섭을 차단해야 합니다. 이를 통해 데이터 조회의 정확성과 일관성을 보장할 수 있습니다.


이 시나리오는 한 트랜잭션이 동일한 조건으로 데이터를 두 번 조회할 때, 다른 트랜잭션이 중간에 데이터를 삽입하거나 삭제하여 조회 결과가 달라지는 상황을 기반으로 합니다.


  1. 트랜잭션 A가 800 이상의 전자제품을 조회 했는데, 세탁기(1000)와 냉장고(800) 데이터가 조회
  2. 트랜잭션 B가 TV(900)를 상품에 추가
  3. 트랜잭션 A가 동일 조건으로 다시 조회 시 세탁기, 냉장고, TV가 추가

트랜잭션 내에서는 조회 결과가 동일해야하지만, 두 번째 조회에서는 TV가 추가로 조회되어 일관성이 깨지게 됩니다. 이러한 경우에 아래와 같이 해결방안을 생각해 볼 수 있습니다.


  1. 트랜잭션 격리 수준 설정: Serializable로 설정하여 트랜잭션 중 데이터 추가/삭제를 방지.
  2. 범위 락(Range Lock) 사용: 조건에 해당하는 데이터 집합에 락을 설정하여 다른 트랜잭션이 데이터를 추가하거나 삭제하지 못하도록 차단.
  3. 스냅샷 읽기 사용: 트랜잭션 시작 시점의 데이터 상태를 기준으로 조회 결과를 고정.

이 예시는 Phantom Read의 핵심 문제인 데이터 집합의 동적 변경이 트랜잭션 일관성에 어떤 영향을 미치는지 보여줍니다. 이를 방지하려면 높은 수준의 격리와 동시성 제어 기법을 적용해야 합니다.