# API プロバイダーとのウォレット送金：リクエストキューを詰まらせない

> プラットフォームと API プロバイダーの間で連鎖する送金呼び出しがリクエストキューをあふれさせる仕組みと、キュー・即時応答・結果のプッシュでプロバイダーが遅くてもプラットフォームを安定させる方法。

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

プラットフォームはユーザーのウォレットを預かり、API プロバイダーはプラットフォームが提供するサービスの裏側でサーバーを運用しています。ユーザーがあるプロバイダーのサービスを使う前に資金はプラットフォームからプロバイダーへ移り、使い終わると戻ってきます。多くの連携では、この送金を双方向に連鎖する同期 HTTP 呼び出しで作っています。負荷が上がるとこの連鎖が両側のリクエストキューを埋め、遅いプロバイダーが一つあるだけでプラットフォーム全体が巻き込まれます。プラットフォームの安定性が、他社のサーバーに左右されるべきではありません。

## 送金はリクエストの連鎖になっている

```text
sequence: 変更前：1 回の送金を 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 件の送金が 3 つのプロバイダーに分かれ、プラットフォームのプールは 200 worker です。使われる worker 数は[リトルの法則](https://en.wikipedia.org/wiki/Little%27s_law)で出せます。毎秒の送金数 × 1 件が worker を占有する秒数です。

| | 連鎖する HTTP | キュー、即時受付、結果のプッシュ |
| --- | --- | --- |
| ユーザーのリクエストがプラットフォームの worker を占有する時間 | 20 ms + プロバイダー呼び出し 150 ms = 170 ms | 20 ms |
| 毎秒 500 件の送金で使われる worker 数 | 500 × 0.17 秒 = 85 | 500 × 0.02 秒 = 10 |
| 3 つのうち 1 つのプロバイダーが 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/ja/topics/balance-sync-websocket)と同じやり方です。
5. **取りこぼしは一度の確認で。** プラットフォームは数分ごとに、各プロバイダーへまだ処理中の送金をまとめて一度だけ問い合わせます。送金ごとのポーリングはしません。

## プロバイダーが不安定でもプラットフォームは安定させる

- **プロバイダーごとに枠を分ける。** 遅くなったプロバイダーが埋めるのは、ディスパッチャーの自分の枠だけです。ログイン、入金、ほかのプロバイダーの worker はそのまま残ります。
- **応答しないプロバイダーには送るのをやめる。** タイムアウトが続いたらそのプロバイダーのキューを止め、サービスを一時的に利用不可と表示し、少し待ってから再開します。送金は占有された接続ではなく、台帳の中で処理中として待ちます。
- **WebSocket が切れても何も止まらない。** 送金は受け付け続けます。接続が戻れば、まとめた確認でその間の分が処理されます。
- **ユーザーがプロバイダーの応答時間を待つことはない。** 画面はプラットフォーム自身の速さで応答し、結果は用意でき次第表示されます。

## よくある質問

### 遅い API プロバイダーが、なぜプラットフォーム全体を遅くするのですか？

プラットフォームの worker がそのプロバイダーを待っているからです。同期呼び出しはプロバイダーが応答するまで worker を一つ占有し、その worker はほかのすべての機能と共有されています。一つのプロバイダーが遅くなると worker が尽き、そのプロバイダーとは関係のないリクエストまで後ろに並びます。

### WebSocket が送金 API の代わりになるのですか？

いいえ。送金そのものは冪等キー付きの HTTP リクエストのままで、双方の台帳に記録されます。WebSocket が置き換えるのは待つ部分、つまり占有された接続、状態のポーリング、コールバックです。結果はイベントとして届きます。

### 送金が完了したときにプラットフォームが切断されていたら？

何も失われません。双方の台帳にすでに送金が記録されているからです。pusher-js などのクライアントライブラリは自動で再接続し、処理中の送金をまとめて確認すれば取りこぼしも処理されます。適用済みのイベントは送金 ID で無視します。

### 接続はいくつ必要ですか？

プラットフォームのバックエンドから各プロバイダーのチャンネルへ WebSocket 接続が一つあれば、そのプロバイダーとのすべての送金をまかなえます。ユーザー数は関係ありません。Hibira では 1 回の発行が 1 メッセージで、有料プランでは購読者への配信を数えません。[料金](https://hibira.io/ja/pricing)をご覧ください。
