PowerCMS X ブログ

2023-12-15

SSGの「インクリメンタル静的再生成」と PowerCMS X の静的ファイル生成

SSG(静的サイトジェネレーター)によって静的ファイルを生成し、AWS S3などのストレージにファイルを置いて CDNで配信する。

いわゆるサーバーレスとかモダンアプリケーションとか呼ばれるものが最近のトレンドのようですが、PowerCMSや PowerCMS Xは静的ファイルを生成するタイプの CMSですので、SSGを使わなくともこのような構成が取れます。しかも非常に高速にパブリッシュができます。

SSG(静的サイトジェネレーター)のデメリット

  • SSGのデメリットとして、コンテンツ数・ページ数が多くなるとビルドに時間がかかるようになる
  • 全体をビルドしないといけないので、更新頻度の高いウェブサイトに向かない

などという話があります。(私たちにとっては)何を今さら... といった感想しかありません。

インクリメンタル静的再生成 (Incremental Static Regeneration; ISR)

そこで登場したのが「インクリメンタル静的再生成」というのだそうですが、簡単に言うと

一定時間ごとにバックグラウンドでデータの再取得およびページの再レンダリングを行い、HTML を再生成 (regenerate) する手法

ということだそうです。なるほど。

export async function getStaticProps(context) {
  const entryId = context.params.id
  const entry = fetchContent(entryId)
  return {
    props: { entryId, entry },
    revalidate: 300, // <= 300秒 = 5分
  }
}

5分間は生成済みのコンテンツを返し、それ以降にリクエストがあった時には再生成する、という手法のようですが、すべての SSGが同じ仕組み(上記の例は Next.js)というわけでもありません。

確かにすべてをビルド(再構築)するより効率的で無駄がない、と考えられますが、以下の課題の解決には至りません。

  • 5分の間にコンテンツが更新されていたら、ユーザーは古いページを返されることになる
  • 5分経過後のリクエストがあった時、そのページには更新がかかっているの? いないの? 更新がなくても再生成するの?

PowerCMS Xのコンテナと再構築トリガー

PowerCMS Xはこれらの問題をすべてクリアすることを目標としています。あくまでも「目標」ですが、ウェブサイト内の更新されたコンテンツと関連する部分のみをリアルタイムに更新するための細やかな設定が行えるのが特長です。

トップページ、サイトマップ、最新記事リストなどの一覧系ページ

いわゆる「インデックス・アーカイブ」と呼んでいるものです。テンプレートに対するページが基本的に 1ページのみとなるものについては「再構築トリガー」を指定します。

  • PowerCMS / Movable Typeでは再構築トリガーは、コンテンツ(モデル)とインデックスのトリガー指定しかできません。
  • また、スコープを考慮した設定とすることもできません。

PowerCMS Xでは、インデックス・アーカイブに限らず、再構築トリガーを指定できますし、「スコープをまたがる」か「スコープをまたがらないか」を指定できます。

1つのテンプレートから複数のページが出力されるアーカイブのトリガー設定

URLマップの編集画面「再構築トリガー」「コンテナ」の設定項目のスクリーンショット

1つのテンプレート(URLマップ)から複数のページが出力されるアーカイブとは、たとえば記事アーカイブ、ページアーカイブ、カテゴリアーカイブなどを指します。

このようなアーカイブに対してトリガーを指定する際には注意が必要です。

  • たとえば、記事が1000ある場合、「記事」アーカイブに対して「記事」をトリガー指定してしまうと、
  • 1つの記事が更新(追加、更新、削除)されたタイミングで 1000記事をビルド(再構築)することになります。

たとえば最新記事 5件をすべてのページに載せているから、といった理由はわからなくはありませんが、最新 5件目以降の記事が更新されて変化がないのに 1000ページを再構築するのは負荷が高すぎます。

※ インデックス・アーカイブを JSON出力して非同期に読み込むなどの方法を検討すると良いでしょう。

カテゴリアーカイブに対して「記事」を再構築トリガーに指定しているケースを見かけることがありますが、これもお勧めしません。以下を理解していただくと良いと思います。

コンテキストとコンテナ

再構築トリガーの設定とよく似たものに「コンテナ」という設定があります。

  • 「カテゴリ」は「記事」のコンテナ(入れ物)
  • 「フォルダ」は「ページ」のコンテナ(入れ物)

のような関係性を指します(PowerCMS Xは自由すぎるが故にたとえば「ページ」のコンテナに「記事」を指定することすら可能ですが)。

