アルファサード株式会社 代表取締役 野田 純生のブログ


Movable Type をめっちゃ高速化する20の方法 - 2013年Xmasバージョン


公開日 : 2013-12-25 17:52:37


この記事は、Movable Type Advent Calendar 2013の最終日の記事です。

イントロダクション 再構築キュー経由の再構築を並列処理でどこまで高速化できるか

PowerCMS 4のリリースから自分の中では今後は再構築キューをデフォルト、推奨としていく方針なのですが、実際にキューに設定すると画面上再構築処理は数秒とか数分で返ってきます(体感的に速くなったように感じます)。でも実際はサーバー側のプロセスが1ファイルずつ再構築しているわけで、すべてが反映(再構築)されるまでにはそれなりに時間がかかります。

再構築キューのしくみ

実測した環境では、700のキューが予約されているケースで2分半程度かかっていました。

そこで、サーバー負荷はこの際気にせずに、どのくらいの速度で再構築ができるものか、スクリプトを書いてみました。


cd path/to/mt; perl tools/speed-rebuild --per_proc 175 --max_proc 4

この場合、1プロセス175ファイル、プロセスの最大数4という感じになります。このパラメタを調整することで、スレッド数、スレッドあたりの再構築単位を指定できるというものです。指定した数値で一度に再構築キューが消化できなかった場合、プロセス終了後にmax_procを最大数として新たなスレッドが再構築処理を継続します。


perl tools/speed-rebuild --per_proc 700 --max_proc 1  - 2′01″
perl tools/speed-rebuild --per_proc 350 --max_proc 2 - 1′23″
perl tools/speed-rebuild --per_proc 175 --max_proc 4 - 1′00″ 
perl tools/speed-rebuild --per_proc 88 --max_proc 8  - 55″
perl tools/speed-rebuild --per_proc 44 --max_proc 16 - 56″(遅くなった,もしくは変化は誤差の範囲)
perl tools/speed-rebuild --per_proc 22 --max_proc 32 -  (正常終了せず)

アクティビティモニタの状態
まぁ、いくらなんでもこれはやり過ぎですね...

ということで、並列処理数は3-5プロセスくらいが最も効率的という結果になりました。これ以上増やしてもリソース使い果たすだけで正常処理できなきゃ意味がないし。あ、もちろんこのスクリプト自体が再構築処理投げっぱなしの使い捨てスクリプトだし、様々な面でマシンスペックによってはもっといけるかもしれんものなので(もちろんその逆のケースもある)、その点は宜しくお願い。もちろんこれは力技なので、テンプレートの最適化やキャッシュ利用などによる効果は別途見込めます。テンプレート次第でさらに半分、もっと短縮も夢ではありません。

尚、現在のMT標準の MT::Worker::Publish (再構築キュー)はこのスクリプトとは逆に、負荷が一定上がらないように工夫されています。

さて、ただ単純に複数スレッドで並列処理すれば速くなるか? 決して単純に速くなるわけでもないということがお分かりになったと思います。そこで、ここから本題。

サーバーの処理速度について知っておくべきこと、基本的認識

極端な話しをします。ツッコミは負荷、じゃなくて不可。

メモリ<ディスク<DB(SQL)の順に速い。あるいはDB(SQL)>ディスク>メモリの順に遅い。 もちろんディスクでもSSDとかは速い。メモリに近いと考えて良いです。DB(SQL)の速度にはメモリやディスク性能が影響するので、DB(SQL)が悪者というわけでは必ずしもないです。ただ、MT等のテンプレート処理のボトルネックはほぼDB(SQL)です。SQLは一発クエリ発行ってことはなくて、1テンプレート処理で複数、場合によっては数十のSQLが発行されるので、基本的にはSQLクエリを減らしディスクやメモリにその結果をキャッシュしたり、ましてやアプリとDBが別サーバーの場合など、より"DB(SQL)>ディスク>メモリの順に遅い"を意識すると良いと思います。

なので、つまり一般的なキャッシュというのは、時間のかかる処理をメモリやディスクに保存しておき、それを使うということになります。速度的にはメモリ>ディスクですが、メモリは容量に制限があることからディスクキャッシュ等は手軽によく使われます。もちろんデータベースキャッシュというのもありで、数十のSQLクエリの結果を組み立ててビルドしたテンプレート等を1レコードに保存しておき、SQL一発で呼べるようにすればそれはそれでメリットがあります。

また、メモリキャッシュは、Memcachedのような仕組みを除き、(例えばPerl CGIのような仕組みでは)1リクエストごとに破棄され、保持することができません。ファイルキャッシュやDBキャッシュはその点気にしなくて済みます(むしと、いつキャッシュをクリアするか、のほうに配慮しなければなりませんが)。

また、DBキャッシュの場合、アプリが冗長化、複数台構成の時に導入しやすいといったメリットがあります。

