트랜잭션 직렬화 가능성 및 격리

이 페이지에서는 트랜잭션 데이터 경합, 직렬화 가능성, 격리에 대해 설명합니다. 트랜잭션 코드 샘플은 트랜잭션 및 일괄 쓰기를 대신 참조하세요.

트랜잭션 및 데이터 경합

트랜잭션이 성공하려면 읽기 작업에서 검색된 문서가 트랜잭션 외부의 작업에서 수정되지 않은 상태로 유지되어야 합니다. 다른 작업이 이러한 문서 중 하나를 변경하려고 하면 해당 작업이 트랜잭션의 데이터 경합 상태가 됩니다.

데이터 경합
두 개 이상의 작업이 동일한 문서를 제어하기 위해 경쟁하는 경우 예를 들어 한 트랜잭션에서 문서를 일관되게 유지해야 하는 동안 한 트랜잭션은 필드 값을 업데이트하려고 할 수 있습니다.

Firestore는 작업 중 하나를 지연하거나 실패하여 데이터 경합을 해결합니다. Firestore 클라이언트 라이브러리는 데이터 경합으로 인해 실패한 트랜잭션을 자동으로 재시도합니다. 재시도를 일정 횟수 이상 시도하면 트랜잭션 작업이 실패하고 오류 메시지가 반환됩니다.

ABORTED: Too much contention on these documents. Please try again.

실패하거나 지연할 작업을 결정할 때는 동작이 클라이언트 라이브러리 유형에 따라 달라집니다.

  • 모바일/웹 SDK는 낙관적 동시 실행 제어를 사용합니다.

  • 서버 클라이언트 라이브러리는 비관적 동시 실행 제어를 사용합니다.

모바일/웹 SDK의 데이터 경합

모바일/웹 SDK(iOS, Android, 웹, C++)는 낙관적 동시 실행 제어를 사용하여 데이터 경합을 해결합니다.

낙관적 동시 실행 제어
데이터 경합이 불가능하거나 데이터베이스 잠금을 유지하는 것이 효율적이지 않다는 가정을 기반으로 합니다. 낙관적 트랜잭션은 데이터베이스 잠금을 사용하여 다른 작업이 데이터를 변경하지 못하도록 차단하지 않습니다.

모바일/웹 SDK는 지연 시간이 길고 불안정한 네트워크 연결을 갖춘 환경에서 작동할 수 있으므로 낙관적 동시 실행 제어를 사용합니다. 지연 시간이 긴 환경에서 문서를 잠그면 데이터 경합 실패가 너무 많이 발생합니다.

모바일/웹 SDK에서 트랜잭션은 트랜잭션 내에서 읽은 모든 문서를 추적합니다. 트랜잭션 실행 중에는 트랜잭션 실행 중에 변경된 문서가 없을 때만 쓰기 작업이 완료됩니다. 문서가 변경되었으면 트랜잭션 핸들러가 트랜잭션을 다시 시도합니다. 몇 번 다시 시도한 후에도 깔끔한 결과를 가져올 수 없으면 데이터 경합으로 인해 트랜잭션이 실패합니다.

서버 클라이언트 라이브러리의 데이터 경합

서버 클라이언트 라이브러리(C#, Go, 자바, Node.js, PHP, Python, Ruby)는 비관적 동시 실행 제어를 사용하여 데이터 경합을 해결합니다.

비관적 동시 실행 제어
데이터 경합이 있을 가능성이 있다는 가정을 기반으로 합니다. 비관적 트랜잭션은 데이터베이스 잠금을 사용하여 다른 작업에서 데이터를 수정하지 못하게 합니다.

서버 클라이언트 라이브러리는 짧은 지연 시간과 데이터베이스에 대한 안정적인 연결을 가정하므로 비관적 동시 실행 제어를 사용합니다.

서버 클라이언트 라이브러리에서는 트랜잭션에서 읽은 문서에 잠금을 설정합니다. 문서의 트랜잭션 잠금은 다른 트랜잭션, 일괄 쓰기, 비트랜잭션 쓰기가 문서를 변경하지 못하도록 차단합니다. 트랜잭션은 커밋 시 문서 잠금을 해제합니다. 또한 시간이 초과되거나 실패할 경우 잠금 상태를 해제합니다.

트랜잭션이 문서를 잠그면 다른 쓰기 작업이 트랜잭션이 잠금을 해제할 때까지 기다려야 합니다. 트랜잭션은 시간순으로 잠금을 획득합니다.

직렬화 가능한 격리

트랜잭션 간의 데이터 경합은 데이터베이스 격리 수준과 밀접한 관련이 있습니다. 데이터베이스의 격리 수준은 시스템이 동시 실행 작업 사이의 충돌을 처리하는 방법을 설명합니다. 충돌이 발생한 데이터베이스 요구사항은 다음과 같습니다.

  • 트랜잭션에는 정확하고 일관된 데이터가 필요합니다.
  • 리소스를 효율적으로 사용하기 위해 데이터베이스는 작업을 동시에 실행합니다.

격리 수준이 낮은 시스템에서 트랜잭션 내의 읽기 작업은 동시 작업에서 커밋되지 않은 변경으로부터 부정확한 데이터를 읽을 수 있습니다.

직렬화 가능한 격리는 최상위 격리 수준을 정의합니다. 직렬화 가능한 격리란 다음을 의미합니다.

  • 데이터베이스가 연속으로 트랜잭션을 실행한다고 가정할 수 있습니다.
  • 트랜잭션은 동시 실행 시 커밋되지 않은 변경사항의 영향을 받지 않습니다.

데이터베이스가 여러 트랜잭션을 동시에 실행하는 경우에도 이러한 보장이 적용되어야 합니다. 데이터베이스는 이 보장을 손상시키는 충돌을 해결하기 위해 동시 실행 제어를 구현해야 합니다.

Firestore는 직렬화 가능한 트랜잭션 격리를 보장합니다. Firestore의 트랜잭션은 직렬화되어 커밋 시간으로 격리됩니다.

커밋 시간으로 직렬화 가능한 격리

Firestore는 각 트랜잭션에 단일 시점을 나타내는 커밋 시간을 할당합니다. Firestore가 트랜잭션의 변경사항을 데이터베이스에 커밋할 때 트랜잭션 내의 모든 읽기 및 쓰기가 정확히 커밋 시 발생한다고 가정할 수 있습니다.

트랜잭션을 실제로 실행하려면 일정 시간이 필요합니다. 트랜잭션 실행은 커밋 시간 전에 시작되며, 여러 작업의 실행이 중복될 수 있습니다. Firestore는 직렬화 가능한 격리를 유지하고 다음을 보장합니다.

  • Firestore는 커밋 시간 순서대로 트랜잭션을 커밋합니다.
  • Firestore는 이후 커밋 시간이 있는 동시 작업에서 트랜잭션을 격리합니다.

동시 작업 간 데이터 경합이 발생하는 경우 Firestore는 낙관적 및 비관적 실행 제어를 사용하여 경합을 해결합니다.

트랜잭션 내 격리

트랜잭션 격리는 트랜잭션 내의 쓰기 작업에도 적용됩니다. 트랜잭션 내 쿼리 및 읽기는 해당 트랜잭션 내의 이전 쓰기 결과를 볼 수 없습니다. 트랜잭션 내에서 문서를 수정하거나 삭제하는 경우에도 해당 트랜잭션의 모든 문서 읽기는 트랜잭션의 쓰기 작업 전에 커밋 시 문서의 버전을 반환합니다. 문서가 존재하지 않으면 읽기 작업이 반환되지 않습니다.