そして再現へ
エラーの発生条件がわかったところで、メモリダンプからエラーとなったクエリを抜き出して、手元で再現させてみることにしました。ここまでやる必要はなかったと思うのですが、なんとなくやってみたくなったのです。
エラーとなったクエリを同時に2つのセッションから繰り返し実行していればいつかは現象が発生するだろうなと思いましたが、いつ発生するかもわからないエラーを待つのは面倒なので、参照カウンターがずれてしまう微妙なタイミングについてはデバッガで調整することにしました。イメージとしては、1つ目のセッションがあるコードポイントに到達したらいったん動作をフリーズさせて、もう一方のセッションをあるコードポイントまで実行させる。その後、フリーズさせておいたセッションをリジュームさせる… といった感じです。
この手順であれば「まぁ、すぐに再現できるだろうな。」と軽く考えていたのですが、1つ目セッションをフリーズさせた後で、なぜか2つ目のセッションの動作が開始しません。デバッガコマンドの使い方が間違っているのかなぁと思ってヘルプを確認したのですが、手順は間違っていなさそうです。「うーん、困ったなぁ。」と思っていたところで、お昼休みになってしまい先輩達とランチに出かけました。
お店に向かう道中、とある先輩*に「今こういうことやっていて、こういうことしたいんですけど、なんか期待通り動かなくて…」と相談してみました。すると「SQL Serverのスレッドスケジューリングってどんなだっけ?」と一言。私は、ハッとしました。
*第1回に登場した先輩の中の一人です。どうでもいいことですが、これまで記事に登場した先輩たちは実はみんなSQL Serverのエンジニアじゃないというのがなかなかニクイところです。
なぜ、2つ目のセッションは動作しなかったのでしょうか。答えは簡単です。SQL Serverはご存じの通り独自のスレッドスケジューリングを実装していますが、1目のセッションがアサインされたSQL Serverのスケジューラーと2つ目のセッションがアサインされたスケジューラーが同じスケジューラーだったのです。1つ目のセッションが動作している時に無理やりデバッガで停止させてしまうと、スケジューラーの使用権を保持したまま停止していることになります。2つ目のセッションは1つ目のセッションがスケジューラーの使用権を解放しないので動作できない状態にあったのです。デバッガからから見れば、2つ目のセッションに紐づくスレッドというのはいつでも動作できる状態なのですが。
当時私が使用していたマシンは、CPUコアが2つだったのでSQL Serverのスケジューラーは2つでした。その為、かなりの確率で2つのセッションが同じスケジューラーにアサインされるのです。
「いやー、これはめんどうくさいなぁ、どうしよう。」と思いました、今回は事例にヒットしたからいいものの、今後、未知の不具合で同じようなことを検証したいと思う時が来るかもしれません。
でも、よくよく考えると、SQL Serverを開発している人たちも同じようにスケジューラーが少ないと検証に困るんじゃないかなと思いました。そこで、「きっとDBCCコマンドや起動時オプションでスケジューラーの数を調整できるようなものがあるはずだ。」と思い、ざっと調査してみることにしたのです。
すると、期待した通りアンドキュメンテッドな起動時オプション-Pが見つかりました。-Pオプションを使用すると、スケジューラーの数を自由自在に調整できるのです。
このオプションのおかげで、先の現象はスムーズに再現させることができました。最終的に、お客様には既知のバグなので最新のパッチを適用することで問題は解消する旨をお伝えし、この件はこれであっさりと終了です。
今私がメインで使用しているマシンは4コアのハイパースレッディングで、SQL Serverのスケジューラーは8つありますから、もう-Pオプションは使用していません。手元で何かを検証する場合、8つスケジューラーがあれば大抵の場合かち合うことはないのです。
いや、ほんと、大抵は大丈夫なのですけど、この間フリーズさせたスレッドと動かしたいスレッドが同じスケジューラーにアサインされてしまって期待通りに動かない現象に遭遇してしまいました。1分間くらい何が起きたのかがわからなくて「(自分に対して) すぐに気が付けよ。」と思ったのですが、同時に「そういえば、昔は-Pオプションなんてものを設定していたなぁ。」なぁんてことを思い出して、今回の記事に取り上げてみました。
最後に、-PオプションはあくまでMicrosoftエンジニアの検証用オプションです!みなさんは絶対に使用しないように!