たごもりすメモ

コードとかその他の話とか。

#fluentd な今だからこそふりかえる scribed のすべて

最近 fluentd というツールのことがたいへんよく話題に上がっており、かく言う自分もささやかながら使用している身なのだが、それはそれとして比較対象に上がってくるツールに scribed というものがある。これがどういうものなのか、話には聞いていてもよくは知らないという人が多いようなので、これもささやかながら触ってみている自分としてはここらで一度まとめておかねばなるまい、と思った次第である。
日本全国に10人くらいはいるかもしれない scribed のヘビーユーザ各位に捧げる。

なお記憶と経験だけを頼りに書き殴るので、意思決定の重要な局面とかで「これこれこういうブログにたごもりすなる者がこのようなことを書き残しており」などと引用するのはくれぐれも避けていただきたい。
また途中から思いっきりビール飲みながら書いたので文章自体の品質にも問題のある可能性がある。

そも scribed とは何かというと、みんな大好き facebook が2008年だかに公開したオープンソースソフトウェア*1で、主にログの配送と収集・集約を目的とする。同じく facebook が開発した Thrift*2 というRPCプロトコルをベースにしたもので、C++で書かれている。Thrift自体が依存関係にけっこううるさい上に scribed 自体の依存関係もだいぶ激しい状態なのでインストールが極めて面倒くさいのが特徴。詳しいソフトウェアとしての特性は後に譲る。

なお上述リンクのgithubプロジェクトが公開場所なのは間違いないのだが、Commit履歴を見ればわかるとおり、現時点でもう一年半、コードはほぼ放置状態。事実上、開発は停止しているといってもいい。動くのは動くんだけど。*3
このため、既に Boost ライブラリのバージョンについて問題が起きており boost 1.45 以下と組合せてビルドしないと動作しない。なお現時点(2012年2月14日)での boost 最新版は 1.48。詳細については こちら を参照のこと。またThriftが boost 1.36 以降を要求するため、boost はこの間のどれかのものを使う必要がある。これが scribed の面倒な依存関係の一部、ということになる。
その他に Hadoop HDFS への書き込みを使う場合についても、Hadoop側は新しくなり続けているが scribed 側がついていかない、ということになると今後どこかで動作しなくなることが考えられるので注意したい。

なお scribed について知っている限りでは、ドキュメントの類は 公式の設定ファイルの解説 くらいしかない。日本語で書かれた記事となると本当にさわりの使ってみた系のものしか読んだ覚えがないので壊滅的といっていいと思う。

scribedの機能

scribed はログを配送するためのデーモンである。ここでログといったが、実際にはこれは「カテゴリ」「メッセージ」というふたつの文字列からなる。カテゴリはscribedによる配送中にその経路や最終的な書き込み先を決定するためのラベルで、メッセージは通常であればログの行そのものとなる。もちろんscribeプロトコルに乗せてログ以外のものを流すことも、やろうと思えばできる。

scribeプロトコルおよび scribed そのものの作りとしては同期的な処理をするものになっている。クライアントからメッセージの送信がある scribed に対して行われた場合、受け取った側の scribed は設定ファイルの内容に従って手元のディスクへの書き込みなり他の scribed へのネットワーク転送なりを行うが、これらが完了した時点ではじめてクライアントに対して OK というレスポンスを返す。通信は全て TCP で行われ、またネットワーク送信時のエラー制御・再送処理などもしっかり作られているため、これらのメッセージ伝達の確実性においては実はかなり秀でている。*4

受信機能

ログの受信については完全にネットワーク経由のみである。port 1463*5を listen し、そこに送られてきたメッセージはどこからのものであってもすべて平等に(混ぜて)扱う。

libeventを用いた非同期I/Oおよび設定で指定可能なマルチスレッドにより、多数のコネクションを扱うことができる。手元では150程度は1プロセスで全く問題なく捌けることを確認している。

送信機能

特定の scribed にネットワーク経由で送信できる。きわめて安定している上に速くてまったく問題なし。

書き込み機能

