こんにちは。
今回は、自身で運営している個人サイト(GAME PON-POO)の移行時について。そこそこ規模の大きいサイトでも、Cloudflare WorkersとR2の無料枠でも十分に運営が可能だったという内容になります。
対象となるサイトは、ゲーム情報のアーカイブサイトとして、4年くらいWordPressで運営してきました。1983年以降から最新のゲーム情報を掲載しているため、総記事数は数万ページに及び、それぞれの記事にはジャンルやメーカー名など10種類以上のカスタムフィールドと呼ばれる追加データを保持していました。
個人サイトとして運営を続けるにあたり、特に問題ありませんでしたが、数年間で追加・蓄積したデータを整理したかったのですが、多大な時間もかかるため、どうせなら別環境への移行をと、考えていました。
そこで、表示速度の向上とインフラのポータビリティ(移行のしやすさ)の確保を目指し、フロントエンドフレームワークであるAstroへの移行を進めました。今回は、インフラ環境としてCloudflare WorkersとR2を採用しています。
データをファイルベースのサーバーレス構成にしておけば、将来的にVPSや他の安価なサーバーへ再度乗り換えることになった場合でも、移行作業が格段に楽になります。今回は、まずドメイン代のみのコストでこの規模のサイト群を維持できるかを検証するため、Cloudflareの無料枠の範囲内で開発をスタートしました。
しかし、Astro側で動的な探索ロジックを組んで動かしたところ、Cloudflare無料枠の「WorkersのCPU時間10ms制限」は軽く超え、エラーも多く安定性にかけていました。サイト全体のファイル数を考えると、このような事はわかりきっていたことですが、
本記事では、この厳しい制限をクリアして安定稼働に導くために、どのような試行錯誤を経て現在のアーキテクチャに行き着いたのかを書いていきます。
1. 最初の試行錯誤:MarkdownとJSONの分離運用
最初はMarkdownとは別にJSONを生成し、そこから読み込ませる方式で設計を進めました。
しかし、Astro側で動的な探索ロジックを組んで動かしたところ、Cloudflare無料枠の「WorkersのCPU時間10ms制限」を軽く超え、エラーが多発して安定性にかけていました。サイト全体のファイル数を考えると、ある程度予測できていた事態ではありましたが、この厳しい制限下でいかに安定稼働させるかが最初の壁となりました。
2. KVの検討と不採用の理由
この問題を解決するため、URLのキーと実際の記事データをKVに保存して読み込むアプローチを検討しました。しかし、検証の結果、無料枠の制限を考慮すると、数万記事規模のデータをKVで管理・運用するのは現実的ではないと判断し、採用は見送りました。KVによるデータ管理ではなく、R2をベースにした設計へと舵を切ることになりました。
3. 解決策①:ファイルシステムと完全一致させる「URL階層の再設計」
R2単体での運用へ切り替えるにあたり、まずURL維持のために発生していた動的探索による「WorkersのCPU超過エラー」を解消するため、URL階層をR2のオブジェクトキーと完全一致させる構造に変更しました。
・旧仕様のフラットなURL:https://example.com/[slug]
・新仕様の階層化されたURL:https://example.com/[category]/[slug]
階層を明確に分けることで、Workersは総当たりやデータベース的な探索を行う必要がなくなり、リクエストされたパスから直接R2のオブジェクトキーを一発指定でピンポイント取得できるようになりました。これにより探索に伴うクエリ負荷とCPU時間を削減しました。
4. 解決策②:一覧データの最適化と「高負荷処理」の排除
無料枠のメモリ128MB・CPU 10msという制限内で、数万記事のアーカイブを安定してハンドリングするため、以下の最適化を行いました。
① メタデータの圧縮とキー名の短縮
Markdown自体は人間が判読可能な形式で記述されていますが、機械的な読み取りを高速化させるため、スクリプトを用いてメタデータを記号化し、大幅な短縮・圧縮を行いました。属性データを保持しつつJSONサイズを最小化しています。
② URL階層化による全件処理の排除
URLのパス構成を最適化したことで、リクエストに対して全件を走査する必要がなくなりました。特定の階層に対応するデータだけを読み込めるようになったため、設計段階で以下の処理は排除しています。 ・全件検索機能 ・ループ処理による階層別の一覧取得
③ 年別ディレクトリによるJSON細分化
全記事のインデックスを1つにまとめると巨大化するため、R2内部では公開年ごとのディレクトリに配置しました。
・R2内の格納イメージ:r2-bucket/json/[year]/index.json
URLの階層からその年のJSONだけをピンポイントで読み込ませることで、余計な全件処理を走らせない構成にしています。
5. 解決策③:他サイトとの兼ね合いを考慮した「エッジキャッシュ戦略」
同じドメイン(同一アカウント)配下で他のサイトも運用しているため、無料枠(Workers 10万リクエスト、R2 1,000万 Class B)は共通で消費されます。他サイトの余力を残すため、Cache Rulesを活用して更新頻度に応じたTTLを設定しました。
・過去の古い記事および年代別一覧:キャッシュ期間 5年 ・直近の新しい記事:キャッシュ期間 1日
これにより、一度エッジにキャッシュされれば以降はWorkersもR2も経由せず、ドメイン全体の共通枠を保護しています。
6. 全リダイレクトを見送ったトレードオフ
インフラ負荷軽減を優先し、数万記事の全リダイレクト設定は見送りました。1.8万行のマッピング保持自体がWorkersの負荷になるリスクがあるためです。古いURLは404として自然削除させ、新環境へクローラーを誘導する戦略を選択しました。404ページも静的HTMLにしてエッジで即時返せるようにしています。
7. 検証:移行完了直後のWorkersメトリクス
複数サイトが並行稼働する環境下でのメトリクス結果です。
| メトリクス項目 | 計測数値 | ドメイン全体における比率 |
|---|---|---|
| 平均CPU時間 | 8.34 ms | 44.36% |
| ウォールタイム | 204 ms | 46.08% |
| リクエスト期間 | 202 ms | 44.18% |
データの分析
① 10msの制限を8.34 msでクリア
Cloudflare Workersの無料枠における10msのCPU時間制限に対し、実行時のCPU時間は8.34 msに抑え込まれています。事前JSON化とURL階層化によるR2ピンポイント指定、高負荷処理の排除により、CPUの処理負荷を低く保てていることが分かります。
② リクエスト期間202msのレスポンス
処理時間は202msとなっており、Cloudflareのエッジで迅速に処理が完了しています。
③ リアルタイムエラーの解消
過去24時間の集計では、旧バージョンの残りやURL変更に伴うクローラーの空振りによるエラーが一時的に記録されていましたが、直近20時間以上のリアルタイムログでは0を維持しています。
また、システム面の最適化と並行して、Astroのテンプレートやデザイン、コンポーネント構造などの調整も行いました。バックエンドとなるR2やJSONのデータ構造をシンプルにしたことで、フロント側のテンプレート構築におけるデータ取り回しも軽快になっています。
最後に
今回の移行にあたり、インフラ環境を無料で利用させてもらっていることもあり、管理を集約する意味も兼ねてドメイン自体もCloudflareへ移管しました。ドメイン代だけで、これほど大規模な構成を安定運用できるのは、非常に効率的な環境だと感じています。
ドメイン共有による制限を考慮し、システムに負荷のかかる機能を最初から作らない設計と、持続可能性やパフォーマンスを優先するトレードオフを行うことで、Cloudflareの無料枠という制限下でも、エラーのない高速な大規模SSRサイトを維持することが可能になりました。
WordPressから移行する際、ランキングのような動的機能は排除しました。しかし、システム面を最適化したことで、数万ページ規模であってもWP時代と遜色のないサイトとして仕上がっているかと思います。
補足:複数サイトの共有枠について Cloudflareの無料枠はアカウント単位で管理されるため、本サイトの他にも3つの検証用サイトを同じアカウント下で運用しています。各サイトの合計リクエスト数が無料枠内に収まるよう、本サイト側の負荷を徹底的に削減し、最適化を行いました。個人サイトレベルでは問題なく運用可能となっています。
補足:WordPressの運用について WordPressは、プラグインによる拡張性、膨大なノウハウやトラブル解決策の多さ、そして豊富なデザインテーマにより、運用基盤として非常に強力なCMSです。また、ヘッドレスCMSとして利用することも可能なので、懸念であった表示速度にも対策されています。
WordPressは技術者でなくても扱えるように設計されており、サーバー側の標準機能も最初から揃っています。一方で Astro は本来フレームワークで、従来は構築や運用に技術が必要でした。しかし EmDash のように管理画面やデプロイ環境まで含めて整備されたサービスが出てきたことで、Astro 系でも“CMS として運用できる環境”が揃い始め、WordPress と比較して選択肢に入れられる段階になってきています。
技術背景の解説:サーバーレス構成と無料枠の制限
今回のサイト構築で採用した技術と、Cloudflare の無料枠についての基礎知識です。
採用技術の概要
- Astro (SSR): 高速な表示を実現する Web フレームワーク。今回は動的なページ生成を行う SSR(サーバーサイドレンダリング)モードを使用しました。
- Cloudflare Workers: エッジネットワーク上でコードを実行するサーバーレス機能。
- Cloudflare R2: 高速かつ下り転送量が無料のオブジェクトストレージ。今回のサイトのデータ格納庫として使用しました。
無料枠の制限と超過時の扱い
Cloudflare の各サービスには無料枠が設定されています。Workers については、リクエスト数や CPU 時間の制限を超えても、翌日には制限がリセットされ、自動的に無料枠が適用されます。
| 制限項目 | 無料枠の基準 | 超過時の扱い |
|---|---|---|
| Workers CPU時間 | 1リクエストあたり10ms | 制限を超過したリクエストのみエラー(翌日リセット) |
| Workersリクエスト | 1日 100,000回 | 制限を超過したリクエストはエラー(翌日リセット) |
| R2 Class B リクエスト | 月 1,000万回 | 無料枠を超過した分のみ従量課金が発生 |
| R2 ストレージ容量 | 月 10GBまで | 無料枠を超過した分のみ従量課金が発生 |
Workers については制限を超えるとリクエストが拒否される仕様ですが、翌日には自動的に枠が回復するため、超過によって課金が発生するわけではありません。一方、R2 は無料枠を使い切った後にアクセスが発生した場合に限り、従量課金が発生します。
このように、限られた CPU 時間やリクエスト枠の中で動作させるためには、「いかに Workers を動かさずにレスポンスを返すか(キャッシュ戦略)」と「いかに計算量を減らすか(URL 階層化とピンポイントアクセス)」が、制限内での運用およびコスト管理における成功の鍵となります。
無料枠運用という前提について
今回の構成は無料枠を前提に最適化していますが、必要に応じて有料プランへ移行しても負担は大きくありません。また、Workers や R2 のようなサーバーレス特有の制限を気にしたくない場合は、一般的な VPS を利用する方法もあります。VPS であれば CPU 時間やリクエスト数といった制限は基本的に存在しないため、今回のような調整は不要になります。
ただし今回は、無料枠でどこまで実用的に運用できるかを検証する目的があったため、サーバーレス環境に合わせた最適化を行っています。