2011年07月14日

Hello, transaction

さて今回初めてデータベースを使ったわけですが、このカウンタには問題があります。ユーザーがきわめて多くなると、並列実行の問題から正常にカウントアップされないかもしれません!
このページが1000ページビュー/秒を達成したときに備え、トランザクションを用いてプログラムを書き直しましょう。

トランザクションを用いるには、2つの操作が必要です。
・DBに名前を付ける
Db.transactionを用いてDBにアクセスする

DBに名前を付けるには、database文を使います。
database counter = @local("./hogehoge")
この場合、webサーバーを実行した際のカレントディレクトリ上に、hogehoge_から始まる一連のファイルを作成し、データベースをそこに保存する、という意味になります。

トランザクションを用いるには、トランザクション処理の開始から終了までをまとめた関数を作る必要があります。以前のincr_countをそのままリネームしてatomic_incrとしてしまいましょう。後はDb.transactionにDB名と、トランザクション処理関数を与えるだけです。
  result = Db.transaction(counter, atomic_incr);
トランザクションの実行が成功すると、実行結果が{ some=結果 }という値になって返ってきます。一方、トランザクションの実行途中に別のプロセスがDBの同じ値を書き換えてしまった場合、トランザクションの実行は失敗し{ none }という値が返ってきます。この値を取り出すには、match文を使います。
  match result with
| { none } -> incr_count() // トランザクションをやり直す
| { some = result } -> result

なお、{ some = result } -> result なんて書くのはかったるい! という人のために、~{some} -> someという構文が用意されています。~{some}{ some = some }の糖衣構文です。

これで、あなたのカウンタの1000ページビュー/秒対応が完了しました。あとは中身を充実させるだけですね?

database counter = @local("./counter") // DBに名前を付ける
db /counter/cnt : int // DBの"/counter/cnt"はint型

incr_count() =
(
result = Db.transaction(counter, atomic_incr);
match result with
| { none } -> incr_count() // トランザクションをやり直す
| { some = result } -> result
)

atomic_incr() =
(
c = /counter/cnt;
do /counter/cnt <- c + 1;
c + 1
)

hello() =
<p>
あなたは{
incr_count()}番目のお客様です。
<
/>

server =
Server.one_page_server("Hello", -> hello())


(次回に続く)
posted by chun at 00:16| OPA