# API 제공업체와의 지갑 이체: 요청 큐가 막히지 않게

> 플랫폼과 API 제공업체 사이의 연쇄 이체 호출이 요청 큐를 넘치게 하는 방식, 그리고 큐·빠른 응답·결과 푸시로 제공업체가 느려도 플랫폼을 안정적으로 유지하는 방법.

Source: https://hibira.io/ko/topics/wallet-transfers-api-providers
Updated: 2026-11-09

플랫폼은 사용자의 지갑을 보관하고, API 제공업체는 플랫폼이 제공하는 서비스 뒤에서 서버를 운영합니다. 사용자가 어떤 제공업체의 서비스를 쓰기 전에 자금이 플랫폼에서 제공업체로 옮겨지고, 다 쓰면 다시 돌아옵니다. 대부분의 연동은 이 이체를 양방향으로 이어지는 동기 HTTP 호출 사슬로 만듭니다. 부하가 오르면 이 사슬이 양쪽의 요청 큐를 채우고, 느린 제공업체 하나가 플랫폼 전체를 끌어내릴 수 있습니다. 플랫폼의 안정성이 남의 서버에 달려 있어서는 안 됩니다.

## 이체 한 건이 요청 사슬이 됩니다

```text
sequence: 변경 전: 이체 한 건을 HTTP 호출 사슬로 처리
사용자 | 플랫폼 | API 제공업체
1 > 2: 서비스 시작 | 플랫폼의 worker가 기다림
2 > 3: POST /transfer-in | 제공업체를 기다림
3 > 2: GET /wallet/verify | 제공업체의 worker도 기다림
2 >> 3: 200 OK
3 >> 2: 200 | 또는 타임아웃, 상태 알 수 없음
2 > 3: GET /transfer/123 | 완료될 때까지 2초마다
2 >> 1: 시작 가능
```

플랫폼의 worker는 제공업체가 플랫폼을 다시 호출하는 구간까지 포함해 왕복 내내 붙잡혀 있습니다. 제공업체의 worker는 그 콜백을 기다리는 동안 붙잡혀 있습니다. 서로가 서로를 기다리므로 트래픽이 오르면 양쪽 풀이 동시에 찹니다. 타임아웃이 나면 이체 상태를 알 수 없게 되고, 상태 폴링과 재시도가 뒤따릅니다. 멱등 키가 없으면 재시도로 돈이 두 번 옮겨질 수도 있습니다.

## 느린 제공업체 하나가 모두의 문제가 되는 과정

```text
flow: 잠깐의 지연이 요청 홍수가 됨
프로모션 시작|이체 증가 --> 제공업체 C가 느려짐|150 ms → 5초 --> 플랫폼 worker가 붙잡힘|C를 기다림 --> 공유 풀 소진|모든 요청이 대기
요청 타임아웃 --> 재시도와 상태 폴링|요청이 더 늘어남 --> 로그인, 입금, 다른 제공업체 실패|C와 무관
```

