GoでSQLにトレーシングコメントを埋め込んで実行する
アプリケーションが発行するSQLにコメントが埋め込めると便利です。例えば、 /* path/to/logic.go:334 */ SELECT ... のようにSQLに発行元の情報をコメントとして埋め込んでからExecすれば、DB側のログ(general log等)にも記録されるため、SREやDREサイドからも、負荷の高いSQLがアプリケーションのどこから発行されているかが分かりやすくなります。
Goには github.com/shogo82148/go-sql-proxy という、SQL実行をトレースし、フック処理を差し込める便利なライブラリがありますが、今回それにpull requestを送って、SQL実行前にクエリの書き換えができるようにしました。
- https://github.com/shogo82148/go-sql-proxy/pull/61
- https://github.com/shogo82148/go-sql-proxy/pull/62
これらの変更により、SQLにコメントを埋め込んでから実行できるようになりました。例えば、以下のように、PrePrepareとPreExecとPreQueryを定義してクエリ発行前にフックしてSQLを書き換えます。
ここで呼び出されている sqlComment 関数にコメントを埋め込む処理を定義します。PreExecやPreQueryで if stmt.Stmt == nil の判定をしているのは、事前にPrepareされている場合にはそこでコメントが埋め込まれているため二重にコメントが埋め込まれるのを避けるためです。
sqlCommentの実装は例えば以下のようになります。stmt.QueryString を書き換えることで発行するSQLを事前に書き換えられるという寸法です。
ここで、呼び出し元のファイル名と行番号を埋め込むために findCallerという関数を呼び出していますが、この処理は例えば以下のようになります。これは go-sql-proxyのソースコードからコピペして調整したものです。skipForSQLCommentでそこまで遡るかの調整ができますが、中身は利用者側で調整してください。
ということでSQLに呼び出し元のソースコードのファイル名と行情報を埋め込めるようになりました。それ以外にもトレーシングIDなどをコメントに埋め込めると便利です。Hook関数にcontextが渡ってきているためそこから取得すれば良いでしょう。
最近本番にも投入して、非常に便利に使っています。まだ導入のための記述量が多いので、ライブラリとしてもっと簡単に導入できるようになると良いとは思っていますが、便利だと思うので、ぜひ同様に活用してみてください。
余談
このSQLに呼び出し元の情報を埋め込むというテクニックは、日本のPerl界隈ではポピュラーで、TengやDBIx::Sunnyでは大昔に実装されていたテクニックです。Goでもかねてから実現したかったのですが、この度やっと追いつくことができたと感じています。
また、Google、ORMが生成するSQLが遅いときの調査を容易にする「sqlcommenter」をオープンソースで公開。Rails、Spring、Djangoなど主要なフレームワークに対応 という記事が公開されて話題になりそうだったので、焦って(?)このエントリを公開しました。