今度の土曜日11/5に開催されるOSC2011.DBの10:35からのセッション「OSSDB MySQL」に少しおじゃましてgroongaストレージエンジンを紹介します。groongaストレージエンジンがイベントなどで紹介されるのは昨年の全文検索エンジンgroongaを囲む夕べ #1以来のはずなので約1年ぶりになります。その間に初のメジャーリリースである1.0.0がリリースされるなど、だいぶ成長しています。そのため、話題はたくさんあるのですが、その中から特に注目すべきところを選り抜いて紹介します。
groongaストレージエンジンについては1ヶ月後の全文検索エンジンgroongaを囲む夕べ 2でも詳しく紹介されますが、一足早く知りたい人はぜひ参加してください。(参加費用は無料ですが参加登録が必須なので注意してください。)
オライリーより、Firefoxの高度な使い方からアドオン開発のノウハウ、新しいWeb技術まで手広く解説・紹介する書籍「Firefox Hacks Rebooted」が、2011年10月26日に発売されました。弊社でMozillaサポート事業に従事している下田も執筆者の一人として名を連ねています。
Firefox Hacks Rebooted ―Mozillaテクノロジ徹底活用テクニック
オライリージャパン
¥ 16,482
内容が多岐に渡るため、「こういう人に読んでもらいたい!」という想定読者が章ごとにそれぞれ異なるのですが、全体を見た時の内容の充実度からは、Firefox用アドオンの開発に関心がある方に特にお薦めと言えるでしょう。そこでこの記事では、開発者視点から本書の見所をいくつか紹介します。
目次を順番に眺めて目を引くのは、2章におけるVimperatorとKeySnailの解説でしょう。VimとEmacsといえば開発者が使う開発環境の二大巨頭で、よくエディタ戦争のネタにもされますが、本書の2章でも、Vimを模したVimperatorとEmacsを模したKeySnailの戦争が繰り広げられています。
……というのは冗談なのですが、Vimperator開発メンバーの一人であるteramakoさんがVimperatorを、KeySnailの作者であるmoozさんがKeySnailをそれぞれ解説し、最後に二人がそれぞれの設計思想の違いを解説するという構成になっていて、こと両アドオンの解説記事としてはこれ以上無い豪華な内容と言えるでしょう。開発者自らによる解説という事で、内容の信頼性の高さも折紙付です。
快適な開発生活を送る上では、効率の良い情報収集や情報発信の方法を知る事も大切です。また、情報は発信する人の所に集まるとも言います。最新の技術へのフォローを欠かす事ができない開発者の人達にとっても、この章の内容は役立つのではないでしょうか。
3章は、FireGesturesなどの作者としても知られるGomitaさんによるAdd-on SDKの解説です。基礎概念の解説に始まり、少しずつ機能を付け足しながら実際に1つのアドオンを完成させるまでの過程をチュートリアル形式で紹介する事により、Add-on SDKを使ったアドオン開発の一通りの流れを理解する事ができます。
Firefox 4以降のバージョンでは高速リリースが採用された事により、従来の形式のアドオンの開発スタイルだとFirefoxの更新に追従するだけでも大変という状況になっています。Add-on SDKはFirefoxのバージョン間の違いを吸収する層としての働きも備えているため、これから新たにアドオンを開発する場合はSDKを利用した方が良いと言えますが、本章はそのファーストステップとして最適でしょう。
また、後の章にはHTML5関連技術などの新しいWeb標準技術の解説もあります。それらを組み合わせる事によって、SDKが提供している機能を超えた様々な事が実現できるようになります。本書は話題が多岐に渡っていますので、全体を通読する中でそういった発展に繋がるヒントを得やすいのではないでしょうか。
弊社所属の下田は、4章と6章の一部として、SDKに依らない・より低層の部分に関する技術情報を寄稿しています。
4章では、再起動のいらないアドオンを、これまでの一般的なアドオン開発の延長線上にある物として捉えた上で、これまで通りにできる事とそうでない事とを整理して、それらに対する対策となる様々なテクニックを紹介しています。
また、プロセス分離型の設計に移行していくにあたって、そもそもFirefoxではどのようにプロセスの分離が実現されているのかを理解し、プロセスが分離された環境ではどのような事に気をつけなくてはならないのかについても解説しています。
SDKの枠を超えた開発を行いたい時や、SDKに含まれている標準ライブラリの中で行われている事を理解したい時などには、各要素技術への理解が必要になってきます。そういった場合も、この章の情報が手がかりとなるかも知れません。
その他にも、非同期処理の記述を支援する軽量ライブラリ「JSDeferred」のFirefox上での活用事例や、CPUの使用率とメモリの使用量を表示するFirefoxアドオン「システムモニター」でも利用している、C言語で開発されたプラットフォームネイティブのライブラリをJavaScriptから透過的に利用する技術「js-ctypes」の解説(6章に収録)など、SDKベースでの開発にも応用可能な情報もあります。
本書には、Web上にあるFirefoxの断片的な情報を取っ掛かりとして、それらをさらにもう1段階・2段階と掘り下げた情報が多数収録されています。自分で情報を探そうとして挫折した方、見つけた情報が中途半端で途方に暮れてしまった方など、表面的な情報よりももっと本質的な情報を求めている方にお薦めと言えるでしょう。
なお、本書の目次や各章のサンプルがWebで公開されています。Firefoxのヘビーユーザーの方やアドオン開発者の方は、役立つトピックが含まれているかもしれませんので、興味を持たれた際には是非一度目を通してみて下さい。
オープンソースカンファレンス2011 DBのOSSDB MySQLセッションでgroongaストレージエンジンについて紹介してきました。
内容はgroongaストレージエンジンが得意なシチュエーションについてベンチマークデータを紹介するというものです。どういうときにgroongaストレージエンジンが高速に動作するかがわかります。
groongaストレージエンジンは以下のような処理が得意です。
groongaストレージエンジンの性能特性を紹介するためにベンチマークデータを紹介しました。ベンチマークはこれらの得意な処理を実行するシチュエーション向けに複数のパターンで行いました。
groongaの全文検索処理の性能を示すためにtwitterから取得したデータを利用しました。測定する処理はフレーズ検索です。約100万件のtweetに対して「"facebook saved"」というような2単語でフレーズ検索します。このようなフレーズ検索を1万回(1万パターン)実行するためにかかった時間が縦軸になっており、グラフが短いほど高速に全文検索が実行されていることを示しています。
groongaストレージエンジンの方がMySQLの開発版5.6.3-labs-innodb-ftsに含まれるInnoDBの全文検索機能よりも10倍程度高速で、MyISAMよりは2倍程度高速でした。
groongaの位置情報検索処理の性能を示すために国土交通相の位置参照情報ダウンロードサービスの2010年版のデータを利用しました。測定する処理は「MBRContains(GeomFromText('LineString(139.850124 38.718204, 140.447158 37.817489)'), location)」というように位置情報でレコードを絞り込み、「ORDER BY name」というように住所でソートする検索です。このような検索を千回(千パターン)実行するためにかかった時間が縦軸になっており、グラフが短いほど高速に位置情報検索が実行されていることを示しています。
groongaストレージエンジンのほうがMyISAMよりも40倍程度高速でした。
groongaはリアルタイム更新が得意です。リアルタイムで更新するために以下の2点を重視しています。
まず、更新性能が高いことを確認し、次に検索負荷が高いときの更新性能を確認します。
高速に更新できることを示すために、98万件のtweetが登録されたデータベースを用意します。このデータベースに対して、2万件のtweetを登録します。このとき1秒あたりに追加したレコード数が縦軸になっており、グラフが長いほど高速に登録されていることを示しています。
groongaストレージエンジンのほうがInnoDBよりも3倍程度高速、MyISAMよりも2倍程度高速、Sphinxよりも3倍程度高速でした。
検索負荷が高いときでも更新性能が落ちないことを示すために、98万件のtweetが登録されたデータベースを用意します。このデータベースに対して、検索負荷(クエリ数/秒)を変えながら2万件のtweetを登録します。このグラフはそれぞれのストレージエンジン毎に見ます。横軸が検索負荷を表していて、左側になるほど検索負荷が小さく、右側になるほど検索負荷が高いことを示しています。グラフが水平になっているほど検索負荷が高くなっても更新性能が落ちていないことを示しています。
groongaストレージエンジンとInnoDBは検索負荷が高くなっても更新性能はそれほど落ちておらず、MyISAMとSphinxは更新性能が落ちていました。
このように、高速に動作するgroongaストレージエンジンですが、以下のように機能制限があります。
そのため、どのようなケースにでも利用できるわけではありません。しかし、groongaストレージエンジンは他のストレージエンジンと組み合わせて使うことができるため、上記の機能制限の一部を解消することができます。
groongaストレージエンジンを他のストレージエンジンと使う場合は全文検索処理・位置情報検索処理のみをgroongaストレージエンジンが行い、それ以外の処理は連携した他のストレージエンジンが行います。そのため、groongaストレージエンジンとInnoDBを一緒に使うと、トランザクションはInnoDBの機能を用いて、全文検索はgroongaストレージエンジンを用いる、ということができます。この仕組みを使うと「トランザクションをサポートしていない」というgroongaストレージエンジンの機能制限を解消することができます。
ただし、更新性能は組み合わせて使うストレージエンジンの性能に依存するため、groongaストレージエンジンの得意な処理である「リアルタイム更新」性能は発揮できません。「高速な全文検索機能」と「高速な位置情報検索機能」のみ利用できます。
InnoDBと組み合わせて利用した場合のベンチマークデータを以下に示します。
まず、全文検索の性能です。
groongaストレージエンジン単体で使った場合とほとんど同じ性能がでています。
次に、位置情報検索の性能です。
こちらもgroongaストレージエンジン単体で使った場合とほとんど同じ性能がでています。
次に、更新性能です。
groongaストレージエンジン単体で使った場合よりも大きく性能が落ちて、組み合わせて使っているInnoDBと同じ程度の性能になっています。
最後に検索負荷が高いときの更新性能です。
InnoDBが検索負荷が高くても更新性能がほとんど落ちないため、groongaストレージエンジンとInnoDBを組み合わせて使った場合でも更新性能がほとんど落ちていません。
OSC2011.DBでgroongaストレージエンジンが得意なシチュエーションのベンチマーク結果を紹介してきました。もちろん、groongaストレージエンジンが得意ではないシチュエーションもあり、そのようなケースでは他のストレージエンジンの方が性能がよくなります。groongaストレージエンジンが苦手なケースについては今月末(2011/11/29)開催の全文検索エンジンgroongaを囲む夕べ 2で紹介する予定です。興味のある方はこちらに参加してみてください。すでに定員を超えていますが、前回の全文検索エンジンgroongaを囲む夕べ #1では最終的に35名のキャンセルになっていましたので、今からでもギリギリ参加できるのではないでしょうか。
groongaストレージエンジンのより詳しい情報についてはgroongaストレージエンジンのサイトも参照してください。
ApacheとPhusion PassengerでWebアプリケーションをデプロイしている際に、たまにシステムが不安定になることがありました。調査したところ、原因はレスポンスの取得が遅いクライアントであるということが分かりました。この問題を発見し、原因を特定し、Phusion Passengerを修正するまでについて、紹介します。
るりまサーチではApacheとPhusion Passengerを使っているのですが、たまに動作が不安定になることがありました。その不安定の原因を調査することにしました。
調査した結果、遅いクライアントがるりまサーチに接続するのが不安定になるトリガーになっていることが分かりました。
以下に、詳しく説明します。
Phusion Passengerは、遅いクライアントが接続していると、他のリクエストの処理を行いません。その間に、他のクライアントからのリクエストが処理待ちとして大量に溜まります。そして遅いクライアントがレスポンスを受け取り終わると、Phusion Passengerは、一斉に溜まった処理待ちのリクエストを処理し始めるため、システムのリソースを急激に消費します。そして、システムが不安定になります。
この問題の原因を解決するためには、遅いクライアントのリクエストを処理している際にも、他のリクエストの処理ができればいいということになります。
遅いクライアントの時の状況を詳しく説明します。遅いクライアントがレスポンスを受け取るのを待っている間は、サーバー側ではレスポンスの作成の処理は既に完了しているということになります。それにも関わらず他のリクエストの処理を行わないのです。つまり、その間、サーバーは実質的には何も処理を行っていません!
この時間は、無駄な時間と言えます。
なので、この無駄な時間をなくすようにする必要があります。具体的には、Webアプリケーションからの作成されたレスポンスを一旦Phusion Passengerがバッファに保存し、次のリクエストの処理に移ります。遅いクライアントには、そこのバッファから、ゆっくりレスポンスを返せばいいのです。
実は、このような挙動はNginxとPhusion Passengerの構成でデプロイされた場合には実現されています。ですが、ApacheとPhusion Passengerの構成の場合にはそうでは無いのです。
解決の方法が分かったので調べたところ、Phusion Passengerのバグトラッキングシステムにこの問題はすでに登録されていました。
さらにそこには、この問題の解決方法が記載されていました。コメント#3の情報によるとソースコードを書き換えればいいということが分かりました。ソースコードを書き換えることで、Nginxと同じように、Apacheでもレスポンスをバッファに保存する挙動に変更できます。
実際に、るりまサーチで検証したところ、問題は解決されました。遅いクライアントが接続してきた際にも、処理が止まらず、別のリクエストを処理するようになりました。
問題は解決されましたが、この問題を解決するためには、ソースコードを書き換えなければいけません。端的に言って、これは不便です。上のバグレポート内に書いてあるように設定ファイルからレスポンスをバッファに保存するかどうかを切り替えられると便利です。
なので、設定できるようにPhusion Passengerを修正したパッチを作成しました。そしてそのパッチをPhusion Passengerに取り込んでもらうように依頼をしました。
(追記: Fri Nov 25 03:05:28 2011 JSTにPhusion Passengerに無事に取り込んでもらえました。使えるようになるのは、まだリリースされていませんが次のリリースバージョンである、3.0.10からの予定です)
(追記: November 28th, 2011にこの修正が取り込まれたPhusion Passengerの3.0.11がリリースされました(3.0.10はバグがあったためにスキップされました)。
この修正によって、レスポンスをバッファに保存するかどうかを次のように設定することができるようになります。
<VirtualHost *:80> ServerName www.yourhost.com DocumentRoot /somewhere/public PassengerBufferResponse on # PassengerBufferResponse off # レスポンスのバッファへの保存をしたくない場合。 <Directory /somewhere/public> AllowOverride all Options -MultiViews </Directory> </VirtualHost>
遅いクライアント問題はレスポンスをバッファに保存することで解決できます。半面、レスポンスをバッファに保存すると、次の副作用が発生するので注意が必要です。
どちらも、Phusion Passengerがレスポンスをメモリ上のバッファに一旦保存してからクライアントに返すようになるためです。
ApacheとPhusion PassengerでデプロイされたWebアプリケーションが不安定になるという問題を調査し、Phusion Passengerを修正しました。
同じような問題に遭遇している場合には、ぜひ試してみてください。
いらないキャッシュを消すことでRubyスクリプトが倍速で動作するようになった話です。
先日、るりまサーチをRackspaceのクラウドサーバー(メモリ1GB)からさくらのVPS 512(メモリ512MB)に移行しました。理由はRackspaceのサーバーが遠くにあるのでレスポンスがもっさりするからです。最速検索がウリなのにこれでは遅いのでないかと誤解されてしまいます。さくらのVPSにしたら近くにあるためサクサクとレスポンスが返ってくるようになりました。しかも、リソース(主にメモリ量)はスケールダウンしているのにです。
るりまサーチはバックエンドの全文検索エンジンgroongaが高速に動作するのとそれほどアクセスがない(!)ため、CPUやI/Oがネックになることはありません。それよりも、ほとんどメモリを搭載していないマシンで動かしているため、ボトルネックになるとすればメモリです。実メモリ以上にメモリを使おうとしてスワップを使い始めると遅くなります。
るりまサーチに一番負荷がかかるのは1日1回のるりまデータの更新です。もう少し言うとBitClustでHTMLを生成する処理が一番負荷が高いのです。このとき、BitClustのHTML生成プロセスはCPUを100%使い*1、500MB程度のメモリを使用します*2。Rackspaceのときのように1GBのメモリが載っているサーバーであれば耐えられますが、さくらのVPS 512のように512MBしかメモリが載っていないサーバーでは致命的です。スワップを使いはじめてI/O待ちが多発し、システムの応答性が著しく悪化します。
ということで、BitClustがHTMLを生成するときのメモリ使用量を改善したのですが、そのときに2倍くらい高速に動作するようになりました。その高速化の方法を一言でいうと「いらないキャッシュを消す」です。具体的な変更点でいうとr4873(4行)とr4874(1行)です。
キャッシュはメモリを通常より使って処理時間を短くする方法なので、キャッシュを消すことでメモリ使用量が減るのは納得できます。しかし、処理時間も短くなるのは意外ですよね。
なお、メモリ使用量と実行時間は以下のようになりました。Ruby 1.8.7の場合も1.9.3の場合もメモリ使用量は半分以下、実行時間はほぼ半分になっています。
RSS | VSZ | 実行時間 | |
---|---|---|---|
変更前(Ruby 1.9.3) | 187MB | 237MB | 55秒 |
変更後(Ruby 1.9.3) | 84MB | 134MB | 38秒 |
変更前(Ruby 1.8.7) | 525MB | 558MB | 1分52秒 |
変更後(Ruby 1.8.7) | 132MB | 165MB | 59秒 |
メモリ使用量が減って速くなった場合はだいたいGCに関係していると相場が決まっています。では、こんなときはどうやって確認したらよいでしょうか。Ruby 1.9.2以降にはGC::Profilerという便利なモジュールが追加されているのでこれを使います。
GCがどんな感じで実行されているかを調べたい処理の前でGC::Profiler.enable
をし、処理を実行した後にGC::Profiler.report
で結果を表示します。
1 2 3 |
GC::Profiler.enable # ... GCについて調べたい処理 ... GC::Profiler.report |
GC::Profiler.report
は以下のようにGCの統計結果を表示してくれます。
GC 445 invokes. Index Invoke Time(sec) Use Size(byte) Total Size(byte) Total Object GC Time(ms) 1 0.092 964640 2257680 56442 0.00000000000000000000 ...
まず、いらないキャッシュを消さない場合の結果を見てみましょう。
GC 341 invokes. Index Invoke Time(sec) Use Size(byte) Total Size(byte) Total Object GC Time(ms) 1 0.080 964600 2257680 56442 0.00000000000000000000 ... 27 0.348 1893800 4057280 101432 4.00100000000003230838 ... 50 0.764 3283520 7296560 182414 8.00100000000003674927 ... 73 1.588 6627960 13120720 328018 12.00099999999992839150 ... 106 3.720 11035000 23607480 590187 32.00199999999986744115 ... 124 5.532 19510200 42486920 1062173 56.00399999999972067144 ... 151 9.741 36667720 44008400 1100210 96.00600000000092393293 ... 187 16.541 50259800 62249800 1556245 128.00800000000123191057 ... 237 28.470 61845360 72409360 1810234 176.01100000000258205546 ... 328 53.351 63548120 80262160 2006554 148.00900000000183354132
オブジェクト数が増える毎にGCにかかる時間が増えています。
次に、いらないキャッシュを消した場合の結果を見てみましょう。
GC 445 invokes. Index Invoke Time(sec) Use Size(byte) Total Size(byte) Total Object GC Time(ms) 1 0.092 964640 2257680 56442 0.00000000000000000000 ... 27 0.376 1893800 4057280 101432 8.00099999999997990585 ... 50 0.824 3283520 7296560 182414 12.00100000000004030198 ... 73 1.644 6627400 13120720 328018 12.00099999999992839150 ... 106 3.928 6736400 13120720 328018 20.00100000000015754154 ... 256 14.061 10642920 23607480 590187 32.00199999999853162080 ... 302 18.561 18979640 42486920 1062173 52.00299999999913325155 ... 432 36.330 25030040 42486920 1062173 48.00300000000135014488
トータルのGC回数は増えていますが、オブジェクト数の最大値が増えていかないため、1回のGCにかかる時間が短くなっています。この差が全体の実行時間に効いてきているんですね。
速くするためにやみくもにキャッシュしていると、生きているオブジェクト数が多くなるためにGC時間が長くなってしまい、逆に遅くなることがあります。キャッシュしたのに思ったより速くならなかった場合は必要なくなったキャッシュを持ち続けていないか確認し、いらないキャッシュを消すことを試してみましょう。ただし、ある程度のオブジェクト数をキャッシュしていない場合はあまり関係がないでしょう。