ローカルディスク、もしくはHDFSに書き込む機能がある。HDFSに書き込む場合はビルド時に libhdfs を有効にする必要がある。
書き込み先は設定で指定したパス(例: /path/to)の下にカテゴリ名でディレクトリを作り、その下にファイルを作成する。 /path/to/category/file_access_log_00000 のような感じ。00000の部分は連番でファイルのローテート毎に数字が増えていく。
ファイルのローテーションをサポートしており、設定した1ファイルのサイズ上限(デフォルト1GB)を超えるか、もしくは設定した期間を過ぎるか(時間毎、もしくは日毎が指定可能)で次のファイルに書き込みが切り替わる。また最新のファイルに対してシンボリックリンクを張る機能もついていてたいへん便利。

これも極めて安定しつつ高速に動作する機能で、特にファイルのローテーション回りは実運用を考えて作られている、という印象がある。複数のサーバから送られたログが単一のディスクに整然とローテートされて格納されるのは後から調査する側からすると極めて扱いやすい。

バッファリング、分散

scribed はバッファリングの機能を持っている。これは性能特性を向上させるためのものではなく、ある特定のログの配送先(例えばネットワーク送信対象の別ホストの scribed)に対する書き込みが連続的に失敗する場合、一時的にそのログを確保しておき、正規の配送先が復活したときに再送するための一時的な置き場のことだ。詳しくは このあたり を読んでいただきたい。
これは実際にうまく動作する。何度も試したから間違いない。再送パフォーマンスもよく、設定を間違えさえしなければ新しくやってくるログの受け入れに負担をかけないまま障害中の再送を完了させてまたストリーム処理モードに戻ることができる。配送経路の途中のノードを割と気軽に停止できるのは運用の手間が格段に向上するので、なくてはならない機能と言っていいと思う。

またメッセージを複数の宛先に分散する機能もある。multi と bucket だ。multi はメッセージを複数の宛先にコピーして渡す。bucket は設定した複数の宛先にどれかひとつに渡す。

multi を使うことで「ローカルディスクに書き込みつつ次の scribed にネットワーク送信する」ということが可能になる。自分の手元ではこれを用いて複数のサーバのローカルディスクにログを書き込ませることでバックアップの代わりとしている。

bucket はメッセージの先頭にユーザ名などが書かれていた場合に、それをキーに使って配送先を選ぶことができる。したがってログ全体を複数の宛先に分割しつつ、特定のユーザのログは特定の宛先にのみ行くようにする、といった機能だ。またランダムで宛先を選ぶこともできるため、単純なロードバランスにも使うことができる。(……と思っていたんだけどなあ。)

scribedの性能特性

scribed は C++ で書かれており、また前述のように libevent をベースにした非同期I/Oライブラリを使用している。これらの要素と、おそらく真剣にパフォーマンスを考えてコードが書かれているのだろう、性能が良い。きわめて良い。
最終的にピーク時で150Mbpsを超える量のログデータを1プロセスで扱っているが、これでも性能的にはまったく問題ない。4Core HTのCPUの論理CPUひとつ分(12.5%)を使いきるところに全く届いていない。その半分も使ってない感じ。まだまだ行けるだろう。先に disk I/O wait の方がきびしくなると思う。*6

scribedの問題

さて機能も豊富、性能特性もきわめて良い、というscribedだが、問題はある。いっぱいある。順にいこう。

なおインストールが面倒くさいという問題もあるにはあるが、そこはまあアレですよ、要は慣れというか。自分はもう15分あれば1台いけるね。きっとみんなも10回くらいやればそうなるよ。あと他の言語はともかく、日本語に限って言えば自分がだいぶ細かくblogエントリ書いたので、それを読めばなんとかなるんじゃないかなー。そうだといいなー。

で、問題。

枯れてない機能が(いまだに)ある

たとえば先述の bucket だが、ランダムで送り先を指定できる、とあるのでこれがロードバランサに使えるかと思ったら、もう全然使えない。ひと桁メッセージでの各ノードへの配送が100回くらい連続したかと思ったら、その次に突然40000メッセージくらいがどかっと特定ノードに送られたりする。あのな。それじゃロードバランスになってねえだろ。おまえランダムって言葉の意味知ってんのか。いいかげんにしろ。

そのほか、各出力設定の細かいオプションなど、これ本当に動くのか? と言いたいところが随所にある。設定例の最後に書いてある Thriftfile 出力とか用途もわからないし試す気にもなれない。

