Skip to content
isdnetworks
Go back

두 DB를 한 트랜잭션으로 묶고 싶을 때

두 시스템에 데이터를 동시에 넣어야 했는데 각각 다른 DB 다.

A에 기록 → B에 기록

A 는 됐는데 B 가 실패하면 어떻게 되나.

Table of contents

Open Table of contents

트랜잭션이 안 걸렸다

한 DB 안이면 COMMITROLLBACK 으로 묶어서 둘 다 실패하거나 둘 다 성공하게 만든다. DB 가 둘이면 그것이 안 된다.

묶어 주는 방식이 아예 없는 것은 아니다. XA 가 그 자리에 있고 매뉴얼도 여러 트랜잭션 자원이 하나의 전역 트랜잭션에 참여하게 해 준다고 적는다.

다만 준비할 것이 많다. 먼저 모든 갈래에 PREPARE 를 걸어 커밋할 수 있는지 묻는다. 그다음에 COMMIT 이나 ROLLBACK 을 보내는 두 단계로 돌아간다. 그 순서를 지휘하는 트랜잭션 매니저가 따로 있어야 한다. 양쪽 DB 가 다 지원해야 하는데 이 규모에는 과했다.

순서를 정했다

먼저 어느 쪽이 먼저인가를 정했다. 기준은 되돌리기 쉬운 쪽을 나중에 두는 것이었다.

1. B에 기록 (되돌리기 어려움 — 외부 시스템)
2. A에 기록 (되돌리기 쉬움 — 우리 DB)

이러면 2번이 실패했을 때 1번을 취소하는 것보다 1번이 실패하면 2번을 안 하는 것이 쉽다. 순서 하나로 나쁜 경우의 무게가 달라진다.

대기 테이블을 만들었다

순서를 정해도 1번은 성공하고 2번이 실패하는 경우가 남는다. 이때 선택지가 몇 가지였다.

보상 처리      1번을 취소하는 요청을 보낸다 — 그것도 실패할 수 있다
재시도         2번을 다시 시도한다 — 언젠가 되면 정합이 맞는다
기록하고 사람이  불일치를 남기고 사람이 처리한다

셋을 조합했다. 2번이 실패하면 대기 테이블에 넣었다.

pending_sync
  ref_id
  payload
  attempts
  last_error
  created_at

배치가 주기적으로 pending_sync 를 처리해서 성공하면 지우고 몇 번 실패하면 상태를 바꾸고 알린다.

즉시 정합은 포기하고 최종 정합으로 갔고 몇 초에서 몇 분 뒤에 맞는다. 이것이 이 업무에 허용됐다. 실시간 정합이 필요한 것이었으면 다른 설계를 해야 했다.

검증 — 중복 방지와 불일치 집계

재시도하면 같은 요청이 두 번 갈 수 있다. 2번이 실제로는 성공했는데 응답이 유실돼서 실패로 보인 경우다.

{ "request_id": "a3f2c1...", "data": {...} }

요청에 request_id 를 넣어 같은 키를 이미 처리했으면 무시하게 했다. 받는 쪽이 그것을 지원해야 한다. 안 하면 우리가 확인 조회를 먼저 해야 하는데 그것도 완벽하지는 않다.

이 구조는 일시적 불일치를 전제하므로 그것이 얼마나 되는지 알아야 한다. 야간 배치로 양쪽을 대조했다.

A에만 있음: N건
B에만 있음: M건

평소 수치를 알아 두니 갑자기 늘면 신호가 된다. 그리고 pending_sync 에 오래 남아 있는 건이 있는지 봤는데 며칠째 재시도 중인 건은 자동으로 안 풀리는 것이다.

순서를 바꿀 수 없는 경우

한 가지는 순서를 못 정했다. 상대가 우리 쪽 ID 를 요구하는데 그 ID 는 우리가 기록해야 생긴다.

A에 기록 → ID 생성 → B에 그 ID로 기록

A 가 먼저일 수밖에 없다. 이때는 A 에 임시 상태로 넣고 B 가 성공하면 확정으로 바꿨다.

A: status = 'pending'
B: 기록 성공
A: status = 'confirmed'

미확정 상태가 조회에서 빠지도록 했다. 그러면 B 가 실패해도 사용자에게는 안 보이고 중간에 실패한 것만 골라내면 된다.

정리


Share this post on:

Previous Post
로그를 어디서부터 잘라야 하는지
Next Post
사양서를 안 보고 코드부터 짰다