데이터 중심 애플리케이션 설계, 마틴 클레프만 지음. OREILLY.
= 데이터베이스 읽기, 쓰기 작업 N개를 atomic 연산으로 묶은 것.
DB에 접속하는 앱이 편-안 해지려고.
트랜잭션이 보장하는 것은 여러가지가 있다. 이를 줄임말로 표현한게 'ACID' 이다. Atomicity(원자성), Consistency(일관성), Isolation(독립성), Durability(지속성) 4가지를 일컫는 말이다.
위 3가지 속성은 데이터베이스가 가지는 속성이 맞다. 그런데 Consistency는 그렇지 않다.
이런 속성은 DB가 제어할 수 있는게 아니다. 데이터베이스로 쓰기 요청을 보내는 애플리케이션쪽에서 조건을 만족하는 값인지 확인을 하고 DB에 넘겨줘야 한다. 물론 일관성을 보장하기 위한 최소한의 장치는 데이터베이스에도 있다. 키에 대한 외래키 조건, 유일성 조건 등이 있다. 하지만 완벽한 일관성을 보장하는 주체는 데이터베이스가 아닌 애플리케이션이다.
책에는 이런 구절과 함께, ACID가 결국 마케팅 용어로 쓰였다는 내용도 나온다.
따라서 C는 실제로 ACID에 속하지 않는다.
각주) 조 헬러스테인은 하더와 로이터의 논문에서 ACID의 C는 "약어를 만들기 위해 끼어들"었고 당시에는 일관성이 중요하게 생각되지 않았다고 말했다.
여기서 말하는 객체는 테이블, 도큐먼트, 레코드 등을 말한다. 단일 객체 연산은 말 그대로 객체 1개에 대한 연산을, 다중 객체 연산은 객체 여러개에 대한 연산을 말한다. 예를 들어 SNS에서 회원가입 처리를 할 때 회원 테이블 하나만 업데이트한다면 단일 객체 연산을 한 것이고, 다른 테이블도 같이 업데이트를 한다면 다중 객체 연산을 한 것이다.
트랜잭션이 ACID를 보장하기 위해 어느정도로 노력하는지를 나타내는 대표적인 단어다. 아주 강한 정도로(그리고 이게 원래는 기본값이었을 것이다.) ACID를 보장하는 데이터베이스라면 보통 잘못된 트랜잭션 연산을 허용하지 않는다. 즉, abort한다. 반면 어느정도 잘못된 연산을 허용하는 경우를 'best effort를 한다'라고 표현한다.
데드락을 예로 들어보자. 데드락 자체가 절대 발생하지 않게 막는 데이터베이스는 문제의 원인이 되는 트랜잭션을 abort 시킬 것이다. 반면 데드락 발생을 막기 위해 best effort를 하는 데이터베이스는 데드락의 발생 자체를 막지는 않는다. 다만 데드락 발생을 감지해서 그 상황을 빠져나오는 방법을 제공할 것이다.
둘 이상의 입력 또는 조작의 타이밍이나 순서 등이 결과값에 영향을 줄 수 있는 상태.
경쟁 조건이라는 단어 자체는 컴퓨터 공학에서 광범위하게 쓰인다. 이 단어를 볼 수 있는 가장 대표적인 분야로 운영체제가 있다. DB에서 말하는 경쟁 조건은 트랜잭션이 원인이 된다. 클라이언트가 DB에서 할 수 있는 연산은 크개 2가지로 나뉜다. 읽기와 쓰기. 그리고 이 연산이 복잡하게 얽혀서 다양한 경쟁 조건을 만들어낸다. 책에서 ACID에 대해 살펴본 다음에는 이 경쟁 조건을 꽤 자세히 다룬다.
책에 나온 경쟁 조건을 나열해보자면...
...이정도가 된다. 이 책의 좋은 점은 각각의 경우에 대한 정의를 깔끔하게 적어놓았고, 어떤 기법을 써서 해결할 수 있으며 그 해결책의 장단점 및 부작용까지 모두 나와있다는 점이다. 가끔 기술 서적을 읽다보면 필요한 곳에서 용어의 정의를 제대로 설명해주지 않아 직접 구글링을 해야 하는 경우가 생겼는데, 이 책은 그런게 없어서 좋았다.
각 트랜잭션이 서로의 작업에 침범할 수 없도록 격리하는 방법도 나온다. 많이 나온다. 너무 많이 나와서 조금 정신이 없다. 각 방법이 어떤 경쟁 조건을 해결할 수 있는지도 나온다. 이 책을 처음 읽는 지금으로서는 우선은 각각의 방식을 이해하고 넘어가는걸 목표로 했다. (미래의 내가 각 방법과 경쟁 조건을 연결하는 작업도 해줄거라고 믿는다.😇)
갱신 손실은 꽤 흔한 문제다. 해결하기 위한 방법도 다른 경쟁 조건에 비해 비교적 다양하다. 2가지 방식이 있다. 갱신 손실 자체를 막는 방식과 갱신 손실을 허용하고 나중에 어떻게든 잘 수습하는 방식이다.
직렬성 격리는 경쟁 조건을 푸는 가장 강력한 방법이다. 그럴 수밖에 없다. 경쟁 조건이 만들어질 수 있는 환경을 아예 원천봉쇄해버리기 때문이다.
그러니까 멀티스레딩 기법을 쓰지 않고 진짜로 정확히 '동시에' 실행되는 스레드가 없도록 하는 것이다. 데이터베이스 연산은 그 어떤 순간에도 단 하나만 실행된다. 이렇게 간단하고 강력한 방법을 왜 모든 데이터베이스에서 쓰지 않는 걸까? 정답은 확장성 때문이다. 확장성을 포기할 수 없는 애플리케이션이라면 이 방식으로는 절대 충분하지 않다.
이 단어도 운영체제에서 본 적 있는 친구다. 트랜잭션은 락(lock)을 소유할 수 있으며, 락의 종류는 shared, exclusive 모드 2가지가 있다. 그래서 two-phase locking이라고 부른다. 이 락을 구현하는 방식도 다양하다. 책에서는 predicate, index-range, next-key locking에 대해 설명한다.
비교적 최근에 나온, 아주 유망한 방법이라고 한다.
'낙관적 방법'을 사용해서 트랜잭션이 차단되지 않고 진행할 수 있게 한다. 트랜잭션이 커밋을 원할 때 트랜잭션을 확인해서 실행이 직렬적이지 않다면 어보트시킨다.
다음 장에서는 문제가 좀 더 심각해진다. 트랜잭션과 관련된 데이터베이스 자체 결함에서 벗어나서 네트워크와 시계를 의심한다. 모든게 잘못 될 수 있다는 가정을 만드려는 것 같다. 무서운건 모두 현실에서 생길 수 있는 시나리오라는 점이다. 😇