•
송금, 결제 기능을 지원하는 페이팔같은 전자 지갑형태의 플랫폼을 설계해본다.
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 개념을 도입해 트래픽을 분산해볼 수 있다.