TrueTime は、すべての Google サーバー上のアプリケーションに提供される可用性の高い分散クロックです1。TrueTime により、アプリケーションは単調に増加するタイムスタンプを生成できます。タイムスタンプ T が生成される前にタイムスタンプ T' が生成された場合、アプリケーションは T' より大きいことが保証された T を使って計算できます。この保証は、すべてのサーバーとすべてのタイムスタンプにわたって保持されます。
TrueTime のこの機能は、Spanner でトランザクションにタイムスタンプを割り当てるために使用されます。具体的には、トランザクションが発生したと Spanner がみなす瞬間を反映するタイムスタンプが、すべてのトランザクションに割り当てられます。Spanner は複数バージョンの同時実行制御を使用するので、タイムスタンプの順序保証により、Spanner のクライアントは書き込みをブロックすることなく、データベース全体で(複数の Cloud リージョンにわたる場合でも)整合性のある読み取りを実行できます。
外部整合性
Spanner は、トランザクションに対する最も厳格な同時実行制御をクライアントに保証します。これは外部整合性と呼ばれます2。外部整合性のある環境では、Spanner が実際にはパフォーマンスや可用性の向上のために複数のサーバー(場合によっては複数のデータセンター)でトランザクションを実行している場合でも、すべてのトランザクションが順次実行されるかのようにシステムが動作します。さらに、1 つのトランザクションが完了してから別のトランザクションの commit が開始する場合、クライアントは、最初のトランザクションの効果が反映されないまま 2 番目のトランザクションの効果が反映されるという状態に遭遇することはありません。Spanner は、意味的には単一マシンのデータベースと変わりません。Spanner ではこのような強力な保証が提供されながらも、アプリケーションは(高パフォーマンスと引き換えに保証の弱い)データベースに匹敵するパフォーマンスを実現できます。たとえば Spanner では、スナップショット分離をサポートするデータベースと同様に、読み取り専用トランザクションによってブロックされることなく書き込みを実行できますが、スナップショット分離で許容される異常が発生しません。
アプリケーション開発は、外部整合性により大幅に簡素化されます。たとえば、Spanner 上に銀行アプリケーションを作成したとします。そして、あるお客様の当座預金口座と普通預金口座にそれぞれ 50 ドルずつ残高があると仮定します。アプリケーションは、まず普通預金口座に 200 ドルを入金するトランザクション T1 を commit し、次に当座預金口座から 150 ドルを引き落とす 2 つ目のトランザクション T2 を発行するワークフローを開始します。さらに、1 日の終わりに一方の口座の残高がマイナスである場合は、もう一方の口座からそのマイナスが自動的に充当され、その日のいずれかの時点で全口座の合計残高がマイナスとなった場合は、お客様にペナルティが発生するとします。T2 の commit は T1 が完了した後に開始されるため、データベースを読み取る際には、入金の T1 が引き落としの T2 よりも前に実行されたことが外部整合性によってわかります。言い換えれば、外部整合性は、T2 が T1 よりも先に実行されるという状況は観測されないことを保証します。つまり、引き落としによって残高不足のペナルティが発生することはありません。
単一バージョンのストレージと厳格な二相ロックを使用する従来型のデータベースは、外部整合性を提供します。残念ながら、そのようなシステムでは、アプリケーションが最新のデータを読み取ろうとするたびに(「強力な読み取り」と呼ばれる)、システムはそのデータに対して読み取りロックを取得します。そのため、読み取り中のデータへの書き込みがブロックされてしまいます。
タイムスタンプとマルチバージョン同時実行制御(MVCC)
Spanner など多くのデータベース システムでは、書き込みをブロックせずに読み取りを行う目的で、不変なデータ バージョンを複数保持しています(多くの場合、これはマルチバージョン同時実行制御と呼ばれます)。書き込みにより、書き込みトランザクションのタイムスタンプを持つ新しい不変バージョンが作成されます。あるタイムスタンプ時点での「スナップショット読み取り」では、そのタイムスタンプより前の最新バージョンの値が返されるので、書き込みをブロックする必要がありません。したがって、バージョンに割り当てられるタイムスタンプは、トランザクションの commit が認識される順序と整合することが重要です。このプロパティを「正しいタイムスタンプ」と呼びます。正しいタイムスタンプが存在することは、外部整合性と同等であることに注目してください。
正しいタイムスタンプが重要である理由を確かめるため、前のセクションの銀行取引の例を考えてみましょう。正しいタイムスタンプが付かない場合、T2 に割り当てられるタイムスタンプが T1 に割り当てられたタイムスタンプよりも前のものになる可能性があります(たとえば、仮にシステムが TrueTime ではなくローカル クロックを使っていて、T2 を処理するサーバーの時計がわずかに遅れていた場合などです)。その場合、引き落としが開始される前にお客様が入金(T1)の完了を確認したにもかかわらず、スナップショット読み取りでは、引き落とし(T2)は反映されているものの、入金は反映されていない状態になる可能性があります。
正しいタイムスタンプ付けは、単一マシンのデータベースでは簡単に実現できます(たとえば、単調に増加するグローバルな 1 つのカウンタからタイムスタンプを割り当てるだけです)。これを、世界各地のサーバーでタイムスタンプを割り当てる必要がある Spanner などの広範な分散システムで実現しようとすると、効率的に行うことははるかに困難になります。
Spanner は、単調に増加するタイムスタンプを生成するために TrueTime に依存しています。これらのタイムスタンプを 2 つの方法で使用します。まず、グローバル通信の不要な書き込みトランザクション用の正しいタイムスタンプとして使用します。2 番目に、強力な読み取り用のタイムスタンプとして使用します。これにより、強力な読み取りが複数のサーバーにまたがる場合でも、1 回の通信で強力な読み取りを実行できます。
よくある質問
Spanner はどのような整合性の保証を提供していますか?
トランザクション処理システムにおける最も厳格な整合性プロパティである外部整合性を提供しています。Spanner のすべてのトランザクションが、パーティション内のプロパティだけでなく、この外部整合性プロパティを満たしています。外部整合性とは、Spanner がトランザクションを実行する際に、それらが逐次的に実行されているシステムと区別がつかない方法で実行すること、さらにその逐次的な順序がトランザクションの commit された順序と一致していることを意味します。トランザクションに割り当てられるタイムスタンプは、順次実行の順序に対応しているため、別のトランザクション T1 の完了後にトランザクション T2 の commit が開始したことがクライアントで認識された場合、システムは T1 のタイムスタンプより大きいタイムスタンプを T2 に割り当てます。
Spanner は線形化可能性を提供していますか?
はい。実際、線形化可能性はトランザクションの挙動について何も保証していないため、Spanner は線形化可能性より強力なプロパティである外部整合性を提供しています。線形化可能性は、アトミック読み取りまたは書き込みオペレーションをサポートする同時実行オブジェクトのプロパティです。データベースにおいて「オブジェクト」とは通常単一の行であり、場合によっては単一のセルのこともあります。外部整合性は、任意のオブジェクトに対する複数の読み取りまたは書き込みオペレーションを含むトランザクションをクライアントが動的に合成する、トランザクション処理システムのプロパティです。線形化可能性は、単一オブジェクトに対する単一の読み取りまたは書き込みオペレーションのみがトランザクションに含まれる、外部整合性の特殊なケースとみなすことができます。
Spanner は直列化可能性を提供していますか?
はい。実際、Spanner は直列化可能性より厳格なプロパティである外部整合性を提供しています。トランザクション処理システムは、トランザクションを順次実行するシステムと区別されない方法でトランザクションを実行する場合、直列化可能となります。Spanner はまた、逐次実行順序がトランザクションの commit 順序と一致することを保証します。
前述の銀行の例をもう一度見てみましょう。直列化可能性はあっても外部整合性がないシステムでは、お客様が T1 を実行した後で T2 を実行したとしても、システムがそれらの順序を変更できるため、残高不足によるペナルティが引き落としに発生する可能性があります。
Spanner は強整合性を提供していますか?
はい。実際、Spanner は、強整合性よりさらに強いプロパティである外部整合性を提供しています。Spanner のデフォルトの読み取りモードは「強力」です。これにより、どのレプリカが読み取りを受け取るかに関係なく、オペレーションの開始前に commit されたすべてのトランザクションが必ず反映されます。
強整合性と外部整合性の違いは何ですか?
レプリケートされたオブジェクトが線形化可能である場合、レプリケーション プロトコルは「強整合性」を示します。線形化可能性と同様に、「強整合性」はトランザクションの動作に何の影響も与えないため、「外部整合性」より弱いプロパティです。
Spanner は結果整合性(または遅延整合性)を提供していますか?
Spanner は、結果整合性よりはるかに強力なプロパティである外部整合性を提供しています。結果整合性では、高パフォーマンスと引き換えに保証が弱くなります。結果整合性の問題点として、読み取りの結果データベースが実際の状態とは異なる状態だと認識される可能性があります(たとえば、トランザクション A がトランザクション B より前に発生した場合でも、読み取りによって B は commit 済み、A は未 commit と認識される可能性があります)。Spanner が提供するステイル読み取りでは、結果整合性と同等の高いパフォーマンスを実現でき、しかもはるかに強い整合性保証が得られます。ステイル読み取りは、「古い」タイムスタンプからデータを返します。古いバージョンのデータは不変であるため、これによって書き込みがブロックされることはありません。