スキーマとデータモデル

データモデルの概要

Cloud Spanner データベースは、1 つ以上のテーブルを含むことができます。テーブルは行、列、値という構造を持ち、主キーを備えている点で、リレーショナル データベース テーブルに似ています。Cloud Spanner のデータは、厳格に型指定されています。データベースごとにスキーマを定義する必要があり、そのスキーマでは各テーブルの各列のデータ型を指定する必要があります。可能なデータ型には、スカラー型と配列型が含まれます。これらの詳細については、データ型をご覧ください。また、1 つ以上のセカンダリ インデックスをテーブルに定義できます。これについてはセカンダリ インデックスをご覧ください。

親子テーブル関係

1 つのデータベースに複数のテーブルを定義し、データ取得の効率を高めるために行を物理的に同じ場所に配置したい場合は、オプションとしてテーブル間に親子関係を定義できます。たとえば、Customers テーブルと Invoices テーブルがあり、アプリケーションが特定のお客様のすべての請求書を頻繁に取得する場合は、InvoicesCustomers の子テーブルとして定義できます。これは、論理的に独立した 2 つのテーブルの間にデータ局所性関係を宣言することであり、Cloud Spanner に Invoices の 1 つ以上の行を Customers の 1 つの行と物理的に一緒に格納するよう指示することです。

主キー

Invoices の行と Customers の行をどのように組み合わせて格納するかを Cloud Spanner に指示するには、これらのテーブルの主キーを使用します。すべてのテーブルには主キーが必要であり、主キーはそのテーブルのゼロ個以上の列で構成できます。あるテーブルを別のテーブルの子として宣言する場合は、親テーブルの主キー列が、子テーブルの主キーの接頭辞になっている必要があります。つまり、親テーブルの主キーが N 個の列で構成されている場合は、その各子テーブルの主キーも同じ N 個の列で構成され、同じ列で始まる同じ順序になっている必要があります。

Cloud Spanner は、主キーの値の順序で行を格納し、子の行を、その接頭辞と同じ主キーを持つ親の行の間に挿入します。主キーのディメンションに沿って親行の間に子行を挿入するこのような方法はインターリーブと呼ばれ、子テーブルはインターリーブされたテーブルとも呼ばれます(以下のインターリーブされたテーブルの作成のインターリーブされた行の図をご覧ください)。

まとめると、Cloud Spanner を使用すると、関連するテーブルの行を物理的に同じ場所に配置できます。この物理的なレイアウトについては、下記のスキーマの例をご覧ください。

主キーの選択

主キーは、テーブルの各行を一意に識別します。テーブルの既存の行を更新または削除する場合は、テーブルに 1 つ以上の列で構成される主キーが必要です(主キー列のないテーブルに構成できる行は 1 つのみです)。多くの場合、アプリケーションには主キーとして使用するのに適したフィールドがすでに存在します。たとえば、前の Customers テーブルの例では、アプリケーションで提供される CustomerId を主キーとして使用できます。また、行を挿入するときは、生成する一意の INT64 値のような主キーを生成することが必要な場合があります。

どのような場合でも、ホットスポットができないように慎重に主キーを選択する必要があります。たとえば、単調に増加する整数をキーとしてレコードを挿入すると、常にキースペースの最後に挿入されます。Cloud Spanner はキーの範囲でサーバー間にデータを分割するので、このような方法では挿入はすべて単一のサーバーに送られ、ホットスポットが作成されるため、好ましくありません。複数のサーバーに負荷を分散させ、ホットスポットを回避できる方法があります。

データベースのスプリット

テーブル間の親子関係の階層を、最大 7 層まで定義できます。つまり、論理的に独立した 7 個のテーブルの行を同じ場所に配置できます。テーブルのデータのサイズが小さい場合には、単一の Cloud Spanner サーバーでデータベースを処理できるでしょう。しかし、関連するテーブルが拡大され、個々のサーバーのリソース上限に近づくとどうなるでしょうか。Cloud Spanner は分散データベースであるため、データベースが大規模になると、Cloud Spanner はデータを分割して、「スプリット」と呼ばれるまとまりに分けます。各スプリットを他から独立して移動させ、異なる物理的場所にある異なるサーバーに割り当てることができます。スプリットは、トップレベルの(つまり、インターリーブされていない)テーブルの行からなる範囲として定義されます。これらの行は主キーによって順序付けられます。この範囲の開始キーと終了キーは「スプリットの境界」と呼ばれます。Cloud Spanner は、スプリットの境界を自動的に追加および削除するため、データベース内のスプリット数はそれに応じて変化します。

