同期のパフォーマンスとサーバーへの配慮
このページでは、WCPOS v1.10.0 で導入された同期エンジンについて説明します。エンジンの動作の仕組みについては、まず同期エンジンの仕組みをご覧ください。
WCPOSのストアの多くは共有PHPホスティング上で動作しています。PHPワーカーはわずかで、RESTリクエストのたびにWordPressのフルブートストラップが走り、セキュリティプラグインがバーストをレート制限します。クライアントが無料だとみなしているリクエストは、ストアフロントの表示を遅くし、レート制限に引っかかり、キャッシャー自身の通信と競合します。しかもその影響は、稼働しているレジとブラウザータブの数だけ倍増します。
v1.10.0の同期エンジンは、これを後付けの課題ではなく設計上の制約として扱います。販売者のホスティングを守ることは、同期エンジンの仕事の一部です。 このページでは、エンジンが従うルールと、その裏付けとなる測定結果を説明します。
4つの原則
エンジンでスケジュール実行されるすべての同期動作は、4つの不変条件に従います。
- コストは変更された量に比例し、カタログのサイズには比例しません。 一括取得の前には必ず、安価なダイジェストまたはショートサーキットのチェックが行われます。変更のない5万件の商品を抱えるストアを同期し続けるコストは、変更のない500件のストアとほぼ同じです。
- 無制限のファンアウトはありません。 すべてのバックグラウンドレーンは1回の実行あたりのリクエスト上限を宣言しており、自動テストで強制されています。大きなジョブは再開可能なカーソルを使った上限付きのバッチとして実行され、一括処理はバーストせずにスケジュール全体に分散されます。
- メンテナンスがキャッシャーの邪魔をすることはありません。 レジが販売できる状態にするためのリクエストが先に実行され、整合性の監査やバックグラウンドの事前読み込みは、その後のアイドル時間に実行されます。
- 負荷がかかると、まずメンテナンスが後退します。 サーバーが不調のシグナルを出すと、エンジンはキャッシャーに関わる処理に手を付ける前に、自身のバックグラウンド処理を減速させます。
同期がサーバーにかけるコスト
定常状態のコストは、ストアヘルス → パフォーマンス でデバイスごとに選択する同期プリセットによって決まります。
| プリセット | チェック間隔 | リクエストあたりのレコード数 | 1日あたりの名目チェック回数 |
|---|---|---|---|
| エコ | 5分 | 25 | 288 |
| バランス(既定) | 60秒 | 50 | 1,440 |
| リアルタイム | 10秒 | 75 | 8,640 |
どちらのダイヤルも重要ですが、共有ホスティングで実際にコストになるのはたいていページの重さです。レコード数の多いページはサーバーの処理時間を実際に消費し、間隔はそれを掛け算するだけです。最大値である100件のページを使うプリセットはありません。これはカスタムのスライダー(間隔5秒〜5分、レコード数10〜100)でのみ設定できます。
プリセットに加えて、次の仕組みがあります。
- アイドル時のチェックは低コストです。 エンジンは条件付きリクエストを送信するため、変更がないときのチェックにはボディのない
304 Not Modifiedが1つ返されるだけです。リクエスト1件であることに変わりはありませんが、ペイロードはなく、サーバーの処理も最小限です。 - チェックには±20%のジッターがかかります。 そのため、多数のレジがバーストとして同期することなく、リクエストを分散します。
- アイドル状態のレジは減衰します。 10分間操作がないと間隔が長くなり、バーコードスキャンを含むあらゆる操作で即座に元に戻ります。
- バックグラウンドのメンテナンスには上限があります。 ずれのない整合性チェックのコストは、多くても数ページの安価な集計と、詳細取得ゼロです。サーバーと一致することが証明できたバケットは、そのままスキップされます。ずれが見つかった場合でも、詳細の掘り下げは1回の実行につき2件までに制限され、バーストせずに永続化されたカーソルで再開されます。完全な削除監査は1回の実行につき11リクエストに、ダイジェストの事前読み込みは1回の実行につき5チャンクに制限されています。これらの上限はコード内で宣言され、CIで強制されています。
ストアヘルスは、設定された間隔から1日あたり約N件のリクエストという推定値を算出します。これは上限や予測ではなく、名目上のチェック回数として扱ってください。±20%のジッターによって実際の件数は増減しますし、304 も1件のリクエストとしてカウントされます。アイドル時の減衰が件数を減らすのは、下限である60秒より速いプリセットの場合だけで、既定のバランスプリセットがそれ以上遅くなることはありません。
サーバーが苦しいときのバックオフ
エンジンが受け取るすべてのレスポンスは、ストアごとの負荷モニターに送られます。モニターは次の状況に反応します。
- HTTP
429(即座に反応) 5xxエラーや通信の失敗の繰り返し(1分間のローリングウィンドウ内に3回)- 継続的な遅さ(応答時間の中央値が2秒超)
Retry-Afterヘッダー(次回のチェックの下限として厳密に適用されます)
いずれかが検知されると、エンジンは変更チェックの間隔を1段階ずつ2倍にし、メンテナンスレーンは実行を完全にスキップします。検索、バーコードの照会、チェックアウトといったキャッシャー起点のリクエストが絞られることはありません。目的は先送りできる負荷を落とすことであり、販売を遅くすることではありません。
回復は意図的に非対称です。バックオフは即座ですが、間隔が元に戻るのは正常なレスポンスが10回連続した後です。そのため、不安定なサーバーで速い間隔と遅い間隔が行き来することはありません。また、負荷の状態は販売者がより速いプリセットを選んでも維持されます。この保護は設定されたどの段階よりも下のレイヤーで動作しており、無効にする方法はありません。
デバイス側の応答性を保つ
パフォーマンスのもう半分はレジ自体です。バックグラウンドの同期がUIをカクつかせてはいけません。
- 監査はイベントループに道を譲ります。 整合性の監査は処理をチャンクに分け、チャンクの合間に制御を返すことで、中断されない最長のブロックを約30ミリ秒に抑えています。これは人が遅れを知覚する約100ミリ秒のしきい値を下回ります。ローカルの商品1万件で測定した場合、監査1回分の処理コストは約68ミリ秒で、17回以上に分けて実行されます。5万件でも300ミリ秒未満です。
- 負荷がかかっても最初の結果は速いままです。 最初の結果が返るまでの時間は、一括適用や完全な監査が同時に実行されている状況でもCIで固定されています。ベンチマーク環境における一般的なカタログサイズでは、1桁台のミリ秒です。
- アプリ側に渡るのは表示中のページだけです。 クエリの処理(フィルター、並び替え、ページング)はストレージ層の内部で実行されるため、画面の更新で動くのは1万行ではなく10行です。
これらの数値は、エンジンのパフォーマンス契約スイートによるものです。このスイートはCI上で隔離して実行され、測定された定常状態のおよそ1桁上に予算が設定されています。実行環境が遅いだけで失敗させるのではなく、桁違いのリグレッションを検出するための設計です。
実際のストアでの検証
サーバーへの配慮のルールは、ユニットテストだけで確認しているわけではありません。実際のWordPressストアに対するエンドツーエンドのライブチェックにより、実際のホスティングで最も重要な不変条件を検証しています。
- ストアを開いたとき、商品カタログが描画されるまでのメンテナンスリクエスト(整合性やダイジェストの通信)はゼロであること。
- 開店後の待機期間を経た後も、安価な集計ページが数ページと、詳細の掘り下げが2件以下であること。
このシナリオは、実際に発生したリグレッションから作られました。開発中の初期ビルドでは、監査がストアを開くたびにデバイスごとに約41件のリクエスト(1.2MB)を発行していました。これを置き換えたダイジェストによるゲート方式の設計では、ずれのない監査は集計ページだけで済み、それ以外は発生しません。この種のバグは現在、レビューの注意深さではなく、CIで宣言された上限によって防がれています。
主張しないこと
裏付けとなる数値がまだ揃っていないため、このページでは意図的に述べていないことがいくつかあります。
- 「初回同期にX分かかる」というエンドツーエンドの数値は示していません。 初回のダウンロード時間は、サーバーの応答時間とカタログのサイズに圧倒的に左右されます。レコードを適用するデバイス側のコストはわずかで(商品1万件の一括適用で処理時間は10ミリ秒未満)、通信とサーバーが大半を占めます。
- 「v1.9よりX%高速」といった主張はしません。 アーキテクチャが大きく異なるため、1つの比較値を示しても、伝わることより誤解のほうが大きくなります。構造上の違いはv1.10.0での変更点にまとめています。
- 上記のベンチマークの数値は、エンジンのテスト環境(インメモリストレージ、隔離された実行環境)によるものです。実際のデバイスやストアは条件が異なります。だからこそ ストアヘルス → パフォーマンス では、予測値ではなく、過去24時間にレジで実測したリクエスト件数と一般的な応答時間を表示しています。なお、そこに表示されるデータ量は下限値です。サイズが宣言されていないレスポンスは0として扱われるためです。
サーバー側のチューニング(ホスティング要件、HPOS、キャッシュ、応答が遅い場合の診断)については、サーバーのパフォーマンスをご覧ください。