(번역) 카크로치디비(CockroachDB) 블로그 / Serializable이 진정한 트랜잭션입니다

원문: https://www.cockroachlabs.com/blog/acid-rain/

Written by Ben Darnell on Sep 21, 2017

대부분의 데이터베이스는 정확성과 성능 사이의 균형을 제공하는 여러 트랜잭션 격리 수준 중 하나를 선택할 수 있습니다. 그러나, 성능은 개발자에게 트랜잭션 상호작용을 신중하게 연구하거나 미묘한 버그를 유발하는 비용을 필요로 합니다. CockroachDB는 강한(SERIALIZABLE) 격리수준을 기본으로 제공하여 언제나 예상한 데이터가 보이는 것을 보장합니다. 저는 이 글에서 강한 격리수준의 의미와 불충분한 격리수준이 실제 애플리케이션에 어떤 충격을 주는지 설명할 것입니다.

SQL 표준의 격리수준

SQL 표준은 네가지 격리수준을 정의합니다.

  • SERIALIZABLE
  • REPEATABLE READ
  • READ COMMITTED
  • READ UNCOMMITTED

SERIALIZABLE 트랜잭션은 한 번에 하나의 트랜잭션만 실행하는 것처럼 실행됩니다. 다른 격리 수준은 SQL 표준이 “세 가지 현상"이라고 완곡하게 표현하는 더티리드, 논리피티블리드, 팬텀리드를 허용합니다. 이후의 연구는 추가적인 “현상"과 격리수준을 발견했습니다.

최신의 연구에서 이러한 “현상"은 일반적으로 “이상현상” 또는 더 퉁명스럽게 “거짓”이라고 불립니다. SERIALIZABLE이 아닌 격리수준을 사용하면, 잘못된 결과를 반환할 수 있는 권한을 데이터베이스에 부여하고, 올바른 결과를 반환하는 것보다 빨라지기를 기대하게 됩니다. SQL 표준은 이것이 위험하며 SERIALIZABLE이 기본 격리수준이 되기를 요구합니다. 더 약한 격리수준은 잠재적인 최적화를 위해 이런 이상현상을 견딜 수 있는 애플리케이션을 위해 제공됩니다.

실제 데이터베이스의 격리수준

대부분의 데이터베이스는 SERIALIZABLE이 기본값인 것을 무시하고, 안정성보다 성능을 우선시하여 기본값으로 READ COMMITTED 또는 REPEATABLE READ 격리수준을 사용합니다. 좀 더 걱정스럽게도, 데이터베이스 중 일부(오라클, PostgreSQL 9.1버전 이전)는 SERIALIZABLE 격리수준의 구현을 제공하지 않습니다. 오라클의 SERIALIZABLE 격리수준은 실제로 “snapshot isolation(SNAPSHOT 격리)“이라고 불리는 약한 모드입니다.

SNAPSHOT 격리는 초기 SQL 표준화 이후에 개발되었지만, 성능과 일관성의 균형이 잘 유지되므로 여러 데이터베이스 시스템에서 구현되었습니다. 이것은 READ COMMITTED보다 강하지만 SERIALIZABLE보다 약합니다. 이것은 REPEATABLE READ와 유사하지만 정확히 동일하지는 않습니다(REPEATABLE READ는 팬텀리드를 허용하지만 쓰기 이상현상을 방지하고 SNAPSHOT 격리는 반대). SNAPSHOT 격리를 구현한 데이터베이스는 이를 네 가지 SQL 표준 수준에 맞게 조정하는 방법에 대해 서로 다른 결정을 내렸습니다. 오라클은 SNAPSHOTSERIALIZABLE이라고 부르는 가장 공격적인 자세를 취했습니다. CockroachDB와 마이크로소프트 SQL 서버는 보수적으로 SNAPSHOT을 다섯 번째 격리 수준으로 취급합니다. PostgreSQL(버전 9.1부터)은 REPEATABLE READ 대신 SNAPSHOT을 사용 합니다.

