The note to squeeze Wix
Wix の時間切れより手前で、自分から諦める
フロントエンドから呼ぶ webMethod には持ち時間がある。超えると 504 になり、戻り値も console.log も届かない。
集計の関数は1回の呼び出しで5種類のデータを読む。注文、キャンセル記録、実行ログ、それに2種類のマスタである。504 だけを見て速くしようとすると、どれを速くすればよいのか分からないまま、当てずっぽうに手を入れることになる。
そこで、速くするより先に、遅い場所を画面へ出すことにした。
持ち時間を持ち回る
読み取りの入口で予算オブジェクトを作り、各段階へ渡す。ページングの各周回で残りを見て、尽きていれば投げる。公開関数の側でそれを受けて、普通の戻り値に変える。
export function createReadBudget(totalMs) {
const startedAt = Date.now();
const limitMs = Number(totalMs) > 0 ? Number(totalMs) : READ_BUDGET_MS;
const stages = [];
const elapsed = () => Date.now() - startedAt;
const record = (label, ms, info) => {
stages.push({ label, ms: Math.round(Number(ms) || 0), ...(info || {}) });
};
return {
totalMs: limitMs,
stages,
elapsed,
remaining: () => limitMs - elapsed(),
expired: () => elapsed() >= limitMs,
record,
text,
};
}record() の第3引数には、件数、ページ数、どの経路を通ったかを入れる。あとから原因を絞るための材料で、秒数だけでは足りないからである。
画面に出るのはこうなる。
集計が制限時間内に終わりませんでした。下の内訳で、どの読み取りが遅いか分かります。
[詳細] 注文の読み取り(3 ページ目): 読み取りが上限 10 秒を超えました。
読み取り 10.0s / 上限 10s
・注文の読み取り 9.4s (経路=eCom API コレクション失敗=… ページ=8 件数=800)
・キャンセル記録 0.2s (ページ=1 件数=0)
・実行ログ 0.2s (ページ=1 件数=0)
・マスタ 0.1s (件数=2)注文だけが飛び抜けていて、しかもコレクションではなく API 経由で読んでいる。経路が分かれば、遅さの意味も直し方も変わる。504 のままなら、この4行はどこにも出ない。
表示に出す値を選ぶ
内訳は、読む人が見るべき数字だけを残す。空文字、false、0 は表示から省いた。打ち切り=false や コレクション失敗= が並ぶと、肝心の秒数が埋もれる。
ついでに、後から足すキーも最初の宣言に含めるようにした。宣言に無いプロパティへ代入すると、エディタの型チェックが指摘を出す。空や false のキーは表示側が省くので、並べても出力は汚れない。
打ち切りを黙って通さない
ここがいちばん効いた判断だった。途中まで読めた注文で集計すると、数字は静かに小さくなる。その数字を信じて手配してから、足りないと分かる形になる。
起きたこと | 扱い |
時間切れ | 投げる。公開関数が ok: false と内訳に変換する |
ページ上限に到達 | 集計は返すが、成功表示の中に「打ち切られました。件数が不足している可能性があります」を出す |
上限の半分を超えて成功 | 「読み取りに N 秒かかりました(上限 10 秒)」と内訳を添える |
3つ目は予告である。何も言わないと、注文が増えた朝に前触れなく 504 へ変わる。半分を超えた時点で見えていれば、その前に手を打てる。
持ち時間は入力ではなく、実行環境の性質
この予算をどこから渡すかで、一度書き方を変えた。最初は入力オブジェクトに budgetMs を混ぜていたが、エディタの型チェックが「そのキーは型に無い」と指摘した。
型定義にキーを足せば指摘は消える。ただ、足さないほうが正しかった。.web.js は入力をクライアントからそのまま通すので、入力に持ち時間を混ぜると、呼び出し側が読み取りの持ち時間を指定できてしまう。
持ち時間は、画面から呼ばれたのか(プラットフォームの制約がある)、スケジュールジョブから呼ばれたのか(その制約は無い)という、実行環境の性質である。入力とは別の引数にするのが素直だった。型チェックが設計の粗さを指してくれた形になる。
export async function sendReminders(input, budgetMs) { … }
// ジョブから
const result = await sendReminders({ ymd: tomorrow }, 60000);画面と同じ予算をジョブで使うと、対象が多い朝に1通も送れないまま終わる。逆にジョブの予算を画面で使えば、画面のほうが先に 504 になる。同じ関数でも、呼ばれる場所によって妥当な上限が違う。
送信にも同じ考え方を当てる
読み取りだけでなく、メールの送信ループにも予算を効かせた。送信は1通ごとにプラットフォームへ往復するので、対象が増えれば読み取りより時間を食う。そのまま 504 になると、何通届いたのかすら分からなくなる。
各周で残りを見て、尽きたらそこで止めて残りを数える。止めた分は送信済みの記録を書かないので、押し直せば続きから送れる
別の処理から呼ぶときは残り時間を渡す。残りが2.5秒未満なら、送らずに案内だけ返す。そこから送信を始めると 504 になり、直前の処理が成功したことすら画面に出なくなる
失敗した相手は名前で出す。件数だけでは誰へ送り直すのか分からない