Cloud Spanner は、負荷に基づいてデータを分割します。スプリット内の多くのキー間で分散されている、負荷の高い読み込みまたは書き込みが検出されると、自動的にスプリットの境界が追加されます。Cloud Spanner は階層のルートにあるテーブル(つまり、親テーブルでインターリーブされていないテーブル)の行でのみスプリットの境界を設定できるため、ユーザーはデータの分割方法をある程度制御できます。また、インターリーブされたテーブルの行は、同じ主キー接頭辞を持つ親テーブル内の行とともに、ソートされた主キー順に格納されるので、インターリーブされたテーブルの行を親テーブル内の対応する行から分割することはできません(インターリーブされたテーブルの階層の作成のインターリーブされた行の図をご覧ください)。したがって、テーブルの親子関係の定義と、互いに関連するテーブルの行に設定する主キーの値により、内部でデータが分割される方法を制御できます。

負荷に基づいた分割

読み取りホットスポットを軽減する目的で、負荷に基づいた分割を Cloud Spanner で実行する例を示します。データベースに含まれるあるテーブルの 10 行が、テーブル内の他の行よりも頻繁に読み取られるとします。そのテーブルがデータベース階層のルートにある(つまり、インターリーブされたテーブルではない)場合、Cloud Spanner はこれら 10 行のそれぞれの間にスプリット境界を追加できます。これにより、すべての行が 1 台のサーバーで読み取られてリソースを消費する代わりに、これらの各行が異なるサーバーで処理されます。

一般的に、スキーマ設計のベスト プラクティスに従う場合、Cloud Spanner は、インターリーブされていないテーブルの行を対象とした読み取りのホットスポットを軽減できます。これにより、読み取りスループットが数分ごとに向上し、インスタンス内のリソースが飽和状態になる時点まで、あるいは(1 つのスプリットには単一行とそのインターリーブされた子のみが含まれるため)新しいスプリット境界を追加できないという状況になるまで、向上し続けます。

スキーマの例

下記のスキーマの例では、親子関係がある場合とない場合の Cloud Spanner テーブルの作成方法、およびそれに対応するデータの物理レイアウトを示します。

テーブルの作成

音楽アプリケーションを作成していて、歌手データの行を格納する単純なテーブルが必要だとします。

5 行 4 列の Singers テーブル

単純な Singers テーブルの行の論理ビュー。主キー列は、太線の左側です。

テーブルには 1 つの主キー列 SingerId があり(太い線の左側)、テーブルは行、列、値で編成されています。

このテーブルは、Cloud Spanner のスキーマで次のように定義できます。

CREATE TABLE Singers (
  SingerId   INT64 NOT NULL,
  FirstName  STRING(1024),
  LastName   STRING(1024),
  SingerInfo BYTES(MAX),
) PRIMARY KEY (SingerId);

このスキーマの例については、次の点に注意してください。

  • Singers はデータベース階層のルートにあるテーブルです(別のテーブルの子として定義されていないため)。
  • 通常、主キー列には NOT NULL というアノテーションを指定します(ただし、キー列で NULL 値を許容する場合は、このアノテーションを省略できます。詳しくは、キー列をご覧ください)。
  • 主キーに含まれない列は非キー列と呼ばれ、必要に応じて NOT NULL アノテーションを指定できます。
  • STRING 型または BYTES 型を使用する列では、長さを定義する必要があります。これは、フィールドに格納できる Unicode 文字の最大数を表します(詳細については、スカラーデータ型をご覧ください)。

Singers テーブルの行の物理レイアウトはどのようになるでしょう。次の図は、連続する(並べ替えられた順序の)主キーによって格納された Singers テーブルの行を示したものです(つまり、「Singers(1)」、「Singers(2)」、のような順序になっており、「Singers(1)」は Singers テーブルでキーが 1 の行を表します)。

連続したキー順序で格納されているテーブルの行の例。

Singers テーブルの行の物理レイアウト、スプリットが異なるサーバーによって処理されるようになるスプリット境界の例。

