[sqlite3][ruby]busy_handlerを使ってみる

すっごいいまさらですが、sqlite2(Ruby/DBI)→sqlite3(sqlite-ruby)の移行に着手しています。

sqlite2の時は、アクセスが競合してBusyになった時のリトライ処理が見あたらなかったので、自前でこんなことをしていました。
10.times_retry(DBI::DatabaseError,1) {
  @dbh = DBI.connect(@@db_prefix+@db_filename, "", "" ) unless @dbh
}
(DBに接続を試みて、DBI::DatabaseErrorが発生したら、1秒sleepしたあとリトライ、10回試みて、それでも駄目だったらDBI::DatabaseErrorをraiseします。)
Rubyらしいエレガントなコード♪とか思っていたのですが、プログラムのあちこちにこれが散見される状態というのもなんだか悲しい。

sqlite-rubyでは、busy_handlerという機能が実装され、こういう事はライブラリ内でやってくれるようになりました。ただ、これのサンプルが見あたらない。
あれこれ試行錯誤していちおう動くようになったのですが、なんだか謎の部分も残っているので、ご存じの方、教えていただけると嬉しいです。

片方のシェルで、sqlite3を走らせて、ロック状態にしておきます。
sqlite> BEGIN;
sqlite> insert into fragments (user_id,content,lastmod,bornin) values(0,"test","2009-04-01 12:59:15","2009-04-01 12:59:15");     
(sqlite2の頃は、BEGIN;した時点でロックされていたのですが、今のバージョンでは、実際にロックが必要になった時点でロックするみたいです)

もう片方でirbを走らせて、こんな感じでロックに激突させます。
require 'rubygems'
require 'sqlite3'

dbh = SQLite3::Database.new("test.sqlite3")
#dbh.busy_timeout(10000)
dbh.busy_handler{|data, retries|
   print "in busy_handler data is ",data,"\n"
   print "retries is ",retries,"\n"
   sleep 1;
   (retries<=3)
}
dbh.execute("insert into fragments (user_id,content,lastmod,bornin) values(0,'test','2009-04-01 12:59:15','2009-04-01 12:59:15');")

それいけっ!
irb(main):013:0> dbh.execute("insert into fragments (user_id,content,lastmod,bornin) values(0,'test','2009-04-01 12:59:15','2009-04-01 12:59:15');")
in busy_handler data is nil
retries is 0
in busy_handler data is nil
retries is 1
in busy_handler data is nil
retries is 2
in busy_handler data is nil
retries is 3
/usr/lib/ruby/gems/1.8/gems/sqlite3-ruby-1.2.1/lib/sqlite3/errors.rb:94:in `check': database is locked (SQLite3::BusyException)
        from /usr/lib/ruby/gems/1.8/gems/sqlite3-ruby-1.2.1/lib/sqlite3/resultset.rb:76:in `check'
         :
よしよし。

まとめ:busy_handlerの使い方

  •  DBに接続したあたりで、
    dbh.busy_handler{|data, retries|
    # BUSYだった時の処理
    }
    
    という具合にして宣言する。このコードはすぐ実行されるわけではなく、DBにアクセスしてBUSYだった時に初めて実行される。
  • retriesに試行回数が入っている。
  • (謎)busy_timeoutの挙動が謎。APIリファレンスを見ると、busy_timeout(1000)ってしたら、一秒おきにリトライしそうな物ですが、実際には丸無視しているように見えます
  • しょうがないので、busy_handler内でsleepします
  • (謎)dataって何でしょうね?
  • busy_handlerのブロックの評価結果がtrueであれば、リトライが実施されます。falseであれば、例外が投げられます。

最後の項目、return false って書くわけじゃなくて、ブロックの評価結果が使われるところがポイントかと。
上のサンプルでは、3回まではリトライして、それで駄目なら例外を投げるようなコードになっています。
カテゴリ: ,

トラックバック(0)

このブログ記事を参照しているブログ一覧: [sqlite3][ruby]busy_handlerを使ってみる

このブログ記事に対するトラックバックURL: http://mogya.com/mt/mt-tb.cgi/714

コメント(2)

m-ito :

busy_timeoutとbusy_handlerは同時には使えないものではなかったでしょうか。

sqlite3-ruby-1.2.4/api/classes/SQLite3/Database.htmlより引用

| busy_handler( data=nil ) {|data, retries| ...}
|
| 途中省略
|
| See also the mutually exclusive busy_timeout. <-- ここの表現より
|
| 途中省略
|
| busy_timeout( ms )
|
| 途中省略
|
| See also the mutually exclusive busy_handler. <-- そして、ここの表現からも...

間違ってたらゴメンなさい。

もぎゃ Author Profile Page:

あー。なるほど。

$ irb
irb(main):001:0> require 'rubygems';require 'sqlite3'
irb(main):002:0> dbh = SQLite3::Database.new("test.sqlite3")
irb(main):003:0> dbh.busy_timeout(1)
irb(main):004:0> dbh.execute("insert into fragments (user_id,content,lastmod,bornin) values(0,'test','2009-04-01 12:59:15','2009-04-01 12:59:15');")
/usr/lib/ruby/gems/1.8/gems/sqlite3-ruby-1.2.1/lib/sqlite3/errors.rb:94:in `check': database is locked (SQLite3::BusyException)

この場合は一瞬でエラーになるけれど、

irb(main):003:0> dbh.busy_timeout(10000)
irb(main):004:0> dbh.execute("insert into fragments (user_id,content,lastmod,bornin) values(0,'test','2009-04-01 12:59:15','2009-04-01 12:59:15');")
/usr/lib/ruby/gems/1.8/gems/sqlite3-ruby-1.2.1/lib/sqlite3/errors.rb:94:in `check': database is locked (SQLite3::BusyException)

こうやった場合は、10秒ちょっとかかりました。この時間がリトライ待ちなのですね。

おお、理解できました。コメント感謝です。

余談:
「mutually exclusive」なんて説明を見た覚えはないぞ、と思ったら、ボクが見ていたドキュメントが古かったんですね。
http://sqlite-ruby.rubyforge.org/
こいつはsqlite-rubyのものだからもはやメンテされていなくて、sqlite3-rubyの方を見ないといけない。

ここに一つ見つけたけど、公式にWEB上に上がっているものはないのかなぁ。
https://locum.ru/gems_rdoc/sqlite3-ruby-1.2.4/rdoc/README_rdoc.html

コメントする


画像の中に見える文字を入力してください。

このブログ記事について

このページは、 もぎゃが 2009年4月 1日 13:16に書いたブログ記事です。

ひとつ前のブログ記事は「 Evernote Premium 」です。

次のブログ記事は「 [メイドめーる]メール遅延のお詫び 」です。

最近のコンテンツは インデックスページ で見られます。過去に書かれたものは アーカイブのページ で見られます。

Powered by
Movable Type