その上で(またここまで前振りになった)今度こそ本題。

Movable Type をめっちゃ高速化する20の方法 - 2013年Xmasバージョン

アプリ(CMS)&再構築編

1.File::Cacheプラグインを使う

File::Cacheプラグインは、MTの Memcached 互換のファイルキャッシュを提供します。"DB(SQL)>ディスク>メモリの順に遅い"原則から言えば、Memcachedはメモリ、File::Cacheはディスクなので、Memcachedのほうがいいに決まっている。ただ、敢えて一番目にこれをもってきているのには理由があります。

Memcached より導入が楽です。プラグインを設置すればそれでいい。それだけで多分再構築処理性能が20-30%向上するでしょう。

Memcached サーバーが同一マシンでない場合、ネットワークのレイテンシが発生するケース、アプリ(CMS)とDBが別構成の場合、より効果を発揮する(遠くのOracleより近くの"ファイル"、の法則) ディスクの性能は向上している。"DB(SQL)>ディスク>メモリの順に遅い"とはいえ、ディスクとメモリの差異は少なくなっている。

一方、効果が限定的なケースとして、"アプリ(CMS)が冗長構成の場合はキャッシュを共有できないため、効果は限定的(ディスクを共有すれば良いが、ディスクマウントやファイル同期はそれだけでボトルネックになりうる" という点には注意が必要です。

いずれにしても、設置すれば30%再構築が速くなるとすれば、導入の価値有り、です。尚、効果は2回目の再構築で現れます。1回目の再構築では効果は限定的です。効果がなければ? 外してください。それだけ、ダメもとですよ。

※ただ、MTの Memcached 活用 は限定的です。File::Cacheは Memcached実装互換なので、こちらも限定的になります。これは今後改善の余地がまだあります。

2.DBをチューニングする

以下のリンク先を参照ください。背景として、MTやPowerCMSの大規模、中規模案件では、それ(CMS)専用にサーバーを用意するのが一般的になっているのに、RHELはじめ、初期状態では必ずしも占有することを前提としていないということがあります。詳細はリンク先を参照ください。MySQLであればInnoDB化や設定ファイルのパラメタ調整は目に見えて効果がああります。

3.不要なプラグインの退避(削除)

Movable Typeでは、plugins/addonsディレクトリ以下のプラグインはすべて初期時にスキャンされます。これは、"プラグインを無効化"している時も実行される処理です。無効化されていても、一旦初期化・認識された後に有効無効を判別しているので、plugins/addons以下から退避もしくは削除してしまうことで、初期化処理の数秒(1プラグインあたりにすれば0.数秒ですが)が短縮されます。プラグインによってはSQLを発行するものもあります。例えばコミュニティ機能を使わない前提、コメント・トラックバックを使わない前提なら、以下のプラグインが外せます。

  • addons/Community.pack
  • plugins/TypePadAntiSpam
  • plugins/mixiComment
  • plugins/FacebookCommenters

テキスト入力に Markdown等の記法を使っていなければ以下のプラグインが外せます(最近なんだかMarkdown人気だけど)。

  • plugins/Markdown
  • plugins/Textile

その他、下記は本当に必要でしょうか?

  • plugins/WidgetManager
  • plugins/WXRImporter

WidgetManagerなんかは、以下のように記載されています。

このプラグインは、古いバージョンのWidget ManagerのデータをMovable Typeのコアへ統合してアップグレードするために提供されています。アップグレード以外の機能はありません。最新のMovable Typeへアップグレードし終わった後は、このプラグインを削除してください。

4.再構築オプションの適切な選択をする

修正するケースが限定的なCSS/JavaScriptファイルは「手動」。基本ですね。 必要に応じて、もしくはDynamicが使えるなら、アクセスは少ないアーカイブはダイナミックパブリッシィングに。僕自身静的大好きですが、全部それである必要ありますか? というか、どっかでトレードオフです。

もしくは冒頭の話し。再構築キューを活用して、サーバーと作業を分担しましょう。

5.PageButeプラグインを使わない

PageButeプラグインが悪いとはいってないですよ(強調)。ウチのでも、PowerCMSに"Pager"ってプラグインがあって、ほぼ同様の動作をします。ただ、これ、効率が悪いんですよ。100件のブログ記事があって、10件で送っているとき、1件追加すると11アーカイブを再構築しないといけない。

日付アーカイブを使うか、DynamicMTMLでの分割を検討しましょう。

なぜ、日付アーカイブか? 例えば、月間10件のエントリー、10ヶ月分のアーカイブが吐かれていたとして、1件追加した時に再構築が必要なのは...当月のアーカイブだけ、です。

ページ送りの単位と月間、年間、年度、あたり、実態に近い日付アーカイブを選択すれば、変化のないページについては再構築の必要がありません。日付アーカイブで代替可能か検討してみましょう。