上記の図では、可能なスプリット境界も示されています。Singers はデータベース階層のルートにあるため、Singers の任意の行の間で分割できます。また、キー Singers(3)Singers(4) の間でのスプリット境界の例も示されており、結果のスプリットのデータは異なるサーバーに割り当てられます。つまり、このテーブルが拡大すると、Singers データの行は異なる場所に格納される可能性があります。

複数のテーブルの作成

各歌手のアルバムについての基本データを音楽アプリケーションに追加するとします。

5 行 3 列の Albums テーブル

Albums テーブルの行の論理ビュー。主キー列は太線の左側です。

Albums の主キーは 2 つの列 SingerIdAlbumId で構成されており、各アルバムをその歌手と関連付けていることに注意してください。次のスキーマ例では、Albums テーブルと Singers テーブルをどちらもデータベース階層のルートに定義しているので、これらのテーブルは兄弟テーブルになります。

-- Schema hierarchy:
-- + Singers (sibling table of Albums)
-- + Albums (sibling table of Singers)
CREATE TABLE Singers (
  SingerId   INT64 NOT NULL,
  FirstName  STRING(1024),
  LastName   STRING(1024),
  SingerInfo BYTES(MAX),
) PRIMARY KEY (SingerId);

CREATE TABLE Albums (
  SingerId     INT64 NOT NULL,
  AlbumId      INT64 NOT NULL,
  AlbumTitle   STRING(MAX),
) PRIMARY KEY (SingerId, AlbumId);

SingersAlbums の行の物理レイアウトは図のようになります。最初に、Albums テーブルの行が連続主キーで格納され、次に Singers の行が連続主キーで格納されます。

行の物理レイアウト: Albums と Singers の各行はキー値によって格納されます

どちらもデータベース階層のルートである、Singers テーブルと Albums テーブルの行の物理レイアウト。

上のスキーマに関する重要な注意点の 1 つは、Singers テーブルと Albums テーブルは最上位テーブルであるため、これらのテーブルの間にデータ局所性関係が想定されないことです。データベースが拡大されると、Cloud Spanner は上記のいずれかの行の間にスプリット境界を追加する可能性があります。つまり、Albums テーブルの行は Singers テーブルの行と異なるスプリットで終了する可能性があり、2 つのスプリットは互いに独立して移動することがあります。

アプリケーションのニーズによっては、Albums のデータを Singers のデータとは異なるスプリットに配置できるようにすることが適している場合があります。ただし、特定の歌手のすべてのアルバムに関する情報を頻繁に取得する必要がある場合は、AlbumsSingers の子テーブルとして作成し、2 つのテーブルの行を主キー ディメンションに沿って同じ場所に配置する必要があります。次の例ではこれについてさらに詳しく説明します。

インターリーブされたテーブルの作成

音楽アプリケーションを設計するとき、Singers テーブルと Albums テーブルの両方で特定の主キーの行に頻繁にアクセスする必要があることがわかったものとします(たとえば、行 Singers(1) にアクセスするたびに、行 Albums(1, 1)Albums(1, 2) にもアクセスする必要があるような場合)。つまり、SingersAlbums は強いデータ局所性関係を持つ必要があります。

このようなデータ局所性関係は、Singers の子(インターリーブされた)テーブルとして Albums を作成することにより宣言できます。主キーで説明したように、インターリーブされたテーブルとは、子テーブルの行をそれに関連する親の行と一緒に物理的に格納する目的で、別のテーブルの子として宣言されるテーブルです。前に説明したように、子テーブルの主キーの接頭辞は、親テーブルの主キーにする必要があります。

次のスキーマの太字の行は、Singers のインターリーブされたテーブルとして Albums を作成する方法を示します。

-- Schema hierarchy:
-- + Singers
--   + Albums (interleaved table, child table of Singers)
CREATE TABLE Singers (
  SingerId   INT64 NOT NULL,
  FirstName  STRING(1024),
  LastName   STRING(1024),
  SingerInfo BYTES(MAX),
) PRIMARY KEY (SingerId);

CREATE TABLE Albums (
  SingerId     INT64 NOT NULL,
  AlbumId      INT64 NOT NULL,
  AlbumTitle   STRING(MAX),
) PRIMARY KEY (SingerId, AlbumId),
  INTERLEAVE IN PARENT Singers ON DELETE CASCADE;

