Stable Operation of Large-Scale User-Based MyData Service

대규모 사용자 기반의 마이데이터 서비스 안정적으로 운영하기

1️⃣ 서비스 소개와 역할

마이데이터 서비스는 여러 금융기관에 흩어진 개인 금융 데이터를 한 곳에 모아 보여주는 서비스입니다.

토스 앱을 켜면 가장 먼저 보이는 홈 화면의 잔액, 거래내역, 신용점수, 대출 정보 등이 모두 이 서비스를 통해 집계됩니다.

즉, 토스 금융 데이터 플랫폼의 허브 역할을 하며, 하루 평균 7만 TPS(초당 트랜잭션 수, Transaction Per Second)를 처리하는 대규모 트래픽 환경입니다.

📌 TPS(Transaction Per Second): 1초에 처리 가능한 요청 수를 나타내는 지표로, 대규모 시스템 안정성의 핵심 성능 지표 중 하나.


2️⃣ 대규모 트래픽 환경의 위험성

마이데이터 서버는 여러 금융기관과 실시간 API 통신을 수행합니다.

이 과정에서 한 기관의 장애가 곧바로 토스 전체 서비스에 영향을 미칠 수 있습니다.

특히 이런 시스템은 **"나비효과형 장애"**가 쉽게 발생합니다.

작은 API 실패 하나가 → 마이데이터 서버 장애로 → 다른 토스 서비스까지 전파될 수 있는 구조죠.


3️⃣ 장애 대응의 핵심 문제

토스는 처음엔 Resilience4j(서킷 브레이커 오픈소스 라이브러리)를 활용했습니다.

서킷 브레이커(Circuit Breaker)는 특정 API 호출 실패율이 높으면, 자동으로 해당 API 호출을 차단하는 장치입니다.

하지만 Resilience4j는 서버 단위로 동작합니다.

예를 들어,

  • 특정 서버 하나에서만 실패율 100% → 해당 서버 서킷 오픈

  • 다른 서버는 실패율 낮아 서킷이 닫힘

    이렇게 서버마다 상태가 달라져 전체 시스템 동작이 일관되지 않는 문제가 생겼습니다.


4️⃣ 코디네이터(Coordinator) 시스템 도입 배경

이 문제를 해결하기 위해 중앙 집중식 서킷 관리 시스템코디네이터를 만들었습니다.

구현 위치에 대해 2가지 선택지가 있었죠:

  1. 마이데이터 서버 내부에 구현

  2. 별도 서버로 구현 ✅ (선택)

이유: 마이데이터 서버는 장애 가능성이 높아, 장애 전파를 막기 위해 운영·제어 로직을 분리해야 했습니다.


5️⃣ 코디네이터 아키텍처

동작 방식은 다음과 같습니다.

  1. 서버 등록

    마이데이터 서버가 배포 시 코디네이터에 서버 ID를 등록.

  2. 하트비트 체크

    주기적으로 살아있는지 확인.

  3. 트래픽·성공/실패 통계 수집 (Kafka 사용)

  4. 임계치 초과 시 서킷 오픈

  5. 10초 주기 캐싱 & 트래픽 차단

  6. 마스터 서버 표본 수집

    카나리 배포 구조에서 가장 먼저/마지막에 배포된 서버가 표본을 수집, 서킷 해제 판단.

📌 Kafka: 대용량 실시간 데이터 스트리밍 플랫폼. 장애율·성공율 같은 이벤트 데이터를 전송하는 데 사용.


6️⃣ 장애 회복 로직의 디테일

서킷을 닫을 때는 모든 서버가 아니라 마스터 서버만 트래픽을 테스트 호출합니다.

왜냐하면 장애 해소 여부를 모든 서버가 테스트하면 불필요한 부하를 줄 수 있기 때문입니다.

테스트 결과 정상 응답이 나오면 서킷을 닫고, 모든 서버 캐시를 갱신해 트래픽을 복구합니다.


7️⃣ 효과와 성과

코디네이터 도입 후,

  • 주당 55분 업무 절약

  • 장애 대응 자동화

  • 개발자 개입 최소화

  • 시스템 일관성 향상

요약

장애 대응 (Circuit Breaker & Coordinator)

  • 문제: 금융기관 API 장애가 마이데이터 서버와 다른 서비스로 전파

  • 초기 대응: Resilience4j(서킷 브레이커) 사용 → 서버 단위 동작의 한계

  • 해결: 중앙집중식 코디네이터(Coordinator) 설계

    • 서버 상태·실패율 통합 관리

    • Kafka 기반 실시간 통계 수집

    • 마스터 서버 표본 수집으로 서킷 해제 판단

  • 효과: 주당 55분 운영 시간 절약, 장애 대응 자동화


8️⃣ API 응답 지연 문제

마이데이터 API 중에는 1회 요청 → 수십 번 외부 API 호출이 발생하는 구조가 있습니다.

예: 자산 등록 API

  • 토큰 발급: 28초

  • 자산 목록: 25초

  • 상세 정보: 24초

    77초 소요 → 유저 이탈 가능성↑


9️⃣ 긴 응답 시간의 인프라 영향

