全国のDrupalistのみなさん、こんにちは!
今回はQiitaのアドベントカレンダーに参加するということで気合を入れてオリジナル記事を書く・・・はずが時間がなかったので翻訳にさせていただきました。 テーマは「キャッシュ」です。先日のミートアップでキャッシュの話がちらっと出たので、勉強がてら翻訳してみました。

「Cache APIについて 日本語訳」という記事でも触れましたが、Drupalは「アクセス結果からエンティティ、URLまで、直接レンダリング可能なデータや、何をレンダリングすべきかと決めるデータのすべて」のキャッシュを残すことが出来ます。そして、それぞれデータのキャッシュが「どのような条件下で、どれくらいの長さ保存されるか」を「キャッシャビリティ(Cacheability)」と呼び、「キャッシャビリティ・メタデータ」という形式のデータを書くことでそれらの設定を行うことができます。 この文章では特にHTMLレンダリングに使われる 「レンダリング配列」のキャッシャビリティの設定方法について説明されています。 なお、レンダリング配列について詳しく知りたい方は「レンダリング配列について 日本語訳」を御覧ください。

それではみなさんよいクリスマス&お正月を!

(翻訳開始)

レンダリング配列のキャッシャビリティ

原文: Cacheability of render arrays(翻訳時の最終更新日:2018年9月28日)

レンダリング配列は何をユーザーに見せるかを決めます。 そのため、レンダリング配列はレスポンスのキャッシャビリティも決めます。

とてもダイナミックなレンダリング配列を生成するコード(一般的にはたくさんのif文を使っているコード)をレンダリングするということは、Drupalは単純にレンダリング配列によって作られたHTMLをキャッシュできないことを意味します。Drupalは時と場合に応じて(if文を含む)コードを呼び出さなくてはいけません。

別の言葉で言うなら、Drupalはコードがどれだけダイナミックであるのかを知らなくてはいけません。そうでないと、キャッシュされたHTMLを間違ったユーザーに送ってしまうことになります!(筆者注: 例えば、ユーザーAのみが見れるページをキャッシュしたものをユーザーB用に使うことは出来ません。Drupalが現在のユーザーを読み取って個別にキャッシュする必要があります。)

(サイトの表示を適切に効率化する上で)一番重要なことは、レンダリングAPIにレンダリング配列のキャッシャビリティを知らせることです。

レンダー・キャッシュの考え方

レンダリング配列を生成するときはいつでも、以下の5つのステップを使いましょう。:

  1. 「私は何かをレンダリングしようとしている。ということはその部分のキャッシャビリティについて考えないといけない。」
  2. 「その部分はレンダリングするのにたくさんのメモリや時間を消費する=キャッシュする価値があるか? もし答えが「イエス」なら、この部分の表示を一意(他の部分から識別可能)にする要素にはどんなものがあるか?」 それらの要素が キャッシュ・キー(cache key) になります。
  3. 「その部分の表示は権限、URL、インターフェースの言語などによって変わるか?」それらの要素は キャッシュ・コンテクスト(cache context) になります。注: キャッシュ・コンテクストはHTTPのvaryヘッダーと完全な相似になっています。
  4. 「何がその部分の表示を古くさせるか? 別の言葉で言うと、レンダリングしようとしている部分が依存していて、その(依存されてる)要素が更新されることでレンダリングしようとしている部分も更新されないといけないような要素はあるか?」それが キャッシュ・タグ(cache tag) になります。
  5. 「いつその部分の表示が古くなるか?他の言葉で言うと、そのデータは決まった期間のみ有効か?」それが キャッシュの寿命(max-age) になります。デフォルトでは「永遠にキャッシュ可能」(Cache::PERMANENT)となっています。表示が決まった期間のみ有効の場合、キャッシュ寿命を秒単位で指定ましょう。キャッシュの寿命が0の場合、それはキャッシュ不可能であるということです。

キャッシュ・コンテクスト、キャッシュ・タグ、キャッシュ寿命は常に設定されてないといけません。 なぜなら、それらはレスポンス全体のキャッシャビリティに影響するからです。それらのパラメーターは「バブル(伝達)」します。つまり、レンダリングされた部分の親(レスポンス・オブジェクト)は自動的にそれらのパラメーターを受け取るのです。 キャッシュ・キーはそのレンダリング配列がキャッシュされる場合のみ設定される必要があります。

抽象的な例