このスキーマについては次の点に注意してください。

  • 子テーブル Albums の主キーの接頭辞である SingerId は、親テーブル Singers の主キーでもあります。これは、SingersAlbums が階層の同じレベルにある場合は必要ありませんが、このスキーマでは AlbumsSingers の子テーブルとして宣言するため必要です。
  • ON DELETE CASCADE アノテーションは、親テーブルの行が削除された場合、このテーブル内の子の行(つまり、同じ主キーで始まるすべての行)も自動的に削除されることを示します。子テーブルにこのアノテーションがないか、アノテーションが ON DELETE NO ACTION の場合、親の行を削除するためには先に子の行を削除する必要があります。
  • インターリーブされた行は、最初に親テーブルの行で並べ替えられた後、親の主キーを共有する子テーブルの連続行によって並べ替えられます。つまり、"Singers(1)"、"Albums(1, 1)"、"Albums(1, 2)" のようになります。
  • スプリットは Singers テーブルの行の間にのみ挿入できるので、このデータベースが分割されても、各歌手とそのアルバムデータの間のデータ局所性関係は維持されます。
  • 子の行を挿入する前に、親の行が存在する必要があります。

行の物理レイアウト: Albums の行は Singers の行の間にインターリーブされています

Singers とその子テーブル Albums の行の物理レイアウト。

インターリーブされたテーブルの階層の作成

SingersAlbums の間の親子関係は、さらに下位のテーブルまで拡張できます。たとえば、Albums の子として Songs という名前のインターリーブされたテーブルを作成し、各アルバムのトラックリストを格納できます。

6 行 4 列の Songs テーブル

Songs テーブルの行の論理ビュー。主キー列は太線の左側です。

Songs の主キーは、階層内でこのテーブルより上位にあるテーブル(つまり、SingerIdAlbumId)のすべての主キーで構成される必要があります。

-- Schema hierarchy:
-- + Singers
--   + Albums (interleaved table, child table of Singers)
--     + Songs (interleaved table, child table of Albums)
CREATE TABLE Singers (
  SingerId   INT64 NOT NULL,
  FirstName  STRING(1024),
  LastName   STRING(1024),
  SingerInfo BYTES(MAX),
) PRIMARY KEY (SingerId);

CREATE TABLE Albums (
  SingerId     INT64 NOT NULL,
  AlbumId      INT64 NOT NULL,
  AlbumTitle   STRING(MAX),
) PRIMARY KEY (SingerId, AlbumId),
  INTERLEAVE IN PARENT Singers ON DELETE CASCADE;

CREATE TABLE Songs (
  SingerId     INT64 NOT NULL,
  AlbumId      INT64 NOT NULL,
  TrackId      INT64 NOT NULL,
  SongName     STRING(MAX),
) PRIMARY KEY (SingerId, AlbumId, TrackId),
  INTERLEAVE IN PARENT Albums ON DELETE CASCADE;

インターリーブされた行の物理ビューは、歌手とそのアルバムおよび楽曲データの間で、データ局所性関係が維持されることを示しています。

行の物理ビュー: Songs は Albums にインターリーブされ、それらは Singers の間にインターリーブされます

Singers、Albums、Songs テーブルの行の物理レイアウト。インターリーブされたテーブルの階層を形成します。

まとめると、親テーブルとそれより下位にあるすべてのテーブルにより、スキーマでのテーブルの階層が形成されます。階層内の各テーブルは論理的には独立していますが、このように物理的にインターリーブすることで、テーブルが効率的に事前結合され、関連する行にまとめてアクセスできて、ディスク アクセスが最小限になるため、パフォーマンスが向上します。

可能な限り、インターリーブされたテーブルのデータを主キーによって結合する。インターリーブされた各行は、その親行と同じスプリットに物理的に保存されることが保証されます。したがって、Cloud Spanner はローカルで主キーによる結合を行い、ディスク アクセスとネットワーク トラフィックを最小限に抑えることができます。次の例では、SingersAlbums が主キー SingerId で結合されます。

SELECT s.FirstName, a.AlbumTitle
FROM Singers AS s JOIN Albums AS a ON s.SingerId = a.SingerId;

Cloud Spanner ではテーブルのインターリーブは必須ではありませんが、強いデータ局所性関係を持つテーブルの場合はおすすめします。1 つの行とその下位の行のサイズが数 GB より大きくなる可能性がある場合は、テーブルをインターリーブしないでください。

