DevOpsに優しいSQLの管理方法

性能チューニングは、Hardware, OS, Middlewareに対する理解があるかないかで大分かわってくるところがありますが、Hardware, OS, Middlewareといったインフラに詳しいだけでは、踏み込んだチューニングは行えません。それにも関わらず、AP開発と運用は比較的分離されていることが多いため、DevとOpsが協力できるような体制を築くことが必要になります。最近はDevOpsという流れもあり、インフラに詳しいDevOpsを担う人がチューニングを行うケースもよくあるのではないかと思います。ここでは、インフラ側に軸足を置く開発者をDevOpsという用語で書いていくことにします。

DevOps側からみたときに、DBアクセスが中心のAPの場合、テーブル構造、データのライフサイクル、実際のSQLSQLの実行計画を見れば大概のことはわかるので、SQLを中心にした管理にすることで多くの性能チューニングが可能になります。従って、SQLを中心にした管理が重要になります。そのためには、以下の3点が重要になります。

  • SQLを中心にしたDBアクセスラッパ/ORマッパ を使用する
  • SQLには管理コメントをつけ、どこで使われる物なのかがわかるようにする
  • ER図/CRUD図を用意し、SQLでのデータの使われ方がわかるようにする

SQLを中心にしたDBアクセスラッパ/ORマッパ を使用する

よくあるのは、ORマッパを使用しているが、どこでそのSQLが生成されているのかを特定するのに時間がかかるというケースではないかと思います。さらに、場所が特定できた場合にも、特定のSQLをORマッパでどのように生成ればいいのかというのを、ORマッパが生成するSQLを確認しながらデバッグをするという本末転倒な状況が割とおきがちです。これは割と色々なところで見られるよくあるORマッパ使用のアンチパターンなケースです。

従って、どの部分でどのSQLが発行されうるのかを、誰が見ても一目瞭然にしておく必要があります。そのためには、SQLを一元管理でき、発行されるSQLを管理ができるようにすることが重要です。これを実現するためには、色々な選択肢がありえますが、大体以下の3つの選択肢または組み合わせに集約されるのではないかと思います。

  • 外部ファイルにSQLを書いてSQLの一元管理をできるORマッパ/DBアクセスラッパを使う
  • ストアドプロシージャを使う
  • シンプルなSQLはORマッパにまかせて、その他のクエリはORマッパから直接SQLの発行をできるようにする

SQLに管理コメントをつける

どこが発行したものなのかをわかるように規則をもうけて、SQL文そのものにコメントを埋め込みます。

こうすることで、発行されたSQLの箇所がわかるようになるため、StatspackなどでSQLを見た時にどの部位が発行した物なのかがわかるようになります。こうしておくと、問題が起きた時にはDevOps側がStatspackなどのレポートを見てどの部位で問題が起きたかがわかるようになるため原因を追及することが出来、DevOps側はAPのドメインの言葉でAP開発者とコミュニケーションがとれるようになります。

ER図/CRUD図を用意し、データの使われ方がわかるようにする

データのライフサイクルがわかるようにすると、どこでどのようにデータが使われるかが分かり、APの分析が容易になります。ER図はリバースしておこすこともできますが、CRUD図はSQLとテーブル定義を突き合わせる必要があるため、文書としてあったほうがDevとOpsでのコミュニケーションはしやすく、文書として残しておくのがおすすめです。(トランザクション量の分析も事前に出来るとよいですが、それは統計データからも分析可能なので、無くても性能分析のためのコミュニケーションは成立します。)

まとめ

DevとOpsが共同で性能改善に取り組んでいくためには、SQLを中心にした管理が重要です。また、AP開発者とDevOpsの連携を促進するために、SQL管理を容易にするためのSQLの管理コメントの導入のすすめSQLの使われ方を示すためのER図/CRUD図の作成 をすることにより、AP開発者とDevOpsのコミュニケーションを容易にすることが重要であるということになります。

Hardwareの基礎性能を測定するためのツール

インフラの性能でアプリケーションの性能限界が決まってしまうため、Hardwareの基礎性能を把握しておくことはとても重要です。以下では、Hardwareの基礎性能を測るための各種ツールを紹介します。

ディスク用ツール - fio

DBサーバーなどのI/O intensiveなサーバーで、Random Read/WriteのIOPSの限界でアプリケーション性能が決まってしまうというのは良くあるのではないかと思います

ディスクの基礎性能を測定するためのツールとしては、fio があります。

