이 글은 데이터 중심 애플리케이션 서적을 참고했습니다.
https://www.yes24.com/Product/Goods/59566585
어떤 저자들은 2단계 커밋에서 유발되는 성능이나 가용성 문제 때문에 생기는 비용이 너무 커서 이를 지원할 수 없다고 주장했다. 우리는 항상 트랜잭션 없이 코딩하는 것보다 트랜잭션을 과용해서 병목지점이 생기는 성능 문제를 애플리케이션 프로그래머가 처리하게 하는 게 낫다고 생각한다.
- 제임스 코벳 외, 스패너: 구글의 전역 분산 데이터베이스(2012)
현실 세계에서 데이터 시스템은 여러 가지 문제가 생길 수 있고, 신뢰성을 보장하기 어렵다.
이런 문제를 단순화하는 매커니즘으로 몇십년간 채택되어 온 것이 "트랜잭션"이다.
하지만 트랜잭션은 자연 법칙이 아니다. 데이터베이스에 접속하는 애플리케이션에서 프로그래밍 모델을 단순화하려는 목적으로 만든 것이다.
데이터베이스에서 어느 정도의 안전성 보장을 해주기 때문에 잠재적인 오류 시나리오와 동시성 문제를 무시할 수 있다.
따라서 무조건 트랜잭션을 완벽하게 따르기보다 완화하거나 사용하지 않는 것이 좋을 수도 있다.
트랜잭션을 사용할지 말지, 또 어느정도로 사용해야 할 지를 위해 트랜잭션을 정확히 이해해야 한다.
애매모호한 트랜잭션과 ACID
2000년대 후반에 NoSQL이 인기를 얻기 시작하고 Replication과 Partitioning 등이 인기를 얻으면서 새로운 데이터베이스에서는 트랜잭션은 원래 의미보다 훨씬 약한 보장을 의미하는 것으로 재정의되기도 했다.
고가용성을 위해서 트랜잭션을 포기한다거나 중요한 데이터베이스에서는 트랜잭션이 필수적이라는 것은 둘 다 완전한 과장이다.
ACID
산성... 이라는 이름으로 트랜잭션의 성질을 나타내기 위한 단어이다.
그러나 현실적으로 이를 구현하는 것은 제각각이다. ACID를 준수한다고 했을 때 실제로 어떤 식으로 동작하는 지 분명히 알 수가 없다.
데이터 중심 애플리케이션에서는 "ACID는 유감스럽게도 마케팅 용어가 돼버렸다"고 얘기한다.
이 표준을 따르지 않는 경우 BASE를 따른다고 한다. ACID보다 모호한 이 것의 정의는 "ACID가 아니다" 뿐인 것 같다.
Atomicity (원자성)
Commit 혹은 Abort 둘 중 하나를 해야 한다는 뜻이다. 어보트 능력(abortability)가 더 나은 단어일 수도 있다.
Consistency (일관성)
내가 제일 평소에도 모호하게 생각했던 부분이다. 책에서도 특별하게 다룬다.
일관성은 CAP이론 등에서의 일관성, ACID에서의 일관성 등 많은 의미로 쓰이고 있어 혼동될 여지가 많다.
ACID에서의 일관성은 아주 대충 말하자면 데이터베이스가 "좋은 상태"에 있어야 한다는 것이다.
구체적으로는 항상 진실이어야 하는, 데이터에 관한 어떤 선언(불변식(invariant))이 있다는 것이다.
무슨 말이냐면, 어떤 데이터베이스에 나이 필드가 0보다 커야 한다는 조건(불변식)이 있고, 만약 나이가 음수가 된다면 commit이 되지 않는 constraint가 있다던가, trigger 등이 있어야 한다.
하지만, 일관성은 애플리케이션의 불변식에 의존하므로 일관성 유지의 책임은 기본적으로 애플리케이션에 있다.
즉 원자성, 격리성, 지속성은 데이터베이스의 속성인 반면 일관성은 애플리케이션의 속성이다. 애플리케이션의 일관성을 달성하기 위해서는 원자성과 격리성에 기댈 순 있지만 데이터베이스만으로는 안 된다.
따라서 C는 실제로는 ACID에 속하지 않는다.
Joe Hellerstein은 ACID의 C는 약어를 만들기 위해 끼어들었고 당시에는 일관성이 중요하게 생각되지 않았다고 말했다.
- 일관성은 다른 속성들과는 달리 "의미"를 갖는다. 기술적인 문제라기엔 상당히 모호하다.
- 원자성을 위한 Abort, Undo, Redo, 격리성을 위한 많은 격리 수준(스냅숏 격리 등), 지속성을 위한 WAL, Redo 등 구체적이고 복잡한 구현이 존재한다.
- 하지만 일관성은 그럴 수 없다. 그냥 "좋은 상태"라는 것을 의미하므로 원자성, 격리성, 애플리케이션 구현 등이 끼어들어야 하기 때문이다.
Isolation (격리성)
"동시성"이라는 키워드만 생각하면 된다. 동시에 실행되는 트랜잭션은 서로 격리된다는 것을 의미한다. 이것을 Serializable(직렬) 하다고 한다. 하지만 이것을 따르기 위해서는 엄청난 성능 손해를 유발하므로 많은 격리 수준이 존재한다.
Durability (지속성)
성공적으로 커밋됐다면, 데이터가 손실되지 않는다는 보장이다.
디스크가 물리적으로 파괴되는 것을 고려했을 때 완벽한 지속성은 존재할 수 없긴 하다.
보통 WAL등을 통해 데이터가 디스크(비휘발성 메모리)에 저장되고, 특히 복제가 되는 데이터베이스에서는 다른 노드들에 복사된 것을 의미한다.
다중 객체 트랜잭션의 필요성
하나의 객체에 쓰기를 하는 트랜잭션을 생각해보면, 그 경우에도 중간에 장애가 나는 것을 생각하면 트랜잭션이 가지는 의미는 있다. 하지만 보통 트랜잭션은 다중 객체에 대한 다중 연산을 하나로 묶는 매커니즘으로 이해된다.
하지만 많은 분산 데이터스토어는 이를 포기했다. 구현하기가 어렵고 방해가 되는 시나리오도 있기 때문이다. 예를 들어 다중 put을 하더라도 일부 키에 대한 연산은 성공하고 일부는 실패하는 상황이 있을 수 있다.
하지만 구현할 수 없는 것은 아니다. 이후에 9장에서 다시 이에 대해 공부한다.
오류와 어보트 처리
트랜잭션의 핵심 기능은 오류가 생기면 어보트되고 안전하게 재시도할 수 있다는 것이다.
하지만 많은 시스템이 이를 따르지는 않는다. 리더 없는 복제를 사용하는 Dynamo 스타일의 데이터스토어는 Best Effort 원칙을 기반으로 한다. 즉 최선을 다하되, 안되면 오류 복구는 애플리케이션이 해야 한다.
책에서는 안전하게 재시도를 할 수 있게 하는 어보트의 취지를 많은 개발자들이 신경쓰지 않고 낙관적인 상황만 생각한다고 비판한다.
나로서는 생각해볼 수 없던 일이다.
데이터베이스에서 어떤 오류가 나면 그냥 오류가 났다는 에러 메시지만 수없이 봐왔기 떄문이기도 하고
재시도를 해도 고쳐질 수 없는 일이라거나 이메일 전송 등 어보트할 수 없는 일이 섞여 있는 등 재시도의 단점이 분명히 있고, 나도 그걸 많이 생각해왔기 때문이다.
재시도를 지원하는 프레임워크나 서비스 등이 있는지 찾아봐야겠다.