今日はトランザクション処理と採番の実行方法について説明します。
最初から説教じみた話になるので覚悟して読んでください。
補償トランザクションの悪夢
トランザクション処理が何のためにあるのかといえば、それは正しい情報を管理するためです。
トランザクションコミットで「正しい情報」、ロールバックで「正しくないか、まだ受け付けられていない情報」とすることで常に整合性が保たれた状態になります。
MongoDBやCassandraなど多くのKVSではACIDトランザクションをサポートしていません。使い勝手の良さから、これらを利用して業務アプリケーションを構築している事例もよく見かけますが、大抵はトランザクション処理で嵌っているようです。
そして、自力で解決しようとしてさらに酷い状態に陥っているのも見かけます。いわゆる補償トランザクションというやつです。
ですが、トランザクション処理を甘くみてはいけません。私も数年前に酷い経験をしました。
まず、補償トランザクションの悪夢を読んでみてください。
読みましたか?
ACIDトランザクションを採用しないで補償トランザクションなどで解決しようとするのは、少々腕に自信があるエンジニアに限って陥りがちな罠なのです。
トランザクションを理解しているがゆえに運用を甘く見てしまうのです。
ブログに書いたように、時間の無駄であり、周りを不幸にします。
私の師である中島氏の言葉を借りれば、
「クラウドの名の下に、既に確立された現行のシステム作りのノウハウをスクラップにして、またまた現場のシステム造りが混乱するのではないかという心配です。努力の割には、余り付加価値を生まない新しいお作法作りや置き換えに、優秀で貴重な頭脳や時間を浪費してしまうのではないかという危惧です。」
といったところでしょうか。
業務アプリケーションを作る上ではACIDトランザクションは必須だと考えてください。
ちゃんとACIDトランザクションをサポートしているミドルウェアを使いましょう。
トランザクションスクリプトパターンの導入
では、ACIDトランザクションさえサポートしていればいいのかというと、それでもまだ不十分だと思います。
メインフレームやDBMS、JDBCなどで使われてきた、commit/rollbackに代表されるコード埋め込み型のトランザクション管理は元々問題あるとされてきました。
それは、commitやrollbackなどの記述漏れに起因する不具合の発見に苦労するからです。
ソースを全部引っ張り出して「抜け」を探すことになる。「抜け」とは、開始点だけあるような処理、例えば、SELECT FOR UPDATEだけ実行されていてcommmit/rollbackされてない箇所のこと。他人のソースをひっくり返してロジックを追う作業は本当につらい。ぶいてく
そこで、APIの入り口と出口にトランザクションポイントを置くトランザクションスクリプトというデザインパターンで処理する方法を考えます。要は、API内部の実行が全部成功すればcommitとし、失敗すればrollbackとします。
これはサービスの最小単位がAtomicとなり、分かりやすいパターンです。
vte.cxにおけるトランザクション処理
vte.cxでは、Feed単位のAtomicトランザクション(分離レベル:REPEATABLE READ)、かつ、Entry単位のバージョン比較(分離レベル:SNAPSHOT ISOLATION)をサポートしています。
Feed単位のAtomicトランザクション
トランザクションスクリプトパターンにより、Feedに含まれるEntryは1トランザクションで実行されます。
例えば、Feedに在庫と受注の2つのEntryを入れることで、在庫引き当てと受注登録を1つのトランザクションとして実行させることができます。
詳しくは、トランザクション処理を参照してください。
Entry単位のバージョン比較
Feedに含まれるEntryにはリビジョン番号があります。
FeedをまとめてPUT更新するときに元のリビジョンと比較することで更新できるかどうかをチェックします。元のリビジョンが異なれば誰かによって既に更新されていることになりますので楽観的排他エラーになります。
これは上記のFeed単位のAtomicトランザクションと組み合わせて使うことができます。
つまり、Entryのどれか一つでも楽観的排他エラーになるとFeed全体の更新がロールバックされます。
採番処理について
vte.cxでは採番のためのAPIを用意しています。
採番もACIDトランザクションがないと実装が難しい機能です。
Writeロックを採用しており、FeedのPUT更新を使うより高速に取得できます。
PUT /{任意のURI}?_allocids={採番数} で任意のURIごとに指定された採番数だけ番号を採番します。
その他、setidsやaddids、rangeidsなどのAPIがあります。
詳しくは、採番と採番カウンタを参照してください。
採番処理とFeed更新の組み合わせ
採番をした値をキーにして登録したいことがよくあります。
その場合は、Entryに採番した値をセットしてからFeed更新することになります。
もし、Feed更新でエラーになってロールバックしても採番したキーは元には戻りません。
その場合、値を捨てて構わないのであればそのままでよいし、歯抜けをなくしたいのであればaddids()でマイナスの採番をすればOKです。
ちなみに、POST登録でKeyを指定しなければユニークな番号が自動で振られます。(自動採番)
詳しくは、Entry登録を参照してください。
本日はこれで終わりです。
明日はセキュリティについて説明します。
それでは。