このツールでは、read/write, DirectI/O, I/O sizeなど、色々なI/Oパターンを指定して測定ができます。様々なI/Oパターンで、ディスクのIOPS・転送速度を簡単に測定できるので、とても便利です。

メモリ用ツール - stream

CPUの処理性能の向上率に比べてメモリ帯域はそれほど伸びていないという現状があり、コア数が増えていくにつれてそれが問題になってきます。特にコア数が多く・memory-intensiveなアプリケーションでは、実際にどの程度のメモリ帯域があるかの目安を知っておくことは重要になります。

メモリの帯域幅を測定するためのツールとしては、「stream」があります。

このツールでは、Copy, Scaleなどいくつかのメモリのアクセスパターンに応じてメモリの帯域幅を測定することができます。
-O3、openmp使用でコンパイルし、帯域幅を使いきれる状態にして測定しましょう。

ネットワーク用ツール - nuttcp, nepim

多量のトランザクションIMDBなどの使用、大きなサイズのファイルの転送など、ネットワークのスループット・レイテンシが重要になるケースなどは、割とあるのではないでしょうか。

スループットなどのネットワークの性能測定用のツールとしては、nuttcp, nepimがあります。

これらのツールでは、バッファサイズやキューサイズなどを変更したテストが簡単にできるため、重宝しています。

# 他に、このツール便利だよ!というのがあれば、是非教えてください!

継続的プロファイリング

Google-Wide Profiling: A Continuous Profiling Infrastructure for Data Centers
http://research.google.com/pubs/pub36575.html

継続的プロファイリングの仕組み

  • 測定はOprofileとGoogle Perf Tools
    • OprofileでのHPMのプロファイル
    • Google Perf Toolsを使ってリモートからHTTPでプロファイル結果をとれる仕組み
      • ProfilerStart(), ProfilerStop()などのAPIがあるので、C++のアプリの場合、任意の部分をリモートからプロファイルできそうですね。こういう仕組みは、一部のサーバーのデバッグ用などにもとても良さそうです。
  • 回収・解析・表示の仕組み
    • Oprofileなどの結果をCollectorで集めて、集めた結果をMapReduceで処理してProfile Serverにいれて、Web Serverから表示できるようにしています

Googleの継続的プロファイリングの面白い点

 Hardwareの違いも含めてプロファイリングを通じて最適化するための仕組みを用意しているのはとても面白いなと思いました。処理性能も、コア数、メモリのバンド幅、メモリの構成、L1, L2, L3キャッシュなどの量などの関係、さらにアプリケーションの処理特性(CPU-intensiveな処理か、Memory-intensiveな処理なのかなど)になどによって、まるで性能は変わってしまい、それを完全に予測するのは難しいのは事実です。だからこそ、ハードに近いレイヤで、それを運用を通じて測定することで、性能改善をかけられる仕組みを用意しているのはGoogleらしいと思いました。
また、最適化した結果のコスト削減結果も、‘‘dollar amount per performance change,’’ has become popular among Google engineers. と書かれているように、コスト視点のメトリクスで見られ、性能改善結果を最終的なコストに直結させている点も面白いです

perfの使い方

CPUネックのサーバーの状況をリアルタイムに知りたいケースというのは、科学計算、暗号処理、画像処理などのCPU boundなアプリケーションでは良くあるんじゃないかと思います。

perfはoprofileのリアルタイム版といった感じで、hardware event, software eventの測定をでき、その場の状況を測定できるのでとても便利です。リアルタイムに状況を知りたい場合の使い方を中心にまとめてみました。

全体の測定

まずは、最初何も状況がわからなければ。

perf top

特定関数の測定

perf top実行後、s keyを押して、perf topで表示されたfunction名を入力。
これで、特定関数が測定できる。perf topで眺めながら、問題のある関数が把握できたらその関数だけ見るといった使い方が便利。

特定プロセス・スレッドの測定

問題のあるプロセス・スレッドが特定できているという場合には、それだけをモニタリングするというのは良くやりたくなることの一つです。以下で確認できました。

特定プロセスの測定

perf stat -e cycles -p プロセスID sleep 5
  • e で指定するイベントなどは適宜。

特定スレッドの測定

特定イベントのperformance counterの取得。
ps -L axなどでスレッドIDは決めて、以下を実行。

perf stat -t <スレッドID> sleep 5
  • e で指定するイベントなどは適宜。

特定イベントの測定

問題が特定イベントに絞り込まれている場合に。これは上記のいずれのものとも組み合わせられるので、適宜使うこと。

  • イベントはperf listで確認
  • perf stat -e event1,event2 で絞り込んで測定

