すべてのプロダクト
Search
ドキュメントセンター

Tablestore:テーブル設計のベストプラクティス

最終更新日:Jun 18, 2026

Tablestore は、パーティションキーの範囲に基づいてデータを自動的に複数のパーティションに分割します。パーティションキーの選定が不適切だと、ホットスポット、データスキュー、またはスケーリングのボトルネックにつながります。プライマリキー設計、属性列設計、テーブル分割のベストプラクティスに従って、高性能でスケーラブルなデータテーブルを設計してください。

プライマリキー設計

Tablestore は、パーティションキー (最初のプライマリキー列) の範囲に基づいてデータを自動的に複数のパーティションに分割し、サービスノードに分散します。データとアクセス負荷を均等に分散し、ホットスポットを回避できるようにプライマリキーを設計してください。

パーティションキー設計の原則

パーティションキーは、データテーブルにおける最初のプライマリキー列です。Tablestore は、パーティションキーの範囲に基づいてデータを自動的に複数のパーティションに分割し、各パーティションを別々のサービスノードに割り当てます。パーティションキーを設計する際は、次の原則に従ってください:

  • 単一のパーティションキー値配下のデータ量を 10 GB 未満に抑えます。

  • 異なるパーティションキー値配下のデータは、論理的に独立させます。

  • 連続するパーティションキー値の狭い範囲にアクセス負荷が集中しないようにします。

説明

ワークロードが低い場合 (TPS/QPS が 1,000 未満、データ量が 10 GB 以内、かつ大幅に増加する見込みがない場合)、ホットスポットの影響は最小限となり、単一パーティションで負荷を処理できます。ただし、単一パーティションの処理能力に依存するアーキテクチャは設計しないでください。

データ分散

分散データシステムでは、データ分散が不均一だと次の問題が発生します:

  • 読み取り/書き込みスループットが単一パーティションに制約され、明確なボトルネックが生じます。

  • ホットスポットの分布が不均一だとロングテールが発生し、全体の処理が遅くなります。

  • ホットスポットのパーティションがビジネスパイプライン全体のボトルネックとなり、上流/下流システムに影響を及ぼします。

データがパーティション全体に均等に分散されると、読み取り/書き込み負荷はすべてのパーティションに分散されます。各リクエストはローカルデータのみを対象とし、リソースを追加することで水平スケールできます。

代表的なホットスポット問題と解決策

監視シナリオとして、各マシンの各時点のメトリック値を格納するデータテーブルを考えます。プライマリキー設計は次のとおりです:

Timestamp (パーティションキー)

MachineIp

問題

1718000001

10.10.0.1

すべての書き込みがテーブル末尾 (最後のパーティション) に追記され、テールホットスポットが発生します。

1718000002

10.10.0.2

1718000003

10.10.0.1

データはパーティションキーの範囲で分割されるため、すべての書き込みが最後のパーティションに集中し、パーティション分割では解消できないテールホットスポットが発生します。

アプローチ 1:パーティションキーの並べ替え

MachineIp を最初のプライマリキー列に移動し、Timestamp を 2 番目にします。調整後のプライマリキーは MachineIp = "10.10.0.1", Timestamp = 1718000001 になります。これにより、書き込み負荷はマシン単位でパーティションに分散されます。UserIdDeviceIdOrderId など、自然に分散される他のフィールドもパーティションキーとして同様に有効です。

説明

特定の IP 範囲からの書き込みが単一パーティションに集中する場合、Tablestore はそのパーティションを自動的に分割し、複数のパーティションに負荷を分散します。

アプローチ 2:MD5 プレフィックスの付与

MachineIp の MD5 ハッシュを計算し、先頭 4 文字の 16 進文字列を IP の前に付与してパーティションキーにします。たとえば、10.10.0.1 の MD5 の先頭 4 文字が a1b2 の場合、パーティションキーは a1b2,10.10.0.1 になります。この方法により IP 範囲の連続性が崩れます。また、16 進プレフィックスはシステムの事前分割にも役立ちます。

