このトピックでは、Spanner で実行する挿入オペレーションと更新オペレーションごとに commit タイムスタンプを書き込む方法について説明します。
commit タイムスタンプの概要
TrueTime テクノロジに基づく commit タイムスタンプは、トランザクションがデータベースに commit された時刻を表します。トランザクションの commit タイムスタンプを列にアトミックに格納できます。テーブルに格納された commit タイムスタンプを使用すると、ミューテーションの正確な順序を確認し、変更履歴のような機能を構築できます。
データベースに commit タイムスタンプを挿入するには、次の手順に沿って操作します。
SPANNER.COMMIT_TIMESTAMP
タイプの列を作成します。次に例を示します。CREATE TABLE Performances ( ... LastUpdateTime SPANNER.COMMIT_TIMESTAMP NOT NULL, ... PRIMARY KEY (...) ) ;
DML を使用して挿入や更新を実行する場合は、
SPANNER.PENDING_COMMIT_TIMESTAMP()
関数を使用して commit タイムスタンプを書き込みます。あらかじめ準備されたステートメントまたはミューテーションを使用して挿入または更新を実行する場合は、commit タイムスタンプ列にプレースホルダ文字列
SPANNER.COMMIT_TIMESTAMP()
を使用します。クライアント ライブラリによって提供される commit タイムスタンプ定数を使用することもできます。たとえば、Java クライアントではこの定数はValue.COMMIT_TIMESTAMP
です。
Spanner で、これらのプレースホルダを列の値として使用してトランザクションを commit すると、実際の commit タイムスタンプが指定された列に書き込まれます。この列の値を使用して、テーブルの更新履歴を作成できます。
commit タイムスタンプの値は一意であるとは限りません。重複しない一連のフィールドに書き込むトランザクションの場合、同じタイムスタンプが存在する可能性があります。重複する一連のフィールドに書き込むトランザクションの場合は、タイムスタンプは一意になります。
Spanner の commit タイムスタンプはマイクロ秒単位の精度であり、SPANNER.COMMIT_TIMESTAMP
列に保存されるときにナノ秒に変換されます。
キーとインデックス
commit タイムスタンプ列は、主キー列または主キー以外の列として使用できます。主キーは ASC
または DESC
で定義します。
ASC
(デフォルト)- キーを昇順にすると、特定の時間以降を照会する場合に便利です。DESC
- キーを降順にすると、最終行がテーブルの先頭になります。これにより、最新のレコードにすばやくアクセスできます。
ホットスポットを回避する
次のシナリオで commit タイムスタンプを使用すると、データのパフォーマンスが低下するホットスポットが作成されます。
テーブルの主キーの最初の部分としての commit タイムスタンプ列:
CREATE TABLE Users ( LastAccess SPANNER.COMMIT_TIMESTAMP NOT NULL, UserId bigint NOT NULL, ... PRIMARY KEY (LastAccess, UserId) ) ;
セカンダリ インデックスの最初の部分としてタイムスタンプの主キー列を commit します。
CREATE INDEX UsersByLastAccess ON Users(LastAccess)
または
CREATE INDEX UsersByLastAccessAndName ON Users(LastAccess, FirstName)
ホットスポットは、書き込み率が低い場合でもデータのパフォーマンスを低下させます。インデックスが作成されていないキー以外の列に commit タイムスタンプを有効にする場合は、パフォーマンスのオーバーヘッドは発生しません。
既存のテーブルに commit タイムスタンプ列を追加する
commit タイムスタンプ列を既存のテーブルに追加するには、ALTER TABLE
ステートメントを使用します。たとえば、Performances
テーブルに LastUpdateTime
列を追加するには、次のステートメントを使用します。
ALTER TABLE Performances ADD COLUMN LastUpdateTime SPANNER.COMMIT_TIMESTAMP;
DML ステートメントを使用して commit タイムスタンプを作成する
DML ステートメントで commit タイムスタンプを作成するには、SPANNER.PENDING_COMMIT_TIMESTAMP()
関数を使用します。トランザクションが commit されると、Spanner が commit タイムスタンプを選択します。
次の DML ステートメントは、commit タイムスタンプで Performances
テーブルの LastUpdateTime
列を更新します。
UPDATE Performances SET LastUpdateTime = SPANNER.PENDING_COMMIT_TIMESTAMP()
WHERE SingerId=1 AND VenueId=2 AND EventDate="2015-10-21"
ミューテーションを使用して行を挿入する
行の挿入時に Spanner が commit タイムスタンプ値を書き込むようにするには、列リストに列を指定し、値として spanner.commit_timestamp()
プレースホルダ文字列(またはクライアント ライブラリ定数)を渡す必要があります。次に例を示します。
C++
C#
Go
Java
Node.js
PHP
Python
Ruby
複数のテーブルの行に変異がある場合は、各テーブルの commit タイムスタンプ列に spanner.commit_timestamp()
(またはクライアント ライブラリ定数)を指定する必要があります。
ミューテーションを使用して行を更新する
行の更新時に Spanner が commit タイムスタンプ値を書き込むようにするには、列リストに列を指定し、値として spanner.commit_timestamp()
プレースホルダ文字列(またはクライアント ライブラリ定数)を渡す必要があります。行の主キーは更新できません。主キーを更新するには、既存の行を削除して新しい行を作成します。
たとえば、LastUpdateTime
という名前の commit タイムスタンプ列を更新するには、次のようにします。
C++
C#
Go
Java
Node.js
PHP
Python
Ruby
複数のテーブルの行に変異がある場合は、各テーブルの commit タイムスタンプ列に spanner.commit_timestamp()
(またはクライアント ライブラリ定数)を指定する必要があります。
commit タイムスタンプ列をクエリする
次の例では、テーブルの commit タイムスタンプ列をクエリで取得します。
C++
C#
Go
Java
Node.js
PHP
Python
Ruby
commit タイムスタンプ列に独自の値を指定する
コードでは、列の値として spanner.commit_timestamp()
(または使用可能なクライアント ライブラリ定数)を渡すのではなく、commit タイムスタンプ列に独自の値を指定することもできます。ただし、値は過去のタイムスタンプでなければなりません。タイムスタンプの作成コストを抑え、オペレーションを高速で実行するために、この制限が設定されています。CURRENT_TIMESTAMP
SQL 関数で返された値と比較すると、過去の値かどうか簡単に確認できます。過去のタイムスタンプでない場合、サーバーは FailedPrecondition
エラーを返します。
変更履歴を作成する
テーブルで発生する変異の変更履歴を作成し、その履歴を監査で使用するとします。たとえば、ワープロ文書の変更履歴を格納するテーブルがあるとします。commit タイムスタンプを使用すると、タイムスタンプによって変更履歴の項目順が決まるため、変更履歴を簡単に作成できます。次のようなスキーマを使用して変更履歴を作成し、特定のドキュメントに対する変更履歴を保存します。
CREATE TABLE Documents (
UserId int8 NOT NULL,
DocumentId int8 NOT NULL,
Contents text NOT NULL,
PRIMARY KEY (UserId, DocumentId)
);
CREATE TABLE DocumentHistory (
UserId int8 NOT NULL,
DocumentId int8 NOT NULL,
Ts SPANNER.COMMIT_TIMESTAMP NOT NULL,
Delta text,
PRIMARY KEY (UserId, DocumentId, Ts)
) INTERLEAVE IN PARENT Documents;
変更履歴を作成するには、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 bigint NOT NULL,
VenueId bigint NOT NULL,
EventDate timestamp with time zone NOT NULL,
Revenue bigint,
LastUpdateTime spanner.commit_timestamp,
PRIMARY KEY(SingerId, VenueId, EventDate)
);
このクエリには、テーブルの commit タイムスタンプ列と定数式(この場合)とより大きい値または等しい値が比較されるため、前述の commit タイムスタンプの最適化のメリットがあります。この場合、リテラルは以下のとおりです。
SELECT * FROM Performances WHERE LastUpdateTime >= '2022-01-01';