call chainを表示してbottleneckを発見

これは、perf top, perf statと違って記録を残して分析するための使い方なのですが、とても便利なので書いておきます。

sudo perf record -g yourprogram
sudo  perf report -g

TwitterのBlenderに近いアーキテクチャになっているMessage Pack RPCのJava版の実装を読んでみた

TwitterBlenderはNIOの使い方として面白い使い方だなあと思っていたので、それに比較的近い実装をしているMessagePack-RPCのJava版の実装を読んでみました(MessagePack RPCのJava版でも同様にNettyを使っています)。

読んでみた感じだと、MessagePack RPCのJava版の実装のポイントは、以下の3つでした。

  • NettyによるNon Blocking I/Oサーバーのイベントループ
  • 入力と出力のMessagePackによるエンコード/デコード
  • 入力のメッセージからのRPC先の実装の決定方法(Reflection, JavaAssistの実装でのInvoker実装)

基本的なこの流れは、NettyのPiplineを使って実現しています。コードだと以下のような感じになってますね。ServlerやWSGI, PSGIなどを使っている方は理解しやすいんじゃないかと思います。

	public ChannelPipeline getPipeline() throws Exception {
		ChannelPipeline p = Channels.pipeline();
		p.addLast("msgpack-decode-stream", new MessagePackStreamDecoder());
		p.addLast("msgpack-encode", new MessagePackEncoder());
		p.addLast("message", new MessageHandler(handler));
		return p;
	}

簡単に動作をまとめると、「Nettyでイベントループをまわして、メッセージを受け取ったらMessagePackでデコードして、その後でMessageHandlerで呼び出し先を決めて(Reflection or JavaAssist)、オブジェクトのメソッドを呼び出しをして、結果はMessagePackのフォーマットで従って返す」ということになります。

Nettyはとても綺麗にNIO実装が抽象化されており、さらにPipelineやPiplineへのメッセージのエンコーダ・デコーダを切り替えられる仕組みなどを提供しており、サーバー実装に必要なものを簡単に書けるようになっているので、MesagePack RPCのJava実装もとてもシンプルな実装になってますね。MessagePack RPCのJava実装は、RPC呼び出しをJavaのメソッド呼び出しに変換する部分(Invoker)と、MessagePack形式のメッセージのエンコード/デコード部分が基本で、それをNettyにつなぐという形の実装になっています。

TwitterBlenderでは、メッセージのエンコード/デコードにはThriftを使っているようなので同じようにProxy Layerの部分ではPiplineに、OneToOneDecoder, OneToOneEncoderを実装したThrift用のDecoder, Encoderを実装して実現しているのでしょう。また、Blenderでは、バックエンドサービスの呼び出し手順を規定したワークフローは、NettyのPipelineとして実現されているということなので実装も大分イメージはしやすいですね。シーケンシャルにしたい部分をNettyのPipelineとして実装してるんだろうなと。

TwitterのBlenderのアーキテクチャのポイント

http://engineering.twitter.com/2011/04/twitter-search-is-now-3x-faster_1656.html

「バックエンドサービスのI/Oが同期になると、I/O待ちで遅くなり、フロントが詰まってしまうので遅い。だから、Non BlockingなI/O呼び出しをしI/Oを多重化し、その呼び出し結果を集約することで高速化した」というのがBlenderのポイントのようです。工夫としては、Back Endサービスの呼び出しに依存関係があるので、全部を並列に呼び出すことはできないので、サービス呼び出しの依存関係を考慮して、一部並列にするところは並列に呼び出して、それ以外の部分はシーケンシャルに呼び出していると。そうすることで、I/Oを多重化できる単位を分割して呼び出すことを実現しています。

Non Blocking I/O系サーバーはイベントモデルのなじみにくさから、比較的処理がシンプルな、このようなAPIサーバーの呼び出し結果の集約など、I/Oの多重化をすることで並列化できるところ以外に適用部分はないんだろうなあとは思ってました。ちょっと前だとFriendFeedが、Tornado使って近いことをやってましたよね。

TwitterBlenderは、そのアイデアから一歩踏み込んで、ワークフロー内のサービスの依存関係を考慮して、サービス呼び出しのI/Oを多重化して、一部サービス呼び出しを並列に実行するのを汎用のミドルとして実現したのは面白いですね。

# 実装が透けて見えるレベルで解説されているので、そのうちOSS化されるのかもしれません。