6.mt-static 以下のファイルをminifyする

JavaScript/CSSについては、圧縮することができます。サーバー側でgzip圧縮してもいいですが、以下のスクリプトで圧縮することができます。動的に圧縮もできるのですが、toolsのスクリプトを叩くことで、実際にスタティックなJavaScript/CSSをコード圧縮することもできます。

7.テンプレートを最適化する、テンプレートキャッシュを活用する

さて、ここです問題は。基本は、"DB(SQL)>ディスク>メモリの順に遅い" を意識した上でどのように処理時間を短縮するか 。特に効果的なのは以下の2点です。

  • 共通部分を静的インデックス・アーカイブ化してインクルードする
  • 共通部分をキャッシュする

テンプレートキャッシュにはMT標準のテンプレートモジュールキャッシュが使えますが、以下のプラグインが便利です。キャッシュしたいブロックをブロックタグで囲むとキャッシュしてくれます。メモリ>ディスク(DB)の順でキャッシュを使います。


<MTbuildcache ttl="有効期限(秒)" key="キー">
この中の処理が次回以降省略され、キャッシュを使うようになる
</MTbuildcache>

一点、過去に嘘?書いていたので懺悔とともに訂正

また <MTBlogIfCCLicense> とか一度決めてしまえば不要なもの、あるいは「追記(more)」を使用していないのであれば「 <MTIfNonEmpty tag="EntryMore" convert_breaks="0">」のブロックとかも不要。 カテゴリーアーカイブが必ず吐かれる設定であるのなら「<MTIfArchiveTypeEnabled archive_type="Category">」のブロックもいらない。「タグ」を使っていないとか、自分のスタイルにあわせて不要な部分を削除していく。その分プログラムがデータをチェックする(僅かではあるが)処理が省略できる。

これ、嘘です。ほとんど効果がないです。なぜなら、SQLロード済みでBlogオブジェクトがメモリに格納された状態での分岐は誤差の範囲です。新たにSQLを発行するようなものやループで多くのオブジェクトを読み込むものなんかならともかく、この程度の分岐でテンプレートの見通しやメンテナンス性を悪くするのはお勧めしません。

8. psgi/FastCGIを使う

psgi / FastCGIといった常駐型のPerl事項環境が使えるなら、起動にかかるオーバーヘッドがなくなるため、快適なレスポンスが得られるでしょう。

良いこと尽くめのように見えますが、いくつか問題がなくもない。プラグインやモジュールの入れ替えは即時に反映されず、プロセスの再起動等が必要になります。開発段階や開発環境では導入しづらいと言えます。また、導入の敷居は決して低くないです。むしろ高いと思う。PSGI化については以下の記事を参考にしてください。

9.ハイスペックサーバーにする

これを1番にしてもいいくらいですが... お金で解決というか、専用サーバー、クラウド、VPS、値段さががってます。共用サーバーとかを選択する理由も最早ないのではないかと思います。選択の前提としては、DBの設定ファイル等、最適化のためにその辺りを触れる権限が与えられていること。とれる選択肢が違ってきます。台数を増やして(アプリとDBを別にするとか)対応する方法とか。単純明快でパフォーマンスが向上する施策として、是非ご検討を。

10.クライアントマシン(CMSを操作する側のマシン)をハイスペックにする

もう一つ忘れては行けないのが、クライアントマシンのスペックです。今どきのMT、CMSではJavaScriptによってフロントエンドのUXを高めるような仕組みがふんだんに盛り込まれています。だって、以前に記事書いたときって、MT3とかだったんだもの!

リッチテキストエディタなどは、クライアントマシンのスペックによって速くも遅くもなります。操作する側のマシンのスペックも、上げれば上げるほど快適になります。 予算がない? であれば、IEをChromeに変えるとか、そういう方法もあります。スペック上がらなくても、JavaScriptエンジンが高速になれば快適になります。

Dymnamicパブリッシング/DynamicMTML編

11.PHPアクセラレータを導入する

eAccelerator、もしくはAPCあたり。プラグインファイルが多いとき、ディスク/IOが気になるとき、確実に効果が見込めます。これも「入れれば効く」類いのものです。まずはこれを入れてみること。

12.不要なプラグインの退避(削除)

"管理画面と再構築編"と同じお題ですが、退避する対象が異なります。もちろんaddons、plugins以下のプラグインディレクトリそのものを削除(退避)してしまえばいいのですが、「スタティック・パブリッシング」で利用していて、「ダイナミックパブリッシング(DynamicMTML)」で未使用なプラグインは、addons/plugins以下のphpディレクトリ以下を削除してしまって問題ありません。また、MTディレクトリ以下のphp/lib以下にはテンプレートタグ毎に1ファイル、大量のphpファイルが格納されています。利用していないタグであれば、これらの一部は削除しても動作には影響せず、確実に速くなります(依存関係のあるものに注意が必要ですが)。

