1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PostgreSQLのカラム順序がテーブルサイズに与える影響

Last updated at Posted at 2024-02-22

カラム順序がテーブルサイズに与える影響

テーブルのカラム順序を最適化することで、ストレージの利用効率を向上させられるというお話です(使い古されたネタです)。

主に次の記事を元に記載しています。

事前知識

テーブルサイズ取得には次の関数を使用します

関数 説明
pg_column_size 個々のデータ値を格納するのに使用されるバイト数を表示します。
pg_column_compression 個々の可変長値で使われた圧縮アルゴリズムを表示します。 値が圧縮されていなければ、NULLを返します。
pg_size_pretty バイトサイズを、サイズ単位(バイト、kB、MB、GB、TB、PBのうちの適切なもの)を使った、より人間が読みやすい形式に変換します。
pg_total_relation_size 指定テーブルが使用している、インデックスとTOASTデータを含む全ディスクスペースを計算します。 結果はpg_table_size + pg_indexes_sizeと等価です。

実行環境

postgres=# select version();
                                                                version                                                                 
----------------------------------------------------------------------------------------------------------------------------------------
 PostgreSQL 14.10 (Ubuntu 14.10-0ubuntu0.22.04.1) on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0, 64-bit
(1 row)

データベースの準備

postgres@masami-L ~> createdb -U postgres sample
postgres@masami-L ~> psql -U postgres -d sample
psql (14.10 (Ubuntu 14.10-0ubuntu0.22.04.1))
Type "help" for help.

カラム順序整列前テーブルのサイズ計測

様々な型のカラムを持つテーブルを定義します

postgres=# CREATE TABLE diverse_types_table (
     id SERIAL PRIMARY KEY,
     name VARCHAR(255),
     description TEXT,
     age INTEGER,
     salary NUMERIC(10, 2),
     created_at TIMESTAMP WITH TIME ZONE,
     updated_at TIMESTAMP WITH TIME ZONE,
     is_active BOOLEAN
 );
CREATE TABLE

データを挿入します

sample=# DO $$
BEGIN
    FOR i IN 1..100000 LOOP
        INSERT INTO diverse_types_table (name, description, age, salary, created_at, updated_at, is_active)
        VALUES (
            'Name ' || i,
            'Description ' || i,
            i % 100,
            (i % 100)::numeric + 0.99,
            NOW() - ((i % 365) || ' days')::interval,
            NOW(),
            i % 2 = 0
        );
    END LOOP;
END $$;
DO

リレーションサイズを確認します

sample=# vacuum analyze diverse_types_table;
VACUUM
sample=# select pg_size_pretty(pg_total_relation_size('diverse_types_table'::regclass));
 pg_size_pretty 
----------------
 12 MB
(1 row)

カラム順序整列後テーブルのサイズ計測

カラムの順番を求めます

sample=# SELECT a.attname, t.typname, t.typalign, t.typlen
   FROM pg_class c
   JOIN pg_attribute a ON (a.attrelid = c.oid)
   JOIN pg_type t ON (t.oid = a.atttypid)
  WHERE c.relname = 'diverse_types_table'
    AND a.attnum >= 0
  ORDER BY t.typlen DESC;
   attname   |   typname   | typalign | typlen 
-------------+-------------+----------+--------
 created_at  | timestamptz | d        |      8
 updated_at  | timestamptz | d        |      8
 id          | int4        | i        |      4
 age         | int4        | i        |      4
 is_active   | bool        | c        |      1
 salary      | numeric     | i        |     -1
 description | text        | i        |     -1
 name        | varchar     | i        |     -1
(8 rows)

この順番でカラムを並び替えて、テーブルを再定義してみます

sample=# CREATE TABLE sorted_diverse_types_table (
    created_at TIMESTAMP WITH TIME ZONE,
    updated_at TIMESTAMP WITH TIME ZONE,
    id SERIAL PRIMARY KEY,
    age INTEGER,
    name VARCHAR(255),
    salary NUMERIC(10, 2),
    is_active BOOLEAN,
    description TEXT
 );
CREATE TABLE

カラム順序整列前のテーブルからデータをコピーします

sample=# INSERT INTO
 sorted_diverse_types_table
SELECT
 created_at,
 updated_at,
 id,
 age,
 name,
 salary,
 is_active,
 description
FROM
 diverse_types_table
; 
INSERT 0 100000

カラム順序整列後のテーブルのリレーションサイズを確認します

sample=# vacuum analyze sorted_diverse_types_table;
VACUUM
postgres=# sample=# select pg_size_pretty(pg_total_relation_size('sorted_diverse_types_table'::regclass));
 pg_size_pretty 
----------------
 11 MB
(1 row)

比較

2つのテーブルを作成し、テーブルサイズを比較した結果です

テーブル サイズ
カラム順序整列前 12 MB
カラム順序整列後 11 MB

※例は可変型のカラムが良い感じに補完しあっていて、削減効果が弱いです。
実際には2~3割削れることも多いです。

各テーブルのカラムサイズを確認します
カラム整列後の方が4バイト小さくなっています

postgres=# select pg_column_size(diverse_types_table.*) FROM diverse_types_table limit 1;
 pg_column_size 
----------------
             81
(1 row)

postgres=# select pg_column_size(sorted_diverse_types_table.*) FROM sorted_diverse_types_table limit 1;
 pg_column_size 
----------------
             77
(1 row)

カラムは8バイト区切りでアライメントされて、ディスクに記録されるため、8バイト区切りでデータを格納できない場合はパティングされる可能性があります。

簡単な例です。

sample=# SELECT pg_column_size(row()) AS empty,
       pg_column_size(row(0::SMALLINT)) AS byte2,
       pg_column_size(row(0::BIGINT)) AS byte8,
       pg_column_size(row(0::SMALLINT, 0::BIGINT)) AS byte16_padding,
       pg_column_size(row(0::BIGINT, 0::SMALLINT)) byte16_nopadding;
 empty | byte2 | byte8 | byte16_padding | byte16_nopadding 
-------+-------+-------+----------------+------------------
    24 |    26 |    32 |             40 |               34
(1 row)

byte16_paddingとbyte16_nopaddingのタプルを構成する型は同じですが、6バイトの差が発生しています。
※タプルには24バイトのヘッダーが必要なので、それを除いて考える必要があります

結論

  • カラム順序は8バイトアライメントの影響を受けないように並び替えるとテーブルサイズが削減できる(ストレージ効率の向上、パフォーマンス向上)

備考

  • 関連性の強いカラムの位置を近づけた方が保守性の高いテーブルになるので、トレードオフが発生する可能性もある
  • Postgres はカラムの並べ替えをネイティブにサポートしていないので、作成済みのテーブルのカラムの入れ替えはできません。そのため、自前でコピーする必要がある
  • この記事では詳しく書きませんが、カラム順序整列はカラムアクセス速度に対する恩恵もあります
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?