★PostgreSQLカンファレンス2024 12月6日開催/チケット販売中★
他のバージョンの文書 16 | 15 | 14 | 13 | 12 | 11 | 10 | 9.6 | 9.5 | 9.4 | 9.3 | 9.2 | 9.1 | 9.0 | 8.4 | 8.3 | 8.2 | 8.1 | 8.0 | 7.4 | 7.3 | 7.2

55.2. TOAST

本節ではTOAST(過大属性格納技法:The Oversized-Attribute Storage Technique)の概要について説明します。

PostgreSQLは固定長のページサイズ(通常8キロバイト)を使用し、複数ページにまたがるタプルを許しません。 そのため、大規模なフィールド値を直接格納できません。 大規模なフィールド値を圧縮したり、複数の物理的な行に分割したりすることで、この限界はなくなりました。 これはユーザからは透過的に発生し、また、バックエンドのコード全体には小さな影響しか与えませんでした。 この技法はTOAST(または"パンをスライスして以来最善のもの")という愛称で呼ばれます。 [訳注:“パンをスライスして以来最善のもの(the best thing since sliced bread)”は素晴らしいものを意味します。]

一部のデータ型のみがTOASTをサポートします。 大規模なフィールド値を生成することがないデータ型にオーバーヘッドを負わせる必要はありません。 TOASTをサポートするためには、データ型は可変長(varlena)表現を持たなければなりません。 格納する値の最初の32ビットワードにはバイト単位の値の(このワード自体を含む)長さが含まれます。 TOASTは残りの表現について制限しません。 TOAST可能なデータ型をサポートするC言語関数はすべて、TOAST化された入力値を注意して扱わなければなりません (通常これは、入力に対して何か作業をする前にPG_DETOAST_DATUMを呼び出すことで行われますが、もっと効率的な方法が可能な場合もあります)。

TOASTはvarlenaの長さワードの2ビット(ビッグエンディアンのマシンでは上位ビット、リトルエンディアンのマシンでは下位ビット)を勝手に使用します。 そのため、すべてのTOAST可能なデータ型の値の論理サイズは1ギガバイト(230 - 1バイト)までになります。 両ビットが0の場合、値はそのデータ型の普通のTOAST化されていない値となり、長さワードの残りのビットはデータの(長さワードを含む)総サイズ(バイト単位)となります。 上位側または下位側のどちらか片方のビットが設定された場合、値は通常の4バイトのヘッダを持たず1バイトのヘッダを持ちます。 また、長さワードの残りビットはデータの(長さワードを含む)総サイズ(バイト単位)となります。 特殊な状況として、長さワードの残りビットがすべて0(自身の長さを含みますのでありえません)の場合、その値は別のTOASTテーブルに保存される行外データへのポインタです。 (TOASTポインタのサイズはデータの第2バイト内で与えられます。) 単一バイトヘッダを持つ値は特定の境界に整列されません。 最後に上位側または下位側のビットが0で隣のビットが設定されている場合、データの内容は圧縮され、使用前に伸長しなければなりません。 この場合、長さワードの残りビットは元データのサイズではなく圧縮したデータの総サイズになります。 圧縮が行外データでも起こりえますが、varlenaヘッダには圧縮されているかどうかについての情報がないことに注意してください。 その代わりTOASTポインタの内容にこの情報が含まれています。

テーブルの任意の列がTOAST可能な場合、そのテーブルは連携するTOASTテーブルを持ちます。 TOASTテーブルのOIDはテーブルのpg_class.reltoastrelid項目に格納されます。 詳細は後で説明しますが、行外のTOAST化された値はTOASTテーブル内に保持されます。

使用される圧縮技術は、LZ系の圧縮技術の1つで単純かつ非常に高速なものです。 詳細はsrc/backend/utils/adt/pg_lzcompress.cを参照してください。

行外の値は(圧縮される場合は圧縮後に)最大TOAST_MAX_CHUNK_SIZEバイトの塊に分割されます (デフォルトではこの値は4チャンク行が1ページに収まり、およそ2000バイトになるように選ばれます)。 各塊は、データを持つテーブルと連携するTOASTテーブル内に個別の行として格納されます。 すべてのTOASTテーブルはchunk_id列(特定のTOAST化された値を識別するOID)、chunk_seq列(値の塊に対する連番)、chunk_data(塊の実際のデータ)列を持ちます。 chunk_idchunk_seqに対する一意性インデックスは値の抽出を高速化します。 したがって、行外のTOAST化された値を示すポインタデータには、検索先となるTOASTテーブルのOIDと指定した値のOID(chunk_id)を格納しなければなりません。 簡便性のために、ポインタデータには論理データサイズ(元々の非圧縮のデータ長)と実際の格納サイズ(圧縮時には異なります)も格納されます。 varlenaヘッダバイトに収納するためにTOASTポインタデータの総サイズは、表現される値の実サイズに関係なく、18バイトになります。

TOASTのコードは、テーブル内に格納される値がTOAST_TUPLE_THRESHOLD(通常2キロバイト)を超える時にのみ実行されます。 TOASTコードは、行の値がより小さくなるかそれ以上の縮小ができなくなるまで、フィールド値の圧縮や行外への移動を行います。 更新操作中、変更されない値は通常そのまま残ります。 行外の値を持つ行の更新では、行外の値の変更がなければTOASTするコストはかかりません。

TOASTコードでは、以下のTOAST可能な列を格納するための4つの異なる戦略を認めます。

TOAST可能なデータ型はそれぞれ、そのデータ型の列用のデフォルトの戦略を指定します。 しかしALTER TABLE SET STORAGEを使用して、あるテーブル列の戦略を変更することができます。

この機構には、ページをまたがる行の値を許可するといった素直な手法に比べて多くの利点があります。 通常問い合わせは比較的小さなキー値に対する比較で条件付けされるものと仮定すると、エクゼキュータの仕事のほとんどは主だった行の項目を使用して行われることになります。 TOAST化属性の大規模な値は、(それが選択されている時)結果集合をクライアントに戻す時に引き出されるだけです。 このため、主テーブルは行外の格納を使用しない場合に比べて、かなり小さくなり、その行は共有バッファキャッシュにより合うようになります。 ソート集合もまた小さくなり、ソートが完全にメモリ内で行われる頻度が高くなります。 小規模な試験結果ですが、典型的なHTMLページとそのURLを持つテーブルでは、TOASTテーブルを含め、元々のデータサイズのおよそ半分で格納でき、さらに、主テーブルには全体のデータのおよそ10%のみ(URLと一部の小さなHTMLページ)が格納されました。 TOAST化されないようにすべてのHTMLページを7キロバイト程度に切り詰めてたTOASTかつ圧縮化しないテーブルと比べ、実行時に違いはありませんでした。