pixiv insideは移転しました! ≫ https://inside.pixiv.blog/

pixivのデータストア/キャッシュ戦略 その3

HHKB Professional Type-Sが欲しいインフラ兼ソフトウェアエンジニアのbokkoです。

普段はHHKB Proの日本語配列キーボードを愛用しています。英語配列は苦手です。このことを同僚のエンジニアに言うとジト目で見つめられ・・・睨みつけられること請け合いです。

本連載の最後となる今回はpixivのデータストア/キャッシュ戦略を支える周辺ミドルウェアについて解説していきます。

memcachedからKyotoTycoonへ移行した際に発生した問題

前回の記事の最後にもあったようにpixivではAPの数だけあったmemcachedへのリクエストを少数のKyotoTycoonにまとめたことで一部のKyotoTycoonサーバへのTCPコネクション数が爆発してKyotoTycoonサーバのCPUやメモリリソースには余裕があるのにネットワークで詰まるという問題が起こりました。

元々pixivではサーバにOS(Debian Squeeze)をネットワークインストールする際にセットアップスクリプトでnet.ipv4.tcp_fin_timeoutやnet.ipv4.ip_local_port_rangeをはじめとするカーネルパラメータをチューニングしているのですが、それでもアクセスのピーク時になるとKyotoTycoonに対するTCP接続が失敗するケースが頻発していました。

memcachedプロキシの導入

この問題に対処するため、pixivではneoagentというmemcachedのプロキシサーバを全アプリケーションサーバ(以下AP)上で稼働させています。

アプリケーションは直接KyotoTycoonにTCP接続するのではなく、AP上で稼働しているneoagentに対してUNIXドメインソケット経由でアクセスします。

neoagentはあらかじめリモートのKyotoTycoonサーバへのTCPコネクションを生成しておき、neoagentに接続してきたクライアントに対して個別にあらかじめ生成されているTCPコネクションを割り当てます。そしてそのクライアントとの通信が終わったらまた別の接続してきたクライアントにTCPコネクションを割り当てます。

いわゆるコネクションプーリングというやつです。このようにあらかじめ生成されたTCPコネクションを使いまわすことで各APからリモートのKyotoTycoonサーバへのアクティブなTCPコネクションの数を劇的に減らすことが可能になります。

去年memcachedからKyotoTycoonに移行した当時はmemagentという別のmemcachedプロキシサーバをいろいろと改造して導入していたのですが、元々memagentはpixivのために開発されたわけではないので、一部pixivの用途に合わない部分がありました。

当初はpixivに必要な機能を追加したり細かいバグ修正のパッチをあてる改造を繰り返していたのですが、それでもいくつか問題や課題があり、段々メンテナンスするのも難しくなってきたので昨年に行われた弊社の開発合宿にて筆者が新規にneoagentを開発しました。現在も継続的にメンテナンスを行っています。

neoagent~A yet Another Memcached Protocol Proxy~

neoagentは以下の特徴や機能を備えたmemcachedプロキシサーバです。言語はCで書かれています。

  • コネクションプーリング
  • リクエストパイプライニング
  • コネクションプールの使用状況や現在/ピーク時の同時接続数等のモニタリング
  • 設定ファイルがJSON
  • memcachedのASCIIプロトコルのサブセットをプロキシする(set、get、incr、decr、add、delete、quit)
  • アクティブ/スタンバイのフェールオーバー

pixivに必要な機能のみを実装しているので汎用的ではありませんが、規模が小さく使用しているライブラリやコメントなどを除けば行数は3500行ほどになります。

memagentもコネクションプーリングの機能をもっているのでKyotoTycoonサーバへのアクティブなTCPコネクションの数を減らすという目的は達成できていましたが、さらに以下の目標を達成するためにneoagentを開発することにしました。

  • multi-getの高速化
  • コネクションプールの使用状況や現在/ピーク時の同時接続数のモニタリング

また、memagentがシングルスレッドでリクエストを処理するのに対し、neoagentは(v0.5.xから)マルチスレッドでリクエストを処理するのでmemagentよりも高いスループットを出すことに成功しています。

multi-getの高速化

元々memagentは大量のキーを使うmulti-getをうまく扱えるように設計されていないので、キー数の多いmulti-getのリクエストを処理しようとするとレスポンスを返すのに非常に時間がかかることがありました。

これはmemagentがmulti-getのリクエストを1個1個、個別のgetリクエストに分割して対象サーバにリクエストするので、キーの数に比例して応答時間が長くなるためです。

neoagentではmulti-getのリクエストを極力パイプライン処理することでキー数の多いmulti-getのリクエストであっても非常に高速に処理できるようになっています。

コネクションプールの使用状況や現在/ピーク時の同時接続数のモニタリング

memagentでは、稼働しているmemagentプロセスに対して任意のタイミングでどれだけの同時接続数があるのか、あるいはコネクションプールはまだ余裕があるか、といった情報を取得あるいはロギングするのが実装上困難でした。そこでneoagentは開発当初からモニタリング機能を搭載することを意識して設計・実装しました。

neostat~neoagentモニタ~

neostatは特定のneoagentプロセスの状態をモニタリングするプログラムでPythonで書かれています。neoagentはクライアントからのリクエストを受け付けるポートとは別に、接続してきたクライアントに対してneoagentの現在の状態をJSONで返すためのポートを持っています。neostatはneoagentから現在の状態を1秒おきにJSONで受け取ってTUIで整形して表示します。

また、neoagentの状態をMuninのグラフで描画したいという要望がインフラチームからあったのでv0.4.8で単にneoagentから受け取ったJSONを出力して終了するオプションを追加しました。

まとめ

memcachedプロキシサーバであるneoagentとその開発目的や機能・特徴について解説しました。 全3回にわたってpixivのデータストア/キャッシュ戦略について解説してきましたがいかがだったでしょうか。

pixivではPHPRubyJavaScriptといった所謂LLによる開発が大部分を占めますが、今回紹介したneoagentのようにCやC++で低レイヤーなコンポーネントを開発・最適化するといった仕事もありますので、興味のある方はぜひ採用サイトからご応募ください!