> For the complete documentation index, see [llms.txt](https://wonjoon.gitbook.io/joons-til/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://wonjoon.gitbook.io/joons-til/techtalk-review/slash-toss/stable-operation-of-large-scale-user-based-mydata-service.md).

# 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 단위)이 안정성 확보에 핵심임을 알게 되었습니다.