ドキュメントがない

先述した通り公式の設定例解説みたいなものが唯一あるだけで、それも実際にはどう書いていいものかよくわからない説明だったりすることもある。ソースコードツリーの中の example を頼りになんとか推定し、それでもわからないものは実際に書いて動かし試してみるしかない。ひどく不便。
またコードが C++ なので、いや C++ を言い訳にするのは俺の C++ が劣っているせいなので良くないのだが、ともかくどう設定すればいいのかコードを読んでもぱっとは理解できないのがつらい。自分の感覚だと試行錯誤して確認した方がまだ早くて確実だった。

その上、唯一の頼りの設定例にすら 「(may not be in open-source yet)」と書かれた設定項目があったりして憤死しそうになる。あのな。いいかげんにしろ。俺はその設定項目が欲しいんだよ。外に出して釣りしてんじゃねえ。*7

拡張性がない

プラグインとかまったく考えていない構造になっており、自分でちょっとコード書いて動作追加して動かしたいなー、と思ってもそのようなことをする余地は一切ない。いや scribed そのものにがっつり手を入れる気になればやれないことはないんだろうが、そもそもコード全体が fb303 という facebook 内部のフレームワークべったりなためかなりクセがある。fb303べったりになる覚悟があるならやりきれるかもしれないが、自分には無理である。

周辺ツールがない

scribed はあくまでデーモンとして動作する単独のソフトウェアであり、普通なら他に必要となるような周辺ツールがほとんど無い。たとえばデーモンの起動・停止・再起動・状態報告をするための init script であったり、たとえばネットワーク経由で scribed にログを送りつけるためのエージェントツールであったり。
ログを送信するためのツールとしては scribe_cat というものが申し訳程度にコードに含まれているが、これは stdin からの入力をすべて読み込み、全部一括してサーバに送る、というたいへん難儀なつくりになっており、マトモに運用に耐えられるようなものではない。

これらについて、自分は エージェントinit script も自作することでどうにか運用まで持っていったが、このような時間/手間を払える場所ばかりでもないだろうことを考えると、世間一般で使われるには難易度が高過ぎるだろう、という気がする。

根拠もほとんどない妄想みたいな想像だけど、facebookはきっとWebサーバも自分たちで書いてて、そこから直接 Thrift のコード経由でログを scribed に送り付けてるんだろうなあ。あんだけ大きけりゃそれはアリかもしれないけど、俺達にはそれはちょっと無理だし。

先がない

さっきも書いたが、開発が止まって久しい。boostもHadoopも着々とバージョンが上がっている中で scribed のコードがいつまでも対応しないと、いずれはこれらのバージョンアップに置いていかれる可能性がある。
boostは最悪いちどビルドしてしまえばそのまま置いておけるが、Hadoopについてはクラスタのバージョンを一切上げないことなどできるわけもないので、HDFS書き込みを有効にして使用している場合は特にこの制約がいつか現実的なものになるだろうと思う。

まとめ

性能的には極めて優れている。いっぽうであれこれやったりするには拡張性・メンテナンス性のうえで難がある。

以下のようなケースでは scribed を使うのは現実的で、コストパフォーマンスに優れているだろう。

  • 大規模なWebサーバ群を抱えており、それらのログを集約してディスクに書き込んでおくことでサーバ障害によるログの紛失から守り、また有事のログ検索性を上げたい

以下のようなケースにはあまり向いていないと思う。

  • そもそもサービス規模が小さく高パフォーマンスを要求されない
  • ログに対して多種・多様な処理を行ったり、細かい配送先制御を行ったりしたい
  • 最新のストレージシステムへの書き込みなどに柔軟に対応したい

特にscribedがいかんと言うわけでない。自分が面倒を見ているシステムではついに稼動中の scribed 台数は2台にまで減ってしまったが、その2台、つまりアーカイブ目的での最終的な集約とディスクへの書き込みについては scribed を使い続けるだろうと思われる。
何事も目的とメリット・デメリットである。

Flume? 誰かがそのうちこの記事と似たようなのを書いてくれるんじゃないかなー。期待したいなー。