PerlプロジェクトでテストカバレッジのCIをする方法
前回から大分時間が経ってしまいましたが、今回はPerlのテストカバレッジ結果のサマリをJenkinsにIntegrationする方法について説明します。
概要
以前、proveのJUnit用のFormatterを使ってテスト結果をJUnit形式のXMLに変換する方法を紹介しましたが(http://dann.g.hatena.ne.jp/dann/20100404/p1)、カバレッジについても、Java用のプラグインの形式にフォーマットを変換して使うことで、Jenkinsに簡単に統合することができます。
Java界隈ではCloverというテストカバレッジ用ツールがあり、Jenkins用にプラグインが用意されています。一方、PerlではDevel::Coverという有名なテストカバレッジ取得用ツールをがあります。要するに、Devel::Coverの出力結果を、Clover用のXMLに変換できればいいわけです。
Devel::CoverにはReport機能が用意されており、それによって出力方法を変更することが可能になっています。これは、proveのformatterの機能と同等のものになります。テスト結果を、proveのJUnit形式のformatterでJenkinsに統合したように、Devel::CoverもReport機能で出力形式をClover形式にあわせることで統合できます。
Jenkinsへの統合方法
既にCPANには、Devel::Cover::Report::CloverというClover用のレポートモジュールが用意されているので、これを使えばよいだけです。
build stepを追加して、以下のようなコマンドを追加します。coverコマンドの後のreportオポションがポイントです。
perl Makefile.PL cover -delete HARNESS_PERL_SWITCHES=-MDevel::Cover=+ignore,inc prove -lv t cover -report clover
これにより、プロジェクトのトップページにテストカバレッジのサマリが表示されるようになります。そして、各ビルドには各ファイル毎のカバレッジ詳細が見えます。
これに加えて、Devel::CoverのHTMLも出力して詳細を確認できるようにしておくとよいでしょう。カバレッジが極端に低い場合には、サマリだけでなく詳細を確認していきたくなるからです。以前、やり方を紹介してますので、ご興味あれば参考にしてみてください。
http://dann.g.hatena.ne.jp/dann/20100331/p1
テストカバレッジ取得のCIの重要性
品質は、人が作り込むもので、毎日の積み重ねが重要です。それを実現するためには、品質が高まっていることが目に見えるということがモチベーションを高める上で大事になります。従って、CIによりテストカバレッジのサマリがプロジェクトのトップページから簡単にみれるというのは重要なわけです。
高いテストカバレッジになっていることで、リファクタリングの安心度があがり、プロダクトの改善を進めやすくなります。これにより、プロダクト規模が大きくなってきた場合にスピードを維持しながら機能改善・リファクタリングを行っていくことが可能になります。結果として、高いカバレッジが効果として実感できるようになり、よりカバレッジが高まっていくというよいサイクルを生み、結果として品質を高めていきます。
このようにテストカバレッジをCIに組み込むことで、高いカバレッジを維持しながら楽しいリファクタリングライフを送ることが可能になります。皆さんもJenkinsさんのアメリカンな笑顔を見ながら、テストカバレッジを楽しんでみるのはいかがでしょうか。
Enjoy!
大規模(AP/DB) サーバーでのLarge Pageの使用のすすめ
近年はメモリも安くなり、性能を向上させるために多量のメモリを使うことで性能を維持するのは、ある程度の大規模環境であれば一般的なことではないかと思います。特に、JVM、Oracleなどで多量のメモリを使うアプリケーションでは、ヒープサイズが数Gb-数十Gbだったり、buffer cacheのサイズが数十Gbだったりといったことも多くなっているのではないかと思います。
このような場合には、Large Pageの使用により、以下の3点の理由から性能が向上するケースがあります。
1ページのサイズが512倍と大きくなるため、TLBでカバーされる範囲が大きくなるため、キャッシュヒット率が向上します。ミスヒット時のペナルティは大きいので、これは一つ大きな効果があります。
また、1pageあたり4kで管理されていたメモリが、HugePageにより1pageあたり2mbになるわけで、メモリ容量をアロケーションする回数は1/512になるので、メモリアロケーションが高速になります。
さらに、TLBミスヒットした際に、2Mbの場合にページ階層が4->3になるため、アドレス変換も高速になるというわけです。
Large Pageの使用は、I/O性能の問題は解決できていて、問題がCPU/メモリに移っているケースでないとあまり効果は見られません。非常に早いディスクを使っているケースやほぼ全ての処理がメモリ上で行われるケースでは大きな効果が出ます。例えば、JVMではGC回数/時間の削減という形で非常に効果が見えるので、x64環境でGCに困っている方は試してみられることをお勧めします。
Large Pageを使用するための設定方法
仮に6Gb程度のヒープサイズを確保したとしましょう。
この場合、/etc/sysctl.confを変更します。
次のように設定します。
8Gb shared memory segment sizeを確保すると、1024 * 1024 * 1024 * 8 = 8589934592 のbyte数が必要なので、
kernel.shmmax = 8589934592
と設定します
また、hugetlbが使えるように、groupのIDを設定します。
java, mysql, oracleなどのユーザーのgroup idを次のように設定します。
vm.hugetlb_shm_group = <group id>
次にlarge pageのページ数を設定します。確保するメモリは6Gbなので、次のように計算します。
6Gb/ 2Mb = 3072 pages
設定は次のようにして設定します
vm.nr_hugepages = 3072
最後に物理メモリにpinします
javauser soft memlock 6291456 javauser hard memlock 6291456
最後にsysctl -p で設定を反映して、cat /proc/meminfo でHugePage数が設定できているかを確認して、3072が設定できていれば完了です。このページ数はサンプルですので、ページ数は実際の規模にあわせて設定してください。
Javaで使う場合には、上記に加えて、 XX:+UseLargePagesをJVMオプションに加えます。
Oracleなどで使う場合には、初期化パラメータにuse_large_pages=onlyを設定して使います。
また、この設定により、HugePageがRAMにPINされるため、ページアウトが起きなくなります。非常に大きなSGAサイズを確保しており、OSでのpage out回数の問題で性能が劣化するような大規模なトランザクションを扱っているケースなどでも効果があります。
大規模DBサーバーでのInfiniBandの利用のすすめ
大規模なトランザクションが求められるシステムでは、ストレージには一定以上の投資をしたほうが全体の運用コストは下がります。アーキテクチャをシンプルに出来、かつ台数を少なくできることでオペレーションコストが劇的に下がるからです。
従来はHDDを中心にしたストレージサブシステムが利用されていたため、ネットワークの帯域やレイテンシがDBのレイヤで問題になるほどの性能が求められるハードを調達できるケースは少なかったのですが、FusionI/OをはじめとするEnterpriseレベルのSSDの登場でネットワークのレイテンシ/帯域を考慮する意味のある場面が出だしています。これは、ストレージレイヤでの問題が解消されることで、ネットワークおよびCPUにボトルネックが移ってきているためです。
このような場合、Infinibandは10Gbps Ethernetとも比較してレイテンシが低いため、非常に高いトランザクション量が求められるDatabase Serverには適しています。
この低いレイテンシの低さは、次の2点の機構で実現されています。
1点目は、InfiniBandのRDSをUDPソケットとして使用する機能を用いていることです。これにより、例えば、Oracleでは以下のようにTCP/IPのレイヤをバイパスしています。
Oracle/RDS (User Mode) -> RDS(Kernel Mode) -> HCA Oracle/UDS(User Mode) -> (TCP -> IP -> IPoIB (Kernel Mode)) -> HCA
2点目は、RDS のゼロコピー実装である ZDP(Zero-loss Zero-copy Datagram Protocol)を使うことで、ブロックの不要なコピーを削減していることです。
OSのシステムバッファーへのデー タコピーを抑え、カーネルの仕事量を減らすことでCPU使用率を下げ、ネットワークを効率的に利用できるようにしているわけです。
このようなプロトコルのオーバーヘッドの削減とメモリコピー回数の削減により、非常に低いネットワークのレイテンシを実現しています。このように、Infinibandの低レイテンシを活かすことにより、ネットワーク のレイテンシおよび帯域幅が問題となるアプリケーションでは大幅に性能を向上することが可能になります。
特に非常に大規模なトランザクションが求められるシステムでは、アプリケーションだけの改善では十分な効果を得られないことがあり、ハードウェア、ミドルウェア、アプリケーションまでを含めてトータルな改善が必要になります。
Infinibandとそれに対応したミドルウェアの利用はそのような場合の一つの解になります。特にストレージサブシステムの進化が目覚ましい昨今では、利用により効果がみられるシーンも増えてきており、大規模トランザクションに困っているケースには十分検討の価値があります。
OracleでSQLのParse回数を軽減するための方法
OracleではSQLの実行手順は、次の3フェーズから構成されます。
- Parse
- Execute
- Fetch
その中でParse処理は、次のような手順で行われます。
- セッションにキャッシュされたカーソルが存在しない場合
- カーソルをオープン
- 共有プール上に共有カーソルがある場合
- soft parse
- 共有プール上に共有カーソルが無い場合
- Hard Parse
- 共有プール上に共有カーソルがある場合
- カーソルをオープン
- セッションにキャッシュされたカーソルが存在する場合
- そのカーソルを利用
特にCPU負荷の高いHard Parseの処理では
という処理をします。処理を見ればわかるように、この処理が毎回走ってしまってはとてもCPUコストの高い処理になってしまいます。
そのため、共有プールに既に解析済みSQL情報があれば、Hard ParseはパスしてSoft Parseをし、共有プールから解析済みSQL情報を取得します。セッションにキャッシュされたカーソルがあれば、その部分についてはSoft Parseもパスできます。
従って、Parse回数の削減のためには、共有プールサイズに解析結果を乗せてHard Parseが発生しないようにし、Soft Parseになるようにするようにすればいいわけです。それに加えて、Session Cursor CacheでSoft ParseもさせないSofter Soft Parseをするようにすることで共有プールのチェックもしないようにすれば、よりCPUには優しくなります。
Parseについては、OracleとAPの話と両方あるのでまとめると、SQLのパース処理は次のように削減することができます。
- Oracle
- ハードパースの削減
- 共有プールにキャッシュ
- ソフトパースの削減
- Session Cursor Cacheの利用
- ハードパースの削減
- AP
- Prepared Statemenc CacheでRDBMS側のPrepare作業をさせないようにすることでParse回数を削減する
このような方法で、SQLのParse回数を削減することで、CPU負荷は確実に削減できます。ここでのパース回数の削減結果は、StatspackのInstance Efficiency Sectionで確認できます。I/O性能に余裕があり高負荷の場合には、CPU側がネックになるということもあり、少しでもOracleのCPU Timeを削減したいということがあります。このチューニングは容易に出来るものでもあるので、あらかじめやっておくのがいいでしょう。
ここからは余談ですが、この話を書いていた時に、DeNAのHandler Socketでは、このSQLのパース処理がコスト高ということで、パース処理をショートカットすることでQPSを高めるということをやっているという話を思い出しました。Prepated Statementなどでキャッシュできる場合を考慮すると、Handler Socketでパース処理コストを削減と書いているのは、正確には上記で書いたパース処理の範囲より広範囲なものをさしていると推測されますが、SQL関連処理をばっさり切ってしまっていることでCPU Timeの削減を図るというのは面白いなと思いました。KVS的に使う場合には、SQLをほぼ捨てることで、このように過激に性能改善をする方法もあるのは面白いと思ったのと、ここらあたりの仕組みを外からプラガブルに組み込めるmysqlは非常に素晴らしいなと思ったのでした。
# 追記: Handler Socketの存在意義について
なんで、そんな何回もHard Parseする必要があるのだろうと思ってたんですが、sh2さんのコメントをみてわかりました。「MySQLには共有プールなんてないしPrepared Statementをキャッシュしても実行計画は都度生成する、など考えるとHandlerSocketの存在意義がよく分かる」と。
Persistent Connection + Prepared Statementを使いにくいという事情があるのはConsumer系ではよくある話なので、それで難しい部分はあるのかなとは思ってました。ただ、共有プールもないとすると、そもそもMySQLでは毎回Hard Parseするしかないということなんだなあと。これはちょっといけてないですね。MySQLも、ここらはまだこれからって感じなのかな。
OracleのSQLのチューニングに使うツール
Oracleでは性能に関連するデータが色々と取れるため、性能問題の解決がかなり容易になっています。OracleのSQLのチューニングによく使うツールと使い方のポイントをまとめてみました。
性能状況の統計的分析 (Statspack)
問題箇所の全体に占める割合によって性能改善の効果が決まるため、測定されたデータから統計的に性能比率を分析し、チューニングすべき箇所を見つけるのは、SQLレベルでのチューニングではなくてもよくやることだと思います。このために使うのはStatspackです。
ある期間における統計をとれるので、全体の中から問題になるSQLを発見することができます。Level7でStatspackを取得すれば大体チューニングに必要な情報が取れます。Standard EditionでもEnterprise Editionでも利用できるため、よく使われているツールです。
では早速、Statspackをセクション別に簡単にポイントを説明していきます。
- Load Profile
- Hard Parseが多発してないか、Physical Readが多発してIOPS不足になってないか、生成されるREDOサイズなど基礎的な状況の確認などをします
- Instance Efficiency
- バッファキャッシュのヒット率が低くないか、Hard Parse回数が多くないかなど
- Load ProfileやInstance Efficencyのセクションで問題になるケースは、殆どのケースでDBA不在といっても過言ではないです。
- 大概がサーバーパラメータファイルの設定で解決できるので、このレベルでは少し知っている人がみれば解決できることが殆どで、通常はあまり問題にはなりません
- Top 5 Timed Events
- SQL ordered(SQL ordered by CPU Time, Elapsed Time, Executionsなど)
- CPU時間がかかりすぎているもの, Elapsed Timeが長すぎるもの(待機イベントが殆ど)、Executionsが多すぎるもの(実行回数が多いもの)はここを見ればわかります。全体の中でどれだけのCPU Timeを占めているかなどの割合がわかるので、どこを改善すればどの程度性能向上の余地があるのかがわかるという点で、何をチューニングする必要があるのかがわかるので、とても貴重です。
- CPU TimeやElapsed Timeが長い場合
- Executionsで実行回数が多い場合
- 1回あたりのCPU Timeは短いけれど、実行回数が多く性能が劣化しているケースはよくあります。良くあるのはN+1問題などで、無駄なSQLの発行回数が増えてしまっていることはあり、そのような問題を見つけやすいとはいえます。
- Segments
- どの表、どの索引の負荷が問題になっているかがわかります。
このように、Statspackによる統計データは、問題の全体像、問題箇所、問題の割合を明確にできるという点でとても貴重なレポートです。しかし、Statspackで問題の大体の推測は出来ても、ある一時点での問題や複数の待機イベントの時間が突出して問題が混在している場合などには、Statspackでは完全に問題の原因を突き止めるのは難しいケースがあります。
ある時点での状況分析 (EM or ASH Viewer)
Statspackは統計データによる分析ですが、問題は統計だけではわからないというのはよくあります。今その瞬間でどのSQLがどの程度CPU Timeを使っていて、どの程度の待機イベントが発生しているのかを見なければ問題を特定できないことがあります。例えば、Statspackで待機イベントの待ち時間が長いものが複数存在している場合に、どのSQLがいつどの程度の割合で待機イベントで待っているかを判断しようとしてもわかりません。全体の中での統計とある一時点の状況は異なるからです。
このようなときに使うのが Enterprise Manager(or ASH Viewer) です。
Oracleがとても素晴らしいのは、どのSQLの実行時にどの待機イベントでどれだけ待っていたかというのを、Enterprise Managerを使うと時系列で見ることができます。Statspackのような期間での統計情報ではなく、まさにその遅延している状況で、特定のSQLについて待機イベントを把握することができるというのがポイントです。
発生する待機イベントの問題をどう解決するかはOracleの内部のアーキテクチャに密接に関連するため、チューニング方法は待機イベントによって異なります。しかし、Oracleのアーキテクチャをよく理解していれば、何が問題で待機イベントが発生しているかは想像がつきやすいものでもあります。
OSの性能データの分析(iostat, vmstat, mpstat, sar)
Statspackである期間の統計をとっても、ASHでみても分からないのは、OSの性能データです。
Oracleのようなミドルではなく、Hardwareのレベルでボトルネックになってしまっている場合には、ミドルレベルでいくら改善しようとしてもHardewareの性能限界からそれ以上の性能を出すことはできません。
OracleというMiddlewareの問題なのか、Hardeareの問題なのかの切り分けは、このレイヤで判断する必要があります。まずMiddleの性能を使い切れるようなHardwareを選定できているかを検証することは重要です。
Hardewareの基礎性能を測るツールについては先日のエントリに書いたので参考にしてみてください。
ディスクのIOPSレベルで問題が出ているために、待機イベントが発生しているのか、CPUリソースの不足で待機イベントが発生してしまっているのかなどは、OS側の基礎情報もつきあわせないと分からないことは、高負荷環境ではよくあるのではないかと思います。OSレベルで問題がない場合は、ASHを中心に特定のSQLでの待機イベントを中心にして、ボトルネックの推定をOracleのアーキテクチャを考量した上でしていくことになります。
基本的に、OSの性能データは、iostat, vmstat, mpstat, sarで見ます。これを時系列で記録し、EMなどと突き合わせるとどのような状況で問題がおきているかはわかりやすくなります。
ストレージのI/O周りでよくあるのは、Random Read/Write周りが限界にくることです。これはディスクの仕組みからくる物で、Random WriteだとSSDにするか、BBWCを使うか、Random ReadだとSSDにするかなど、はたまたディスク本数や回転数を上げるといったことでIOPSをあげ、Hardwareレベルでの限界値をあげておくというのはよくやるのではないかと思います。このIOPSを解決すると、CPU周りで詰まることが多い訳ですが、ここは色んなケースで詰まるのでCase by Caseです。このような場合、SQL発行回数の削減や非正規化などは、比較的よく使われるテクニックではないかと思います。
まとめ
全体の統計データと時系列データの2点を組みあせて、全体の中からどこを解決しなければいけないかを見つけた上で、問題の全体像が見えたら、問題を深追いしてボトルネックの原因を見つけるという手順になります。
それを実現するためには、OracleではStatspack + ASH + OSの基礎情報(iostat, vmstat, mpsta)を使って性能分析をします。これらのツールで詳細に性能に関する詳細データが統計的にも時系列的にも取ることが可能なため、ブラックボックスであってもある程度内部の動作の推測はできるので、ボトルネックの問題分析が容易になっています。
# 最近はMySQLもPerformance Schemaという近い物がでてきたので、もう少し時間が立つとOracleと同じような形になっていくのではないかと思います。今後のOracleの動向に注目したいところです。
DevOpsに優しいSQLの管理方法
性能チューニングは、Hardware, OS, Middlewareに対する理解があるかないかで大分かわってくるところがありますが、Hardware, OS, Middlewareといったインフラに詳しいだけでは、踏み込んだチューニングは行えません。それにも関わらず、AP開発と運用は比較的分離されていることが多いため、DevとOpsが協力できるような体制を築くことが必要になります。最近はDevOpsという流れもあり、インフラに詳しいDevOpsを担う人がチューニングを行うケースもよくあるのではないかと思います。ここでは、インフラ側に軸足を置く開発者をDevOpsという用語で書いていくことにします。
DevOps側からみたときに、DBアクセスが中心のAPの場合、テーブル構造、データのライフサイクル、実際のSQL、SQLの実行計画を見れば大概のことはわかるので、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文そのものにコメントを埋め込みます。
こうすることで、発行されたSQLの箇所がわかるようになるため、StatspackなどでSQLを見た時にどの部位が発行した物なのかがわかるようになります。こうしておくと、問題が起きた時にはDevOps側がStatspackなどのレポートを見てどの部位で問題が起きたかがわかるようになるため原因を追及することが出来、DevOps側はAPのドメインの言葉でAP開発者とコミュニケーションがとれるようになります。
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使用でコンパイルし、帯域幅を使いきれる状態にして測定しましょう。