selectlab.prove('the events file is nothing but 8 KB pages: 288,137,216 bytes is exactly 35,173 pages',pg_relation_size('events')=288137216andpg_relation_size('events')%8192=0andpg_relation_size('events')/8192=35173);
selectlab.prove('lower = 24-byte header + 56 line pointers x 4 bytes = 248; upper = 384; 136 bytes free',lower=24+4*56andlower=248andupper=384andupper-lower=136)frompage_header(get_raw_page('events',0));selectlab.prove('136 free bytes is too few for another events row (136 to 144 bytes, plus a line pointer)',(selectupper-lowerfrompage_header(get_raw_page('events',0)))<(selectmin(lp_len)+4fromheap_page_items(get_raw_page('events',0))));
selectlab.prove('the page size is fixed when Postgres is built: block_size is 8192, and no setting can change it',current_setting('block_size')='8192'and(selectcontextfrompg_settingswherename='block_size')='internal');
selectlab.prove('WAL blocks are a separate compile-time constant, also 8 KB here',current_setting('wal_block_size')='8192'and(selectcontextfrompg_settingswherename='wal_block_size')='internal');
selectlab.prove('projects rows 1 and 3 (not archived) carry a null bitmap 11110100; row 2 (archived) has none',array_agg(coalesce(t_bits,'')orderbylp)='{11110100,"",11110100}')fromheap_page_items(get_raw_page('projects',0))wherelp<=3;selectlab.prove('row 2 is archived and 8 bytes longer, because it stores the timestamp',(selectlp_lenfromheap_page_items(get_raw_page('projects',0))wherelp=2)=(selectlp_lenfromheap_page_items(get_raw_page('projects',0))wherelp=1)+8and(selectarchived_atisnotnullfromprojectswherectid='(0,2)')and(selectarchived_atisnullfromprojectswherectid='(0,1)'));
padded: 8168 usable bytes / (72 + 4) per row = 107 rows per page
packed: 8168 usable bytes / (56 + 4) per row = 136 rows per page
动手验证 · lab 03-storage
selectlab.prove('a padded row is 72 bytes; the same columns packed take 52',(selectpg_column_size(p.*)frompaddedplimit1)=72and(selectpg_column_size(p.*)frompackedplimit1)=52);selectlab.prove('page 0 of padded holds 107 tuples, page 0 of packed holds 136',(selectcount(*)fromheap_page_items(get_raw_page('padded',0)))=107and(selectcount(*)fromheap_page_items(get_raw_page('packed',0)))=136and8168/(72+4)=107and8168/(56+4)=136);selectlab.prove('9,346 pages versus 7,353: padded is about 27% bigger',pg_relation_size('padded')/8192=9346andpg_relation_size('packed')/8192=7353andpg_relation_size('padded')::numeric/pg_relation_size('packed')between1.26and1.28);
createtabletoast_edge(bodytext);insertintotoast_edgevalues(repeat('a',2000)),(repeat('a',2010));selectlab.prove('a 2,028-byte row is stored as is; a 2,038-byte one crosses 2,032 bytes and is compressed',(array_agg(lp_lenorderbylp))[1]=2028and(array_agg(lp_lenorderbylp))[2]<100and(selectarray_agg(pg_column_compression(body)isnotnullorderbylength(body))fromtoast_edge)='{f,t}')fromheap_page_items(get_raw_page('toast_edge',0));
selectlab.prove('random hex doesn''t compress, so rows 4 and 5 move out of line',bool_and(pg_column_compression(body)isnull)andbool_and(pg_column_toast_chunk_id(body)isnotnull)andarray_agg(pg_column_size(body)orderbyid)='{3200,32000}')fromdocswhereidin(4,5);selectreltoastrelid::regclassastoast_tablefrompg_classwhererelname='docs'\gsetselectlab.prove('out-of-line values are cut into chunks of at most 1,996 bytes: 2 chunks for row 4, 17 for row 5',(selectarray_agg(norderbyn)from(selectcount(*)asnfrom:toast_tablegroupbychunk_id)c)='{2,17}'and(selectmax(length(chunk_data))from:toast_table)=1996);
altertabledocsaddcolumntitletext;selectpg_column_toast_chunk_id(body)aschunk_beforefromdocswhereid=5\gsetupdatedocssettitle='renamed'whereid=5;selectlab.prove('an update that doesn''t change a toasted value keeps the same TOAST pointer',pg_column_toast_chunk_id(body)=:chunk_before)fromdocswhereid=5;updatedocssetbody=body||'x'whereid=5;selectlab.prove('an update that changes a toasted value stores the whole new value, every chunk',pg_column_toast_chunk_id(body)<>:chunk_beforeand(selectcount(*)from:toast_tabletwheret.chunk_id=pg_column_toast_chunk_id(d.body))=17)fromdocsdwhereid=5;
每一列还有一个存储策略(plain、main、external、extended;text 和 jsonb 默认使用 extended,先压缩再移出)。对于已经压缩的数据(例如图像),可以设置为 external,使用 alter table ... alter column ... set storage:再次压缩只会浪费 CPU,而且子串操作可以只读取所需的块。
createtablettt_256(atext,btext)with(toast_tuple_target=256);insertintottt_256values(repeat('a',1500),null);selectlab.prove('toast_tuple_target = 256 does not touch a 1.5 KB row: toasting still starts at about 2 KB',(selectpg_column_compression(a)fromttt_256)isnulland(selectpg_column_size(a)fromttt_256)=1504);
selectlab.prove('the FSM stores one byte per page in 32-byte steps: events page 0 (136 free) is recorded as 128',avail=128andavail%32=0)frompg_freespace('events')whereblkno=0;
updateaccountssetname=namewhereid=1;selectlab.prove('any change to a page clears its all-visible bit',(selectall_visiblefrompg_visibility_map('accounts',0))=falseand(selectflags&4frompage_header(get_raw_page('accounts',0)))=0);vacuumaccounts;selectlab.prove('vacuum sets it again once every transaction can see the change',(selectall_visiblefrompg_visibility_map('accounts',0))=trueand(selectflags&4frompage_header(get_raw_page('accounts',0)))=4);