plugins/Foo/php以下のファイルでは、特に、init.Foo.php。例えば、ダイナミック処理でカスタムフィールドを利用していないならば、以下のファイルが削除可能です。

  • addons/init.CustomFields.php

これを退避すれば、CustonFieldをすべてロードして、ループで回す初期化処理がスキップされます。スタティックでカスタムフィールド使ってるけど、ダイナミックでは使っていない、その時は退避することで初期化処理がまるっと省略できます。

13.静的化できる部分を静的化する

トレードオフですね。再構築に時間かけるか、表示が遅いのが困るのか。MTネイティブのダイナミックパブリッシングの場合は、静的にパブリッシュしたものをインポートするという発想になり、DynamicMTMLでは静的にパブリッシュできる部分を静的パブリッシュとして、ファイル中の動的処理部分のみをダイナミック処理にしてやるようにします。

PowerCMSのダイナミックサーチが重い場合なんてのは、ブログ記事テンプレートにテンプレートマップを用意して、ブログ記事の検索結果部分を静的スニペットとして吐いてしまう(そして検索結果でインクルードする)という方法もあり、そのほうが速いといったケースもあります。"DB(SQL)>ディスク>メモリの順に遅い"法則に照らし合わせると、SQl実行の結果を静的にディスクに吐き出してそいつをインクルードするほうが速い! ということです。

14.キャッシュを使う

キャッシュ、これも"管理画面と再構築編"と同じお題ですが、ダイナミックの場合いくつかの選択肢があります。

  • MT標準、Smartyのキャッシュを使う(お手軽ですが、パラメタ付きのリクエストを同じキャッシュとしてしまう制限があります)
  • DynamicMTMLの"ビルド結果をキャッシュする"を利用する(クエリ付きキャッシュを別のキャッシュにしてくれます)
  • "DynamicMTMLで条件付きGETを有効にする"特に問題がない限り、チェックしましょう。ブラウザキャッシュを利用するようにしてくれます。
  • MTBuildCacheタグはダイナミックパブリッシングでも有効です。

尚、Smartyのキャッシュ、DynamicMTMLともにテンプレートやエントリの更新でキャッシュクリアされます。会員サイトやマルチデバイス分岐等の理由がない限り導入するのが良いと思います。

15.最新版 DynamicMTMLと Memcached

PowerCMSでは、DynamicMTMLがMemcachedに対応しました。詳細はPowerCMSのBlogに近々書きます。

16.Minify、画像のBase64エンコード、gzip圧縮

他のカテゴリと被るのは、まぁ仕方ないのですが。以下のページを参考にしてください。

17.フロントサーバーとアプリケーションサーバーの分離、あるいはCDN

そんなに大切なら、分けてしまいましょう。Amazon S3でも、Windows Azure Blobストレージでも、CDNでも、配信する手段はあります。PowerCMSなら、ね。

プラグイン開発編

18.PerlプラグインではMT->Requestでデータを使いまわす、PHPプラグインでは、$app->stash、$ctx->stashでデータを使いまわす

Memcachedが使えるなら、使い回せるものはMemcachedにもキャッシュしましょう。以下の記事を参考にしてください。

DataAPI編

19.PSGIで動かす、キャッシュを活用する

アプリと再構築編と同じですね。もうここら辺になると繰り返しです。PSGI環境で動かして起動のボトルネックを解消しましょう。PHPで書いたキャッシュ付きゲートウェイもあります。必要に応じて利用してください。

20.ミッションクリティカルなサービスで、読み出し系のものは、静的JSONのパブリッシュも検討しよう

DataAPI以前から使われていたと思うのですが、元々MTから吐き出すものがHTMLである必要がはありませんし、XMLでもCSVでもJSONでも何でも吐き出せますよね。静的パブリッシュ可能なMTのひとつの大きなメリットかと思います。ログイン認証が必要でないもの、動的に変化しないものについては、アーカイブにしてパブリッシュしたものを併用することを考えても良いでしょう。

21.ご相談ください。課題を解決するのは、いつも、プロフェッショナルです。

おまけ

エラー画面(案w)

まぁ、少々遅くてもエラー出たとしても、笑て生きよや。な、横山(謎)。

よいクリスマスと、よい新年を。おやすみなさい。よい夢を。

カテゴリ


このブログを書いている人
野田純生の写真
野田 純生 (のだ すみお)

大阪府出身。ウェブアクセシビリティエバンジェリスト。 アルファサード株式会社の代表取締役社長であり、現役のプログラマ。経営理念は「テクノロジーによって顧客とパートナーに寄り添い、ウェブを良くする」。 プロフィール詳細へ