このエントリは、Magento Advent Calendarの8日目です。前編はこちらです。

さて後編では、Magentoを高速化するために不可欠な「フルページキャッシュ」を出来るだけスムーズに導入するための方法について解説したいと思います。 

そもそもフルページキャッシュとは

前編で少し触れましたが、フルページキャッシュとはあるページ全体(フルページ)を保存し(キャッシュ)、再利用する仕組みです。

商品一覧や商品ページといった動的生成ページは、概して1リクエストに対するレスポンスHTMLデータを生成するために相応の処理量が必要になります。それはレスポンスタイムという形で現れ、昨今では1秒以下を指定されるケースもあります。

フルページキャッシュを実現する場合、単にHTMLを保存しておいて再利用するだけのケースはMagentoの場合ほとんどありません。というのも、ECサイトの場合はログイン状態やカートの状態をはじめとした様々な訪問者固有の状態が存在し、これらの情報が第三者に見えないように配慮することがフルページキャッシュを実現する際に必要になります。

Punch Hole Cache(パンチホールキャッシュ)という概念

第三者に個人情報が見えないようにするために、Magentoで実装されているフルページキャッシュの仕組みは、「パンチホールキャッシュ」と呼ばれる仕組みを用いています。

この仕組みは、ある特定のページをキャッシュする際に、一部を敢えて欠落した状態でキャッシュしておき、アクセスがあった時点で欠落した部分だけを動的に生成して埋め込んでクライアントに送信します。

ページの生成で一番処理が重い部分を上手にキャッシュしてやることで、ページの生成にかかる計算量を大幅に削減することができ、結果1アクセスに対するレスポンスが非常に高速になります。もちろん、Magentoの場合はデータベースに強く依存しているため、キャッシュ化によってデータベース問い合わせの回数も減ります。

つまりフルページキャッシュを導入することで、1ページを処理するためにかかるサーバーの負荷をキャッシュが効いている間は大幅に減らすことができます(キャッシュが効いていない場合は未導入時とさほど性能的には変わりません)。結果的に単位時間あたりでより多くのアクセスを処理することができ、同じサーバー維持費であれば、より高い投資対費用効果を得ることができます。

フルページキャッシュを上手に導入するためには

フルページキャッシュを導入するためには、Magentoのテーマが正しい形で作られていることが重要です。
この「正しい形」というのが殊の外難しく、安易なカスタマイズをしてしまうと、フルページキャッシュ導入時に障害になります。

では、どういうテーマがフルページキャッシュを導入しやすいのでしょうか?

ポイントは、「1つのテンプレートに1つの役割だけを実装する」ということです。

Magentoのテンプレートは特別なテンプレートエンジンを使わずに、普通のPHPスクリプトで書かれています。PHPで使える構文や記法が全て使えるので、PHPのわかる方であれば無理矢理なカスタマイズもできてしまいます。

その延長線上にあるのが、1つのテンプレートに複数の役割を実装してしまうケースです。

例えば左右のナビゲーションやヘッダー部分にあるテンプレートには様々なパーツを配置したくなりがちですが、これらの場所に個人情報(ログイン情報やカートの内容)を表示させてしまうのはNGです。
そういったパーツはきちんとそれぞれテンプレートを分けて、別のブロックとして実装するようにしましょう。

他にもよくあるパターンとしては、サードパーティ製エクステンションのテンプレートの実装不備や、相性問題ということがあります。
あまりたくさんエクステンションを入れず、カスタマイズは程々にしておくのが良いでしょう。

Lesti::Fpcを使って安価に爆速なMagentoを手に入れよう

Lesti::Fpcは以前「Lesti::Fpcを使用してMagentoを高速化する」で解説した、Magento Community Edition向けのフルページキャッシュ実装です。defaultテーマやrwdテーマに対してテストされているので、標準に近い構成のサイトであれば、比較的簡単に導入ができます。

Lesti::Fpc導入のコツ

Lesti::Fpcを上手に導入するためには、

  • Lazy Blocks
  • Dynamic Blocks
  • URLパラメータ(Uri Params, Miss Uri Params, Category Session Params)

の調整が鍵です。

「Lazy Block」というのは「後読みするブロック」という意味で、フルページキャッシュに対して後からはめ込まれるブロックです。
「Dynamic Blocks」も似た仕組みですが、主にこちらはシステムメッセージなどを設定します。
「Uri Params」「Miss Uri Params」「Category Session Params」は、フルページキャッシュ自体をURLパラメータ毎に作成するために用います。ここに設定されたパラメータ名と値のパターン毎にキャッシュを作成します。

ここの設定が正しくできていないと、カテゴリの絞込や個人別のキャッシュがうまく作成されません。

そこまで複雑な設定は不要なので、本番運用前にあれこれ試してみると良いでしょう。

フルページキャッシュがあるとどれくらい性能が出るのか

以下のグラフは、とあるサイトでアクセス集中が起きた際のものです。この例では1時間あたり通常の7倍のアクセスが殺到しました。(通常は1時間あたり2,000PV以下ですが、この時は14,000PVを超えていました)

アクセスの突発

このサイトではフルページキャッシュを導入済みで、以下の構成になっています。

  • ELBによるSSL終端と負荷分散
  • EC2 c4.largeインスタンス x2
  • RDS db.m3.largeインスタンス
  • ElastiCache cache.t2.mediumインスタンス

WebサーバーであるEC2インスタンスには、Nginxとphp-fpmでリクエストを処理するように設定してあります。
この構成でピーク時にはEC2インスタンス1台あたり150Mbpsを超えるデータの送出と、RDSインスタンスでは秒間800クエリを超えるSQLの実行がありました。

CPU使用率

クエリ実行数 

このサイトの場合はアクセスが瞬間的に増えましたが、かなりのアクセスを滞り無く捌くことができたので、20分後にはほぼ正常値まで落ち着くことができました。

フルページキャッシュがないとどうなるのか

フルページキャッシュがない環境では似たような状況になると、秒間3,000クエリを超えるSQLが実行されるだけでなく、Webサーバーを3台起動してもまだ処理が追いつきません(前編の冒頭で紹介したCPU使用率のような状況です)。
1回のリクエストを処理し切るまでにかかる時間がフルページキャッシュありの場合よりもかかってしまうことが最大の原因ですが、その原因も結局は複雑な処理とデータベース問い合わせを全てのリクエストに対して行っているからです。

つまり、より多くのトラフィックを処理し続けるためには、フルページキャッシュは不可欠だということになります。
サイトが見られなくて離脱してしまうことは、ECサイトにとって最大の機会損失なので、可能な限り良好なパフォーマンスが出るように調整しておくほうが良いでしょう。 

Magento2だとどうなっているのか?

Magento2は、Community Editionでもフルページキャッシュが付属しています。ですから標準状態で1リクエストあたり150ミリ秒程度のレスポンスタイムが実現できます。
ただし、フルページキャッシュがない状態のMagento2は、下手をするとMagento1.xよりもパフォーマンスが出ないことがあります。

Magento2については別途調査結果をご紹介したいと思います。

さて、9日目は再びkzkiq2ndさんです。よろしくお願いします!