上記の考えを抽象的な例に当てはめてみましょう。

  • キャッシュ・キー : ノードをティーザー・ビュー・モードでレンダリングする場合、キャッシュ・キーは['node', 5, 'teaser']のようになります。
  • キャッシュ・コンテクスト : ノードのティーザーの表示が閲覧ユーザーのタイムゾーンによって変わる場合、キャッシュ・コンテクストは['timezone']のようになります。
  • キャッシュ・タグ : ノードのティーザーがノードのタイトル、作成日、著者、著者の写真、本文を表示する場合、そのティーザーはノード本体と、著者のユーザーエンティティ、著者の写真を格納するファイルエンティティ、本文に使われているテキストフォーマットに依存しています。それら依存先の要素が一つでも変われば、そのティーザーのHTMLは書き換えられなくてはいけません。このとき、キャッシュ・タグは['node:5', 'user:3', 'file:4', 'config:filter.format.basic_html']のようになります。
  • キャッシュの寿命 : ティーザーは時間にかかわらず、ノードが変更されるまで有効です。そのため、キャッシュの寿命を設定する必要はありません。デフォルト(Cache::PERMANENT)のままにしておいて問題ないでしょう。

(注意: (ティーザーの場合)Drupalは自動的に上記のキャッシャビリティ・メタデータを設定してくれます。エンティティ・ビュー・ビルダー(Entity View Builder)とフィールドフォーマッター(Field Formatter)がそれを担当します。この例は誰でもDrupalのキャッシュを理解できるようにとあえて作ったものです。)

具体的な例

注意してほしいのは、すべてのエンティティとコンフィグがCacheableDependencyInterfaceを実装しているということです。そのインターフェースはエンティティ/コンフィグオブジェクトが編集されたらいつでもレンダリング配列のキャッシュが無効化するようなキャッシャビリティ・メタデータを提供しています。 詳しくはCacheableDependencyInterface & friendsをご覧ください。

$renderer = \Drupal::service('renderer');

$config = \Drupal::config('system.site');
$current_user = \Drupal::currentUser();

$build = [
  '#markup' => t('Hi, %name, welcome back to @site!', [
    '%name' => $current_user->getUsername(), 
    '@site' => $config->get('name'), 
  ]),
  '#cache' => [
    'contexts' => [ 
      // "current user"は上で定義されているように、リクエストによって変わります. 
      // そのため、Drupal に 'user' ごとにキャッシュのバリエーションを変えるように教えます.
      'user', 
    ],
  ], 
];

// 上記のキャッシャビリティ・メタデータに、コンフィグ・オブジェクトのキャッシャビリティ・メタデータと、
// ユーザーエンティティのキャッシャビリティ・メタデータを合成します.
$renderer->addCacheableDependency($build, $config);
$renderer->addCacheableDependency($build, \Drupal\user\Entity\User::load($current_user->id())); 

ここではユーザー名とサイト名を含むマークアップを生成しています。ユーザー名はユーザーエンティティに紐づけられているので、キャッシュ・タグを関連付けます。そしてそのアウトプットが閲覧するユーザーごと異なるようにしたいので、キャッシュ・コンテクストにuserを設定する必要があります。サイト名はコンフィグに格納されているので、Configオブジェクトのキャッシュ・タグも関連付けます。 ちょっとした長さのコードにはなりましたが、このレンダリング配列とマークアップのキャッシャビリティをDrupalに的確に知らせることが出来ました!

閲覧者によって内容を変えずに、すべての人に対して同じ表示メッセージを表示するパターンも見てみましょう。

$renderer = \Drupal::service('renderer');

$config = \Drupal::config('system.site');

$build = [
  '#markup' => t('Hi, welcome back to @site!', [ 
    '@site' => $config->get('name'),
  ])
]; 
$renderer->addCacheableDependency($build, $config); 

画面に生成されるマークアップは少ししか違いませんが、コードはかなりシンプルになりました。すべてのユーザーに対して同じマークアップを適用できるので、必要とされる キャッシャビリティ・メタデータが少ないのです。特筆すべき点はキャッシュ・コンテクストが存在していないことでしょう。

ヘッダー(デバッグ)

あるページがどのキャッシュ・コンテキストのバリエーションを表示しているのか、どんなキャッシュ・タグによって期限切れにされているのかを知るのはとても簡単です。HTMLの X-Drupal-Cache-ContextsX-Drupal-Cache-Tags ヘッダーを見ればいいのです。 それらのヘッダーを見れるようにするには、こちらをご覧ください。

(翻訳終わり)