アプローチ 3:グローバルな順序付けが必要な場合の代替案

ビジネス要件として時間でグローバルに順序付けされたクエリが必要で、かつテールホットスポットを回避したい場合は、次の代替案を検討してください:

  • ローカル順序:分散キーとなるフィールドを 1 列目に配置します。同じ分散フィールド値を持つデータは、時間順のまま保持されます。たとえば、特定の MachineIp 配下のデータは Timestamp により順序付けられたままです。

  • バケットベースの書き込み:時刻値の N による剰余を最初のプライマリキー列 (0~N-1) とし、時刻を 2 列目にします。たとえば 16 バケットの場合、書き込みのパーティションキーは timestamp % 16 = 3 になります。読み取り時は 16 バケットすべてを並列にクエリし、結果をマージします。バケット数を増やすほど負荷は均等に分散されますが、読み取り時の並列クエリ数が増えます。

  • 検索インデックス:データが均等に分散されるようにデータテーブルに書き込み、検索インデックス を使用して、時間や他のフィールドで順序付けされたクエリを実行します。検索インデックスはデータを複数のシャードに自動的に分散し、クエリ時に結果をマージします。

プライマリキー列長とデータ量の制約

  • 単一のパーティションキー値配下のデータ量を 10 GB 以内に抑えます (ハード制限はありません)。同一のパーティションキー値を持つ行は、それ以上分割できません。

  • プライマリキー列長の上限は 1 KB です。プライマリキーを短くすると、クエリパフォーマンスが向上します。

列の連結によるパーティションキーの作成

単一のパーティションキー値配下にあるすべての行の総データ量が 10 GB を超える可能性がある場合は、複数のプライマリキー列を連結して新しいパーティションキーを作成し、データを分散します。連結する際は、次のルールに従ってください:

  • 連結後のパーティションキーは、元々同じパーティションキー値を共有していたレコードを、異なるパーティションキー値に効果的に分割できる必要があります。

  • 整数のプライマリキー列は、先頭に 0 を付与してソートの整合性を維持します。たとえば、OrderNumber = 123 は 0 埋め後に 00000123 となり、辞書順が数値順と一致します。

  • 新しいパーティションキーの辞書順を維持するために、データ内のすべての文字より ASCII 値が小さいデリミタ文字を選択します。具体的な選択は、ビジネスデータの文字セットに依存します。たとえば SellerID の値に数字と英字が含まれる場合 (a100a1001 など)、デリミタの違いによってソートは次のように変わります:

    デリミタ

    連結後のソート順

    理由

    結果

    :

    000054:a1001000054:a100: より前にソートされます

    : の ASCII 値は数字より大きいため、共通プレフィックス後の比較で、a10011: よりも小さいと判断されてしまいます。

    誤ったソート

    ,

    000054,a100,000054,a1001 より前にソートされます

    , の ASCII 値は数字と英字のいずれよりも小さいため、辞書順が元データの順序と一致します

    正しいソート

パーティションキーへのハッシュプレフィックスの追加

順次増加する列をパーティションキーとして使用する必要がある場合は、パーティションキーにハッシュプレフィックスを付与し、テーブル内で隣接データの分布をランダム化してアクセス負荷を均等に分散します。

説明

ハッシュプレフィックスを追加すると、これまで連続していたデータは分散されます。論理的に連続するデータを範囲読み取り操作で読み取ることはできなくなります。範囲クエリの代替として検索インデックスを使用してください。