キー列

テーブルのキーは変更できません。既存のテーブルにキー列を追加したり、既存のテーブルからキー列を削除したりすることはできません。

NULL の格納

主キー列は、NULL を格納するように定義できます。主キー列に NULL を格納する必要がある場合は、スキーマでその列に NOT NULL 句を指定しないようにします。

次に示すのは、主キー列 SingerIdNOT NULL 句を省略した例です。SingerId は主キーであるため、Singers テーブル内でその列に NULL が格納されるのは多くても 1 行であるので注意してください。

CREATE TABLE Singers (
  SingerId   INT64,
  FirstName  STRING(1024),
  LastName   STRING(1024),
) PRIMARY KEY (SingerId);

主キー列の NULL 値可能プロパティは、親テーブルの宣言と子テーブルの宣言の間で一致している必要があります。この例では、Albums.SingerId INT64 NOT NULL は使用できません。キーの宣言では NOT NULL 句を省略する必要があります。これは、Singers.SingerId でそれが省略されているためです。

CREATE TABLE Singers (
  SingerId   INT64,
  FirstName  STRING(1024),
  LastName   STRING(1024),
) PRIMARY KEY (SingerId);

CREATE TABLE Albums (
  SingerId     INT64 NOT NULL,  -- NOT ALLOWED!
  AlbumId      INT64 NOT NULL,
  AlbumTitle   STRING(MAX),
) PRIMARY KEY (SingerId, AlbumId),
  INTERLEAVE IN PARENT Singers ON DELETE CASCADE;

許可されない型

以下のものを ARRAY 型にすることはできません。

  • テーブルのキー列。
  • インデックスのキー列。

マルチテナンシーのための設計

異なるお客様に属するデータを格納する場合は、マルチテナンシーを提供することをおすすめします。たとえば、音楽サービスでは、個々のレコードラベルのデータを個別に保存する必要がある場合があります。

従来のマルチテナンシー

マルチテナンシーを設計する従来の方法は、お客様ごとに個別のデータベースを作成することです。この例では、各データベースに独自の Singers テーブルがあります。

データベース 1: Ackworth Records
SingerId FirstName LastName
1MarcRichards
2CatalinaSmith
データベース 2: Cama Records
SingerId FirstName LastName
3MarcRichards
4GabrielWright
データベース 3: Eagan Records
SingerId FirstName LastName
1BenjaminMartinez
2HannahHarris

Cloud Spanner でマルチテナンシーを設計する方法として、お客様ごとに異なる主キーの値を使用することが推奨されます。CustomerId キー(または同様のキー)列をテーブルに含めます。CustomerId を最初のキー列にすると、各お客様のデータに良好な局所性がもたらされます。Cloud Spanner によって、サイズと読み込みパターンに基づいてノード間で自動的にデータが分割されます。次の例では、すべてのお客様を対象とした単一の Singers テーブルがあります。

Cloud Spanner マルチテナンシー データベース
CustomerId SingerId FirstName LastName
11MarcRichards
12CatalinaSmith
23MarcRichards
24GabrielWright
31BenjaminMartinez
32HannahHarris

テナントごとに個別のデータベースを用意する必要がある場合は、以下の制約に注意してください。

  • インスタンスあたりのデータベース数とデータベースあたりのテーブル数には制限があります。顧客の数によっては、個別のデータベースやテーブルを持つことができない場合があります。
  • 新しいテーブルとインターリーブされていないインデックスの追加によって、長い時間がかかる場合があります。スキーマ設計が新しいテーブルとインデックスの追加に依存する場合、必要なパフォーマンスを得ることができない場合があります。

個別のデータベースを作成する場合は、各データベースの 1 週間あたりのスキーマ変更数が少なくなるようにデータベース間でテーブルを分散すると、成功することが多くなります。

アプリケーションのお客様ごとに個別のテーブルとインデックスを作成する場合は、同じデータベースにすべてのテーブルとインデックスを入れるのは避けてください。それらを多数のデータベースで分割して、多数のインデックスを作成する際のパフォーマンスの問題を緩和してください。データベースあたりのテーブルとインデックスの数にも制限があります。

このページは役立ちましたか?評価をお願いいたします。

フィードバックを送信...

Cloud Spanner のドキュメント