皆さん、こんにちは。Sitecore MVPの赤澤 光世です。
こちらの記事はSitecore Advent Calender 2014の12月22日の記事です。
Sitecoreのアドベントカレンダーもかれこれ3年目となりました。
今回はSitecoreのHTMLキャッシュについて、ご説明させていただきたいと思います。
動的表示プラットフォームSitecore
Sitecoreではサイト訪問者のブラウザ情報を始め行動履歴など様々な情報をもとに サイト表示を動的に切り替え、パーソナライズすることが出来ます。
このため、ひとりひとりの訪問者にとって適切なサイト体験を提供することが可能です。
一方で、この動的CMSという特性が故にサイト表示時におけるパフォーマンスについては考慮が必要です。
HTMLキャッシュとは
Sitecoreでは快適なサイト閲覧環境を実現するために数々のキャッシュ機構を持っておりますが、 今回は中でも一番使用頻度が多いであろう「HTMLキャッシュ ※」について取り上げます。
※ アウトプットキャッシュとも呼ばれます。Sitecoreが扱うキャッシュ詳細については文末に幾つか参考リンクをご紹介させていただきますので必要に応じて参照してください。
Sitecoreはページを表示する為にページの構成要素を「サブレイアウト」といったパーツに分割作成し管理します。
HTMLキャッシュ機能とは、このサブレイアウトの単位で一度表示した表示内容をキャッシュしておき、 2回目以降の表示を高速化する機能のことです。
サブレイアウトの表示する内容や処理内容にもよりますが、記事の一覧を表示するようなサブレイアウト等の場合、 2回目以降はデータの取得処理自体が割愛されますので非常に高速に動作します。
各サブレイアウトはそれぞれ表示する内容や条件などが違いますので、 管理画面上でサブレイアウトをどのような単位でキャッシュするかを設定することが出来、 デフォルトでは以下の単位でHTMLキャッシュを分けることが可能です。
一般的なサイトであればこの条件を駆使することですべてのHTMLキャッシュを管理することが可能となるかと思いますが、 この条件以外で出力内容を変化させる場合にはカスタマイズが必要となってきます。
ここでは具体的な例として、HTTPとHTTPSで表示内容を分ける場合を紹介したいと思います。
HTTPSサイトでの出力をキャッシュする
よくある例としてHTTP/HTTPSで画像やjsなどの読み込みURLを分けることがあるかと思います。
例えば、画像やcssなどのコンテンツ配信をCDN(Contents Delivery Network)経由で行っている場合、 CDN側で独自ドメインでのHTTPSがサポートされていなかったり、追加料金が発生したりということがあります。
そのためHTTPSの領域が少ないサイトであれば、HTTPSの時はCDNを利用しないような工夫することがあります。
HTTPの時はCDN配信、HTTPSの時はSitecoreサーバーから直接配信。というような形です。
表示したい内容としては同じなのですが HTTPSでのサイト表示時に一部でもHTTPでアクセスするファイルが混在していると、 ブラウザは以下のような警告を表示するため、HTTP/HTTPSの表示を適切にキャッシュしてあげる必要があります。
このようにHTTP/HTTPSでキャッシュを分ける必要がある場合、 さきほどのキャッシュ条件の他に「HTTP/HTTPS別に異なるキャッシュを使用」といった条件が必要です。 ただしサブレイアウト一つ一つで、HTTPSの領域で使われる可能性があるのか判断してキャッシュの設定していくような形ですと、まず間違いなく設定ミスや漏れが発生します。
そこでここではシステム全体で自動的にHTTPとHTTPSのキャッシュを分ける実装をしてみたいと思います。
HTTP/HTTPSでキャッシュを分けるカスタマイズ
ここからは具体的なカスタマイズ例をご覧いただこうと思います。
まず、環境としてクリーンなSitecore環境を用意し、 動作確認用にHTTPとHTTPSで画像を出し分けるサブレイアウトを作成してみました。
HTTP/HTTPSでの表示切り替えサブレイアウトのascxファイル Image.ascx
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="Image.ascx.cs" Inherits="NextScape.Blocks.Image.Image" %> <%@ Register TagPrefix="sc" Namespace="Sitecore.Web.UI.WebControls" Assembly="Sitecore.Kernel" %> <%if (this.IsSSL) { %> <img src="/secure.png" /> <% } else { %> <img src="/non-secure.png" /> <% } %>
表示としては以下のようなイメージです。
この段階でキャッシュ有効にしてみましょう。
該当サブレイアウトアイテムを開きキャッシュ情報の中からキャッシュ有効にチェックを付け、パブリッシュします。
その後で、HTTP、HTTPSの順番でアクセスしてみると最初に表示したHTTP時点の内容がキャッシュされ、2回目以降はキャッシュされたものが表示されていることがわかります。
キャッシュ有効後のHTTPでのアクセス
キャッシュ有効後のHTTPSでのアクセス
ここで、実際にどんな形でHTMLキャッシュが管理されているか見てみましょう。
Sitecoreのキャッシュ内容を確認する
Sitecore標準の
/sitecore/admin/cache.aspx
という管理画面でキャッシュがどれだけ利用されているかなど統計的な情報が確認可能です。
この画面はチューニングの際にはよく利用することとなりますが、一方で詳細な情報は確認できません。
そこで今回は、もう少し具体的にキャッシュ内容を見ることができる管理画面をSitecoreマーケットプレイスで公開してくれている方がいらっしゃいますので、こちらを利用して確認したいと思います。
Caching Manager - Sitecore Marketplace
https://marketplace.sitecore.net/en/Modules/Caching_Manager.aspx
※ 対応Sitecoreバージョンが6.5までの表記ですが、v7.2でも正常に動作するようです。
上記マーケットプレイスから「DOWNLOAD」を選択し、Legal Noticeが表示されたら 「I AGREE」を選択した上でファイルをダウンロードしてください。
ダウンロードしたzipファイルの中に「CacheUI.aspx」というファイルがひとつだけ入っていますので、 Sitecoreが動作しているWebsite配下に設置して使用します。
ここでは例として、/sitecore/admin/CacheUI.aspx に設置します。
設置後、ブラウザからアクセスするとSitecoreが管理するキャッシュを検索、クリアできる画面が利用可能になっています。
それでは先程のキャッシュの内容をみてみましょう。
「Caches By Sites」タブを開き、Typeで[html]、System Names(サイト名)で[website]を選択し、 「Get Cache Entries」をクリックします。
すると[website]で生成されたHTMLキャッシュの一覧が表示されます。
Sitecoreキャッシュを紐解く
実はSitecoreのキャッシュはKey-Valueの形でデータを保持する仕組みとなっています。
実際に、先ほど生成されたキャッシュを見てみるとキャッシュキーは
/layouts/Image.ascx_#lang:JA-JP
というような形で設定されています。
これはサブレイアウトアイテムで定義しているサブレイアウトファイルのPathと表示言語、 +キャッシュ設定で設定した情報が付与される形になっています。
要するにSitecoreのHTMLキャッシュはサブレイアウトを表示する際、 サブレイアウトアイテムに指定されたキャッシュ条件とアクセス情報を元にキャッシュキーを生成し、 そのキャッシュキーでキャッシュを検索して、発見できたキャッシュを使用する仕組みなっています。
それでは試しに、サブレイアウトのキャッシュ情報の全条件にチェックを入れた場合に キャッシュキーがどのように変化するか見てみましょう。
と設定しパブリッシュしておきます。
その上で
http://localhost/?querystring1=querystringvalue1
といった形でアクセスすると以下のようなキャッシュキーになります。
/layouts/Image.ascx_#lang:JA-JP_#data:/sitecore/content/Home_#dev:Default_#login:False_#user:extranet\Ano
指定したキャッシュ条件がキャッシュキーに含まれていることがわかります。
システム全体でHTTP/HTTPSでHTMLキャッシュを分ける
キャッシュの仕組みが理解できたらあとは簡単です。
要はHTTPのときとHTTPSの時でキャッシュキーを分けるようにカスタマイズすれば良いことになります。
単純にサブレイアウト作成時にキャッシュを分ける場合であれば、 Sitecore.Web.UI.WebControls.Sublayoutを継承したサブレイアウトを作成し、 GetCachingID()メソッドを上書きすることで対応が可能です。
以下の様なイメージになります。
Image.ascx.cs
using NextScape.SitecoreLibrary.Utils; using Sitecore.Web.UI.WebControls; namespace NextScape.Blocks.Image { /// <summary> /// Sublayout for Image. /// </summary> public partial class Image : Sublayout { /// <summary> /// Gets a value indicating whether this instance is SSL. /// </summary> /// <value> /// <c>true</c> if this instance is SSL; otherwise, <c>false</c>. /// </value> protected bool IsSSL { get { return RequestUtil.IsSSLRequest(Sitecore.Context.Page.Page.Request); } } /// <summary> /// Gets the caching identifier. /// </summary> /// <returns></returns> /// <remarks> /// If an empty string is returned, the control will not be cached /// </remarks> protected override string GetCachingID() { string key = base.GetCachingID(); if (string.IsNullOrEmpty(key)) { return key; } return string.Format("{0}_#isSSL:{1}", key, this.IsSSL); } } }
ただし、前述のとおり、システム全体でもれなくキャッシュキーを変えるような場合は工夫が必要です。
Sitecoreがサブレイアウトとして利用するベースクラスを差し替え、 その中でキャッシュキーを場合によって出し分けるようなカスタマイズが必要です。 以下にカスタマイズ方法を記載します。
サブレイアウトの拡張クラスを作成し、SSLの際にキャッシュキーを適切に追加する処理を実装します。
NextScape.SitecoreLibrary.Web.UI.NSSublayout
using NextScape.SitecoreLibrary.Utils; using Sitecore.Web.UI.WebControls; namespace NextScape.SitecoreLibrary.Web.UI { /// <summary> /// Nextscapeのサブレイアウト拡張 /// </summary> public class NSSublayout : Sublayout { /// <summary> /// Gets the cache key. /// </summary> /// <returns>the cache key</returns> public override string GetCacheKey() { string key = base.GetCacheKey(); if (string.IsNullOrEmpty(key)) { return key; } return string.Format("{0}_#isSSL:{1}", key, RequestUtil.IsSSLRequest(Sitecore.Context.Page.Page.Request)); } } }
Sitecoreのサブレイアウトインスタンス化の処理として、先ほど作成した拡張クラスを使用するようにします。
NextScape.SitecoreLibrary.Web.UI.SublayoutRenderingType
using System.Collections.Specialized; using System.Web.UI; using Sitecore.Reflection; using Sitecore.Web.UI; namespace NextScape.SitecoreLibrary.Web.UI { /// <summary> /// サブレイアウトの拡張クラス用Factory /// </summary> public class SublayoutRenderingType : RenderingType { /// <summary> /// Gets the control. /// </summary> /// <param name="parameters">The parameters. /// <param name="assert">if set to <c>true</c> [assert]. /// <returns>the control</returns> public override Control GetControl(NameValueCollection parameters, bool assert) { NSSublayout sublayout = new NSSublayout(); foreach (string text in parameters.Keys) { ReflectionUtil.SetProperty(sublayout, text, parameters[text]); } return sublayout; } } }
サブレイアウトインスタンス化に使用するクラスを差し替えるようにconfigを設定します。
\Website\App_Config\Include\ZOverride.NextScape.SublayoutCache.config
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:x="http://www.sitecore.net/xmlconfig/"> <sitecore> <renderingControls> <!-- カスタマイズしたSublayoutクラスのインスタンスを生成するようにFactory差し替え --> <control patch:instead="control[@template='sublayout']" template="sublayout" type="NextScape.SitecoreLibrary.Web.UI.SublayoutRenderingType, NextScape.SitecoreLibrary" propertyMap="Path=path"/> </renderingControls> </sitecore> </configuration>
カスタマイズとしては以上となります。
余談ですが、AWSのELBやリバースプロキシ環境などですと、 LBなどの機器でSSL暗号化処理を行うことが多く、LBからサーバーには常にHTTPでアクセスするような場合が多くあります。
その場合には以下のように「X_FORWARDED_PROTO」などのヘッダー情報を元に判別してあげる必要があります。
SSL処理判定-NextScape.SitecoreLibrary.Utils.RequestUtil
using System.Web; using Sitecore.Diagnostics; namespace NextScape.SitecoreLibrary.Utils { /// <summary> /// リクエストに関するをユーティリティクラス /// </summary> public static class RequestUtil { private const string ForwardedRequestProtoHttpHeader = "X_FORWARDED_PROTO"; /// <summary> /// Gets the scheme. /// </summary> /// <param name="req">The req. /// <returns>the scheme</returns> public static string GetScheme(HttpRequest req) { Assert.IsNotNull(req, "HttpRequest must be set."); string forwardedProto = req.Headers[ForwardedRequestProtoHttpHeader]; if (!string.IsNullOrEmpty(forwardedProto)) { return forwardedProto; } else { return req.Url.Scheme; } } /// <summary> /// SSLでのリクエストかどうか取得する /// </summary> /// <param name="req">判定対象のリクエストインスタンス /// <returns> /// SSLでのリクエストの場合は真 /// </returns> public static bool IsSSLRequest(HttpRequest req) { Assert.IsNotNull(req, "HttpRequest must be set."); return GetScheme(req) == "https"; } } }
カスタマイズ後、先ほど作成したサブレイアウトのキャッシュがどうなったか確認してみましょう。
「キャッシュ有効」のみチェックを入れなおして、その他の条件はすべてチェックしない状態に戻してパブリッシュしておきます。
HTTP、HTTPSの順番でページにアクセスしてみると、 それぞれ正しく画像が表示されることが確認できるかと思います。
キャッシュ内容は以下のようになりました。
無事にHTTPとHTTPSでキャッシュキーを分け、キャッシュ内容を別々に格納することが出来ました。
最後に
現在はスマートフォンやタブレット向けのサイト構築などが当たり前となってきました。
サイト訪問者に対する体験を最適化する上でも、 「回線が貧弱なモバイル環境であっても、サイトが快適な速度で表示される」ということは非常に重要な要素となっています。
Sitecoreのキャッシュ機能を充分に活用し、あなたのお客様により素早く適切なメッセージを伝える努力をしてみてはいかがでしょうか。
Sitecoreキャッシュに関する参考情報:
(英語)Learn Sitecore - Sitecore caching – an overview
(日本語)キャッシュ構成 リファレンス - SDN
(日本語)プレゼンテーション コンポーネント リファレンス