属性列設計

  • 行幅の制御:Tablestore はワイド行 (最大で数十万の属性列) をサポートしますが、極端にワイドな行を 1 回のリクエストで読み取るとタイムアウトする可能性があります。1 行あたりの属性列数は 1 万未満に抑えてください。列名指定またはページネーションを使用してワイド行を読み取ってください。

  • 属性列サイズの上限:単一の属性列の値は 2 MB を超えられません。この上限を超えるデータは複数の列に分割するか、Object Storage Service (OSS) に保存してください。

  • アクセス頻度によるテーブル分割:1 行に多数の属性列があり、アクセス頻度が大きく異なる場合は、高頻度列と低頻度列を別テーブルに保存してください。たとえば商品管理システムでは、商品数量や価格は頻繁にアクセスされる一方、商品説明 (大きなテキスト) はアクセス頻度が低いことがあります。2 つのテーブルに分割してください。

  • 大きなテキストの圧縮:大きな属性列テキストを圧縮してバイナリとして保存することで、ストレージ使用量を削減するとともに読み取り/書き込みのオーバーヘッドを低減できます。

  • 金額には Long 型を使用:Tablestore は BigDecimal 型をサポートしていません。金額など正確な計算が必要なフィールドでは、Long 型で最小単位の値を保存してください (例:5.32 USD を 532 セントとして保存)。Double 型による精度損失を回避できます。

テーブル分割と容量計画

テーブル分割

単一の検索インデックスのデータ量は 200 億行以内に抑えてください。データ量が 200 億行を超える場合は、Tablestore テクニカルサポートに連絡し、テーブル分割の評価と設計を行ってください。

たとえば、現時点で最大のログテーブルが 61 億行で、年間 21 億行増加する場合、3~5 年以内に 200 億行を超えないため分割は不要です。既存データが大きく、かつ急速に増加している場合は、テーブル分割戦略を事前に計画してください。

ホット/コールドデータ分離

データアクセスパターンは通常、時間の影響を受けます。直近のデータは頻繁にアクセスされ、古いデータは次第にコールドデータとなります。ホットデータとコールドデータを同じテーブルに保存すると、アクセス負荷が不均一になり、予約済み読み取り/書き込みスループットが十分に活用されません。

ホットデータとコールドデータは、予約済み読み取り/書き込みスループットが異なる別テーブルに保存してください。ホットデータテーブルには多条件クエリ用に検索インデックスを作成し、コールドデータテーブルには固定的なクエリパターン向けにセカンダリインデックスを使用してコストを削減します。検索インデックスはデータ TTL (time-to-live) をサポートしており、ホットデータとコールドデータを自動的に分離できます。

一括データインポート

Tablestore にデータを一括インポートする際、プライマリキー順に書き込むと、書き込み負荷が単一パーティションに集中してインポートが遅くなります。次の対策を実施してください:

  • データセットを小さなサブセットに分割し、複数のワーカースレッドでサブセットをランダムに選択して並列にインポートしてください。

  • データを書き込む前に事前分割を実施し、開始時点からデータが複数のパーティションに分散されるようにするには、Tablestore テクニカルサポートにご連絡ください。

  • 高並列の非同期書き込みには TableStoreWriter を使用してください。バケット分散とバッチ送信が自動的に処理されます。

設計事例:学生証の取引記録

次の例では、大学の学生証取引システムを用いて、テーブル設計の意思決定プロセスを示します。データテーブルのプライマリキー列には、CardID (学生証 ID)、SellerID (加盟店 ID)、DeviceID (端末 ID)、OrderNumber が含まれます。ビジネスルールは次のとおりです:

  • 各学生証は 1 つの CardID にマッピングされ、各加盟店は 1 つの SellerID にマッピングされます。

  • 各端末はグローバルに一意な DeviceID にマッピングされます。

  • OrderNumber は単一端末内で一意で、時間とともに増加しますが、グローバルに一意ではありません。

  • 取引記録はリアルタイムで書き込まれます。

プライマリキーとパーティションキーの選定

最初にパーティションキーを決定します。各プライマリキー列の特性に基づき、異なるパーティション分割アプローチの影響を比較します:

パーティション分割アプローチ

分析

CardID をパーティションキーにする

学生 1 人あたりの 1 日の取引回数は限定的です。書き込み負荷は数万枚のカードに分散され、データ分散は良好です。推奨します。

