본문 바로가기

Dot Programming/MSA

실전 MSA 설계: 데이터 중심 기업별 전략 분석

    쿠팡 Core Serving Platform, 높은 처리량 낮은 지연으로 Realtime 데이터 서빙하기

    e-Commerce 데이터 비즈니스 로직은 다양하고 복잡하다. 상품을 구입해서 직배송하는 형태로 로켓 배송, 프레시 등 다양한 비즈니스 존재한다. 그리고 이러한 다양한 데이터를 MSA 아키텍처 형태로 관리하고 있어서 각 서비스 별로 데이터 성격이 다르다.

    • Catalog: 이미지, 타이틀
    • Pricing & Benefit: 가격
    • Stock & Fullfillment: 도착 보장 
    • Review: 리뷰
    • Benefit: 적립, 혜택

    e-Commerce Data

     

    각각의 데이터는 개인화되어있으며 실시간 변화하고 있다. 만약 상품을 보관하는 특정 HUB에 코로나 확진자가 발생한다면 해당 HUB을 닫고 해당 HUB에 있는 모든 상품의 재고 정보와 도착 보장 메시지가 실시간 변경되어야 한다. 이러한 다양한 데이터를 각 고객 페이지에 호출하기 위해서는 각 백엔드 시스템이 고가용성이어야 하고 공통 로직들은 중복으로 존재해있다. 

     

    common logic

     

    Core Serving Platform

    모든 페이지에서 공통으로 사용되어야 하는 데이터와 비즈니스 데이터를 처리하는 마이크로서비스가 필요함을 느꼈고, 해당 단점을 커버하기 위해서 복잡성을 줄이고 고가용성과 낮은 지연으로 데이터를 서빙하는 코어 서빙 플랫폼을 만들었다.

    1. Data Read Layer

    • 고가용성(HA)을 제공
    • 낮은 지연과 높은 처리량 추구
    • 데이터 최신성 보장

    2. Business Logic Layer

    • 모든 도메인에 공통 비즈니스 로직 제공(중복 제거)
    • 프론트엔드 복잡성 감소

     

    Core Serving Layer

    모든 페이지에서 호출할 수 있게끔 코어 서빙은 다음과 같이 모든 마이크로 서비스의 앞에 facade 형태로 위치해 있다.

    • 해당 서비스는 모든 페이지에 필요한 데이터와 비즈니스 로직을 서빙한다.
    • 코어 서빙 레이어가 각각의 도메인을 조회하기 보다는 각각의 도메인이 데이터가 변경될 때마다 큐에 보내고 해당 데이터를 NoSQL 스토리지에 저장한다.
    • 그래서 하나의 상품 정보가 있다면 각각의 필드가 다른 도메인 서비스에 의해 비정규화된 형태로 채워진다. (Catalog팀은 image, title, Pricing 팀은 가격 정보 등) 이는 최종적 일관성(Eventual Consistency)을 추구하는 방향으로, 각 도메인 DB(Catalog DB, Pricing DB 등)에서 모든 요청이 NoSQL에 도달해야 하나의 레코드가 완성된다.
      • 그런데 어떻게 최종 일관성을 보장하고있을까? 만약 materialized view를 만드는 데 하나의 서비스에서 장애 발생으로 데이터가 도달하지 않는다면? 도메인에서 커밋했는데 롤백된다면 전파 타이밍을 어떻게 고려해야 할까?
        • 각 서비스 고가용성 유지 및 신뢰있는 네트워크 프로토콜 구축 필요
    • 이렇게 중복된 데이터를 NoSQL 스토리지에 다시 비정규화한 형태 저장하여 얻을수 있는 이득은 읽기 처리량이다. 

    Core Serving Layer

     

    Cache Layer 

    이렇게 하나의 비정규화된 데이터를 통하여 여러 도메인 서비스들로 I/O 처리량을 많이 줄일 수 있고 또 Common Storage로 높은 처리량을 낼 수 있었지만 더 많은 읽기 요청 트래픽을 처리하기 위해서 Cache Layer가 필요했다. Common Storage는 데이터를 영구적으로 보관하는 공간이라면 Cache Layer는 이러한 걱정은 하지 않고 빠른 속도로 높은 처리량을 핸들링할 수 있다. 

    • Cache Layer 도입으로 인해 10배 높은 처리량과 1/3 더 작은 레이턴시로 데이터를 제공할 수 있다. 
    • 주의해야 할 점은 Storage와의 일관성 문제이다. Storage에는 반영되었는데 Cache에 반영되지 않는 경우를 조심해야 한다. 이러한 문제를 해결하기 위해 Cache Invalidator가 존재한다. 데이터가 변경될 때마다 해당 데이터들은 Notification Queue에 보내서 Cache에 있는 데이터를 최신 데이터로 변경한다. 

     

    Core Serving Cache Layer

     

    이러한 메커니즘을 통해 99.9% 데이터는 최신성을 보장한다. 그런데 이는 분 단위까지만 보장을 해준다. 가격, 배송 장소, 재고와 같이 초 단위 변경 데이터는 다른 방식이 필요하다.

     

    실시간 데이터 스트리밍 (초단위 최신성 보장하기)

    e-Commerce에서는 가격 할인 정보, 재고와 같이 중요한 데이터들이 실시간으로 변한다. 할인 정보가 바뀐 뒤에 이를 고객에게 판매가 된다면 유저 경험은 안좋아지고 플랫폼 신뢰성이 떨어지게 될 것이다. 로켓 배송 상품은 실제로 배송하기 때문에 각 지역별 재고와 배송 처리 능력에 따라 상품 배송 시간을 바꿔야 할 수도 있고 품절을 시켜야 할 수도 있다. 이러한 이유로 Realtime Data Streaming을 도입하였다. 

    1. Realtime Data Streaming은 다른 마이크로 서비스에서 변경된 데이터를 큐에서 읽고 별도의 캐시(Realtime Cache)에 바로 쓴다.  
    2. Common Serving Layer는 Cache와 Realtime Cache를 동시에 읽고 보다 최신 데이터가 존재하는 경우 해당 데이터를 제공할 수 있게끔 설계하였다. 

    Realtime Cache Serving

     

    고가용성 시스템 구축하기

    고가용성이란 어떠한 상황에서도 고객 경험을 망치는 장애를 최소화함을 뜻한다. 만약 Realtime Cache에 장애가 나는 경우 고객이 보는 페이지가 다운되는 것보다는 최신 데이터를 서빙하지 않는 것이 고객 경험을 덜 망치는 방법이다. 이러한 목표를 달성하기 위해 Core Serving Layer는 모든 I/O가 일어나는 곳에 장애 발생시 해당 장애를 격리시켜 전파되지 않도록 해야 한다. 

     

    CSP(Critical Serving Pass)

    쿠팡에는 많은 페이지가 존재한다. 그 중 홈, 검색, 주문 등 고객 경험에 중대한 영향을 미치는 페이지들이 존재한다. 그래서 해당 페이지들만을 위한 클러스터가 존재하는데 이를 CSP 클러스터라고 한다. 그 외 페이지는 N-CSP라고 부른다. 

    • 이렇게 클러스터를 분리함으로써 장애 전파를 방지한다. 서로에게 영향을 주지 않는다. 
    • N-CSP에서 장애가 발생하더라도 CSP에 영향을 미치지 않는다. 
    • CSP에 문제가 생기면 배포없이 동적으로 CSP에 해당하는 모든 페이지가 N-CSP를 바라보게 할 수있다.

    CSP, N-CSP

     

     

    위와 같은 설계로 실제로 95% 트래픽을 캐시에서 처리하고 있다. 그래서 캐시 클러스터의 가용성 문제가 전체 시스템 문제와 크게 직결되어 있다. 쿠팡은 클라우드 환경을 사용하고 있는데, 캐시 클러스터의 크기가 크다보니 개별 노드가 다운되는 상황을 자주 접한다. 가끔은 underlying host(클라우드 서버) 문제로 여러 노드가 동시에 다운되는 현상도 발생한다. 이러한 다운 현상은 1분에서 길게는 10분 동안 다운되었다. 

     

    Issue 1. 부분 노드 장애 처리하기

    그래서 해결책이 필요했다. 일부 노드 다운으로 인한 문제는 전체 트래픽 중 일부분이므로 서킷 브레이커로는 해결할 수는 없다. 캐시 클러스터가 빠르게 실패 노드를 찾아서 클러스터 토폴로지를 변경해주지만 이러한 복구 작업에도 시간이 소요되고 해당 장애 기간동안 요청들은 타임아웃이나 내부 큐에 쌓이게 되어서 앱은 불안정한 상태가 된다. 클러스터 토폴로지 변경이 실제 사용하는 tcp connection과 일치하지 않아서 다운된 노드로 계속 트래픽이 들어가는 상황도 발생했다. 

     

    Sovle 1. 요청 타임아웃 기간을 짧게하여 빠른 노드 장애 탐지하기

    그래서 다운된 노드를 빠르게 배재하는 방법을 선택하였다. 캐시 클러스터가 제공하는 토폴로지 리프레시 기능 뿐만 아니라 tcp connection들이 응답이 빠르게 오는지 감시했다. 실제로 캐시 응답은 평균 수 ms 정도여서 1초가 넘게 걸리는 노드는 장애로 판단하여 배재하였다. 이를 통해 장애 노드 감지 속도가 빨라졌고 안정성도 높아졌다. 

     

    요청 타임아웃 기간을 짧게하여 빠른 노드 장애 탐지하기

     

    Issue 2. 클러스터 복구 실패 문제 해결하기

    높은 트래픽은 다운된 노드가 캐시 클러스터로 다시 회복하는 과정에도 문제를 일으켰다. 다운된 노드가 복구되어 다시 클러스터로 들어오면 Full-sync 과정을 거쳐서 클러스터로 들어오게 된다. 이때 클러스터의 마스터 노드는 Full-sync 과정중 write 명령어들을 버퍼에 저장하는데 캐시에 들어오는 높은 트래픽은 버퍼보다 더 높은 write 명령어들을 발생시켜 이러한 복구 과정이 계속 실패하게 되었다.

     

    Solve2. replica의 복제 지연으로 인한 중복 쓰기 작업 문제 해결

    단기적인 해결책으로는 Cache Invalidation을 중지하는 기능을 만들어서 5~10분동안 복구 과정을 처리하거나 클러스터를 새로 만들어 교체하여야 했다. 그러나 이러한 해결책은 운영 부담만 늘어났고 근본적인 해결책이 필요하였다. 

    • Full-sync 과정동안 write 명령어의 사이즈를 예측하여 적절한 버퍼 사이즈를 찾으려고 했다. 그러나 이 문제는 쉽게 해결되지 않았다. 
    • 매번 Full-sync 과정에서 매번 늘 예상했던거 보다 많은 write 명령어들이 발생한다는 것을 알아냈다. 예상보다 많았던 이유는 Full-sync 과정에서 새로운 replica 들이 empty data set으로 데이터를 서빙하고 있었다. 이러한 과정으로 인해 캐시 클러스터에 데이터가 없다고 인식시켜 write 명령어들이 급격하게 늘어난 것이다. 
      • 정리하면 master 쓰기 후 replica로 복제가 일어나는데 복제 되기 전에 replica에서 읽기 요청해서 이미 쓴 데이터도 또 쓰게 된 것이다. 흔히 master-replica에서 발생하는 복제 지연으로 인한 읽기 일관성 문제이다.
    • replica 데이터 사이즈나 상태 정보를 이용하여 정상적으로 서빙할 수 있는 replica가 아니면 트래픽을 차단해서 문제를 해결하였다

     

    replica의 복제 지연으로 인한 중복 쓰기 작업 문제 해결

     

    Issue 3. 캐시 클러스터 노드 별 트래픽 불균형 문제

    각 노드별로 받는 트래픽 불균형 문제가 발생하였다. 크게는 5~10배까지 차이가 났다. 여기서 문제는 캐시 클러스터의 크기를 정하려고 할 때였다. 가장 큰 트래픽을 받는 클러스터를 기준으로 할 수 밖에 업었고 그러다보니 불필요한 리소스 낭비가 심해졌다. 이러한 불균형의 원인은 캐시 클라이언트에 있었다. 

     

    Solve 3. 요청 당 랜덤으로 노드 연결하기

    • 기존 캐시 클라이언트는 최초 커넥션을 연결할 때 가장 빠른 응답을 주는 노드랑 통신하도록 설계되어있었다. 
    • 이 부분은 커넥션 모드를 조정하여 최초 커넥션시 연결되는 고정되는 노드가 아닌 요청시 마다 랜덤으로 노드를 선택하도록 하여 문제를 해결하였다.  (핫 키가 존재할 수 밖에 없어서 해당 문제를 해소하기 위해 커넥션당 노드 연결이 아니라 요청당 노드 연결로 바꾸고 랜덤으로 연결하였다고 하는데 그러면 해시는 아닐 것이고 랜덤 함수를 사용한 듯 하다) 
    • 개인적으로 여기서 궁금한 것은 데이터를 어떠한 전략을 취했는가이다. 모든 데이터를 일관되게 모든 노드가 다 저장하고 있는지 아니면 해당 키에 대한 정보를 가지고 있는 노드를 선별 후에 랜덤으로 돌리는지 

    요청 당 랜덤으로 노드 연결하기

     

    토스 뱅크 , 거래 내역 무한 스크롤 (신뢰성있는 프로토콜 구축하기)

    여기서 봐야할 것은 계정계를 단일 서버로 구축한 이유 (분산 트랜잭션의 어려움), Kafka 실시간 이벤트 처리, 채널계와 계정계의 동기화를 위한 신뢰도있는 프로토콜 형성 과정이다. 일반적으로 은행 시스템은 채널계와 계정계로 나누어진다. 

    • 채널계: 유저의 요청을 직접 받아 처리하는 영역. 돈을 직접 다루지 않고 계정계로 전달.
    • 계정계: 실제로 유저 돈을 다루며 원본 데이터가 저장되는 영역. 장애나 오류에 치명적이기 때문에 높은 신뢰도가 요구됨.

    토스뱅크 시스템 구조

     

    토스 뱅크 또한 다음과 같이 설계되어있다.

    • 채널계는 복수 개의 앱, DB 서버로 이루어져 있으며 Kubernetes 클러스터로 운영한다. DB, 네트워크 구조가 복잡하여 트랜잭션 처리가 어려운 경우가 있다. 하지만 유연한 스케일 아웃이 가능한 구조로 높은 트래픽에 대한 부하 분산 처리에 능숙하다는 장점이 있다. 
    • 계정계는 하나의 앱, DB 서버로 구축되어있다. 모놀리틱한 구조이기 때문에 트랜잭션 처리에 유리하다. 그러나 유연한 확장이 불가능하여 급증하는 트래픽을 핸들링하기 까다롭다. 계정계는 오류없이 높은 신뢰도를 요구하기 때문에 성능이나 확장성을 희생하더라도 이러한 구조를 택하였다. 

     

    유저 거래내역은 계정계에서 관리한다. 일반 뱅크 시스템에서 계정계는 무한 스크롤링과 같은 성능을 요하는 기능을 희생하고 거래내역의 정확도에 초점을 맞춘다. 그래서 UI에서 디폴트 조회범위를 정해놓고 있는 것이다. 그런데 토스 뱅크 앱은 무한 스크롤 기능이 있다. 방법은 채널계에 있는 송금 서버에 계정계의 데이터 복사본을 가지고 있어서 거래내역 조회에 대한 유저 요청을 채널계에서 처리하도록 하였다. 그러면 채널계와 계정계의 거래내역 데이터가 계속해서 동기화가 되어야 한다. 이를 구현하기 위해서 토스는 Kafka와 높은 신뢰도를 요구하는 프로토콜을 구축하였다.

     

    예외 상황 1) 송금 실행 도중 코어 뱅킹 서버 타임아웃 발생하는 경우

    [유저 이체 실행 > 송금 DB 저장  > 코어뱅킹 DB 저장]의 구조는 토스 앱이 아닌 타행 서버 거래 내역은 계정계로 전송되기 때문에 문제가 발생한다. 만약 송금 실행을 하는 데 코어 뱅킹 서버에서 제대로 요청을 처리하지 못하는 경우에 대한 대비가 필요하다. 그래서 코어뱅킹 서버가 Kafka 토픽에 메시지를 프로듀싱하고 송금 서버가 컨슘하여 송금 DB에 저장한다. 

    1. 우선 Kafka를 통해 이벤트 전송으로 해결할 수 있다. 송금 API 실행중 타임아웃이 발생하더라도 송금이 완료되었을 때 코어뱅킹 서버가 Kafka 토픽을 통해 송금 서버에게 이체 실행결과 알려준다.
    2. Kafka로 해결되었지만 이미 유저는 에러 메시지를 보고 재송금하는 경우가 있다. 이러한 문제를 막기 위해 송금 서버는 송금 상태(요청, 완료, 실패 등)를 저장한다. 그래서 만약 완료되지 않은 송금 상태가 있는 경우 그 이후에 재송금하는 요청은 거절한다. 

    송금 실행 도중 코어 뱅킹 서버 타임아웃 발생하는 경우

     

    예외 상황 2) 송금 실행 도중 코어 뱅킹 서버 영원히 타임아웃 발생하는 경우

    Kafka가 처리할 수도 없게 코어뱅킹에 도달하지 못한 경우도 있다. 만약 송금 요청이 코어뱅킹 서버에 영원히 도달하지 않는다면 유저는 예외1)의 2번 케이스에서 말한 완료되지 않는 송금 상태가 있어서 영원히 송금할 수 없게 된다.

    1. 이러한 케이스를 방지하기 위해서 송금 서버는 코어뱅킹 서버에게 주기적으로 상태를 조회한다. 해당 케이스인 경우 코어뱅킹 서버에 도달하지도 못했으므로 송금 실패 처리를 한다.
    2. 그런데 만약 지연되는 요청으로 송금 DB에는 송금 실패 처리를 하였는데 뒤늦게 송금 요청이 코어뱅킹 서버에 전달되는 경우가 발생하게 되는데 그러면 코어뱅킹 서버에서는 트랜잭션이 실행될 수 있다. 그래서 이러한 경우에는 타임아웃 설정으로 코어뱅킹 서버에서 요청을 거절한다.
      1. 송금 서버 → 코어 뱅킹 서버에게 송금 요청 완료 요청
      2. 송금 서버 타임 아웃 시간(ex. 2,000ms)이 지나 송금 실패 처리함
      3. 코어 뱅킹 서버에서 뒤늦게 전송됨. 하지만 해당 송금 요청 시간 3초 전에 발생한 건이므로 코어 뱅킹 서버에서도 거절함

    송금 실행 도중 코어 뱅킹 서버 영원히 타임아웃 발생하는 경우

     

    예외 상황 3) 송금 DB 장애가 발생하는 경우

    앞서 Kafka를 통해서 송금 완료 여부를 확인하여 송금 DB에 송금 이력을 저장하는 것을 알아봤다. 그런데 송금 DB에서 장애가 발생할 수도 있다.

    1. 이러한 경우 Kafka로 다시 컨슘해서 재시도하여 저장한다.
    2. 계속해서 실패하는 경우 Consumer DeadLetter라는 토픽에 해당 메시지를 저장하고 개발자가 직접 원인을 파악하여 문제를 해소한다. 

    송금 DB 장애가 발생하는 경우

     

    예외 상황 4) Kafka 동기화 실패하는 경우 (데이터 최신성 보장하기)

    이렇게 계속 Kafka를 통해 송금 서버에 동기화하는데 송금 이력 데이터가 누락되는 경우가 발생할 수도 있다. 1번 이벤트가 '500원 입금', 2번 이벤트가 '100원 출금'이라고 해보자. 두 이벤트가 모두 동기화된 뒤 토스 앱이 거래 내역을 요청하면 정상적으로 조회된다. 

    1. 만약 1번 이벤트가 실패가 발생하여도 재시도를 하여 저장하면 되므로 문제가 없다. 만약 뒤늦게 발생한 2번 이벤트가 먼저 동기화되었더라도 데이터를 저장할 때 순서를 보장하게끔 한다. (모든 거래내역에는 거래 순서대로 증가하는 일련번호가 붙어있어서 순서 보장이 가능하다.)
    2. 그런데 1번 이벤트가 실패하고 2번 이벤트가 성공하였는데 1번 이벤트 동기화 전에 유저가 거래내역을 조회하는 경우에 문제가 발생한다. (-100원)
      1. 이 문제를 해결하기 위해 Kafka에서 송금 완료 메시지를 받았을 때 해당 이벤트만 동기화하는 것이 아니라 그 이전 다른 거래가 있는지 코어 뱅킹 서버에 직접 조회해서 동기화 한다. 그래서 2번 이벤트를 동기화할 때 1번 이벤트 누락을 확인하고 해당 이벤트를 먼저 동기화 한다. 
      2. 그런데 1번 이벤트가 계속 실패한다면 2번 이벤트 또한 동기화하지 않는다. 
    3. 그런데 유저가 모든 이벤트가 동기화되기 전에 직접 조회를 하는 경우가 있다. 그러면 500원 입금, 100원 출금 내역 둘 다 보이지 않게 된다. 
      1. 송금 DB에 진행중인 거래 내역을 저장한다. 유저가 계좌 내역을 조회할 때 아직 진행중인 거래가 있다면 그 즉시 게정계와 거래내역을 동기화한다. 

    Kafka 동기화 실패하는 경우 (데이터 최신성 보장하기)

     

    예외 상황 5) Kafka 동기화 지연되는 경우  (100만 트래픽 핸들링하기)

    대기업이 100만이 넘는 고객에게 이벤트로 100원씩 입금하여 한 번에 동기화해야 하는 일이 발생할 수도 있다. 그러면 100만 명이 토스 고객에게 한 시간에 걸쳐서 입금이 완료된다면 1초에 300건 정도 입금이 실행이 될 것이다. Kafka를 통해 송금 서버에게 입금을 알리면 송금 서버는 순서대로 하나씩 동기화하여 1초에 10건씩 처리(1시간 36,000건)한다. 그러면 1초당 남은 290건들은 점점 밀리게 된다. 한시간이 지나면 거의 백만건이 밀려있어 이를 다 처리하려면 대략 28시간 정도 소요된다. 해당 시간에 다른 일반 고객이 입출금을 하여도 동기화가 지연되어 거래 결과가 안보이는 문제가 발생한다.

    1. Kafka는 다행히도 단일 스레드 구조가 아니다. 여러 파티션으로 나눠 여러 컨슈머가 동시에 요청을 처리한다.
    2. 파티션을 한 번 늘리면 줄일 수 없어서 지속적으로 리소스를 낭비하게 될 수도 있다. 따라서 파티션의 개수는 적당히 확장하고 컨슈머별로 워커 스레드를 충분히 할당한다.
    3. 또한 각 워커 스레드가 동일한 계좌를 처리하는 동시성 문제를 방지하기 위해서 유저가 지정한 키(계좌)를 기준으로 파티션을 나누어 준다. 때문에 하나의 계좌는 하나의 컨슈머에서만 처리되어 동시성 문제를 방지할 수 있다. 

    그래서 해당 문제의 경우 파티션 30개 정도면 충분히 1초 300건의 요청을 처리할 수 있게 된다. 이러한 모든 방어에도 불구하고 예상치 못한 문제로 인해 유저가 동기화된 안된 상황을 맞이할 수도 있어서 직접 동기화할 수 있는 버튼을 앱에 제공하고 있다. 

     

     

    정리

    우선 쿠팡의 대규모 트래픽을 다루는 백엔드 전략은 쿠팡의 전체적인 MSA 아키텍쳐 개요에 대해 알아볼 수 있었다.

    1. MSA 아키텍처 전체적인 구조와 facade 전략을 통해 리소스를 늘려서 분산된 시스템의 복잡성을 해소하고 성능을 챙기는 전략을 배우게 되었다
    2. 장애 전파 방지 및 중요한 도메인의 고가용성을 위한 전략인 CSP, N-CSP에 대한 개념에 대해 학습할 수 있었다
    3. 실시간 데이터를 제공하기 위해서 2개의 캐시를 두고 하는 방식이 좀 신기했다
      1. 이는 최종적 일관성을 보장하는 형태라서 최신성을 그나마 빠르게 보장하기 위한 전략 때문인데 하나의 document가 (A 도메인 데이터, B 도메인 데이터, C 도메인 데이터)이렇게 합쳐져 있음.
      2. 여기서 B 서비스 데이터가 업데이트가 된다면 B 도메인 DB에 데이터가 써지고 큐를 통해 Common Storage에 업데이트 됨. 그리고 나서 또 큐 이벤트를 통해 일반 Cache 쓰여짐. 결국 클라는 Cache로 읽음 (Look-aside 전략) 
      3. 그런데 2번 방식은 분 단위 실시간성만 보장함. 그래서 초 단위를 보장하기 위해 Realtime Cache를 사용함. 이는 Common Storage와 동시에 같이 쓰여짐. 
      4. 여기서 개인적인 생각은 결국 각 도메인에 쓰여지고 이를 다시 이벤트를 통해 Common Storage든 Realtime Cache든 저장되는데 해당 기간의 오차는 어떻게 핸들링하는지 궁금하다. 그런데 또 든 생각이 돈 거래가 발생하는 것이 아니라면 이 정도로도 충분할 수 있겠다라고 생각했지만 재고의 오차는 돈 거래가 발생하게 한다. 재고 선택 후 돈 거래 시점에 다시 한 번 체크하면 괜찮을 수도 있겠다. (저번에 상품 선택 창에서 2개 선택하였는데 주문 리스트에서 최종 주문 시점에 1개만 가능하다고 경험한 적이 있다.)
    4. 그리고 고가용성을 하기 위해 캐시 클러스터의 여러 장애 케이스와 해결 사례를 접할 수 있어서 좋았다. 그런데 또 궁금한 것은 위에 글에서도 말했지만 커넥션 당 노드 연결을 트래픽 분산을 위해 요청당 랜덤 노드 연결로 바꾸었는데 클러스터 당 데이터는 어떻게 저장했는지도 궁금하다. 핫 키가 있었으면 해당 클러스터에만 트래픽이 몰렸을 텐데... 
      1. 리소스가 엄청 필요하긴 하겠지만 데이터를 모든 노드에 일관되게 저장하고 있다면 궁금증은 해결이다.
      2. 그런데 만약 일관성 해싱, 해시 슬롯과 같은 방식으로 데이터를 분산 저장한다면 랜덤으로 돌릴 수 있는 방법을 생각해 볼 수 있는 것은 두 가지다.
        1. 요청 포워딩: 우선 랜덤으로 노드를 결정하고 해당 노드에 해당 키가 존재하지 않으면 해당 키를 들고 있는 노드를 찾아준다.
        2. 메타데이터 서비스: 키와 해당 키가 저장된 노드 정보(메타데이터)를 들고 있는다. 그래서 해당 키가 존재하는 노드 리스트를 가져오고 거기서 랜덤을 돌려서 노드를 정한다. 

     

    이렇게 전체적인 그림을 짧은 시간에 제공해주어 재밌었지만 좀 더 구체적인 내용은 들을 수 없어서 아쉬운 점도 있었다. 이러한 갈증을 토스 뱅크 발표를 통해 다루고자 하는 어느정도 해소하였다. 직접적으로 쿠팡에서 얻은 궁금증을 해결해준 것이 아니라 해당 발표에서 토스 뱅크의 무한 스크롤링 기능을 위해 어떻게 전략을 구성했는지 디테일하게 설명해주고 있었기 때문이다.

    1. 분산 트랜잭션 기능(속도, 확장성과 신뢰성을 같이 갖고 가기 어려움)으로 높은 신뢰도를 요구하는 서버는 단일 트랙잭션 처리를 위해 단일 서버로 구성하는 전략을 접할 수 있어서 좋았다 (돈 거래를 어떻게 일관성을 지킬지 궁금했었다)
    2. Kafka가 제공하는 실시간 이벤트 처리 시스템으로 신뢰도가 높은 대신 가용성이 좋지 않은 계정계와 확장 및 대규모 처리에 능숙한 채널계를 동기화 해주었다.
    3. 그리고 이러한 동기화 과정에서 발생할 수 있는 많은 예외 상황 시나리오를 작성하여 그에 맞는 대응책을 만들어 신뢰성있는 프로토콜을 만들었다. 여러 예외 케이스(중복 요청, 에러 처리, 요청 타임아웃)에 대해 대비하고, 높은 트래픽 동시성 처리 방법에 대해 설명한다. 그리고 마지막에 완벽한 신뢰성을 보장하는 방법은 존재하지 않아서 유저가 직접 동기화 버튼을 누를 수 있는 UI를 제공한다는 부분이 좋았다.

     

    어떻게 보면 쿠팡, 토스 뱅크 둘 다 데이터를 받아서 클라이언트 요청을 직접 처리하는 고가용성을 보장하는 서비스에 서빙하는 모습을 보여주고 있다.

    • 쿠팡은 공통으로 처리하는 부분들이 많아서 데이터를 한 곳으로 모아서 고가용성이 되는 코어 서빙 플랫폼애서 데이터를 서빙하고자 함
    • 토스 뱅크는 무한 스크롤 기능 필요한데 거래내역을 다루는 서버는 트랜잭션이 중요해서 단일 서버로 구성되어 있음. 그래서 고가용성이 되는 채널계로 데이터를 전달하여 서빙하고자 함

     

    Resources

    Reveal2021 - 쿠팡의 대규모 트래픽을 다루는 백앤드 전략

    토스ㅣSLASH 22 - 왜 은행은 무한스크롤이 안되나요