レートリミットを超えずに効率的にリクエストを送る方法

APIを利用していると、レートリミットの制限に引っかかって 「429 Too Many Requests」のエラーが出ることがあります。特に、短時間に大量のリクエストを送る処理では、この制限を意識しないとAPIが一時的にブロックされてしまうこともあります。

そこで今回は サーバーサイド（Node.js）で、レートリミットの制限にかからないように、一定の間隔を空けながらリクエストを送る方法を考えてみました。

## レートリミットとは
指定された時間内に実行できる操作の数を制限することです。

https://developer.mozilla.org/ja/docs/Glossary/Rate_limit?utm_source=chatgpt.com

## サーバーサイド(Node.js)
Node.jsを使用してレートリミットの制限に引っかからないようにコードを作成していきます。
APIは地図システム開発支援Web APIである[TerraMap API](https://www.mapmarketing.co.jp/terramap-api?grid=qiita&utm_source=qiita.com&utm_medium=referral&utm_campaign=qiita_rate_limit_788b702287e254ee2c36_01)の利用を想定しています。
TerraMap APIは、エンドポイントごとにレートリミットが設定されており、10分間に500リクエストまでとなっています。
600 秒(10分) / 500リクエスト = 1.2秒
以上の情報から1.2秒ごとにリクエストするとレートリミットが超えないようになります。
この記事のサンプルコードの実装はシングルプロセスが前提になっています。
リクエストが来たらキューに入れて1.2秒ごとに外部APIからデータを取得しています。
キューに関してはグローバル変数を使って実装しましたが、Node.jsにはキューを実現するライブラリもあります。必要に応じて活用してください。
```javascript:server.js
const express = require('express');
const https = require('https');
const app = express();

const port = 8000; // ポート番号
const rateLimitInterval = 1200; // 1.2秒間隔（ミリ秒）
let queue = []; // リクエストを保持するキュー（先入れ先出し）
let interval; // 定期的にキューを処理するタイマー

// リクエストパラメータ
const params = JSON.stringify({
  layer_id: "00104",
  area_type: "circle",
  center_lat: 35.681236,
  center_lng: 139.767125,
  radius: [100],
  output: "polygon,point,data",
  stats: [
    {
      stat_id: "001012000",
      stat_item_id: [15776]
    }
  ],
});
// URLとapiキーはダミーです
const url = 'https://tmapi.example.jp/api/area';
const apiKey = 'YOUR_TerraMap_API_APIKEY';

// APIリクエスト送信関数
function sendRequest() {
  return new Promise((resolve, reject) => {
    const options = {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-API-KEY': apiKey,
      }
    };
    const req = https.request(url, options, (res) => {
      let data = '';

      res.on('data', (chunk) => {
        data += chunk;
      });

      res.on('end', () => {
        resolve({
          headers: res.headers,
          data: JSON.parse(data)
        });
      });
  });

  req.on('error', (err) => {
    reject(err);
  });

  req.write(params);
    req.end();
  });
}

// キューを定期的に処理する関数
function startProcessingQueue() {
  if (interval) return; // タイマー動作中は実行しない

  interval = setInterval(() => {
    if (queue.length === 0) {
      clearInterval(interval);
      interval = null; // 停止している状態にする
      return;
    }

    // キューからリクエストごとのオブジェクトを取得する
    const request = queue.shift();

    sendRequest()
      .then(response => {
        // レスポンスヘッダーを設定
        request.res.setHeader('X-Rate-Limit-Limit', response.headers['x-rate-limit-limit']);
        request.res.setHeader('X-Rate-Limit-Remaining', response.headers['x-rate-limit-remaining']);
        request.res.setHeader('X-Rate-Limit-Reset', response.headers['x-rate-limit-reset']);

        // レスポンスを返す
        request.res.status(200).json({ response });
      })
      .catch(err => {
        request.res.status(500).json({ error: err.message });
      });
    }, rateLimitInterval);
}

// エンドポイント
app.get('/', (req, res) => {
    // リクエストごとにresを含めたオブジェクトをキューに追加
    queue.push({ res });

    // キュー処理を開始
    startProcessingQueue();
});

// サーバー起動
app.listen(port, () => {
    console.log(`Server running at http://localhost:${port}`);
});

```
今回の実装では一定間隔の1.2秒ごとにリクエストするようにしましたが、X-Rate-Limit-Remainingの値（レートリミットの残り回数）によってリクエスト間隔を動的に長くすることも可能です。

### Node.jsでサーバーを起動する

実行する前に以下のコマンドを実行します。
※Node.jsは既にインストールされている想定です。
```
npm install express
```
以下のコマンドでサーバーを起動します。
```
node server.js
```

## どのようなケースで有効か
今回作成したコードがどのようなケースで有効か考えてみます。
一つ目はリクエスト数がレートリミットを超えると事前に予測できるケースが考えられます。例えば、バッチ処理等で大量の処理を行う場合です。

二つ目は不特定多数のエンドユーザーが特定のタイミングで一斉にリクエストを行うケースが考えられます。例えば、システム上で特定の時間帯にアクセスが集中する場合です。
現在時刻を取得し、特定の時間帯に該当する場合のみリクエスト間隔を制御することで対応できます。

不特定多数のエンドユーザーが任意のタイミングでリクエストを行うケースに関してはレスポンス速度が問われない場合に限り有効だと考えられます。