////
Search
🌄

12장. 전자 지갑

송금, 결제 기능을 지원하는 페이팔같은 전자 지갑형태의 플랫폼을 설계해본다.

1단계: 문제 이해 및 설계 범위 확정

요구사항

전자 지갑 간 이체
1,000,000 TPS
99.99%의 안정성
트랜잭션
재현성

개략적 추정

TPS 즉, 초당 트랜잭션 수가 언급됨은 트랜잭션 기반 데이터베이스를 사용한다는 뜻이다.
본 설계안의 데이터베이스는 대략 초당 1,000 TPS의 트랜잭션을 지원할 수 있다고 가정하면 1,000개의 데이터베이스 노드가 필요함을 알 수 있다.
유저 입장에서의 트랜잭션이 발생시키는 트랜잭션은 입금, 출금 2개이므로 대략 2,000개의 데이터베이스 노드가 필요하다.

2단계: 개략적 설계안 제시 및 동의 구하기

API 설계

POST /v1/wallet/balance_transfer

인메모리 샤딩

레디스를 사용해서 서비스를 구현한다고 가정했을때, 레디스 노드 한 대로 100만 TPS는 처리가 어려우므로 분산이 필요하다.
이 과정에서 파티셔닝, 샤딩할 때 키의 해시 값을 토대로 나누어 저장하는 것이 좋다.
모든 레디스 노드의 파티션 수 및 주소는 한군데 저장하므로 높은 가용성을 보장하는 설정 정보 전문저장소 주키퍼를 이 용도로 쓰면 좋다.

분산 트랜잭션

데이터베이스 샤딩
서로 다른 두 개 저장소 노드를 갱신하는 연산을 원자적으로 수행하려면 어떻게 해야 할까?
첫 번째는 각 레디스 노드를 트랜잭션을 지원하는 관계형 데이터베이스 노드로 교체하는 것이다.
이후 각각의 노드가 원자적 트랜잭션이 됨을 보장해야 한다.
분산 트랜잭션: 2단계 커밋
분산 트랜잭션의 구현법으로 저수준 방안과 고수준 방안이 있다.
저수준 방안에서는 데이터베이스 자체에 의존하는 방안으로 가장 일반적으로 사용되는 것이 2단계 커밋이다.
1.
최상위 서비스가 입금, 출금 서비스에 쓰기, 읽기 작업을 수행한다.
2.
애플리케이션이 트랜잭션을 커밋하려 할 때 최상위 서비스가 모든 데이터베이스에 트랜잭션 준비를 요청한다.
3.
두 번째 단계에서 조정자는 모든 데이터베이스의 응답을 받아 다음 절차를 수행한다.
a.
모든 데이터베이스가 예라고 응답하면 조정자는 모든 데이터베이스에 해당 트랜잭션 커밋을 요청한다.
b.
어느 한 데이터베이스라도 아니요를 응답하면 조정자는 모든 데이터베이스에 트랜잭션 중단을 요청한다.
분산 트랜잭션: TC/C
1.
조정자는 모든 데이터베이스에 트랜잭션에 필요한 자원 예약을 요청한다.
2.
조정자는 모든 데이터베이스로부터 회신을 받는다.
a.
모두 예라고 응답하면 모든 데이터베이스에 작업 확인을 요청하는데, 이것이 시도-확정(Try-Confirm) 절차다.
b.
어느 하나라도 아니요라고 응답하면 조정자는 모든 데이터베이스에 작업 취소를 요청하며 이것이 시도-취소(Try-Cancel) 절차다.
분산 트랜잭션: 사가
선형적 명령 수행
사가(Saga)는 유명한 분산 트랜잭션 솔루션 가운데 하나로 MSA에선 사실상 표준으로 사용된다.
1.
모든 연산은 순서대로 정렬된다. 각 연산은 자기 데이터베이스에 독립 트랜잭션으로 실행된다.
2.
연산은 첫 번째부터 마지막까지 순서대로 실행된다. 한 연산이 종료되면 다음 연산이 개시된다.
3.
연산이 실패하면 전체 프로세스는 실패한 연산부터 첫 번째 연산까지 역순으로 보상 트랜잭션을 통해 롤백된다.

이벤트 소싱

배경
전자 지갑 서비스가 감사를 받는 경우, 자료들을 제공하기 위해서 이벤트 소싱을 사용해볼 수 있다.
정의
명령
외부에서 전달된 의도가 명확한 요청이다.
이벤트 소싱에서 순서는 아주 중요하다. 따라서 명령은 일반적으로 FIFO 큐에 저장된다.
이벤트
명령은 의도가 명확하지만 사실은 아니기 때문에 유효하지 않을 수 있다.
따라서 유효성을 검사해야하는데, 이때 검사를 통과한 명령은 반드시 이행되어야한다. 이때의 명령 이행 결과를 이벤트라고 부른다.
상태
상태는 이벤트가 적용될 때 변경되는 내용이다.
상태 기계
이벤트 소싱 프로세스를 구동한다. 크게 두 가지 기능이 있다.
1.
명령의 유효성을 검사하고 이벤트를 생성한다.
2.
이벤트를 적용하여 상태를 갱신한다.

3단계: 상세 설계

고성능 이벤트 소싱

파일 기반의 명령 및 이벤트 목록
명령과 이벤트를 카프카와 같은 원격 저장소가 아닌 로컬 디스크에 저장하는 방안을 생각해 볼 수 있다.
파일 기반 상태
상태 정보도 로컬 디스크에 저장할 수 있다.

4단계: 마무리

첫 번째 설계안에서는 레디스 같은 인메모리 키-값 저장소를 사용하는 솔루션을 살펴보았다.
두 번째 설계에서는 인메모리 캐시를 트랜잭션 데이터베이스로 바꿨다. 여러 노드에 걸친 분산 트랜잭션을 지원하기 위한 2PC, TC/C, 사가와 같은 다양한 트랜잭션 프로토콜도 살펴보았다.
그 다음으로는 이벤트 소싱을 소개했는데, 첫 구현인 외부 데이터베이스와 큐의 경우, 그다지 성능이 좋지 않은 방안이므로 로컬 파일 시스템에 저장하여 개선해볼 수 있다.
그러나 이 경우 SPOF가 될 소지가 있으므로 시스템 안정성을 높이기 위한 래프트 합의 알고리즘 등을 도입하는 등, 복제 방안을 추가해 신뢰성을 확보해볼 수 있다.
이외에도 CQRS 개념을 도입해 트래픽을 분산해볼 수 있다.