ブログ一覧

Cloudflare D1 の無料枠を守るためだけに作らなかった 4 つの仕組み

Cloudflare の 1 アカウントで 8 サイトを運用していて、D1 の読み取りが 1 日 2,110 万行(無料枠 500 万行の 422%)に達したことがあります。テンプレートとキャッシュの見直しで 190 万行前後(38〜42%)まで下げ、その水準で落ち着いています。

下げる過程で、「次はこれを作れば、さらに減る」という仕組みの案が 4 つ出ました。4 つとも、作らないことにしました。どれも作るのは半日で、効果も数字で見えていたものです。作らなかった理由は共通していて、入れるのは安いが、持ち続けるのが高いからです。その 4 つと、判断の材料を書きます。

1. 公開時にキャッシュ済みの 404 を消す仕組み

何が起きていたか。 公開前に記事の URL を一度でも開くと、404 のレスポンスがエッジに 30 日キャッシュされ、記事を公開しても素の URL が 404 のまま残ります。公開時に失効させるキャッシュタグ(コレクション名と記事 ID)が、404 には付いていないためです。経緯は Astro + Cloudflare で公開した記事が 404 のままになる原因 に書きました。

作ろうとしたもの。 記事の公開 API への書き込みを捕まえて、レスポンスの slug から公開 URL のパスを組み立て、そのパスのタグだけを失効させるミドルウェア。テストを含めて実装し、CI も通り、PR まで出しました。

持ち続けるもの。

  • slug から URL を組み立てる規則。CMS が持っている url_pattern は実際のルーティングと食い違っているサイトがあり、結局コード側に規則を持つことになる。サイトを足すたび、URL の設計を変えるたびに見直しが要る
  • 予約公開は Cron から実行されるのでミドルウェアを通らず、この仕組みでは捕まえられない。穴が残ったまま「対処済み」の顔をする
  • purge のコードそのもの。EmDash のキャッシュの仕組みが変わるたびに追従が要る

守っているものの価値。 404 を 30 日キャッシュし続けることで節約できるのは、同じ存在しない URL が繰り返し踏まれたときの Worker 起動だけです。スキャナは毎回違う URL を試すのでキャッシュに当たらず、404 の描画は軽いので D1 の読み取り量にも効いていませんでした。

代わりにしたこと。 404 ページで Astro.cache.set({ maxAge: 300, swr: 0 }) を呼び、404 のキャッシュだけ 5 分にする 1 行。公開・予約公開・復元・スラッグ変更のどの経路で起きても、最長 5 分で直ります。保守するものはありません。

2. 404 ページを D1 を読まない静的なページにする

何が起きていたか。 存在しない URL への 404 も、記事ページと同じ Base レイアウトで描画しています。ヘッダーのメニュー、サイト設定、フッターの固定ページ一覧を D1 から引くので、404 を 1 回返すのに数十行の読み取りが乗ります。スキャナは 1 日 1,000 件前後この 404 を踏みます。

作ろうとしたもの。 404 ページだけ Base を通さず、メニューもリンクも固定で書いた静的なページにする。読み取りは 0 行になり、1 の 404 キャッシュを消す仕組みも要らなくなる(キャッシュしなくても安いので)。

持ち続けるもの。

  • レイアウトが 2 系統になる。メニューやフッターを変えるたびに、404 側にも手で反映する。忘れると 404 だけ古いサイトの顔になる
  • 8 サイトぶん。サイトごとに Base の中身が違うので、静的版もサイトごとに書く

守っているものの価値。 1 日 1,000 件 × 数十行で、多くても 5 万行。日次の枠の 1% です。8 月に問題だったのは通常ページの集計処理で、404 の読み取りは最初から主因ではありませんでした。

代わりにしたこと。 ウィジェット(タクソノミーの件数集計)だけは 404 で描画しないようにしてあります。これは 1 行の条件分岐で、レイアウトを分けずに済む範囲です。それ以上は、404 の件数が桁で増えたときに考えます。

3. リダイレクト表を Cloudflare の Bulk Redirects へ同期する仕組み

何が起きていたか。 EmDash のリダイレクトミドルウェアは、有効なリダイレクトを D1 から全件読んでから照合します。Worker の isolate が温まっていればメモリのコピーを使いますが、冷えた isolate では毎回読みます。8 サイトのうち WordPress から移行したサイトの 1 つは、リダイレクト行が多く、この select * from _emdash_redirects where enabled = ? が 1 回 350 行、そのサイトの読み取りの 55%、アカウント全体の約 18% を占めていました。

作ろうとしたもの。 D1 のリダイレクト行を Cloudflare の Bulk Redirects へ同期し、同期できた行を D1 側で無効にする仕組み。Bulk Redirects は Worker より前で処理されるので、旧 URL へのアクセスは Worker を起動しなくなり、読み取りも起動枠も両方減ります。コードの変更も patch も要りません。