벤치마크가 아니라 계산 예시입니다(2026-11-09 작성). 초당 500건의 이체가 세 제공업체에 나뉘어 있고, 플랫폼의 풀은 worker 200개입니다. 사용 중인 worker 수는 [리틀의 법칙](https://en.wikipedia.org/wiki/Little%27s_law)으로 구합니다. 초당 이체 수 × 한 건이 worker를 붙잡는 초입니다.

| | 연쇄 HTTP | 큐, 빠른 접수, 결과 푸시 |
| --- | --- | --- |
| 사용자 요청이 플랫폼 worker를 붙잡는 시간 | 20 ms + 제공업체 호출 150 ms = 170 ms | 20 ms |
| 초당 500건 이체 때 쓰이는 worker 수 | 500 × 0.17초 = 85 | 500 × 0.02초 = 10 |
| 세 제공업체 중 하나가 5초 타임아웃까지 멈춤 | 약 900: 200개 풀이 바닥나고 모든 제공업체가 실패 | 그대로 10: 그 제공업체의 이체만 기다림 |
| 결과를 기다리는 동안의 요청 | 대기 중인 이체마다 2초마다 상태 폴링 | 없음, 제공업체마다 몇 분에 한 번 일괄 확인 |

## 변경 후: 빨리 접수하고, 뒤에서 정산하고, 결과를 푸시

```text
sequence: 변경 후: 양쪽 모두 빨리 응답하고 결과는 이벤트로 도착
사용자 | 플랫폼 | API 제공업체 | *Hibira
1 > 2: 서비스 시작
2 >> 1: 처리 중 | 원장 기록, worker 반환
2 > 3: POST /transfers | 멱등 키, 큐에서 전송
3 >> 2: 202 Accepted | 기록 완료, worker 반환
note: 제공업체는 자기 큐에서 자기 속도로 정산
3 > 4: transfer.settled | private-partner-acme
4 > 2: transfer.settled | id 123, 금액
2 > 4: balance.updated | private-wallet-42
4 > 1: balance.updated
```

1. **플랫폼이 먼저 사용자에게 응답합니다.** 이체를 처리 중으로 원장에 기록하고 큐에 넣은 뒤 응답합니다. 사용자 화면에는 "처리 중"이 표시되고 이벤트가 오면 저절로 바뀝니다.
2. **디스패처가 각 제공업체로 이체를 보냅니다.** 멱등 키, 짧은 타임아웃, 제공업체마다 정해진 슬롯 수를 둡니다. 제공업체는 이체를 기록하자마자 202를 돌려주므로 같은 키로 재시도해도 항상 안전합니다.
3. **콜백은 없습니다.** 요청에 제공업체가 필요한 모든 것을 서명과 함께 담으므로, 제공업체가 확인하려고 플랫폼을 다시 호출할 필요가 없습니다.
4. **결과는 푸시로.** 제공업체는 그 플랫폼용 비공개 채널, 예를 들어 `private-partner-acme`에 `transfer.settled`를 발행합니다. 플랫폼 백엔드는 WebSocket 연결 하나를 유지하며 제공업체의 인증 엔드포인트 승인을 받아 구독합니다. 원장을 갱신하고 사용자의 지갑 채널에 `balance.updated`를 발행합니다. [제품 간 잔액 동기화](https://hibira.io/ko/topics/balance-sync-websocket)와 같은 방식입니다.
5. **놓친 것은 한 번의 확인으로.** 플랫폼은 몇 분마다 각 제공업체에 아직 처리 중인 이체를 일괄 요청 하나로 묻습니다. 이체마다 폴링하지 않습니다.

## 제공업체가 불안정해도 플랫폼은 안정적으로

- **제공업체마다 슬롯을 따로.** 느려진 제공업체는 디스패처에서 자기 슬롯만 채웁니다. 로그인, 입금, 다른 제공업체의 worker는 그대로 남습니다.
- **응답하지 않는 제공업체에는 보내지 않습니다.** 타임아웃이 이어지면 그 제공업체의 큐를 멈추고 서비스를 일시적으로 이용할 수 없다고 표시한 뒤, 잠시 후 다시 시도합니다. 이체는 붙잡힌 연결이 아니라 원장 안에서 처리 중으로 기다립니다.
- **WebSocket이 끊겨도 막히는 것은 없습니다.** 이체는 계속 접수됩니다. 연결이 돌아오면 일괄 확인이 그사이의 일을 정산합니다.
- **사용자는 제공업체의 응답 시간을 기다리지 않습니다.** 화면은 플랫폼 자신의 속도로 응답하고, 결과는 준비되면 나타납니다.

## 자주 묻는 질문

### 느린 API 제공업체 하나가 왜 플랫폼 전체를 느리게 하나요?

플랫폼의 worker가 그 제공업체를 기다리기 때문입니다. 동기 호출은 제공업체가 응답할 때까지 worker 하나를 붙잡고, 그 worker는 다른 모든 기능과 공유됩니다. 한 제공업체가 느려지면 worker가 바닥나고, 그 제공업체와 무관한 요청까지 뒤에 줄을 섭니다.

### WebSocket이 이체 API를 대체하나요?

아니요. 이체 자체는 멱등 키가 붙은 HTTP 요청으로 남고 양쪽 원장에 기록됩니다. WebSocket이 대체하는 것은 기다림, 즉 붙잡힌 연결, 상태 폴링, 콜백입니다. 결과는 이벤트로 도착합니다.

### 이체가 정산될 때 플랫폼 연결이 끊겨 있으면요?

잃는 것은 없습니다. 양쪽 원장에 이미 이체가 기록되어 있기 때문입니다. pusher-js와 다른 클라이언트 라이브러리는 스스로 재연결하고, 처리 중인 이체를 일괄 확인하면 놓친 것도 정산됩니다. 이미 적용한 이벤트는 이체 ID로 무시하세요.

### 연결은 몇 개가 필요한가요?

플랫폼 백엔드에서 각 제공업체 채널로 WebSocket 연결 하나면 그 제공업체와의 모든 이체를 처리할 수 있습니다. 사용자 수와는 상관없습니다. Hibira에서는 발행 한 번이 메시지 하나이고, 유료 플랜에서는 구독자에게 가는 전달을 세지 않습니다. [요금](https://hibira.io/ko/pricing)을 보세요.
