👌

TiDB のパーティショニング その3:ハッシュとキー

2025/03/08に公開

今日はTiDBのパーティショニングシリーズ第三回です。

過去二回の記事で、範囲、Listのパーティショニングを見てきました。
https://zenn.dev/kameoncloud/articles/2dc11e5645f38c
https://zenn.dev/kameoncloud/articles/69e82ac560bb98

今日はハッシュ分割とキー分割を見ていきます。

この2つは範囲、Listと異なるメリットが開発者に提供されます。
範囲とListはアプリケーションが認識できるレベルでのデータ分割を提供します。一方ハッシュとキーはデータベースレイヤーが一定の法則にのっとりデータを分散して保存します。
このためドキュメントの記載にはこのような違いがあります。

範囲パーティション分割、範囲列パーティション分割、List パーティショニング、およびList COLUMNS パーティショニングは、アプリケーションでの大量の削除によって発生するパフォーマンスの問題を解決し、パーティションの迅速な削除をサポートするために使用されます。
ハッシュ パーティション分割とキー パーティション分割は、書き込み回数が多いシナリオでデータを分散するために使用されます。ハッシュ パーティション分割と比較して、キー パーティション分割は、複数の列のデータの分散と非整数列によるパーティション分割をサポートします。

この違いを理解するためにはTiDBにおけるリージョンについてまず触れていきます。

TiDB のリージョン

TiDBのリージョンはパブリッククラウドのリージョンとは異なる概念です。
https://docs.pingcap.com/ja/tidb/stable/tidb-storage/
NewSQLといわれる分散データベースであるTiDBではデータはTiKVというキーバリューストアに分散して格納されます。

そしてTiKVはデータをリージョンという単位で分割して保存しています。

そしてこのリージョンの集合体がRocskDBRaftのコンセンサスアルゴリズムによって一つの巨大なストレージを疑似的に構成します。このため、大規模なデータセットであってもコンピュートリソースが分散され高速性を維持しています。

開発者により明示的に作成されたパーティションは、パーティション単位で個別のリージョンを形成し、パーティション同志のパフォーマンスはお互い干渉しづらい構成となっています。このため範囲Listの例で示したような大量のデータ削除(drop partition)などはパーティション単位で行った方が、データを構成する全リージョンに影響を与える幅広いデータ範囲を指定するDELETEクエリより効率が良くなります。

範囲Listはアプリケーションが認識している単位でデータを分割しています。特定パーティションへの大量なデータ書き込みは特定リージョンのみの負荷を向上させますが、データ領域全域にわたる大量のデータ書き込みは、アプリケーションが意図しない形で特定リージョンへの負荷を向上させてしまうケースがあります。なぜならアプリケーションが大量のデータ書き込みを発生させた場合、ストレージ内部のリージョンにおけるデータ分布を認識していないためです。
アプリケーションが書き込みを行った際、その値に応じてデータを分散させるのが、ハッシュとキーです。つまり、アプリケーションが意図的にリージョンを指定することはできない一方で、アプリケーションが発行するクエリの中身に従ってデータをリージョンに分散させる仕組みを実現するのはハッシュ分割とキー分割です。

さっそくやってみる

ハッシュパーティション分割

まずは以下のSQLを実行します。

CREATE TABLE employees (
    employee_id INT,
    name VARCHAR(100),
    department_id INT,
    hire_date DATE
) 
PARTITION BY HASH(department_id) 
PARTITIONS 4;

この場合、department_idの値が4分割されます。department_idが等しく分散する場合、パーティション割り当ても同じ分散となります。一方department_idがある特定の重複した値をとる場合、用いられるパーティションも偏ります。TiDBのDocumentではそれを以下のように表現しています。

最も効率的なハッシュ関数は、単一のテーブル列に対して動作し、その値が列の値に応じて一貫して増加または減少する関数です。

ここで指定可能なカラムは整数である必要がありますが、以下のように関数をとることも可能です。

CREATE TABLE t1 (col1 INT, col2 CHAR(5), col3 DATE)
    PARTITION BY HASH( YEAR(col3) )
    PARTITIONS 4;

なぜ整数である必要があるかといえばcol3がどのパーティションに格納されるかといえば単純な割り算の余り数(MOD)を用いています。つまりハッシュ関数を使っているわけではないと思います。具体的にはyearを4で割ると以下のようになります。

2020 /4 = 505 余り0 Partion 1
2021 /4 = 505 余り1 Partion 2
2022 /4 = 505 余り2 Partion 3
2023 /4 = 505 余り3 Partion 4
2024 /4 = 506 余り0

結果としてcol3の値が2025-01-01のように日付は等しく分散したとしても、YEAR(col3)が偏っていればこのハッシュ分割は意味をなさないことになります。
ハッシュ分割というとハッシュ関数で値を散らしているように思えますので注意が必要です。

キーパーティション分割

ハッシュパーティション分割が整数式をとるのに対して、こちらはvarcharなどの値も指定が可能です。
以下のSQLを実行してみます。

CREATE TABLE users (
    user_id INT,
    name VARCHAR(100),
    email VARCHAR(255),
    join_date DATE
)
PARTITION BY KEY(email)
PARTITIONS 4;

以下のように複数カラムを条件に指定することも可能です。

use test;

CREATE TABLE users (
    user_id INT,
    name VARCHAR(100),
    email VARCHAR(255),
    join_date DATE
)
PARTITION BY KEY(user_id, email)
PARTITIONS 4;

またオンライン状態のまま以下のようにパーティションの統合が可能です。

ALTER TABLE users COALESCE PARTITION 1;
ANALYZE TABLE users;

パーティションの再構成を行ったのちは必ずANALYZE TABLEを行います。
また同様にパーティションを増やすこともできます。

ALTER TABLE users ADD PARTITION PARTITIONS 8;

Discussion