「カテゴリ」アーカイブのコンテキストでは、コンテナに指定された記事については、そのカテゴリに関連づいているものだけがリストアップされます(ignore_archive_context指定をしない限りは)。

この時、記事の更新と連動して更新されるカテゴリは以下のルールによります。

  • その記事が属して「いる」カテゴリ
  • その記事が属して「いた」カテゴリ

たとえばカテゴリを1つ指定して記事を投稿すると、カテゴリが100個あっても連動して更新されるカテゴリアーカイブのページは 1ページのみです。
たとえば既に「エンゼルス」カテゴリ指定があり、カテゴリを変更して「ドジャース」に変更した場合、「エンゼルス」「ドジャース」のカテゴリアーカイブのページが再構築されます。

例 : ページのコンテンツの下に「このページと関連する記事一覧」というコンテンツリストを掲載したい

  • 「記事」モデルから「ページ」モデルをリレーションで関連づける
  • ページアーカイブのコンテナに「記事」を指定

リレーションによる関連付けのUI(スクリーンショット)

こうしておけば、記事が更新(追加、更新、削除)されたタイミングで再構築されるファイルはリレーションによって関連づけられている(いた)ページのみとなって効率的です。

この、URLマップのモデルとコンテナとの関係性ですが、効率的で良いのですが再構築トリガーと異なり、問題としては以下のようなものが考えられます。

  • 1つの URLマップに対して 1つのモデルしか指定できないこと
  • コンテナ指定されたオブジェクトの更新とは連動するが、逆は連動しない

という点です。前者は例えば「情報分類」モデルを複数のモデル(例えば記事、ページ)で利用していた場合、記事とページのどちらかしか指定できません。
また、後者は、例えば記事が更新されればカテゴリアーカイブは更新されますが、カテゴリのラベルを変更して保存した時、カテゴリに属する記事は再構築されません。

RebuildRelations(プラグイン)の利用

この再構築トリガーとコンテナの弱点を補うのが「RebuildRelations(プラグイン)」です。

例 : 写真が差し替えられたらその写真が利用されているページをすべて連動して再構築したい

RebuildRelationsなら、それができます。例えば以下のように設定すれば実現できます。

参照元モデル「記事」「ページ」「ウィジェット」リレーションモデル「アセット」にチェック

アセットの場合は通常はアーカイブはありませんが(PowerCMS Xは自由すぎるので、アセットアーカイブをつくることは可能)、
さきほどの「ページのコンテンツの下に「このページと関連する記事一覧」というコンテンツリストを掲載したい」のようなケースでは、以下のように設定することで実現できます。

先ほどの設定に加えてリレーション先モデルの「ページ」にチェックを追加

この時、さきほどのコンテナの例と異なるのは「相互の」更新を反映して再構築ができる、という点です。

  • 記事を更新すれば記事が参照している(いた)ページが再構築される
  • ページを更新すれば、そのページを参照している記事が再構築される

いずれにしても、効率的に更新をかけたい部分のみをピンポイントで更新されるような設定ができます。尚、RebuildRelationsによる再構築もカテゴリが「エンゼルス」から「ドジャース」に変わった時に両方を再構築してくれます。

URLマップの「ファイル出力」の使い分け

URLマップには「ファイル出力」という設定項目があります。

ファイル出力のドロップダウン選択メニュー

規定値は「静的」ですが、ダイナミック(動的)も指定できます。以下の選択肢があります。

  • ダイナミック(動的)
  • 静的
  • 静的(遅延)
  • オンデマンド
  • キュー
  • 手動(インデックス・アーカイブのみ)

ダイナミックは文字通り動的生成で、サイト内検索やフォーム、ログインページなどで利用します(PowerCMS Xでは、フォームや検索などのページを静的に書き出しておいて、パラメタ付きリクエストや POSTリクエストの時だけ動的にする、といったこともできます)。

静的と静的(遅延)の違いは、再構築のタイミングです。静的は管理画面からのオブジェクトの保存などの時に再構築を行いますが、静的(遅延)の場合は画面にレスポンスを返した後に再構築を行います(画面レスポンスは速くなりますが、画面上でエラーが拾えません)。

最後の「手動」はビューの編集画面で「保存と再構築」ボタンをクリックした時のみ再構築を行います。

このうち、あまり活用されていないように思えるのが「オンデマンド」と「キュー」です。

