このトピックでは、Spanner で実行する挿入オペレーションと更新オペレーションごとに commit タイムスタンプを書き込む方法について説明します。この機能を使用するには、TIMESTAMP
列に allow_commit_timestamp
オプションを設定し、各トランザクションの一部としてタイムスタンプを書き込みます。
概要
TrueTime テクノロジに基づく commit タイムスタンプは、トランザクションがデータベースに commit された時刻を表します。allow_commit_timestamp
列オプションを使用すると、commit タイムスタンプを列にアトミックに格納できます。テーブルに格納された commit タイムスタンプを使用すると、ミューテーションの正確な順序を確認し、変更履歴のような機能を構築できます。
データベースに commit タイムスタンプを挿入するには、次の手順に沿って操作します。
タイプが
TIMESTAMP
の列を作成し、スキーマ定義で列オプションallow_commit_timestamp
をtrue
に設定します。次に例を示します。CREATE TABLE Performances ( ... LastUpdateTime TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp=true) ... ) PRIMARY KEY (...);
DML を使用して挿入や更新を実行する場合は、
PENDING_COMMIT_TIMESTAMP
関数を使用して commit タイムスタンプを書き込みます。ミューテーションを使用して挿入や更新を実行する場合は、commit タイムスタンプ列の挿入時または更新時に、プレースホルダ文字列
spanner.commit_timestamp()
を使用します。クライアント ライブラリによって提供される commit タイムスタンプ定数を使用することもできます。たとえば、Java クライアントではこの定数はValue.COMMIT_TIMESTAMP
です。
Spanner で、これらのプレースホルダを列の値として使用してトランザクションを commit すると、実際の commit タイムスタンプが指定された列(たとえば、LastUpdateTime
列)に書き込まれます。この列の値を使用して、テーブルの更新履歴を作成できます。
commit タイムスタンプの値は一意であるとは限りません。重複しない一連のフィールドに書き込むトランザクションの場合、同じタイムスタンプが存在する可能性があります。重複する一連のフィールドに書き込むトランザクションの場合は、タイムスタンプは一意になります。
Spanner の commit タイムスタンプはマイクロ秒単位の精度であり、TIMESTAMP
列に保存されるときにナノ秒に変換されます。
commit タイムスタンプ列の作成と削除
commit タイムスタンプのサポートを追加または削除するには、allow_commit_timestamp
列オプションを使用します。これにより、次のことができます。
- 新しいテーブルを作成して、列で commit タイムスタンプがサポートされるように指定する。
- 既存のテーブルを変更する場合:
- commit タイムスタンプをサポートする新しい列を追加する。
- 既存の
TIMESTAMP
列を変更して commit タイムスタンプをサポートする。 - 既存の
TIMESTAMP
列を変更して commit タイムスタンプのサポートを削除する。
キーとインデックス
commit タイムスタンプ列は、主キー列または主キー以外の列として使用できます。主キーは ASC
または DESC
で定義します。
ASC
(デフォルト)- キーを昇順にすると、特定の時間以降を照会する場合に便利です。DESC
- キーを降順にすると、最終行がテーブルの先頭になります。これにより、最新のレコードにすばやくアクセスできます。
親テーブルと子テーブルの主キーには、同じ allow_commit_timestamp
オプションを設定する必要があります。主キーの間で異なるオプションを指定すると、Spanner がエラーを返します。スキーマを作成または更新している間は、このオプションが一致していない可能性があります。
次のシナリオで commit タイムスタンプを使用すると、データのパフォーマンスが低下するホットスポットが作成されます。
テーブルの主キーの最初の部分としての commit タイムスタンプ列:
CREATE TABLE Users ( LastAccess TIMESTAMP NOT NULL, UserId INT64 NOT NULL, ... ) PRIMARY KEY (LastAccess, UserId);
セカンダリ インデックスの主キーの最初の部分:
CREATE INDEX UsersByLastAccess ON Users(LastAccess)
または
CREATE INDEX UsersByLastAccessAndName ON Users(LastAccess, FirstName)
ホットスポットは、書き込み率が低い場合でもデータのパフォーマンスを低下させます。インデックスが作成されていないキー以外の列に commit タイムスタンプを有効にする場合は、パフォーマンスのオーバーヘッドは発生しません。
commit タイムスタンプ列を作成する
次の DDL では、commit タイムスタンプをサポートする列を持つテーブルを作成します。
CREATE TABLE Performances (
SingerId INT64 NOT NULL,
VenueId INT64 NOT NULL,
EventDate Date,
Revenue INT64,
LastUpdateTime TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp=true)
) PRIMARY KEY (SingerId, VenueId, EventDate),
INTERLEAVE IN PARENT Singers ON DELETE CASCADE
オプションを追加すると、次のようにタイムスタンプ列が変化します。
- 挿入と更新で
spanner.commit_timestamp()
プレースホルダ文字列(またはクライアント ライブラリの定数)を使用できます。 - 列には過去の値しか保存できません。詳細については、タイムスタンプに独自の値を指定するをご覧ください。
オプション allow_commit_timestamp
大文字と小文字が区別されます。
既存のテーブルに commit タイムスタンプ列を追加する
commit タイムスタンプ列を既存のテーブルに追加するには、ALTER TABLE
ステートメントを使用します。たとえば、Performances
テーブルに LastUpdateTime
列を追加するには、次のステートメントを使用します。
ALTER TABLE Performances ADD COLUMN LastUpdateTime TIMESTAMP
NOT NULL OPTIONS (allow_commit_timestamp=true)
タイムスタンプ列を commit タイムスタンプ列に変換する
既存のタイムスタンプ列を commit タイムスタンプ列に変換できます。ただし、これを行うには Spanner で既存のタイムスタンプ値が過去の値であることを検証する必要があります。次に例を示します。
ALTER TABLE Performances ALTER COLUMN LastUpdateTime
SET OPTIONS (allow_commit_timestamp=true)
SET OPTIONS
を含む ALTER TABLE
ステートメントで、列のデータ型または NULL
アノテーションを変更することはできません。詳細については、データ定義言語をご覧ください。
commit タイムスタンプ オプションを削除する
列から commit タイムスタンプのサポートを削除する場合は、ALTER TABLE
ステートメントでオプション allow_commit_timestamp=null
を使用します。commit タイムスタンプの動作は削除されますが、列はタイムスタンプのままです。オプションを変更しても、型や NULL 値(NOT NULL
)など、列の他の特性は変更されません。例:
ALTER TABLE Performances ALTER COLUMN LastUpdateTime
SET OPTIONS (allow_commit_timestamp=null)
DML ステートメントを使用して commit タイムスタンプを作成する
DML ステートメントで commit タイムスタンプを作成するには、PENDING_COMMIT_TIMESTAMP
関数を使用します。トランザクションが commit されると、Spanner が commit タイムスタンプを選択します。
次の DML ステートメントは、commit タイムスタンプで Performances
テーブルの LastUpdateTime
列を更新します。
UPDATE Performances SET LastUpdateTime = PENDING_COMMIT_TIMESTAMP()
WHERE SingerId=1 AND VenueId=2 AND EventDate="2015-10-21"
次のコード例では、PENDING_COMMIT_TIMESTAMP
関数を使用して、LastUpdateTime
列に commit タイムスタンプを作成します。
C++
C#
Go
Java
Node.js
PHP
Python
Ruby
Ruby
commit タイムスタンプは、allow_commit_timestamp=true
オプションでアノテーションを設定した列にのみ書き込まれます。
複数のテーブルの行に変異がある場合は、各テーブルの commit タイムスタンプ列に spanner.commit_timestamp()
(またはクライアント ライブラリ定数)を指定する必要があります。
commit タイムスタンプ列をクエリする
次の例では、テーブルの commit タイムスタンプ列をクエリで取得します。
C++
C#
Go
Java
Node.js
PHP
Python
Ruby
commit タイムスタンプ列に独自の値を指定する
列の値として spanner.commit_timestamp()
(またはクライアント ライブラリ定数)を渡すのではなく、commit タイムスタンプ列に独自の値を指定することもできます。ただし、値は過去のタイムスタンプでなければなりません。タイムスタンプの作成コストを抑え、オペレーションを高速で実行するために、この制限が設定されています。未来のタイムスタンプが指定されると、サーバーは FailedPrecondition
エラーを返します。
変更履歴を作成する
テーブルで発生する変異の変更履歴を作成し、その履歴を監査で使用するとします。たとえば、ワープロ文書の変更履歴を格納するテーブルがあるとします。commit タイムスタンプを使用すると、タイムスタンプによって変更履歴の項目順が決まるため、変更履歴を簡単に作成できます。次のようなスキーマを使用して変更履歴を作成し、特定のドキュメントに対する変更履歴を保存します。
CREATE TABLE Documents (
UserId INT64 NOT NULL,
DocumentId INT64 NOT NULL,
Contents STRING(MAX) NOT NULL,
) PRIMARY KEY (UserId, DocumentId);
CREATE TABLE DocumentHistory (
UserId INT64 NOT NULL,
DocumentId INT64 NOT NULL,
Ts TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp=true),
Delta STRING(MAX),
) PRIMARY KEY (UserId, DocumentId, Ts),
INTERLEAVE IN PARENT Documents ON DELETE NO ACTION;
変更履歴を作成するには、Document
の行を挿入または更新するトランザクションで、DocumentHistory
に新しい行を挿入します。DocumentHistory
に新しい行を挿入するときに、プレースホルダ spanner.commit_timestamp()
(またはクライアント ライブラリ定数)を使用し、commit タイムスタンプを列 Ts
に書き込むように Spanner に指示します。DocumentsHistory
テーブルと Documents
テーブルをインターリーブすることで、データの局所性が維持され、挿入や更新を効率的に行うことができます。ただし、親の行と子の行を一緒に削除する必要があります。Documents
の行を削除した後も DocumentHistory
に行を残す場合には、テーブルをインターリーブしないでください。
commit タイムスタンプを使用して最近のデータクエリを最適化する
commit タイムスタンプを使用すると Spanner を最適化でき、それによって特定の時間よりも後に書き込まれたデータを取得する際のクエリ I/O を削減できます。
この最適化を有効にするには、クエリの WHERE
句に、テーブルの commit タイムスタンプ列と指定した特定の時刻の間の比較を、次の属性とともに、含める必要があります。
定数式として特定の時間を指定します。リテラル、パラメータ、または独自の引数が定数として評価される関数が該当します。
>
演算子または>=
演算子を使用して、commit タイムスタンプが指定時刻より新しいかどうかを比較します。必要に応じて、
AND
でWHERE
句にさらに制限を追加します。OR
で句を拡張すると、クエリはこの最適化の対象から除外されます。
たとえば、commit タイムスタンプ列を含む次の Performances
テーブルについて考えてみましょう。
CREATE TABLE Performances (
SingerId INT64 NOT NULL,
VenueId INT64 NOT NULL,
EventDate DATE,
Revenue INT64,
LastUpdateTime TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp=true)
) PRIMARY KEY (SingerId, VenueId, EventDate);
このクエリには、テーブルの commit タイムスタンプ列と定数式(この場合)とより大きい値または等しい値が比較されるため、前述の commit タイムスタンプの最適化のメリットがあります。この場合、リテラルは以下のとおりです。
SELECT * FROM Performances WHERE LastUpdateTime >= "2022-05-01";
次のクエリも、commit タイムスタンプと、クエリの実行中に引数がすべて定数として評価される関数との間で「より大きい」比較が実行されるため、最適化の対象となります。
SELECT * FROM Performances
WHERE LastUpdateTime > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 30 DAY);
次のステップ
- commit タイムスタンプを使用して、Go で変更ログを作成する。