# 原稿 はじめまして、須藤と言います。 RubyKaigi 2014も今日で最終日です。みなさん、RubyKaigi 2014を楽しめましたか? 残りのセッションはあと2つになりました。 最後のセッションは今日の英語でのキーノートです。そして、このセッションは今日の日本語でのキーノートです。(注: 冗談です。) それでは始めましょうか。 今日はRubyの使い方を3種類紹介します。最初は細かいことまでいろいろ話そうと思ったのですが、資料をまとめていたら細かく話していると時間がなくなることがわかったので、細かいことは少しだけにとどめています。細かいことを聞きたくなったら遠慮なく発表が終わった後に声をかけてください。 みなさん知らないと思うので最初に少しお知らせをします。 私はクリアコードという会社の社長なのですが、そのクリアコードという会社はRubyKaigi 2014のシルバースポンサーをしています。シルバースポンサー一覧の一番上に載っているのは一番最初にシルバースポンサーの申し込みをしたからです。 さて、最初に今日の話のゴールを説明します。話し終わった後にこれらが実現できていたら今日の私の話は成功です。 今日はRubyの使い方を3種類紹介します。今日の話を聞いて、みなさんがこれらの使い方をできるようになるわけではありませんが、こういう使い方もできるんだということを知ってもらいたいと思っています。 1つめはハイレベルなインターフェイスを実装するという使い方です。 2つめはグルー言語としてRubyを使うという使い方です。 3つめはRubyを組み込んで使うという使い方です。 いざというときにみなさんがこれらの使い方を思い出せるようになっていればよいなぁと思っています。そうすれば、今よりもいろんな場面でRubyを活かせるはずです。Rubyを活かして楽しくプログラムを書ける分野が広がるといいなぁと思います。 それぞれの使い方の対象者は違っています。後の方ほど難易度が高くなっています。なお、今日はCRubyを使っている人が対象で、JRubyを使っている人は対象外です。JRubyの場合はC/C++と書いているところがJavaになるくらいで同じような傾向になると思います。 まず、ハイレベルなインターフェイスを実装するという使い方の対象者はピュアなRubyistです。ここでいうピュアというのはRubyだけを使ってプログラムを書いているという意味で、書いている人がピュアかどうかは関係ありません。 次の、グルー言語としてRubyを使うという使い方の対象者は、必要ならC/C++も書けるよ、くらいのRubyistです。 最後のRubyを組み込んで使うという使い方は、普段からRubyも書くしC/C++も書くよ、というRubyistです。 徐々に対象者の人数は減っていくと思います。 ちなみに、自分がピュアRubyistだと思っている人は手を上げてみてください。(ニヤニヤしながら)ふーん。(注:アドリブです。) それでは順に説明していきます。今日は、Rubyで分散全文検索エンジンを実装する、場合を例にして説明します。Distributed full-text search engineは長いので、これ以降の資料ではDFTSEと略して書いています。話すときは省略しないで話すのでわからなくなることはないはずです。 ところで、Rubyで分散全文検索エンジンを実装したことがある人はいますか?みなさん、ないですね。それでは自分が実装するとしたらどうするかということを新鮮な気持ちで考えながら聞いてください。 分散全文検索エンジンの動きを単純化して図にするとこうなります。 分散全文検索エンジンの中にはたくさんの全文検索エンジンがあります。全文検索処理をリクエストされたら、それを1つ以上の全文検索エンジンに分配して全文検索処理をします。それぞれの全文検索エンジンで全文検索をしたらそれを集めて1つにまとめて結果を返します。 単純化するとこんな動きをするものが分散全文検索エンジンです。 どうして分散全文検索エンジンを例にするかというと、今、私はDroongaという分散全文検索エンジンを作っているからです。 それでは1つめの使い方、ハイレベルなインターフェイスを実装する使い方を説明します。対象者はピュアRubyistです。Rubyだけを使ってプログラムを書いている人ですね。 ハイレベルなインターフェイスはローレベルな機能を上のレイヤーに提供します。このとき、詳細を隠蔽してシンプルなAPIにしたり、より使いやすいAPIにすることが期待されます。使いにくいAPIでは、ローレベルな機能をそのまま使ってもいいじゃん、となり、うれしさが少ないからです。 図にするとこんなイメージです。 ローレベルな機能を使いやすくする便利なAPIをRubyで作る、という使い方です。 みなさん、Droongaを知らないと思うので、もっと有名なソフトウェアを例にしてどういう使い方かを説明します。 例えば、Vagrantです。Vagrantは開発環境を簡単に構築するためのツールです。Vagrantの設定ファイルはRubyスクリプトで記述します。設定ファイルではベースとするイメージはどれにするとか、メモリはどのくらいにする、などを指定すればいい感じに開発環境を作ってくれます。具体的にどうやってメモリを設定しているかを気にする必要はありません。便利ですね。 もうひとつの例はActive Recordです。Active RecordはRDBMSのデータをオブジェクト指向らしいAPIで操作できるAPIを提供しています。普通のオブジェクトの属性にアクセスするように書けばテーブルのカラムにアクセスできます。実際はSQLを使ってデータにアクセスしていますが、その詳細をいい感じに隠蔽して便利に使えるAPIを提供しています。 このように、ハイレベルなインターフェイスを作るという使い方はRubyが得意とする使い方の1つです。Rubyだけで実現できることも多いのでピュアRubyistでもできます。すでにこのように使っている人も多いでしょう。抽象化のバリアー(abstraction barrier)という概念を知っている人ならピンときやすいと思います。 では、Droonga、分散全文検索エンジンでは、どのあたりにこのハイレベルなインターフェイスを作るという使い方をするとよいでしょうか。 Droongaではメッセージングシステムでこの使い方をしています。 図にするとこうです。 全文検索リクエストを受け取って、それをいい感じに分配して、それを集めて返す処理です。 このメッセージングシステムを使うとユーザーはどうやって分散して検索しているかを気にする必要はありません。お願いしたらあとはいい感じに検索してくれます。便利ですね。 さて、このメッセージングシステムのそれぞれの処理の性能特性を考えてみましょう。 どのように分散して検索するかを考えるところが一番性能に効いてきます。いい感じに分散できたときと、無駄に分散してしまったときでは100倍以上の差が出ることもあります。 リクエストを分配するところは主にネットワーク処理が性能に影響します。 レスポンスを集めるところは主にCPUを使う処理とネットワーク処理が性能に影響します。 ということで、どのように分散するかが大事ということです。 もしかしたら、新しく分散方法を考えなければいけないかもしれないですし、既存の分散方法を改良しないといけないかもしれません。 そのためには、手早く動くものを作って、測定して、フィードバックを得るということを繰り返すというやり方が向いています。 Rubyは手早く動くものを作ることがしやすい言語なのでこのような開発方法に向いていますね。 なお、Droongaのように速度を改善するという場合ではなく、使い勝手を改善するという場合もこのようなフィードバックループは有効です。なので、ハイレベルなインターフェイスを実装するという使い方はRubyが得意な使い方です。 次の使い方はグルー言語として使う使い方です。対象者はC/C++も書けるRubyistです。 C/C++も書けるRubyistな人はどのくらいいますか? グルー言語として使う場合、2つのパターンがあります。 1つめは他の言語の機能をRubyから使えるようにするという機能です。バインディングや拡張ライブラリーと呼ばれているものです。 2つめは複数の機能を組み合わせる機能です。 実例はまたActive RecordとVagrantです。 Active RecordはMySQLへアクセスする機能を得るためにmysql2 gemというライブラリーを利用しています。このmysql2 gemがグルーです。 Vagrantは仮想マシンを作ったり作った仮想マシンをセットアップする機能など、外部の機能をつなぎあわせています。Vagrantの主な仕事がグルーです。 どうしてグルー言語として使うかということ、既存の機能を再利用したいからです。 グルーの作り方は3つあります。 1つめはmysql2 gemのように拡張ライブラリーを作る方法です。 2つめはVagrantのように外部のコマンドを実行する方法です。 3つめは外部のサービスを使うクライアントを実装する方法です。例えば、Twitterクライアント機能を実装する、といったことです。 Droongaの中でもいろんなグルーを使っています。 既存のGroongaという全文検索エンジンを使うためにRroongaというバインディングを使っています。 既存の高性能なイベントループを機能を使うためにCool.ioというバインディングを使っています。 Droongaクラスター内のノードを管理するためにSerfを使っています。Serfはコマンドを実行して使っています。 Rroongaの使い方についてもう少し説明します。 Droongaの中ではそれぞれのノードで全文検索エンジンを使うためにRroongaを使っています。 Droongaの中で全文検索エンジンは速くなければいけません。そして全文検索はCPUが性能に影響する処理です。メモリーも影響するんですが、メモリーに載らなくなったら性能的に負けなのでそこは前提と考えた方が適切です。 全文検索機能は速くなければいけないので、Rroongaも速くなければいけません。Groonga自体は速いのですが、Rroongaを経由したら遅くなった、はまずいのです。 では、どうすればRroongaも速くなるか、ポイントをいくつか紹介します。 まず、重い処理はCで処理するようにします。このとき、RubyらしいAPIをできるだけ維持することが望ましいです。RubyらしいAPIについては去年のRubyKaigiで話したので興味がある人は探してみてください。るびまにも記事を書きました。 次のポイントはメモリーをできるだけ割り当てないことです。これは、内部でキャッシュ用のバッファーを持つことになります。 次のポイントはマルチプロセス処理です。Rubyは1プロセスでは複数のCPUコアを使い切れないのでマルチプロセス構成にします。なお、Groongaは複数プロセスからデータベースを開けるので、マルチプロセス構成と相性がよいです。 今日は最初の重い処理はCで処理するについて少し説明します。 Rroongaでは全文検索はこのように書きます。 データベースを開いて、テーブルを持ってきて、descriptionカラムに対して"Ruby"という文字列で全文検索するというコードです。Rubyでは「=~」がマッチ演算子なので同じ演算子で全文検索できるようにしています。 このコードは重い処理をCで実行しています。 それでは、参考のために重い処理もRubyでやるコードを見てみましょう。 データベースを開いてテーブルを持ってくるところまでは同じです。find_allはおなじみのEnumerableのメソッドで、「/Ruby/」は正規表現でのマッチです。 このコードは各レコードに対してRubyのブロックを評価しています。この各レコードに対して実行する処理が重い処理です。 それでは、最初のコードがどのように実行されるかを見てみましょう。 実は、ブロックの中身は1度しか評価されません。1度実行して、それをGroonga用の式にコンパイルします。それを使ってGroonga内で検索処理を実行します。各レコードに対する処理はすべてCで行われます。 図にするとこうです。 ブロックの中で式を作って、それを使ってGroonga内部で評価して結果を返す、という流れです。 これでホントに速いのか気になりますよね。ベンチマークを取ってきました。 Rubyから使ったときとCから使ったときの速度を比較します。 なお、Cで書くとこうなります。 結果はこの通り、0.2msくらいしか変わりません。これならRubyから使っても十分速いと言えます。 それでは、最後の使い方、Rubyを組み込む使い方です。対象者は普段からRubyもC/C++も書いているというRubyistです。 どのくらいいますか? Rubyを組み込んだことがある人はどのくらいいますか?rubyコマンドは除きます。 Rubyを組み込むとは2種類の使い方があります。 アプリケーションやライブラリーの中で使うエンジンとしてRubyインタプリターを使うという使い方です。 アプリケーションやライブラリーのAPIや設定にRubyを使うという使い方です。インターフェイスとしてRubyを使う使い方です。 例を示します。 内部のエンジンとして使っているのはGroongaで、これはこれから説明します。 インターフェイスとして使っているのはVIMです。私はEmacs派なのでよく知らないんですが、RubyでVIMの設定をできたりするんですよね? なお、GroongaにRubyを組み込んでいるというのはDroongaから見るとこうなります。 Droonga自体はRubyで書かれていてその中でRroongaを使っていてそれ経由でGroongaを使っていてさらにその中にmrubyがいます。 組み込むRuby実装はCRubyもmrubyも使えます。私は両方組み込んだことがあるので、その経験から違いを説明します。 CRubyは普段使っているRubyの機能をすべて使えることが魅力です。ただ、それほど組み込まれることを想定していないので、初期化するとシグナルハンドラーを設定するなどします。なので、初期化時などにひと工夫必要です。 mrubyは1つのプロセスに複数のインスタンスを作れることが魅力です。シグナルハンドラーを設定したりしないのでわりとすんなり組み込むことができます。ただ、普段使っている機能がなかったりするのでいつも通り書くとうまく動かないことがあります。 Groongaではスレッドごとにインタプリターを作りたかったのとフットプリントが小さくしたかったのでmrubyを使っています。 Groongaではmrubyをどのように使っているかを説明します。 今はクエリーオプティマイザーとして使っています。 今後はGroongaのコマンドやプラグインを書くAPIとしても使っていこうと思っています。 クエリーオプティマイザーについて少し説明します。 クエリーオプティマイザーとはどのように検索するかを考える処理です。 これが結構面倒な処理です。 その後に実行する全文検索に比べたら軽い処理です。 データに依存するので動的な要素もあります。 このような範囲検索をするクエリーを考えます。 単純な実装を考えます。まず200より小さいレコードを集めます。次に100より大きいレコードを集めます。それらの共通部分を求めます。これで結果がでます。 これはデータが多いと遅くなります。 最適化するとこうなります。最初から下限と上限を指定して検索します。これならデータが多くなっても大丈夫です。 では、組み込んでホントに割り合うのでしょうか。計測してみましょう。 計測するのは次の2項目です。 1つはmrubyを組み込んだことによるオーバーヘッドです。 もう1つは最適化した結果どのくらい速くなるかです。 オーバーヘッドです。 クエリーが複雑なほど解析する時間がかかります。複雑化どうかはざっくり言えば項目が多いかどうかです。mrubyを組み込むことで0.Xms秒増えました。全文検索処理はXms以上かかるので、これくらいなら許容範囲です。 次はどのくらい高速になったかです。 100レコードでもmrubyを組み込んだときの方が速いです。レコード数が多くなるとその差は明らかです。オーバーヘッドが許容範囲で、最適化が効くと十分高速になるので割にあいますね。 注意 今日は触れていませんが、組み込むのはけっこういろいろやることがあります。バインディングを書いたり、ビルドシステムにmrubyのビルドを組み込んだりいろいろあります。 mruby部分のテストをどうするの?デバッグは?gdbは使えないよ、みたいなことがあります。 興味がある人は後で話してください。 まとめです。 Rubyの使い方を3種類紹介しました。 1つめはハイレベルなインターフェイスを実装するという使い方です。 2つめはグルー言語として使うという使い方です。 3つめはRubyを組み込むという使い方です。 ハイレベルなインターフェイスを実装するという使い方はピュアRubyist向けです。 便利なインターフェイスを提供する使い方です。 Rubyの柔軟性が役に立ちます。 グルー言語として使うのはC/C++も書けるRubyist向けです。 既存の機能を再利用する使い方です。 速さが必要ならできるだけCで処理を動かすようにしましょう。 Rubyを組み込む使い方はRubyと同じくらいC/C++も書くRubyist向けです。 Rubyを使って面倒なプログラムをしなくても済みます。 ただ、面倒を避けるためにそこそこの作業が必要になるので、本当に割に合うかはよく考えてください。 割に合うならとても役立ちます。 興味がある人は私に相談してください。 ということで私の話は終わりです。 最後にお知らせです。 クリアコードはシルバースポンサーです。なので、少し宣伝していいよね。 開発者を募集しています。こんなRubyの使い方に興味のある人は検討してみてください。 私はリーダブルコードという本の解説を書いたのですが、その縁もあってリーダブルコードワークショップというのを開催する予定です。日々リーダブルコードを書きたい人は検討してみてください。 どっちもチラシを配っています。私に声を書けても説明しますし、クリアコードのサイトをみても載っています。 Groongaに興味を持った人は11/29(いい肉の日)にイベントがあるので予定を開けておいてください。同じ日に地域Ruby会議もあると思いますが、よく考えてみてください。