SERIALIZABLE 모드는 더 약한 격리가 기본인 데이터베이스에서 자주 사용되지 않으므로, 종종 철저히 테스트되거나 최적화되지 않았습니다. 예를 들어, PostgreSQL은 SERIALIZABLE 트랜잭션 간의 충돌을 추적하는 데 사용되는 고정 크기 메모리 풀을 가지고 있고, 이 풀은 과부하 상태에서 고갈 될 수 있습니다.

대부분의 데이터베이스 공급 업체는 강한 트랜잭션 격리수준을 예외적인 일관성 요구사항이 있는 애플리케이션에서 사용하는 이국적인 옵션으로 취급합니다. 하지만 대부분의 애플리케이션은 빠르지만 안전하지 않은 약한 격리수준으로 동작할 것으로 예상됩니다. 문제에 대한 이러한 접근방식은 애플리케이션을 많은 미묘한 버그에 노출시킵니다. Cockroach 연구소에서 우리는 격리수준의 이상현상을 너무 많이 생각한 나머지 회의실 이름으로 격리수준을 사용하고 있지만, SERIALIZABLE 대신 SNAPSHOT 격리수준을 선택하는 것이 안전하고 유익하다고 조언하기 힘들었습니다. 우리는 안전과 함께 시작하여 성능을 향상시키는 것이 다른 방법보다 효과적이라는 철학을 가지고 있습니다.

ACIDRain: 트랜잭션 버그를 찾기

스탠포드의 최근 연구는 약한 격리수준이 실제 버그로 이어지는 정도를 탐구했습니다. Todd Warszawski와 Peter Bailis는 12개의 전자 상거래 애플리케이션을 조사하여 트랜잭션과 관련된 22개의 버그를 발견했으며, 그 중 5개는 높은 격리수준을 이용하여 피할 수 있었습니다. 이 버그 중 상당수는 악용하기 쉽고 직접적인 재정적 영향을 줍니다. 예를 들어 다섯 가지 애플리케이션에서 다른 브라우저에서 결재를 진행하며 장바구니에 아이템을 추가하면 무료로 주문에 추가할 수 있었습니다. 연구자들은 이러한 취약점을 반자동 방식으로 확인하는 도구를 개발하여 비슷한 공격(“ACIDRain”)이 더 널리 전파되게 하였습니다.

약한 트랜잭션 격리수준을 기본으로 하는 데이터베이스는 SELECT 문에 대한 (비표준) FOR UPDATELOCK IN SHARE MODE와 같은 대안을 제공합니다. 올바르게 이런 변형을 사용하면 약한 격리수준에서도 트랜잭션을 안전하게 사용할 수 있습니다. 그러나 이것은 잘못 사용하기 쉽고, 일관되게 사용하더라도 이러한 확장은 SERIALIZABLE 모드의 단점을 대부분 도입합니다(실제로 READ COMMITTED에서 SELECT FOR UPDATE를 과용하면 공유 잠금만 필요한 경우에도 베타적 잠금을 사용하므로 SERIALIZABLE보다 성능이 떨어질 수 있음). ACIDRain 연구는 이 기술의 한계를 보여줍니다. SELECT FOR UPDATE 기능을 사용하려고 시도한 애플리케이션 중 1/3만 올바르게 사용했고, 나머지는 취약점을 노출했습니다.

결론

약한 격리수준을 권장하는 데이터베이스는 데이터 안정성보다 성능에 높은 우선순위를 주기 때문에, 트랜잭션간 미묘한 상호작용을 고려하여야 하고 구현 중 에러가 발생하기 쉽습니다. CockroachDB는 기본적으로 SERIALIZABLE 트랜잭션을 제공하여 트랜잭션 데이터베이스에서 기대되는 일관성을 항상 보장합니다.

Illustration by Lisk Feng