持ち続けるもの。

  • 同期そのもの。サイトが増えたとき、スラッグ変更で CMS が自動生成するリダイレクトが増えたとき、同じ手順を回し続ける。一度きりの移行ではなく運用になる
  • 2 つの場所にある同じ情報の整合。D1 側を無効化した行と Bulk 側の行がずれたとき、どちらが正しいかを判断する手順
  • Bulk Redirects 側の無料プランの上限(リストの件数)との付き合い

守っているものの価値。 アカウント全体の 18% は小さくありません。ただし D1 の日次は 38〜42% で、枠の半分も使っていません。18% を消しても「40% が 33% になる」だけで、それで何かが可能になるわけではありません。

代わりにしたこと。 何もしていません。再訪の条件を数字で置きました。D1 の日次が 50% に近づいたら、このときの設計メモから着手する。それまでは持たない。

4. スキャナのアクセスをエッジで落とすルール

何が起きていたか。 WordPress 時代の URL(/wp-login.php/wp-json/…/xmlrpc.php)を狙うスキャナが、8 サイトのうちの 1 つだけで週 7,410 件来ていました。全部 404 ですが、Worker は起動します。

作ろうとしたもの。 Cloudflare の WAF カスタムルールで、Worker の手前で切る。パスの列挙(ブロックリスト)はやらず、「正規の URL にはドットが出てこない」という性質を使った構造的な規則 1 本にする案でした。8 サイトの記事スラッグ・タクソノミー・リダイレクト元にドットを含むものがゼロで、静的ファイルの拡張子が 6 種類だけであることまで実測して、規則の形は決めていました。

持ち続けるもの。

  • 例外の管理。静的ファイルを 1 種類足すたび、ドットを含む正規の URL を 1 つ作るたびに、ルールを見直す。忘れると生きているページを自分で落とす
  • 無料プランの WAF は正規表現が使えず、contains / starts_with / ends_with の組み合わせだけ。表現力の天井が低いまま例外を増やすと、事故の形が読めなくなる
  • 誤ブロックの切り分け。ルールに当たったリクエストは Worker に届かないので、アプリ側のログには何も残らない

守っているものの価値。 Worker の起動は日平均 34,000 回で、無料枠 10 万回の 34%。スキャナの分を全部消しても 30% を切る程度です。しかも、以前アプリ側に入れた同種のブロック(.php/wp-json を素の 404 で返すミドルウェア)は、狙っていた 503 の抑止に効いていませんでした。503 の原因はスキャナではなく通常ページの集計処理で、そちらを直したら消えました。的を外したまま仕組みだけが残る、という前例が自分の手元にありました。

同じ目的で Bot Fight Mode も検討し、こちらは別の理由(無料プランでは例外が作れない)で見送っています。Bot Fight Mode を無料プランで使うと GitHub Actions が 403 になる に書きました。

代わりにしたこと。 何もしていません。再訪の条件は Worker の起動が 1 日 7 万回に近づいたとき。そのときも第一手はルールではなく Workers Paid(月 5 ドルで 1,000 万回/月)です。副作用が無く、確実に効きます。

4 つに共通していた判断の形

作らなかった 4 つを並べると、判断の材料は毎回同じ 3 つの問いでした。

  • 何を守るための仕組みか。 404 のキャッシュ、404 の描画コスト、D1 の 18%、Worker の起動枠
  • それは今、圧迫されているか。 D1 は 40%、Worker は 34%。どれも圧迫されていない
  • 持ち続けるものは何か。 URL の規則、同期の運用、例外の管理。どれも「作った日」ではなく「その後ずっと」に発生する

守る対象が圧迫されていないなら、仕組みを増やす理由がありません。圧迫されたときに取る手も、4 つとも「課金」の方が確実で副作用が無い、という結論でした。1 人で 8 サイトを持っている構成では、仕組みの数がそのまま将来の自分の作業量になります。

作らない判断にも記録が要ります。4 つとも Issue に「やらない理由」と「再訪する条件」を書いて閉じました。数字が動いたときに、同じ調査をやり直さずに済むようにするためです。

この構成で気をつけること

  • 「作れば減る」と「減らす必要がある」は別。減らす必要があるかは、枠に対する今の比率で決める
  • 仕組みの費用は作った日ではなく、その後の見直しと切り分けに出る。サイトが増える、CMS が上がる、静的ファイルが増える、のたびに払う
  • 圧迫されたときの第一手は課金。月 5 ドルで確実に効く手があるなら、それより保守の重い仕組みを先に入れない
  • 作らない判断は、再訪の条件(数字)と一緒に記録する。条件を書かずに閉じると、次に同じ調査をやり直す

関連

この記事をシェア