DjangoのORMが実行してるSQLの見方 (とdjango-debug-toolbar のススメ)

django-debug-toolbar 使おう。

以下の様な記事があった。

django.db.connection.queries を使うとよいそうです。でもこれを覚えるのはちょっと大変。

ここで django-debug-toolbar 使えばもっと楽にできるのでオススメしたい。 どうするのかというと、 django-debug-toolbardebugsqlshell という機能を使う。 これは普段の manage.py shell とほとんど同じなんだけど、SQLが実行されたときは、そのSQLを表示してくれるというもの。

(django-debug-toolbar は画面上にデバッグ用のパネルを表示してくれたりする、頼もしいやつ)

django-debug-toolbar をインストールすると管理コマンドで debugsqlshell というのが 使えるようになるので、設定とかもそれほど必要ない。

インストール:

$ easy_install django-debug-toolbar

設置ファイルに記述:

# settings.py
INSTALLED_APPS += ('debug_toolbar',)

ってすれば、もう使える。 django-debug-toolbar の機能をモリモリ使うなら他にも設定は要るけど、 debugsqlshell だけなら、まぁこれでもOK。

# $ ./manage.py debugsqlshell

>>> from django.contrib.auth.models import User
>>> User.objects.all()
SELECT "auth_user"."id",
       "auth_user"."username",
       "auth_user"."first_name",
       "auth_user"."last_name",
       "auth_user"."email",
       "auth_user"."password",
       "auth_user"."is_staff",
       "auth_user"."is_active",
       "auth_user"."is_superuser",
       "auth_user"."last_login",
       "auth_user"."date_joined"
FROM "auth_user" LIMIT 21  [0.40ms]

[<User: hirokiky>]
>>> qs = User.objects.all()[:10]
>>> qs
SELECT "auth_user"."id",
       "auth_user"."username",
       "auth_user"."first_name",
       "auth_user"."last_name",
       "auth_user"."email",
       "auth_user"."password",
       "auth_user"."is_staff",
       "auth_user"."is_active",
       "auth_user"."is_superuser",
       "auth_user"."last_login",
       "auth_user"."date_joined"
FROM "auth_user" LIMIT 10  [0.35ms]

[<User: hirokiky>]
>>>

こんなかんじ。 クエリセットが評価されてSQLが投げなれるタイミングもよくわかる。

でも django-debug-toolbar の本領はこんなもんじゃなくて、画面上にデバッグ用のパネルを表示してなんぼのもの。 使うデバッグ用のパネルは設定で選べるんだけど、とくに良いのが debug_toolbar.panels.sql.SQLDebugPanel 。 これを追加してあげれば、1リクエスト中に走ったSQLと、その実行時間をガントチャートで表示してくれる。 やたら遅い画面とかあるときに原因 (クエリ1000件以上投げてるじゃん!とか) をすぐ発見できるのでオススメ。

django-debug-toolbar がいないと生きていけない。

「それZopeだよ」って言われたので作ったもの

URLディスパッチャーとか飽きたので、もっと他のことがしたかった。 URL(リクエストのPATH INFO)にマッチしたパターンに対応するビューなど言ってないで、 より汎用的な構造はないかと考えていた。

思いついてつぅいーとしてた:

URLディスパッチャとか無しで、「リクエストオブジェクトを受け取り、レスポンスオブジェクトを返す、呼び出し可能オブジェクト」が、自身のパスにマッチしないリクエストであれば他の呼び出し可能オブジェクトに移譲するっていう何かを妄想した ( https://twitter.com/hirokiky/status/312183468127834113 )

というと「それZopeっていうんだよ」と教えてもらった。

あとは Traversal というのもあるらしかった。

今日、ちょっとTraversalを試して、分かった気になったりもしたけど、 結局自分で書いてみないと納得いかないので、書いた。

書いたもの

これ

ただ「自身のパスにマッチしないリクエストを移譲する」のではなく 「子を呼ぶ条件にマッチすると、それに移譲する」ものを書いた。

node というデコレーターを使って、どのWSGIアプリケーションが呼び出されるかを定義する。 gistにもあるサンプルを下にそのまま貼ったので、それで説明していく。

@wsgify
def node2(request):
return "OK. I'm node2"

@wsgify
@node({'a': 'node1', 'b': 'node2'})
def node3(request):
return "OK. I'm node3"

@wsgify
@node({'a': 'node2', 'b': 'node3'})
def node1(request):
return "OK. I'm node1"

if __name__ == '__main__':
httpd = make_server('', 8080, node1)
httpd.serve_forever()

各関数に付いている node というデコレーターが今回書いたもの。

ここではまず node1 が呼ばれる。けど @node の引数に渡されている辞書とPATH_INFO をみてマッチするものがあれば、さらにその子が呼ばれる。 例えば /a/ にアクセスされている場合、 node1 のマッピングに a が存在しているので node2 が呼ばれる。 node2 には @node がないのでそのままレスポンスが返される。

マッチするものがなかったら (例えば /c/) デコレートされている関数 (この場合 node1) がそのまま呼ばれる。

/b/a/ という場合、まず /b/ にマッチする node3 が呼ばれる。 1度呼び出しがあると次の @node/ 区切りで、その隣を見る。ここでは anode3 では a に対して node1 が当てられているので /b/a/ では最終的に node1 が呼ばれることになる。

ちなみに辞書の値には関数の文字列を与えればよく、呼び出し時に読み込まれるので、関数を書く順番には 気を遣わなくていい。

拡張するなら

今回は辞書と、それに path_info がマッチしているかをみているだけだったが、 リクエストオブジェクトを受け取ってブール値を返す関数を与えて、その返り値が True の 場合に子を呼び出すようにすれば、どのような条件にも対応できる。 あるいはそのような関数のタプルを渡して、それらがすべて True の場合に呼び出すと、 PyramidのPredicateっぽくなるかもしれない。

できれば単にデコレーターで受け継ぐだけじゃなくて、その間に処理を挟みたい。 他の呼び出しがある前に、リクエストオブジェクトに対して何らかの処理を すればいいだけなので、それでミドルウェアの実装もできそうではある。

あとは「自身にマッチしなければ移譲する」ものを作りたかったので、それについて考えるのもいいかも しれない。