オンデマンド再構築

オブジェクトの保存時などには再構築を行わず、リクエストがあった時に初めてページをビルドするタイプの設定です。アクセスの少ないページやアクセスがさほど多くないのに更新が頻繁に行われるようなページで恩恵を受けられます。

この時、ビルドされたページはそのままディスクに書き出されるため、次回以降にアクセスしてきたユーザーにはそのコンテンツをそのまま返却します。「インクリメンタル静的再生成」に近いと言えるかもしれませんが、ファイルの生存期間を決めるわけではなく、ページに更新がなければファイルはずっとそのままです。

更新は適切なタイミングで行われる

では、更新があった時どうするのかですが、再構築トリガー、コンテナ、RebuildRelationsの設定に従い、更新すべきものは更新すべきタイミングでディスクに書き出されたファイルを削除してしまいます。その後、リクエストがあった時に初めてページをビルドする、という流れになります。

コンテンツがいくら更新されてもリクエストがなければビルドは行われません。その代わりに、ページがない状態でアクセスしたユーザーは動的生成のレスポンス(つまり、少し待たされること)になります。

キューによる再構築

最後に、キューによる再構築をご紹介します。再構築キューは、CMSの管理画面でオブジェクトを保存したり再構築した段階ではファイルをビルド(再構築)せず、cronに登録されたスケジュールタスクコマンドによって再構築を行うものです。

  • 非常に重たいアーカイブのテンプレートになっている
  • オブジェクト数がとても多い
  • 更新が頻繁にかかるが、多少のタイムラグがあってもサーバーの負荷を一気に上げるのは避けたい

といった場合に「キュー」を選択することになります。ポップアップウィンドウを開いて、何時間も再構築が終わるのを待つのもなかなかしんどいところがあります。ただ、頻繁に更新されるページの再構築を纏められること、管理画面操作で再構築を待たなくてもいいこと、一気に負荷を上げずに非同期に再構築ができること、などのメリットがあります。

再構築キューが好まれない理由

これはもう「リアルタイムに変更が反映されない」につきると思います。この欠点を補うため、以下の環境変数を指定します(次のリリースでの機能追加)。

    "save_queue_realtime": true,

これを設定することで、記事アーカイブをキューにしていたとしても、その記事を「保存」したときは例外的にリアルタイムで再構築されます。反映を待つ必要はありません。

また(これも次のリリースになりますが)、スケジュールタスクによって公開、差し替えられたコンテンツはそのタスクの処理内で再構築されることが保証されるようになります。

再構築キューは 1回の処理数を少なく、頻繁に起動するように設定する

PHPのバッチ処理は 1つのインスタンスで長時間にわたる重たい処理を行うのにはあまり向いていません。メモリに大きなデータが溜まってくると速度が落ちていくことがあるからです。

再構築キューは、短い頻度で起動し、少しづつ繰り返して処理を実行するのがポイントです。

*/5 * * * * apache cd /home/www/PowerCMSX/ && /usr/bin/php ./tools/worker.php --task_ids publish_queue --publish_queue_limit 25
*/5 * * * * apache cd /home/www/PowerCMSX/ && /usr/bin/php ./tools/worker.php --task_ids publish_queue --sleep 150 --publish_queue_limit 25 --publish_queue_offset 25

この設定では、5分おきに2プロセス 25件づつ再構築キューを処理します。重複しないように片方は 2分半待ってから実行され、publish_queue_offset分をスキップしてから実行されます。

通常のスケジュールタスクについては、逆に --exclude_ids publish_queue を指定して、別のタスクにしておくのがポイントです。

*/10 * * * * apache cd /home/www/PowerCMSX/ && /usr/bin/php ./tools/worker.php --exclude_ids publish_queue

ポップアップウィンドウからのすべての再構築は最終手段

構築フェイズでのテンプレート修正、デザイン反映などは別にして、「反映されないときは右上の再構築ボタンから再構築してください」みたいなマニュアルを作っておけば? みたいな設定は、サイトの規模が小さければ良いかもしれませんが、巨大なメディアサイトや商品点数が莫大な ECサイトなどでは通用しません。

ビュー(テンプレート)を最適化して高速なパブリッシュができるようにするのはもちろん、関連するコンテンツの再構築連動も最適化していくようにしましょう。PowerCMS Xなら、それができます!

カテゴリー:技術情報 | プラグイン

投稿者:Junnama Noda

ブログ内検索

アーカイブ