Imagine I have a table in Postgres 13 like this:
CREATE TABLE public.people (
id integer PRIMARY KEY,
full_name character varying(255),
bio text
);
I then insert a row with enough characters for the bio to be written to a TOAST table (4000 random bytes that should compress to > 2Kb):
# insert into people values (1, 'joe toast', (SELECT array_to_string(ARRAY(SELECT chr((65 + round(random() * 25)) :: integer) FROM generate_series(1,4000)), '')));
INSERT 0 1
Then insert a row with enough characters for the bio fit inline (3000 repeated bytes that should compress to < 2Kb):
# insert into people values (2, 'joe compressed', (SELECT array_to_string(ARRAY(SELECT chr(65) FROM generate_series(1,3000)), '')));
INSERT 0 1
Finally insert a row with only a few characters in the bio so it'll store inline (10 repeated bytes):
# insert into people values (3, 'joe inline', 'aaaaaaaaaa');
INSERT 0 1
Is there any way for me to detect the storage strategy for the bio in each tuple? Can I report on the percentage of rows that are inline or in TOAST ("22% of tuples store the bio inline, 78% in TOAST")?
A related question: is there anyway for me to know the number of bytes on disk for the tuple broken down by inline, inline compressed, and TOAST storage?
context: I'm working with a partitioned table that has more than a billion rows in aggregate, and I'm interested in knowing how often a particular column is stored inline vs in TOAST.
Research
I can get the size on disk for each bio, in one case it's clearly the inline-compressed size:
# select id, full_name, pg_column_size(bio) from people order by id;
id | full_name | pg_column_size
----+----------------+----------------
1 | joe toast | 4000
2 | joe compressed | 44
3 | joe inline | 11
(3 rows)
Comparing that size to the size of the uncompressed data tells us something about the compression, but can it tell us anything about the TOAST state?
# select id, full_name, pg_column_size(bio), length(bio) from people order by id;
id | full_name | pg_column_size | length
----+----------------+----------------+--------
1 | joe toast | 4000 | 4000
2 | joe compressed | 44 | 3000
3 | joe inline | 11 | 10
I can manually check there's some rows in the TOAST table:
# select relname from pg_class where oid = (select reltoastrelid from pg_class where relname='people');
relname
----------------
pg_toast_20138
# select chunk_id, sum(length(chunk_data)) from pg_toast.pg_toast_20138 group by chunk_id;
chunk_id | sum
----------+------
20149 | 4000
Is the following true in the general case?
# select id, full_name, pg_column_size(bio), length(bio),
case
when pg_column_size(bio) < length(bio) then 'inline-compressed'
when pg_column_size(bio) = length(bio) then 'toast'
else
'inline'
end as storage_strategy
from people order by id;
id | full_name | pg_column_size | length | storage_strategy
----+----------------+----------------+--------+-------------------
1 | joe toast | 4000 | 4000 | toast
2 | joe compressed | 44 | 3000 | inline-compressed
3 | joe inline | 11 | 10 | inline