🪣

WordPress REST API Batch Frameworkで記事を一度に大量に登録してみる

に公開

WordPressで大量のコンテンツを投稿・更新する機会があり、REST APIで1件ずつ入れていると結構時間がかかった。

そこで色々調べていると、複数のAPIをまとめて送るエンドポイントがあったのでそれを使ってみたのでクセも含めてまとめる。

結論正直古く、ドキュメントもほぼ見当たらなく不安はあるものの記事の引っ越しや大量のインポートを行う時には使えそうだった。

使ったもの

WordPress 5.6(2020年12月リリース)で導入された REST API Batchというもの

これを使って複数の記事を作成するPOSTをまとめて投げた。

基本的な使い方

POST /wp-json/batch/v1 に対して以下のように送る

  {
    "requests": [
      {
        "method": "POST",
        "path": "/wp/v2/posts",
        "body": {
          "title": "記事タイトル1",
          "content": "本文1",
          "status": "draft"
        }
      },
      {
        "method": "POST",
        "path": "/wp/v2/posts",
        "body": {
          "title": "記事タイトル2",
          "content": "本文2",
          "status": "draft"
        }
      }
    ]
  }

レスポンス

  {
    "responses": [
      { "status": 201, "body": { "id": 123, ... } },
      { "status": 201, "body": { "id": 124, ... } }
    ]
  }

TypeScriptで実装する時はこんな感じ

  const DEFAULT_BATCH_SIZE = 25;

  interface BatchRequest {
    method: "POST" | "PUT";
    path: string;
    body?: Record<string, unknown>;
  }

  interface BatchResponse {
    status: number;
    body?: Record<string, unknown>;
  }

  async function batchRequest(
    requests: BatchRequest[],
    batchSize: number = DEFAULT_BATCH_SIZE
  ): Promise<BatchResponse[]> {
    const allResponses: BatchResponse[] = [];

    // 25件ずつ分割して送信
    for (let i = 0; i < requests.length; i += batchSize) {
      const batch = requests.slice(i, i + batchSize);

      const response = await fetch(`${WP_URL}/wp-json/batch/v1`, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          Authorization: `Basic ${btoa(`${username}:${appPassword}`)}`,
        },
        body: JSON.stringify({ requests: batch }),
      });

      const data = await response.json();
      allResponses.push(...data.responses);
    }

    return allResponses;
  }

デフォルトの値は25件が上限。それ以上を送りたい場合は

  // 例:50件に増やす
  add_filter( 'rest_get_max_batch_size', function() {
      return 50;
  });

functions.phpに書く。確認したい時はOPTIONS /wp-json/batch/v1をリクエストすると、endpoints[0].args.requests.maxItemsに上限値が返ってくる。手元だとこんな感じ

$ curl -s -X OPTIONS http://localhost:3000/wp-json/batch/v1 | jq '.'
{
  "namespace": "",
  "methods": [
    "POST"
  ],
  "endpoints": [
    {
      "methods": [
        "POST"
      ],
      "args": {
        "validation": {
          "type": "string",
          "enum": [
            "require-all-validate",
            "normal"
          ],
          "default": "normal",
          "required": false
        },
        "requests": {
          "type": "array",
          "maxItems": 25,  ← これ

パフォーマンス改善

ローカルのdocker環境で記事100件を作成する処理で検証したところ

  • before(1件ずつ)
    • 836ms
  • after(25件ずつ)
    • 93ms

とかなり速くなった。

とはいえ、裏の処理がバルクインサートなど最適化されている訳ではおそらくなく、DBの負荷などが軽くなったりはしなかった。それでもリクエストの数のオーバーヘッドがなくなり、速度が改善した模様。

slugがうまく反映されない(多分不具合)

slugの指定がうまくできなかったため、それも行いたいときには個別にAPIを実行する必要がある。例えば以下のように article-a article-b article-c と指定する。

  {
    "requests": [
      {
        "method": "POST",
        "path": "/wp/v2/posts",
        "body": {
          "title": "記事A",
          "status": "draft",
          "slug": "article-a"
        }
      },
      {
        "method": "POST",
        "path": "/wp/v2/posts",
        "body": {
          "title": "記事B",
          "status": "draft",
          "slug": "article-b"
        }
      },
      {
        "method": "POST",
        "path": "/wp/v2/posts",
        "body": {
          "title": "記事C",
          "status": "draft",
          "slug": "article-c"
        }
      }
    ]
  }

レスポンスは以下。全て1つ目のものになってしまう。DBの値(post_name)を見ても全てarticle-aだった。内部で何かが共有され、slugが引き継がれてしまっている模様。

  {
    "responses": [
      { "status": 201, "body": { "id": 1, "slug": "article-a", "title": { "rendered": "記事A" } } },
      { "status": 201, "body": { "id": 2, "slug": "article-a", "title": { "rendered": "記事B" } } },
      { "status": 201, "body": { "id": 3, "slug": "article-a", "title": { "rendered": "記事C" } } }
    ]
  }

ちょっとうまく動かない箇所やパフォーマンス面で改善しきれない部分もあるが、使い所を誤らなければかなりバッチ処理などのチューニングの役に立った。

似たような事象でお困りの人の助けになれば幸いです。

SMARTCAMP Engineer Blog

Discussion