Lobiはメインの言語としてPerlを採用しています。 サーバーサイドで使用するコードは、Webアプリケーションから手動実行用のスクリプトまで、 ほとんどがPerlで書かれています。
(なぜかPerl6のコードがあることになっていますが、さすがにまだ使ってません)
が、そこは適材適所。 Goの方が適していると判断した部分では積極的に利用しています。
Goの使いどころ
単機能を高いパフォーマンスで実現する必要がある場合はGoの出番です。 バイナリひとつを配置すれば動作するというポータビリティも魅力的です。 これらのツール・アプリは単独で実行され、一部はアプリケーションの要求に応じてその機能を提供します。
- spam-filter
- maintainer
- gunfish
- katsubushi
- stretcher
- rin
- fluent-agent-hydra
- nuko
それでは各ツール・アプリについて簡単に説明していきましょう。 なお、GithubリポジトリへのURLがないものは非公開のプロダクトとなっています。
spam-filter
Lobiの主たる機能はチャットコミュニティです。 ユーザーがメッセージを投稿するわけですが、その中にはスパムや荒らしも含まれています。 これらの投稿は単純なNGワードで防ぐことは困難です。 対策として、すべての投稿をGoで実装されたベイジアンフィルタに通してチェックしています。
スパムメッセージの教示は人間が行う必要が有るため、 spam-filterが判断に迷ったメッセージの蓄積と人力によるスパム判定を専用の管理画面から行なっています。 spam-filter自体は教示を受け付けるインターフェイスしか持たず、管理画面とは完全に独立しています。
Lobiに投稿されるすべての投稿をspam-filterが1プロセスでチェックしています。
maintainer
サービスを提供しているからにはいつでも安定稼働することが理想ですが、 やむをえずメンテナンスを実施することがあります。 メンテナンス中にもユーザーからのアクセスは発生します。 リバースプロキシとして使用しているnginxから静的ファイルを返せば事足り・・・ればいいのですが、 クエリパラメータなどでレスポンスのフォーマットを指定できるAPIがあるため、そうはいきません。 パラメータを解釈して適切なフォーマットでメンテナンス情報を返す必要があります。
関連するnginx.confの設定箇所を抜き出すと以下のようになります。
upstream maintainer {
server 127.0.0.1:8090;
}
set $maintenance "false"
// 各種メンテ状態判定
if (...) {
$maintenance = "true";
}
...
// メンテ有効であればmaintainerにreverse proxy
if ($maintenance = "true") {
proxy_pass http://$maintainer;
break;
}
ngx_http_lua_moduleを使ってluaで実装することもできますが、 使用言語をむやみに増やしてもメンテナンスコストが上がるだけなので Goで専用アプリケーションを書くことにしました。
平常時は1台のアプリケーションサーバーにつき数十のPerlプロセスがリクエストをさばいていますが、 メンテナンス時は同サーバー上のmaintainerが1プロセスでメンテナンス情報を返しています。
(そもそもこんな仕組みを用意しなくてもいいように、 API設計時にメンテナンス中のレスポンスフォーマットを統一のものとして定義しておくのが理想でしょう)
Gunfish
2016-05-12追記
公開しました! https://github.com/kayac/Gunfish
追記ここまで
iOSのpush通知はAnyEvent::APNSで送っていました。 メインのアプリケーションからHTTPで通知専用アプリケーションにリクエストを送り、 それを元に非同期でAPNsに通知内容を送信する、という仕組みになっていました。 しかし、昨年発表されたHTTP/2を使ったAPNs Provider APIには対応していません。
Goで書かれたpush通知送信ツールとしては、gaurunなどがありますが、 こちらも当時はAPNs Provider APIには対応していませんでした。 (本記事公開時点では対応したPull Rquestがマージされているようです)
APNs Provider APIに対応しつつ、Lobi内で使用していた既存のフォーマットに互換のあるAPNs通知送信ツールとして実装したのがGunfishです。
Gunfishについて詳しくは以下の発表資料を参照下さい。
AnyEvent::APNsからGunfishに置き換えることで、 全ユーザーに対して送信する場合に300分程度かかっていた所要時間を90分程度まで短縮することができました。
katsubushi
https://github.com/kayac/go-katsubushi
katsubushiはTwiter社のsnowflakeのアルゴリズムを採用したid発番器です。 主にチャットIDの発番に使用しています。
タイムスタンプ、マシンID、シーケンスIDを元に発番するので、 各アプリケーションサーバーにマシンIDを一意に割り振れば 同期の必要なくユニークなIDを得ることができます。
daemonとして常駐し、idを必要とするアプリケーションとは memcachedプロトコルを使って通信を行います。 既存のプロトコルを流用し対応したライブラリを用いることで、 アプリケーション側のコード追加を最小限に抑えることができます。
stretcher
https://github.com/fujiwara/stretcher
stretcherはデプロイツールです。 特に初回のデプロイでは、依存するライブラリ等がある場合それらもインストールする必要がありますが、 Goで書いた場合はビルドして作られたバイナリを配置するだけなので非常にお手軽です。
stretcherを用いたデプロイについては以下のエントリーを参照下さい。
rin
https://github.com/fujiwara/rin
rinはAmazon SQSからの通知をトリガーにAmazon Redshiftにデータを投入するツールです。 Lobiではfluentdで集約したアクセスログをRedshiftに取り込むために使用しています。
詳しくは以下を参照下さい。
fluent-agent-hydra
https://github.com/fujiwara/fluent-agent-hydra
fluent-agent-hydraはログファイルを監視してfluentdに送信するagentです。 Lobiでは一日に数十GBのログが生成されており、 これらすべてを各アプリケーションサーバーに配置されたfluent-agent-hydraがfluentdに送信しています。
詳しくは以下を参照下さい。
nuko
おまけ。
nuko(ぬこ)はGoで書かれたSlack botです。 Go習得のためにいままでnodejs(hubot)で書かれていたbotを移植したもので、以下の機能があります。
- Github issue番号の展開
- レビュー依頼のランダム振り分け
シンプルなbotですがどれも開発に必須な機能ということでチーム内で日々利用されています。 レビュー依頼をbotに任せると、レビューさせちゃって申し訳ない感が薄れるのでおすすめ!
次回
今回はLobiで実際に使用しているGoプロダクトについて紹介しました。 次回はさらっと紹介したGunfishについて詳しく書こうと思います。