From 2b32a7e2b4d50892256c9ddf37cde6d85fe83c57 Mon Sep 17 00:00:00 2001
From: Hannu Krosing <hannuk@google.com>
Date: Sun, 23 Aug 2026 20:17:07 +0000
Subject: [PATCH v2 6/8] Add documentation for Direct TOAST

Update the PostgreSQL SGML documentation to describe Direct TOAST:
- doc/src/sgml/storage.sgml: Document Plain and Direct on-disk TOAST formats,
  TID-based direct addressing, single chunk, flat arrays, and hierarchical DAGs.
- doc/src/sgml/config.sgml: Document the toast_flavour configuration parameter.
- doc/src/sgml/ref/create_table.sgml: Document the toast_flavour storage parameter.
---
 doc/src/sgml/config.sgml           | 19 +++++++++
 doc/src/sgml/ref/create_table.sgml | 18 ++++++++
 doc/src/sgml/storage.sgml          | 66 +++++++++++++++++++++++++-----
 3 files changed, 92 insertions(+), 11 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 0165eb9ec02..705c7d243b1 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -10508,6 +10508,25 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-toast-flavour" xreflabel="toast_flavour">
+      <term><varname>toast_flavour</varname> (<type>enum</type>)
+      <indexterm>
+       <primary><varname>toast_flavour</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        This variable sets the default <acronym>TOAST</acronym> storage flavour
+        to use for new writes. Supported values are <literal>plain</literal>
+        (the default, which uses index-based chunk lookups) and <literal>direct</literal>
+        (which stores physical <type>TID</type> pointers to bypass index lookups).
+        This can be overridden for individual tables by setting the
+        <literal>toast_flavour</literal> storage parameter in
+        <command>CREATE TABLE</command> or <command>ALTER TABLE</command>.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="guc-temp-tablespaces" xreflabel="temp_tablespaces">
       <term><varname>temp_tablespaces</varname> (<type>string</type>)
       <indexterm>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index e342585c7f0..c70d366fb01 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -1653,6 +1653,24 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry id="reloption-toast-flavour" xreflabel="toast_flavour">
+    <term><literal>toast_flavour</literal> (<type>enum</type>)
+    <indexterm>
+     <primary><varname>toast_flavour</varname> storage parameter</primary>
+    </indexterm>
+    </term>
+    <listitem>
+     <para>
+      Sets the <acronym>TOAST</acronym> storage flavour for out-of-line data
+      written to this table. Supported values are <literal>plain</literal> (the
+      default, which uses index-based chunk lookup) and <literal>direct</literal>
+      (which stores physical <type>TID</type> pointers to bypass index lookup).
+      If not set, the default is determined by the configuration parameter
+      <xref linkend="guc-toast-flavour"/>.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry id="reloption-parallel-workers" xreflabel="parallel_workers">
     <term><literal>parallel_workers</literal> (<type>integer</type>)
      <indexterm>
diff --git a/doc/src/sgml/storage.sgml b/doc/src/sgml/storage.sgml
index 19924b98d71..f709249ec7f 100644
--- a/doc/src/sgml/storage.sgml
+++ b/doc/src/sgml/storage.sgml
@@ -424,17 +424,61 @@ belonging to the owning table.  Every
 <acronym>TOAST</acronym> table has the columns <structfield>chunk_id</structfield> (an OID
 identifying the particular <acronym>TOAST</acronym>ed value),
 <structfield>chunk_seq</structfield> (a sequence number for the chunk within its value),
-and <structfield>chunk_data</structfield> (the actual data of the chunk).  A unique index
-on <structfield>chunk_id</structfield> and <structfield>chunk_seq</structfield> provides fast
-retrieval of the values.  A pointer datum representing an out-of-line on-disk
-<acronym>TOAST</acronym>ed value therefore needs to store the OID of the
-<acronym>TOAST</acronym> table in which to look and the OID of the specific value
-(its <structfield>chunk_id</structfield>).  For convenience, pointer datums also store the
-logical datum size (original uncompressed data length), physical stored size
-(different if compression was applied), and the compression method used, if
-any.  Allowing for the varlena header bytes,
-the total size of an on-disk <acronym>TOAST</acronym> pointer datum is therefore 18
-bytes regardless of the actual size of the represented value.
+<structfield>chunk_data</structfield> (the actual data of the chunk),
+<structfield>chunk_tids</structfield> (an array of chunk tuple identifiers, used by Direct TOAST),
+and <structfield>chunk_tid_offsets</structfield> (an array of byte offsets, used for hierarchical Direct TOAST trees).
+</para>
+
+<para>
+<productname>PostgreSQL</productname> supports two flavours of on-disk <acronym>TOAST</acronym>
+storage:
+<itemizedlist>
+ <listitem>
+  <para>
+   <firstterm>Plain TOAST</firstterm> (the default): A unique index
+   on <structfield>chunk_id</structfield> and <structfield>chunk_seq</structfield> provides
+   retrieval of the values.  A pointer datum representing a plain on-disk
+   <acronym>TOAST</acronym>ed value stores the OID of the <acronym>TOAST</acronym> table
+   and the OID of the specific value (<structfield>chunk_id</structfield>).
+   Allowing for the varlena header bytes, the total size of a plain on-disk
+   <acronym>TOAST</acronym> pointer datum is 18 bytes.
+  </para>
+ </listitem>
+ <listitem>
+  <para>
+   <firstterm>Direct TOAST</firstterm> (<literal>toast_flavour = 'direct'</literal>):
+   Instead of looking up chunks through a B-Tree index, the <acronym>TOAST</acronym>
+   pointer directly stores the physical Tuple Identifier (<type>TID</type>) of the root/final chunk
+   in a 19-byte pointer datum.  For single-chunk values (up to ~2 kB), reading the data
+   requires only a single buffer pin without any index access.  For multi-chunk values up to 100 chunks,
+   the final chunk contains a <structfield>chunk_tids</structfield> array pointing directly
+   to all data chunks.  For larger values (> 100 chunks), an internal hierarchical tree
+   is constructed with intermediate index nodes storing child TIDs and byte offsets in
+   <structfield>chunk_tid_offsets</structfield>, enabling efficient tree traversal and slice pruning.
+   In Direct TOAST, <structfield>chunk_id</structfield> is set to <literal>NULL</literal>,
+   and the unique index on the TOAST table is a partial index (<literal>WHERE chunk_id IS NOT NULL</literal>),
+   completely avoiding index maintenance and WAL logging overhead for direct chunks.
+  </para>
+ </listitem>
+</itemizedlist>
+</para>
+
+<para>
+For convenience, pointer datums also store the logical datum size (original
+uncompressed data length), physical stored size (different if compression was applied),
+and the compression method used, if any.
+</para>
+
+<para>
+Direct <acronym>TOAST</acronym> is fully compatible with
+<application>pg_upgrade</application> (including <option>--link</option> mode).
+Because <application>pg_upgrade --link</application> preserves the physical block
+and offset layout of heap and <acronym>TOAST</acronym> relation files,
+physical <type>TID</type> pointers remain valid across major version upgrades.
+Furthermore, <literal>plain</literal> and <literal>direct</literal> <acronym>TOAST</acronym>
+pointers can seamlessly coexist within the same table, allowing tables upgraded from
+older <productname>PostgreSQL</productname> versions to be read transparently and
+gradually adopt Direct <acronym>TOAST</acronym> for new writes.
 </para>
 
 <para>
-- 
2.55.0.970.g62bdec98f9-goog