API 실행이 길어질수록,

  • 네트워크 장비, 게이트웨이, 서버 커넥션이 장시간 점유

  • 이는 다른 토스 서비스의 요청 처리 지연으로 이어짐

  • 결국 토스 전체 가용성에 문제 발생


🔟 해결책 – 웹소켓(WebSocket) 전환

처음에는 폴링(Polling)(주기적으로 요청)과 웹소켓을 비교 검토.

웹소켓을 선택한 이유:

  • 폴링은 불필요한 요청 과다 → 서버 부하 증가

  • 웹소켓은 서버-클라이언트 상시 연결로, 서버에서 이벤트 발생 시 즉시 푸시 가능

📌 WebSocket: 클라이언트-서버 간 양방향 통신을 가능하게 하는 프로토콜.


1️⃣1️⃣ 웹소켓 적용 후 구조

  1. 앱 실행 시 웹소켓 서버와 웹소켓 연결

  2. API는 즉시 얼리 리턴 → 공용 리소스 해제 (기존: 응답을 기다려야함)

  3. 서버는 비동기 처리 진행

  4. 처리 완료 시 웹소켓으로 클라이언트에 실시간 업데이트


1️⃣2️⃣ 웹소켓 효과

  • 평균 리소스 점유 시간 3300ms → 58ms (56배 개선)

  • 유저 경험 개선: 진행 상황을 실시간으로 확인 가능

  • 인프라 안정성 강화

요약

시스템 부하 제어 (WebSocket으로 전환)

  • 문제: 일부 API(자산 등록) 실행이 77초 이상 소요 → 네트워크·서버 리소스 장기 점유

  • 원인: 1회 API 요청이 다단계 외부 API 호출로 이어짐

  • 해결: WebSocket 적용

    • API는 얼리 리턴 후 비동기 처리

    • 처리 결과는 WebSocket으로 실시간 푸시

  • 효과: 평균 리소스 점유 시간 3300ms → 58ms (56배 개선)

  • UX 향상: 처리 진행 상황 실시간 표시


1️⃣3️⃣ 배치 트래픽 제어 문제

마이데이터 서버 호출 유형:

  • 유저 호출: 유저 행동 시 발생

  • 배치 호출: 7일마다 자산 갱신 (은행 허용 시간대만 가능)

문제: 허용 시간대(하루 6시간)에 집중 호출 → 트래픽 폭증 → 장애 위험.


1️⃣4️⃣ 배치 트래픽 평준화 전략

유저 사용 패턴 분석:

  • 월급날 오전(25일)에 트래픽 폭증

  • 주말보다 평일 호출량↑

전략:

  • 유저 호출 많은 날 → 배치 호출 줄임

  • 유저 호출 적은 날 → 배치 호출 늘림

    주간 총 호출량은 동일하되, 특정일 집중 피함


1️⃣5️⃣ 1분 단위·100ms 단위 호출 분산

은행 허용 시간대 시작 시:

  1. 하루 전체 대상 유저 수 계산

  2. 1분 단위로 나눔

    1. 제공 기관의 배치호출을 1분단위로 설정 가능하기 때문

  3. 다시 100ms 단위로 호출 분산

    1. 사용자 별 n번의 배치호출 발생, 동시 호출 수를 최소화 하기 위해 100ms로 나눠서 배치호출 실행

📌 이 방식은 "동시성 제어"와 "백프레셔(Backpressure)" 개념이 적용됨.


1️⃣6️⃣ Kotlin Coroutine + Channel 활용

  • Coroutine(코루틴): 경량 스레드, 비동기 처리에 유리

  • Channel: 코루틴 간 안전한 데이터 전달

구현:

  • Producer: DB에서 유저 정보를 일정 간격으로 가져와 채널에 전달

  • Consumer: 100ms 간격으로 배치 호출 실행

  • 구조적 동시성(Structured Concurrency) 회피 위해 코루틴 스코프 분리


1️⃣7️⃣ 배치 제어 효과

  • 동시 호출 수 최소화

  • 제공기관 부하 방지

  • 시스템 안정성 향상

  • 장애 가능성 감소

요약

피크 트래픽 제어 (Batch 요청 평준화)

  • 문제: 배치 호출(7일 주기 자산 갱신)이 특정 시간대·날짜에 몰림 → 장애 위험

  • 원인: 은행 허용 시간대 제한 + 유저 호출량 변동

  • 해결:

    • 주간 총량은 유지하되, 유저 호출 많은 날엔 배치 줄이고, 적은 날엔 늘림

    • 1분 단위·100ms 단위 호출 분산

    • Kotlin Coroutine + Channel로 병렬·비동기 처리

  • 효과: 동시 호출 수 최소화, 제공기관 부하 방지, 시스템 안정성 확보

배운점

  • 이번 내용을 통해 대규모 트래픽 환경에서는 작은 장애도 빠르게 전파되기 때문에, 장애 감지와 회복 검증 구조를 정교하게 설계해야 한다는 걸 배웠습니다.

  • 또한 네트워크 리소스를 효율적으로 사용하기 위해 WebSocket과 같은 비동기·이벤트 기반 통신이 장기 요청 처리에 효과적이라는 점을 이해했습니다.

  • 배치 처리에서는 백프레셔와 세밀한 호출 분산(분·100ms 단위)이 안정성 확보에 핵심임을 알게 되었습니다.

Last updated