アプリケーションのレベルでこそ要求に合ったトランザクションが実装できる~業務システムをRDBなしで作れるのか?(中編) エンジニアサポートCROSS 2016
数年前にNoSQLが登場した当時、NoSQLにはデータの一貫性を保証してくれるトランザクション機能などが十分に備わっていないため、業務システムのバックエンドとして使うのは容易ではないと考えられていました。
しかしその後、NoSQLをバックエンドにした業務アプリケーションは現実にはいくつか登場してきています。ワークスアプリケーションズが2014年に発表したERPの「HUE」もCassandraをバックエンドに採用した、本格的な業務アプリケーションです。
そのHUEの開発に関わるスタッフが、どういう実装ならばNoSQLが業務アプリケーションのバックエンドに使えるのか、それにはどういう意味があるのか、などについて議論したセッション「業務システムをRDBなしで作れるのか?」が2月に横浜で開催されたイベント「エンジニアサポートCROSS 2016」で行われました。
セッションでは、アプリケーションレベルでのトランザクション管理や分散データベースにおけるデータの一貫性についてなど、技術的に興味深い議論がなされました。本記事ではその内容のダイジェストを次の3本の記事で紹介します。
前編:トランザクションの実装にはRDB/NoSQLにかかわらず教科書的な定番がある
中編:アプリケーションのレベルでこそ要求に合ったトランザクションが実装できる
後編:同時実行性制御をアプリケーションで解決しようとすると深刻な問題が発生する
いまお読みの記事は中編です。
ケース1:給与計算バッチ
ワークスアプリケーションズ 堤氏。
ここではERP製品で出てくる、CassandraやNoSQLでまっさきに問題になるようなトランザクションにかかわる問題を、アプリケーションの実装でどうやって解いているのか、いくつかの例をあげて説明していきます。
1つ目は給与計算バッチ。勤怠情報、人事情報、計算式のデータを読んできて、計算して給与を計算するというものです。
エラーケースとして、計算開始後に、入力されていなかった勤怠データが遅れて入力されましたとか、そういうときには正しい計算結果にならない。
ただしこれは業務上、データには締め切りがあるわけで、締め切り後のデータ追加や変更はそもそもエラーケースです。そこで、エラーには気が付きたいと。自動で再計算するとか、間違ったデータは読み込まないようにしたいとか、そういうところまではやらないと。
こういう場合、われわれの実装としては、データを読み込んだ時にデータにタイムスタンプなどを付けて、ある時点でのデータの状態を記録し、計算終了時に状態を比較して新しいデータがあったらアラートを出す。
さっきの要件ならば、最後にチェックをかけるというこれだけのロジックでできます。
計算処理の最後に読み込み処理が入って結果出力が少し遅くなってもいい、というように要件がかなりリラックスできるのであれば、簡単にアプリケーションで解決できるわけです。
ケース2:総勘定元帳
総勘定元帳とは、取引の仕訳を記録していく帳簿です。これを担当者が勘定科目ごとにチェックするので、合計や小計を表示する画面を作りたいというのが今回のケースです。
ただし、いま取り込んだ仕訳の反映は遅れてもいいが、表示は遅らせたくないという要求です。
このときのエラーケースは、ひとつの仕訳の「借方」「貸方」(注:これは2つでセットになっている)の片方しか表示されない、あるいは表示されていても合計に反映されていない、そういう場合には会計上のミスになってしまいます。
しかもCassandraやNoSQLの多くは、一行あたりに対してしかAtomicityを備えていない、複数行を一回で書き換える機能を持っていないので、運が悪いと複数行のデータを書いている途中の状態が見えてしまいます。会計処理では、これは大変よろしくないわけです。
これをCassandraでどう実装したのか。まず総勘定元帳へのデータを取り込むプロセスを一カ所にしました。これによってデータ読み込みの並列実行をできなくしました。
さらに1つの仕訳が総勘定元帳に正しく転記され、合計行が更新されたタイミングで画面表示用のスナップショットデータを作成しました。
これもトランザクションではよくある話で、RDBのようにACIDが実装されていれば簡単に解ける問題ですが、反映は遅らせてもいいが表示は遅らせたくない、というのに細かく対応するために、Case1とは異なる実装をしています。
井上氏。
少しここまでを整理すると、最初の給与計算バッチでは、データが違ったら計算後にやり直すという乱暴な方法で解いているのは、その場合でも一人分の計算だけやり直せるから、という暗黙の前提があるためですね。
例えば全員に関係するような変更、例えば処理の途中で所得税が変わったので全員やり直す、ということはありえないだろうと。
総勘定元帳のケースでは、アプリケーションレベルでスナップショットアイソレーションもどきをやっていますと。
ケース3:シーケンス値の発行
堤氏。
次のケース3とケース4は、アプリケーション側だけでは整合性を保つのが難しいために、データベース側に頼ったり、かなりがんばって実装している例を挙げます。
ケース3はよくある、連番を発行していく処理です。発行は速い方がいいのですが、番号が重複するとシステム的なエラーを引き起こすため、重複番号の発行は絶対に避けたいというものです。
Cassandraは、書き込んだデータがいずれは分散システムのどのノードからでも読めるようになるというEventual Consistencyを備えていますが、われわれの使い方は一貫性のレベルを上げていて、データが書き込まれたらどのノードからでもすぐに必ず読めるようにしています。つまりConsistency LevelがQuorumですと。
シーケンス値の発行は、CassandraのLighweight Transaction機能を利用しています。これは1つのオブジェクトに対して条件節、例えばもし値が1000だったら1001に書き換える、といった設定ができます。
運用している全世界のデータセンターで有効な連番を発行しなければならないので、この条件を全リージョンの全データセンターに聞きに行って、競合が発生しなければシーケンス値を発行することになります。
ケース4:在庫管理
ケース4もトランザクション関連ではよく出てくる在庫管理。在庫数を確認するといった操作ですが、特に在庫が少ない製品で、在庫がないのに購入できてしまう、あるいは在庫があるのに購入できないといったことはエラーケースとして許容しません。
今回はエラーを許容しないとなっていますが、ある程度エラーを許容できるケースもあって、それは買えたと思ったら実は在庫がなくて買えませんでした、というときにエラーメールを出せば何とかなるとか。
実装例として、ロックテーブルをアプリケーション側で実装。ロックが得られなければリトライします。ロックテーブル自体はLightweight Transactionを使って、1オブジェクトに対するAtomicな操作で実装しています。
本来のトランザクション管理に近い領域をアプリ側で実装しなければならないこともあるよね、ということを示すケースですね。
井上氏。
これはRDBでも同じで、いちばん厳しいケースではロックテーブルを使わざるを得ないと思います。
Publickeyによる補足:アプリでのトランザクション管理
堤氏の説明で見えてきたのは、RDBのような十分なトランザクション機能を持たないNoSQLの上でも、アプリケーションレベルで業務に求められるトランザクション処理が実装できること。
しかも給与計算バッチのように処理中に全員に該当するデータ変更は起きないだろうと現実的に要求を解釈して実装できる、という利点があることも示されました。
これによって、RDBに比べてNoSQLやCassandraが優れているとされる高可用性、地理的な分散システムの構築、処理性能といった利点を得ることも可能だと考えられるわけです。
しかしトラディショナルな考えでは、そうした複雑かつ汎用的な処理はアプリケーションよりも下位のレイヤに隠蔽すべきだろうとされています。
次のページでは、そのトラディショナルな立場に立つ劉氏が、あらためてなぜトランザクション処理は下位レイヤに隠蔽すべきなのか、また汎用処理でありつつもできるだけアプリケーションの要求にきめこまかく対応するため、トランザクション処理におけるさまざまな分離レベルについての説明が行われます。
実際のパネルディスカッションでは、劉氏の発言はケース2とケース3のあいだで行われたのですが、本記事ではわかりやすさを優先して順番を入れ替えました。
≫次のページでは、アプリケーションでトランザクションを実装することのデメリットと、分散データベースにおけるデータ一貫性のあり方について議論されます。
あわせて読みたい
同時実行性制御をアプリケーションで解決しようとすると深刻な問題が発生する~業務システムをRDBなしで作れるのか?(後編) エンジニアサポートCROSS 2016
≪前の記事
トランザクションの実装にはRDB/NoSQLにかかわらず教科書的な定番がある~業務システムをRDBなしで作れるのか?(前編) エンジニアサポートCROSS 2016