SellerID をパーティションキーにする

加盟店数が少なく、多くの取引が一部の加盟店に集中するため、ホットスポットが発生しやすくなります。推奨しません。

DeviceID をパーティションキーにする

端末はグローバルに一意であり、分散は良好です。ただし、トラフィックの多い一部の端末 (人気の食堂にあるカードリーダーなど) では、局所的なホットスポットが発生する可能性があります。

OrderNumber をパーティションキーにする

OrderNumber は端末内で時間とともに増加します。これをそのままパーティションキーにするとテールホットスポットが発生します。ハッシュプレフィックスが必要です。

設計の結論

分析の結果、CardID は最も良好なデータ分散 (数万枚のカード、学生 1 人あたり 1 日数回の取引) を提供するため、推奨されるパーティションキーです。プライマリキーの順序は CardID, DeviceID, SellerID, OrderNumber です。多くのビジネスシナリオでは、プライマリキーとパーティションキーを選定するだけで十分です。

高度な最適化

基本的なパーティションキー選定だけでは、パフォーマンス要件を満たせない場合があります。次に、代表的な高度最適化アプローチを 2 つ示します。

シナリオ 1:単一パーティションキーのデータ量が過大な場合に、複数列を連結してパーティションキーを作成する

ビジネス要件として DeviceID によるクエリが必要で、DeviceID をパーティションキーとして選定した場合、単一の DeviceID 配下のデータ量が 10 GB を超える可能性があります。この場合、DeviceID、SellerID、CardID を連結して新しいパーティションキーを作成し、同一端末からのレコードを加盟店と学生証単位で分割します。

連結前

DeviceID (パーティションキー)

SellerID

CardID

OrderNumber

問題

54

10

1001

000001

DeviceID=54 のすべてのレコードが同一パーティションに格納されます。時間の経過とともに、蓄積データ量が 10 GB を超える可能性があります。

54

20

1002

000002

78

20

1001

000003

連結後

DeviceID,SellerID,CardID (新しいパーティションキー)

OrderNumber

効果

54,10,1001

000001

同一端末からの取引記録が、加盟店と学生証単位で異なるパーティションキー値に分割されます。

54,20,1002

000002

78,20,1001

000003

シナリオ 2:パーティションキー値が順次増加する場合に、ハッシュプレフィックスを追加する

ビジネス要件として OrderNumber によるクエリが必要で、OrderNumber をパーティションキーとして選定する場合、端末内での OrderNumber の順次増加によりテールホットスポットが発生します。OrderNumber に対して MD5 のハッシュプレフィックスを計算し、データを均等に分散します:

元の OrderNumber

MD5 の先頭 4 文字

HashOrderNumber (パーティションキー)

000001

e2c8

e2c8,000001

000002

3f79

3f79,000002

000003

a5b1

a5b1,000003

ハッシュプレフィックスを追加した後は、同じアルゴリズムで任意の OrderNumber のハッシュプレフィックスを計算し、対応する HashOrderNumber を取得できます。ただし、ハッシュプレフィックスによって元の順序は分散されるため、論理的に連続するレコードを範囲読み取り操作で読み取ることはできなくなります。

ホット/コールドデータ分離

学生証の取引記録には明確な時間的特性があります。直近の記録は頻繁にクエリされる一方、卒業した学生の記録はほとんどアクセスされません。ホット/コールドデータ分離は、次のとおり計画してください:

  • 直近のアクティブデータ (例:過去 1 年) と履歴データを、予約済み読み取り/書き込みスループットが異なる別テーブルに保存します。

  • アクティブテーブルには高い予約済みスループットを、履歴テーブルには低いスループットを設定し、ストレージコストを削減します。

  • アクティブテーブルには検索インデックスを作成し、多次元クエリ (加盟店や時間範囲など) を実行します。履歴テーブルではプライマリキーによるクエリのみで十分です。

  • 保持期間を超えたデータを、定期的にアクティブテーブルから履歴テーブルへ移行します。