Postgresで大規模なデータベースを操作している場合は、この話に聞き覚えがあるでしょう。 Postgres データベースが成長し続けると、パフォーマンスが低下し始め、ストレージ容量、正確に言えば、ストレージ容量について心配し始めます。 PostgreSQL が大好きですが、あればいいのにと思うものがあります。それは、非常に効果的なデータ圧縮メカニズムです。
PostgreSQL には、ある程度の圧縮メカニズムがあります。
たとえデータセットのサイズを削減できるとしても、TOAST (The Oversize Attribute Storage Technique) は従来のデータ圧縮メカニズムではありません。 TOAST とは何かを理解するには、次のことから始める必要があります。
Postgres のストレージ単位はページと呼ばれ、そのサイズは固定です (デフォルトでは 8 KB)。ページ サイズを固定すると、データ管理のシンプルさ、効率性、一貫性など、Postgres に多くの利点がもたらされますが、一部のデータ値がそのページ内に収まらない可能性があるという欠点もあります。
ここで TOAST が登場します。TOAST は、ページ内に収まらない値を Postgres に効率的に保存および管理するために PostgreSQL が使用する自動メカニズムを指します。このような値を処理するために、Postgres TOAST はデフォルトで内部アルゴリズムを使用して値を圧縮します。圧縮後も値が大きすぎる場合、Postgres は値を別のテーブル (TOAST テーブルと呼ばれる) に移動し、ポインタを元のテーブルに残します。
この記事の後半で説明するように、たとえば特定の列のデータを圧縮しないように Postgres に指示するなど、ユーザーとしてこの戦略を実際に変更できます。
TOAST の対象となる可能性のあるデータ型は主に、標準の PostgreSQL ページのサイズ制限を超える可能性のある可変長データ型です。一方、 integer
、 float
、またはtimestamp
などの固定長データ型は、ページ内に無理なく収まるため、TOAST の対象になりません。
TOAST の対象となる可能性のあるデータ型の例は次のとおりです。
json
とjsonb
大きなtext
列
varchar
およびvarchar(n)
( varchar(n)
で指定された長さが十分に小さい場合、その列の値は常に TOAST しきい値を下回る可能性があります。)
bytea
バイナリデータを保存する
path
やpolygon
の幾何学データと、 geometry
やgeography
などの PostGIS タイプ
TOAST を理解することは、ページ サイズの概念だけでなく、Postgres の別のストレージ概念であるタプルにも関係します。タプルは PostgreSQL テーブル内の行です。通常、タプル内のすべてのフィールドの合計サイズが約 2 KB を超える場合、TOAST メカニズムが作動します。
注意を払ってきた人なら、「待て、でもページ サイズは約 8 KB だ。なぜこのオーバーヘッドが発生するのか?」と疑問に思ったかもしれません。これは、PostgreSQL が 1 つのページに複数のタプルを確実に格納できるようにすることを好むためです。タプルが大きすぎると、各ページに収まるタプルが少なくなり、I/O 操作が増加し、パフォーマンスが低下します。
Postgres は、追加の運用データに適合する空き領域を確保する必要もあります。各ページには、タプル データだけでなく、アイテム識別子、ヘッダー、トランザクション情報など、データを管理するための追加情報も格納されます。
そのため、タプル内のすべてのフィールドの合計サイズが約 2 KB (または後で説明する TOAST しきい値パラメーター) を超えると、PostgreSQL はデータが効率的に保存されるようにアクションを実行します。 TOAST はこれを主に 2 つの方法で処理します。
圧縮。 PostgreSQL では、この記事で後ほど説明する圧縮アルゴリズムを使用して、タプル内の大きなフィールド値を圧縮してサイズを減らすことができます。デフォルトでは、圧縮によってタプルの合計サイズがしきい値を下回るのに十分な場合、データは圧縮形式であってもメイン テーブルに残ります。
アウトオブラインストレージ。圧縮だけでは大きなフィールド値のサイズを減らすのに十分な効果が得られない場合、Postgres はそれらを別の TOAST テーブルに移動します。メイン テーブル内の元のタプルには大きなフィールド値が保持されなくなるため、このプロセスは「アウトオブライン」ストレージとして知られています。代わりに、TOAST テーブル内の大きなデータの場所への「ポインター」または参照が含まれています。
この記事では内容を少し簡略化しています—
pglz
TOAST は PostgreSQL で大きな値を圧縮できると述べました。しかし、PostgreSQL ではどのような圧縮アルゴリズムが使用されており、その効果はどの程度なのでしょうか?
pglz
(PostgreSQL Lempel-Ziv) は、特に TOAST 用に調整された PostgreSQL で使用されるデフォルトの内部圧縮アルゴリズムです。
非常に簡単に説明すると、次のように動作します。
pglz
データの繰り返しを避けようとします。繰り返されるデータを確認した場合、同じ内容を再度書き込むのではなく、以前に書き込んだ場所を指すだけです。この「繰り返しの回避」により、スペースの節約に役立ちます。
pglz
データを読み取る際に、最近見たデータの一部を覚えています。この最近の記憶は「スライディング ウィンドウ」と呼ばれます。
新しいデータが入ってくると、 pglz
このデータを最近 (スライディング ウィンドウ内で) 見たかどうかをチェックします。 「はい」の場合、データを繰り返す代わりに短い参照を書き込みます。
データが新しい場合、または参照が実際のデータよりも短くなるほど繰り返されていない場合、 pglz
それをそのまま書き込みます。
圧縮データを読み取るとき、 pglz
はその参照を使用して元のデータをフェッチします。このプロセスは、参照されたデータを検索し、それが属する場所に配置するため、非常に直接的です。
pglz
メモリ用に別個のストレージ (スライディング ウィンドウ) を必要としません。圧縮中に外出先でビルドし、解凍時にも同じことを行います。
この実装は、TOAST メカニズム内で圧縮効率と速度のバランスをとるように設計されています。圧縮率の観点から見ると、 pglz
の有効性はデータの性質に大きく依存します。
たとえば、反復性の高いデータは、エントロピーの高いデータ (ランダム データなど) よりもはるかに効率的に圧縮されます。圧縮率は 25 ~ 50% の範囲になる場合がありますが、これは非常に一般的な推定値であり、結果はデータの正確な性質に応じて大きく異なります。
デフォルトでは、PostgreSQL は前に説明した手順に従って TOAST メカニズムを実行します (圧縮が十分でない場合は、最初に圧縮し、次にアウトオブライン ストレージを実行します)。ただし、列ごとにこの動作を微調整する必要があるシナリオもあるでしょう。 PostgreSQL では、TOAST 戦略PLAIN
、 EXTERNAL
、 EXTENDED
、およびMAIN
を使用してこれを行うことができます。
EXTENDED
: これはデフォルトの戦略です。これは、データが通常のテーブル ページには大きすぎる場合、別の TOAST テーブルに行外で保存されることを意味します。データを TOAST テーブルに移動する前に、スペースを節約するために圧縮されます。
EXTERNAL
: この戦略は、データが大きすぎて通常のテーブル ページに収まらない場合に、この列のデータを行外に格納するように PostgreSQL に指示し、PostgreSQL にデータを圧縮しないように指示します。値は単にTOASTテーブルそのまま。
MAIN
: この戦略は中間点です。圧縮を通じてメインテーブル内のデータを整列させようとします。データが明らかに大きすぎる場合、エラーを避けるためにデータを TOAST テーブルに移動しますが、PostgreSQL は圧縮されたデータを移動しません。代わりに、値を元の形式で TOAST テーブルに保存します。
PLAIN
: カラムでPLAIN
使用すると、PostgreSQL はカラムのデータを常にメイン テーブルにインラインで格納し、ライン外の TOAST テーブルに移動しないようにします。データがページ サイズを超えると、データが収まらないためINSERT
失敗することに注意してください。
特定のテーブルの現在の戦略を検査したい場合は、次のコマンドを実行できます。
\d+ your_table_name
次のような出力が得られます。
=> \d+ example_table Table "public.example_table" Column | Data Type | Modifiers | Storage | Stats target | Description ---------+------------------+-----------+----------+--------------+------------- bar | varchar(100000) | | extended | |
ストレージ設定を変更したい場合は、次のコマンドを使用して変更できます。
-- Sets EXTENDED as the TOAST strategy for bar_column ALTER TABLE example_blob ALTER COLUMN bar_column SET STORAGE EXTENDED;
上記の戦略とは別に、TOAST の動作を制御するには次の 2 つのパラメーターも重要です。
TOAST_TUPLE_THRESHOLD
これは、TOASTing 操作 (圧縮および行外ストレージ) がサイズ超過のタプルに対して考慮される場合のサイズしきい値を設定するパラメーターです。
前述したように、デフォルトでは、 TOAST_TUPLE_THRESHOLD
は約 2 KB に設定されています。
TOAST_COMPRESSION_THRESHOLD
これは、Postgres が TOASTing プロセス中に値の圧縮を考慮する前に、値の最小サイズを指定するパラメーターです。
値がこのしきい値を超えると、PostgreSQL はその値を圧縮しようとします。ただし、値が圧縮しきい値を超えているからといって、自動的に圧縮されるわけではありません。TOAST 戦略は、データが圧縮されているかどうか、およびタプルとその結果の相対的なサイズに基づいてデータを処理する方法を PostgreSQL に指示します。ページ制限については、次のセクションで説明します。
TOAST_TUPLE_THRESHOLD
はトリガー ポイントです。タプルのデータ フィールドの合計サイズがこのしきい値を超えると、PostgreSQL は、圧縮とアウトオブライン ストレージを考慮して、列に設定された TOAST 戦略に基づいてその管理方法を評価します。実行される正確なアクションは、列データがTOAST_COMPRESSION_THRESHOLD
を超えているかどうかによっても異なります。
EXTENDED
(デフォルトの戦略): タプルのサイズがTOAST_TUPLE_THRESHOLD
を超える場合、PostgreSQL はまず、サイズ超過の列もTOAST_COMPRESSION_THRESHOLD
を超えている場合に圧縮を試みます。圧縮によりタプルのサイズがしきい値を下回った場合、タプルはメイン テーブルに残ります。そうでない場合、データはアウトオブライン TOAST テーブルに移動され、メイン テーブルにはこの外部データへのポインタが含まれます。
MAIN
: タプルのサイズがTOAST_TUPLE_THRESHOLD
を超える場合、PostgreSQL はサイズを超えた列を圧縮しようとします ( TOAST_COMPRESSION_THRESHOLD
を超えている場合)。圧縮によりタプルがメインテーブルのタプル内に収まるようになった場合、タプルはそこに残ります。そうでない場合、データは非圧縮形式で TOAST テーブルに移動されます。
EXTERNAL
: PostgreSQL は、 TOAST_COMPRESSION_THRESHOLD
に関係なく、圧縮をスキップします。タプルのサイズがTOAST_TUPLE_THRESHOLD
を超える場合、サイズを超えた列は TOAST テーブルの行外に格納されます。
PLAIN
: データは常にメインテーブルに保存されます。タプルのサイズがページ サイズを超える場合 (列が非常に大きいため)、エラーが発生します。
戦略 | タプル > TOAST_COMPRESSION_THRESHOLD の場合に圧縮 | タプル > TOAST_TUPLE_THRESHOLD の場合、アウトオブラインで保存 | 説明 |
---|---|---|---|
拡張された | はい | はい | デフォルトの戦略。最初に圧縮してから、アウトオブライン ストレージが必要かどうかを確認します。 |
主要 | はい | 非圧縮形式のみ | 最初に圧縮し、まだサイズが大きすぎる場合は、圧縮せずに TOAST テーブルに移動します。 |
外部の | いいえ | はい | サイズが大きすぎる場合は、圧縮せずに常に TOAST に移動します。 |
無地 | いいえ | いいえ | データは常にメインテーブルに残ります。タプルがページサイズを超えるとエラーが発生します。 |
ここまでで、なぜ TOAST が PostgreSQL にあればよかったと思われるデータ圧縮メカニズムではないのかが理解できたでしょう。最新のアプリケーションでは、毎日大量のデータが取り込まれるため、データベースが急速に(過剰に)成長します。
私たちが愛用する Postgres が数十年前に構築されたときには、このような問題はそれほど顕著ではありませんでしたが、今日の開発者はデータセットのストレージ フットプリントを削減するための圧縮ソリューションを必要としています。
TOAST には技術の 1 つとして圧縮が組み込まれていますが、その主な役割は従来の意味でのデータベース圧縮メカニズムとして機能することではないことを理解することが重要です。 TOAST は主に、Postgres ページの構造的制限内で大きな値を管理するという 1 つの問題に対する解決策です。
このアプローチでは、特定の大きな値が圧縮されるため、ストレージ スペースの節約につながる可能性がありますが、その主な目的はストレージ スペースを全体的に最適化することではありません。
たとえば、小さなタプルで構成された 5 TB のデータベースがある場合、TOAST はその 5 TB を 1 TB に変換するのには役立ちません。 TOAST 内には調整できるパラメータがありますが、これによって TOAST が一般的なストレージ節約ソリューションに変わるわけではありません。
また、PostgreSQL の従来の圧縮メカニズムとして TOAST を使用する場合には、他にも固有の問題があります。次に例を示します。
TOAST データにアクセスすると、特にデータがアウト オブ ラインに保存されている場合、オーバーヘッドが増加する可能性があります。これは、多くの大きなテキストやその他の TOAST 可能なデータ型が頻繁にアクセスされる場合に、より顕著になります。
TOAST には、圧縮ポリシーを指示するための高レベルで使いやすいメカニズムがありません。ストレージ コストを最適化したり、ストレージ管理を容易にしたりすることを目的として構築されたものではありません。
TOAST の圧縮は、特に高い圧縮率を提供するように設計されていません。使用するアルゴリズムは 1 つ ( pglz
) のみで、圧縮率は通常 25 ~ 50 パーセントの範囲で変化します。
大規模なテーブルに圧縮ポリシーを追加すると、
時間ベースの圧縮ポリシーを定義すると、データをいつ圧縮する必要があるかを指定できます。たとえば、7 日より古いデータを自動的に圧縮することを選択できます。
-- Compress data older than 7 days SELECT add_compression_policy('my_hypertable', INTERVAL '7 days');
この圧縮ポリシーにより、Timescale はテーブルを変換します。
フロートのゴリラ圧縮
デルタオブデルタ +
少数の繰り返し値を含む列の行全体の辞書圧縮 (+ 上部の LZ 圧縮)
他のすべてのタイプの LZ ベースの配列圧縮
この列圧縮設計は、PostgreSQL の大規模なデータセットの問題に対する効率的でスケーラブルなソリューションを提供します。これにより、クエリのパフォーマンスを損なうことなく、より少ないストレージでより多くのデータを保存できるようになります (クエリのパフォーマンスが向上します)。 TimescaleDB の最新バージョンでは、圧縮データに対して直接INSERT
、 DELETE
、およびUPDATE
を実行することもできます。
この記事が、TOAST が PostgreSQL ページ内の大きな値を管理するためのよく考えられたメカニズムである一方で、最新のアプリケーションの領域内でデータベース ストレージの使用を最適化するのには効果的ではないことを理解していただければ幸いです。
ストレージを大幅に節約できる効果的なデータ圧縮を探している場合は、Timescale を試してみてください。 PostgreSQL を新たなパフォーマンスの高みに押し上げ、より高速かつ強力にする当社のクラウド プラットフォームを試すことができます。
カルロタ・ソト著。