連載
» 2018年01月31日 05時00分 公開

NoSQLベストプラクティス(7):NoSQLは「一貫性あるトランザクションを実現できない」という誤解 (2/3)

[三浦デニース(Denise Miura),マークロジック株式会社]

NoSQLデータベースでもACIDトランザクションを実現できる

 従来のリレーショナルデータベースにはないアジャイル性と拡張性を得るには、トレードオフとしてACIDトランザクションを諦めるしかない、と信じている開発者も数多く存在します。彼らがこのトレードオフを信じているのは、いわゆるCAP定理のためです。

 その後「CAP定理」として知られるようになる概念は、2000年、エリック・ブリュワーによって提唱されました。ここで彼は、分散データベースにおける3つのシステム属性を取り上げています。

  • 一貫性:全てのノードが、同時に同じデータを見る
  • 可用性:システムへのリクエストは、成功したかしなかったかに関するレスポンスを必ず受け取る
  • 分断耐性(Partition Tolerance):システムの一部に不具合が発生した場合でも、システムは継続して運用されるという性質

 このCAP定理により、ACIDトランザクションを備えた分散データベースは実現不可能だという誤解が生まれました。この結果、分散型NoSQLデータベースではACIDトランザクションを実現できないと考えている人がたくさんいます。多くのNoSQLデータベースではこの理論を根拠として、更新処理に結果整合性モデルを採用したと言っています。

 しかしながらこれは正しくなく、ブリュワー自身も特にACIDにおける一貫性の概念に関して InfoQではっきりと説明し直しています

 それでは、どうすればNoSQLデータベースでACID準拠と完全な一貫性を実現できるでしょうか。ACID実現に必要な機能としては、主に以下のものがあります。

  • ドキュメントのロックで更新時にデータを保護し、他のトランザクションと矛盾が発生しないようにする。
  • ドキュメントへのタイムスタンプにより、その時点で有効であるドキュメントのコピーのみをクエリの対象とする(MVCC:Multi-Version Concurrency Control)。
  • 更新のジャーナルで、コミット前にシステム障害が発生しても、トランザクションを再現(リプレイ)できるようにする。
  • データは一度に全部変更されるか、あるいは全く変更されないかのいずれかとなるようなコミット処理(ホストが複数ある場合でも)を実行する。

ドキュメントのロック

 ロックは、ドキュメントに対する他のトランザクションからの操作を制約します。ロックによって、データベースが扱うデータの「一貫性」(ACIDの「C」)ならびに、他からの「独立性/分離性」(ACIDの「I」)が実現されます。

 ロックには「読み取りロック」と「書き込みロック」の2種類があります。トランザクションでは、ドキュメントの読み取り時に「読み取りロック」を掛けます。「読み取りロック」は、他のトランザクションによるこの同じドキュメントへの読み取りをブロックしません。そのため、これは「非排他ロック」とも呼ばれます。しかし「読み取りロック」は、他のトランザクションによる書き込みをブロックします。「読み取りロック」が掛かっているドキュメントに書き込みを行おうとするトランザクションは、「読み取りロック」が解除されるまで待たなければなりません。

 読み取り中の書き込みを許可しないのは、トランザクションとして読み取りを「一貫」したものにするためです。この場合、1つのトランザクション内でドキュメントを再び読み取る際には、このドキュメントは他のトランザクションで変更されていない(一貫性がある)と想定できます。

 トランザクションでは、ドキュメントの更新時に「書き込みロック」を掛けます。他のトランザクションが「読み取りロック」あるいは「書き込みロック」を掛けている際には、「書き込みロック」はブロックされます。「書き込みロック」は「排他ロック」とも呼ばれます。これは、他のあらゆるトランザクションによるこのドキュメントへのアクセスがブロックされるためです。ある時点においてドキュメントに「書き込みロック」を掛けられるトランザクションは1つだけです。

図1 「読み取りロック」は、他のトランザクションによる読み取りを許可する。しかし書き込みはブロックする。「書き込みロック」は、他のあらゆるトランザクションによる読み取りと書き込みをブロックする

ロックフリーなクエリ

 ロックは、データベースのトランザクションとしてACID準拠の更新を実行する際に必須です。しかし、クエリ(読み取り)だけを実行するトランザクションでは、ロックは必須ではありません。これは「MVCC(Multi-Version Concurrency Control)」と呼ばれるメソッドのためです。

 MVCCでは、各ドキュメントに、「開始タイムスタンプ」と「終了タイムスタンプ」を付けます。開始タイムスタンプは、ドキュメントの最初のコミット時に付けられ、終了タイムスタンプはドキュメントの削除時に付けられます。データベースがドキュメントを更新する場合、その時点でのバージョンを更新するのではなく、このドキュメントの新しいバージョンを作成して、これに新しい開始タイムスタンプを付けます。その際に、これまでのバージョンには終了タイムスタンプが付けられ、最新バージョンではなくなります。その後、時間の経過とともに、データベース内には複数のバージョンのドキュメントが共存するようになります。

 クエリ実行時にはロックが掛けられません。というのも、それぞれのクエリは特定のタイムスタンプに対して実行され、その時点で有効なドキュメントだけが対象になるからです。この方法により、同時に発生している更新から「独立」してクエリが実行され、またある時点において有効なデータだけを「一貫性」を持って扱えます。

オンディスクジャーナル

 NoSQLデータベースは、インメモリキャッシュを利用します。しかしどうすれば、インメモリの情報を「永続性」(ACIDの「D」)があるものにできるのでしょうか。この問題を解決するため、ドキュメントをメモリに書き込む前に更新をディスク上のジャーナルに書き込みます。ジャーナルには、障害(システムの異常終了など)の際に再現(リプレイ)しなければならない可能性がある、データベース内の全ての操作が記録されています。

 ディスク上でトランザクション全体を完了するよりも、ジャーナルの書き込みの方が効率的だと言えるのはなぜでしょうか。これは、ジャーナルは操作のリプレイに必要なデータだけを書き込み、更新自体に付随するインデックス付けやその他のタスクは行わないからです。

 トランザクションの途中で障害が発生したらどうなるでしょうか。データベースは復旧中にこれを認識します。というのも、トランザクションのコミット情報もジャーナルに記録されているからです。これにより、システムは完了しなかったトランザクションを無視します。

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。