🕌

テーブルの水平分割

2025/02/19に公開

Daily Blogging60日目

DBのテーブルにおける水平分割って色々あるんだね

水平分割とは

テーブルにあるレコードを行単位で分割することを水平分割という。
逆に列単位で分割するのは垂直分割という。

分割したものは、それぞれ物理的に別のテーブルにして管理する。

水平分割には大きく3種類あるが、いずれも解決したい課題は同じ

解決したい課題

  • レコード数が増え肥大化したテーブルに対する検索効率の向上が目的
    • レコードを複数の小さなテーブルに分割することで、I/Oを減らす

レコードが増えてくるとテーブルサイズが大きくなり、フルスキャンでもインデックススキャンでも検索効率が落ちてパフォーマンスが悪くなる。

だったらサイズを小さくしたらいいじゃない!というのが分割の考え方
分割して小さいテーブルにすることで、一つあたりのテーブルサイズを小さくする。

物理的な水平分割

まずは普通の水平分割
テーブルを分けたい単位で分割していく。

この場合、アプリケーション側でもそれぞれのテーブルを個別に扱う必要があって面倒くさい。

パーティショニング

これもテーブルは物理的に分ける。
通常の水平分割と違うのは、論理的には1つのテーブルとして扱うことができるという点。

つまり、アプリケーション側では分割された複数のテーブルを意識しなくても良くなる
論理的に一つのテーブルとして扱えるので、アプリケーション側の管理が楽チン

PostgreSQLなどではパーティショニングが標準搭載されている。

このパーティショニング自体も3種類に分けられるよ

範囲パーティショニング

振り分けに使うカラムであるパーティションキーの範囲に基づいてデータを振り分ける手法。
分割されたテーブルの範囲を定義することで、それに合わせて適宜振り分ける。

-- 親テーブル
CREATE TABLE sales (
    id SERIAL PRIMARY KEY,
    sale_date DATE NOT NULL,
    amount NUMERIC NOT NULL
) PARTITION BY RANGE (sale_date);

-- テーブルの範囲決める
CREATE TABLE sales_2024 PARTITION OF sales 
    FOR VALUES FROM ('2024-01-01') TO ('2024-12-31');

リストパーティショニング

パーティションキーの値に応じて振り分ける手法。

-- 1. 親テーブル
CREATE TABLE customers (
    id SERIAL PRIMARY KEY,
    name TEXT NOT NULL,
    region TEXT NOT NULL
) PARTITION BY LIST (region);

-- 2. 地域ごとに分割する
CREATE TABLE customers_japan PARTITION OF customers 
    FOR VALUES IN ('Japan');

CREATE TABLE customers_usa PARTITION OF customers 
    FOR VALUES IN ('USA');

-- 3. デフォルトパーティション
CREATE TABLE customers_other PARTITION OF customers 
    DEFAULT;

ハッシュパーティショニング

レコードを均等に振り分ける手法
パーティションキーの値をハッシュ化して、テーブルの分割数で割ったあまりで振り分け先を決定することで均一に振り分けることができるらしい。
分割のルールを決められない場合にこの手法を採用する。

-- 1. 親テーブル(ハッシュパーティショニングのベースとなるテーブル)
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name TEXT NOT NULL
) PARTITION BY HASH (id);

-- 2. パーティションの作成(ハッシュ値の範囲ごとに分割)
-- MODULAS = 分割数
-- REMAINDER = 余り
CREATE TABLE users_p0 PARTITION OF users 
    FOR VALUES WITH (MODULUS 4, REMAINDER 0);

CREATE TABLE users_p1 PARTITION OF users 
    FOR VALUES WITH (MODULUS 4, REMAINDER 1);

CREATE TABLE users_p2 PARTITION OF users 
    FOR VALUES WITH (MODULUS 4, REMAINDER 2);

CREATE TABLE users_p3 PARTITION OF users 
    FOR VALUES WITH (MODULUS 4, REMAINDER 3);

パーティショニングの使用判断

  • テーブルサイズが物理メモリのサイズより大きい
    • インデックスサイズが大きくなると、効率的にメモリに載らなくなる
  • 検索条件にパーティションキーが使われるか
    • パーティションキーを使った検索では、アクセスするテーブルを物理的に絞るパーティションプルーニングという機能が働く
  • 一括削除の運用があるか
    • DELETEだとデッドタプルが発生するが、テーブル自体をDROPするとそれがない

シャーディング

サーバ自体を分けるシェアードナッシング
物理的に切り離されストレージも共有しない

DBでは難しい水平方向のスケーリングが可能になり、負荷分散にもなる

仮に結合が必要な場合は、DBサーバ自体違うので工夫が必要

共通のメリット

パーティショニングの説明で軽く触れたが、不要なレコードを削除するときにテーブルごと削除するDROPを使えばデッドタプルが発生しなくなるので、DBのサイズが肥大化せず検索効率が落ちることもない。

共通のデメリット

いずれの方法も、分割した複数のテーブルを見にいかなきゃいけない時は逆にパフォーマンスが悪化する可能性が高い。
通常なら一つのテーブルアクセスで済むところが、複数のテーブルを結合しないといけなくなるため

クエリの複雑さでいうと、通常の水平分割とシャーディングは論理的にも異なるテーブルなので明示的にクエリで結合しないといけないので複雑度が上がる。

使い分け

基本的にはパーティショニングで解決
それでも解決が難しいようであれば、シャーディングを検討する

Discussion