update フィルター詳しく
バージョン0.10.0からドキュメントの部分更新ができるようになったようです。また、リビジョンを指定しなくても更新できるようになり、リラックス度が飛躍的に向上しています。
http://d.hatena.ne.jp/z-ohnami/20091110/1257863136
ということで、私も試しています。ひとまず、「これ使うとGET→INCREMENT→UPDATE みたいなことできるの?」という点は突っ込まれると思っています。
update フィルターの並列実行
// test/updates/read_and_write.js function(doc, req){ doc.count = doc.count || 0; doc.count +=1; return [doc, "Now: " + doc.count + "\n"]; }
ひとまず、ab で -n 100 -c 1 とします。100リクエストで、同時実行数1。PUTリクエストするには -u ファイル名 だそうです。今回はリクエストボディはどうでもいいので適当にhogeという空のファイルを指定しています。そして、あらかじめ、test_read_and_write という_idのドキュメントをFutonで作っておいて、abします。
$ URL=http://127.0.0.1:5984/mydb/_design/test/_update/read_and_write/test_read_and_write $ ab -n 100 -c 1 -u hoge "$URL" (snip) Benchmarking 127.0.0.1 (be patient).....done (snip)
で、curl で確認します。
$ curl -X GET http://localhost:5984/mydb/test_read_and_write {"_id":"test_read_and_write","_rev":"326-435041d4fc6cc97e4e895b5cfdb76b87","count":100}
いいですね。100リクエストをシリアルに飛ばしたんですから、100になるはずです。
では次に、10並列で飛ばします。countの値が200になればいいのですが。。。
$ URL=http://127.0.0.1:5984/mydb/_design/test/_update/read_and_write/test_read_and_write $ ab -n 100 -c 10 -u hoge "$URL" (snip) Benchmarking 127.0.0.1 (be patient).....done (snip) Failed requests: 88 (Connect: 0, Receive: 0, Length: 88, Exceptions: 0) (snip)
一応終わりはしました。が、、88個エラーになっちゃいました。実際に確認すると、
$ curl -X GET http://localhost:5984/mydb/test_read_and_write {"_id":"test_read_and_write","_rev":"338-5e5198cf73a875736388643bc5cc6519","count":112}
ということで12個の成功の分だけしかカウントされていません。?_conflicts=true をしても、特に衝突は見つかりませんでした。純粋にHTTPレベルでエラーになってコミットしないんですね。
じゃ、どんなエラーになるの?
困ったことにJavaScriptは、sleep相当の組み込み関数がないので、とりあえずビジーウェイトさせて重い処理にしてしまいます。あまり重すぎると OS Process timeout になるので、注意。適当だけれど以下をフィルタに登録。
// test/updates/read_and_write_heavy.js function(doc, req){ doc.count = doc.count || 0; doc.count +=1; for(var i=0; i<50000000; i++){ // do nothing; } return [doc, "Now: " + doc.count]; }
これで、うちのiMac(3.06GHz 2core / 4GB RAM)だと、1リクエストに3,4秒かかるようになりました。で、適当に2つのターミナルで、
$ curl -v -X PUT http://127.0.0.1:5984/mydb/_design/test/_update/read_and_write_heavy/test_read_and_write
を実行します。すると片方は、
$ curl -v -X PUT $URL * About to connect() to 127.0.0.1 port 5984 (#0) * Trying 127.0.0.1... connected * Connected to 127.0.0.1 (127.0.0.1) port 5984 (#0) > PUT /mydb/_design/test/_update/read_and_write_heavy/test_read_and_write HTTP/1.1 > User-Agent: curl/7.19.7 (i386-apple-darwin10.2.0) libcurl/7.19.7 zlib/1.2.3 > Host: 127.0.0.1:5984 > Accept: */* > < HTTP/1.1 409 Conflict < Server: CouchDB/0.10.0 (Erlang OTP/R13B) < Date: Sat, 12 Dec 2009 11:25:39 GMT < Content-Type: text/plain;charset=utf-8 < Content-Length: 58 < Cache-Control: must-revalidate < {"error":"conflict","reason":"Document update conflict."} * Connection #0 to host 127.0.0.1 left intact * Closing connection #0
こんな感じで返してくるでしょう。ということで、_rev指定で不一致だったときと同じように、409 Conflict になって、いつものJSONのエラーメッセージが帰ってきますよ。
で、GET→INCREMENT→UPDATE みたいなことできるの?
yesといえばyesでしょうか。でも、SELECT FOR UPDATE のようにドキュメントにロックをかけるようなやりかたはない、という点は注意です。失敗することを前提にロジックを組む必要があります。クライアントでリトライを何回か繰り返して、それでもだめなら、今混んでるからあとでやってね、とユーザーに通知するとか。
あるいは、要するにOptimistic Lockだと思って、
// app/updates/lock.js function(doc, req){ if( doc.locked ){ // エラーを返す(省略) }else{ doc.locked = true; return [doc, toJSON({ok : "true"})]; } }
とか、そんな感じで、アプリケーションレベルでロック制御をするのでもいいかもしれません。まぁこれは、ドキュメントがどういう用途で、トランザクションをどう設計するか、でしょうね。