本節では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化されていない値となり、長さワードの残りのビットはdatumの(長さワードを含む)総サイズ(バイト単位)となります。 上位側または下位側のどちらか片方のビットが設定された場合、値は通常の4バイトのヘッダを持たず1バイトのヘッダを持ちます。また、長さワードの残りビットはdatumの(長さワードを含む)総サイズ(バイト単位)となります。 特殊な状況として、長さワードの残りビットがすべて0(自身の長さを含みますのでありえません)の場合、その値は別のTOASTテーブルに保存される行外データへのポインタです。 (TOASTポインタのサイズはdatumの第2バイト内で与えられます。) 単一バイトヘッダを持つ値は特定の境界に整列されません。 最後に上位側または下位側のビットが0で隣のビットが設定されている場合、datumの内容は圧縮され、使用前に伸長しなければなりません。 この場合、長さワードの残りビットは元データのサイズではなく圧縮したdatumの総サイズになります。 圧縮が行外データでも起こりえますが、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_idとchunk_seqに対する一意性インデックスは値の抽出を高速化します。 したがって、行外のTOAST化された値を示すポインタdatumには、検索先となるTOASTテーブルのOIDと指定した値のOIDを格納しなければなりません。 簡便性のために、ポインタdatumには論理datumサイズ(元々の非圧縮のデータ長)と実際の格納サイズ(圧縮時には異なります)も格納されます。 varlenaヘッダバイトに収納するためにTOASTポインタdatumの総サイズは、表現される値の実サイズに関係なく、18バイトになります。
TOASTのコードは、テーブル内に格納される値がTOAST_TUPLE_THRESHOLD(通常2キロバイト)を超える時にのみ実行されます。 TOASTコードは、行の値がより小さくなるかそれ以上の縮小ができなくなるまで、フィールド値の圧縮や行外への移動を行います。 更新操作中、通常変更されない値はそのまま残ります。 行外の値を持つ行の更新では、行外の値の変更がなければTOASTするコストはかかりません。
TOASTコードでは、以下のTOAST可能な列を格納するための4つの異なる戦略を認めます。
PLAINは圧縮や行外の格納を防止します。 さらにvarlena型での単一バイトヘッダの使用を無効にします。 これはTOAST化不可能のデータ型の列に対してのみ取り得る戦略です。
EXTENDEDでは、圧縮と行外の格納を許します。 これはほとんどのTOAST可能のデータ型のデフォルトです。 圧縮がまず行われ、それでも行が大き過ぎるのであれば行外に格納します。
EXTERNALは非圧縮の行外格納を許します。 EXTERNALを使用すると、textとbytea列全体に対する部分文字列操作が高速化されます。 こうした操作は非圧縮の行外の値から必要な部分を取り出す時に最適化されるためです (格納領域が増加するという欠点があります)。
MAINは圧縮を許しますが、行外の格納はできません (実際にはこうした列についても行外の格納は行われます。しかし、他に行を縮小する方法がない場合の最後の手段としてのみです)。
TOAST可能なデータ型はそれぞれ、そのデータ型の列用のデフォルトの戦略を指定します。 しかしALTER TABLE SET STORAGEを使用して、あるテーブル列の戦略を変更することができます。
この機構には、ページをまたがる行の値を許可するといった素直な手法に比べて多くの利点があります。 通常問い合わせは比較的小さなキー値に対する比較で条件付けされるものと仮定すると、エクゼキュータの仕事のほとんどは主だった行の項目を使用して行われることになります。 TOAST化属性の大規模な値は、(それが選択されている時)結果集合をクライアントに戻す時に引き出されるだけです。 このため、主テーブルは行外の格納を使用しない場合に比べて、かなり小さくなり、その行は共有バッファキャッシュにより合うようになります。 ソート集合もまた小さくなり、ソートが完全にメモリ内で行われる頻度が高くなります。 小規模な試験結果ですが、典型的なHTMLページとそのURLを持つテーブルでは、TOASTテーブルを含め、元々のデータサイズのおよそ半分で格納でき、さらに、主テーブルには全体のデータのおよそ10%のみ(URLと一部の小さなHTMLページ)が格納されました。 比較のために全てのHTMLページを7キロバイト程度に切り詰めたTOAST化しないテーブルと比べ、実行時に違いはありませんでした。