멀티 리전 쓰기: 커밋은 실제로 어디에서 확정되는가
라우팅은 요청을 리전으로 보낼 뿐입니다. 쓰기를 원자적으로 만드는 것은 트랜잭션 경계입니다. 불변 조건을 이름 붙이고, 소유자를 정한 뒤, 그 주위로 재시도를 설계하십시오.
두 리전의 애플리케이션 서버 두 대, VPN으로 쓰는 클라이언트 하나, 그리고 아무도 적어 두지 않은 질문: 쓰기가 일어났다고 결정하는 것은 어느 컴포넌트인가? 라우팅은 요청을 프로세스로 보냅니다. 트랜잭션 경계는 변경이 언제 내구성을 갖고 보이게 되는지를 정합니다. 이것은 서로 다른 시스템이 하는 서로 다른 일이며, 그 사이의 틈이 대부분의 멀티 리전 쓰기 버그가 사는 곳입니다.
지도가 아니라 비즈니스 규칙에서 시작하라
지리학이 아니라 비즈니스 규칙에 관한 문장으로 시작하십시오: 어떤 읽기와 쓰기가 함께 성공해야 하고, 그동안 어떤 동시 변경이 불가능해야 하는지. 재고 예약이라면 재고 확인과 예약이 둘 다 적용되거나 둘 다 적용되지 않아야 하며, 그 사이에 다른 주문이 같은 단위를 소비해서는 안 됩니다.
| Requirement | What satisfies it | What does not |
|---|---|---|
| All-or-nothing application | A transaction inside one owning system | A VPN path between regions |
| No double spend | Isolation strong enough to exclude the conflict | Atomicity alone |
| Survive a region loss | Replication the database itself documents | Two independent databases behind one client |
바로 그 가운데 행을 팀들이 건너뜁니다. 충분한 격리가 없는 원자성은 두 트랜잭션이 각각 올바르게 커밋되면서도 비즈니스가 불가능하다고 보는 상태를 만들어 내도록 허용하며, 나중에 어떤 재시도 로직으로도 고칠 수 없습니다.
하나의 권위 있는 경계가 두 지역 서버를 이긴다
지역 애플리케이션 서버는 그대로 있어도 되지만, 작업을 하나의 지원되는 트랜잭션 경계 안에서 수행하는 서비스에 제출해야 합니다. 애플리케이션 계층은 의도의 라우터가 되고, 데이터 계층이 진실을 소유합니다.
그 경계를 짧게 유지하고 그것이 만지는 데이터 가까이 두십시오. 사람이 프롬프트에 답하는 동안이나 호출이 두 리전을 가로지르는 동안 열린 채로 있는 트랜잭션은 잠금을 잡는 데 시간을 쓰고 그 뒤에서 다른 무언가가 시간 초과할 가능성을 높입니다. 그 실패는 데이터베이스 문제처럼 보이지만 실제로는 그렇게 분장한 설계 문제입니다. 결정을 데이터 계층으로 옮기면 보통 완전히 사라집니다.
BEGIN;
UPDATE inventory SET reserved = reserved + 1
WHERE sku = 'A-100' AND available >= 1;
INSERT INTO reservation (sku, order_id) VALUES ('A-100', 'ord-9f');
COMMIT;
그 스케치는 성능 권고가 아니라 경계로 읽으십시오. 요점은 확인과 삽입이 하나의 커밋을 공유하므로 동시 주문이 재고가 소비된 것을 보거나 전혀 보지 못한다는 것입니다.
복제된 데이터베이스가 주는 것과 주지 않는 것
복제된 데이터베이스는 자체 트랜잭션 모델이 지원할 때 더 넓은 경계를 제공할 수 있습니다. Spanner는 단일 데이터베이스 내에서 테이블 전반에 걸친 원자적 읽기-쓰기 트랜잭션을 정의된 격리와 재시도 동작과 함께 문서화합니다. 그것은 읽고 시험하고 의도적으로 의지하는 보장입니다.
그것은 또한 경로가 아니라 제품에서 오는 보장입니다. 리전 사이의 VPN은 패킷이 여행하는 방식과 실패가 드러나는 방식을 바꿉니다. 터널이 아무리 건강해 보여도 트랜잭션 경계를 한 행도 넓히지 않습니다. 이 둘을 혼동하면 모든 컴포넌트가 자기 검사를 통과하는데 데이터는 여전히 일치하지 않는 아키텍처가 생깁니다.
두 데이터베이스는 하나의 시스템이 되지 않는다
독립적으로 관리되는 두 데이터베이스는 클라이언트가 둘 다에 접근할 수 있다는 이유로 공유 트랜잭션 관리자를 얻지 않습니다. 비즈니스가 정말로 그 쌍에 걸친 원자적 확약을 필요로 한다면, 조정 메커니즘과 참가자, 복구 절차를 모두 가정하지 말고 선택하고 검증해야 합니다.
작업이 준비된 뒤 참가자나 링크가 사라지는 순간을 생각해 보십시오. 지원되는 분산 커밋은 복구 절차와 사람의 결정이 필요한 불확실한 상태를 남길 수 있으며, 모든 파티션을 거치면서 모든 리전이 쓰기 가능하게 남는다는 약속은 없습니다.
대신 비동기 조정이 허용된다면 그것을 명시하고 중간 비즈니스 상태와 조정 규칙을 적어 두십시오. 보상이 있는 로컬 커밋 단계의 연쇄는 정당한 설계이지만, 다른 이름을 가진 하나의 원자적 트랜잭션은 아닙니다.
첫 장애 전에 재시도를 설계하라
클라이언트는 작업이 커밋된 뒤 응답을 잃을 수 있으므로 호출자는 타임아웃을 실패로 다룰 수 없습니다. 내구성 있는 요청 식별자를 보존하고 변경을 재시도하기 전에 서비스 결과나 멱등성 메커니즘을 사용하지 않으면 재시도가 두 번째 쓰기가 됩니다.
자동 트랜잭션 콜백은 재시도 가능한 중단 후 한 번 이상 실행될 수 있어 되돌릴 수 없는 외부 효과의 집으로는 부적합합니다. 워크플로가 그들을 위한 명시적 안전 프로토콜을 정의하지 않는 한 콜백 밖에 두십시오. 돈이 들거나 메시지를 보내는 모든 것에도 같은 주의가 적용됩니다. 이중 청구는 이중 로그 줄보다 훨씬 어려운 대화입니다.
인수는 네 가지 시나리오를 재생해야 합니다: 같은 행을 두고 경쟁하는 두 클라이언트, 서비스가 재시도 가능하다고 말하는 중단, 응답이 결코 도착하지 않는 커밋, 그리고 리전이나 그 사이 링크의 상실입니다. 커밋된 비즈니스 결과와 해결되지 않은 상태를 라우팅 설계 옆에 기록하여, 누구도 네트워크를 보장을 제공하는 컴포넌트로 착각하지 않게 하십시오.