createviewcachedasselectc.relname,b.bufferid,b.relblocknumber,b.usagecount,b.isdirty,b.pinning_backendsfrompg_buffercachebjoinpg_classconb.relfilenode=pg_relation_filenode(c.oid)andb.reldatabase=(selectoidfrompg_databasewheredatname=current_database())andb.relforknumber=0;-- the main fork: the table's own pages
selectlab.prove('the buffer pool is one 128 MB block of shared memory (plus 4 KB of alignment)',(selectsizefrompg_shmem_allocationswherename='Buffer Blocks')=16384*8192+4096);selectlab.prove('each buffer has a 64-byte descriptor: 16,384 of them take exactly 1 MB',(selectsizefrompg_shmem_allocationswherename='Buffer Descriptors')=16384*64);selectlab.prove('pg_buffercache shows one row per buffer',(selectcount(*)frompg_buffercache)=16384);
begin;declareccursorforselect*fromaccounts;fetch1fromc\g/dev/nullselectlab.prove('an open cursor holds a pin on the page it is reading (and PostgreSQL 18 pins a few pages ahead)',:pinnedbetween1and9);selectlab.prove('a pinned buffer cannot be evicted',not(selectbuffer_evictedfrompg_buffercache_evict(:pinned_buf)));commit;
after 1 scan after 2 scans after 5 scans
usagecount | buffers usagecount | buffers usagecount | buffers
------------+-------- ------------+-------- ------------+--------
1 | 9 2 | 9 5 | 9
扫描一次,每个页得到一个点。扫描五次,得到五个点,此后就保持不变。
动手验证 · lab 04-buffers
selectcount(*)fromaccounts\g/dev/nullselectlab.prove('one scan: all 9 pages cached, each with usage count 1',(selectcount(*)fromcachedwhererelname='accounts'andusagecount=1)=9);selectcount(*)fromaccounts\g/dev/nullselectcount(*)fromaccounts\g/dev/nullselectcount(*)fromaccounts\g/dev/nullselectcount(*)fromaccounts\g/dev/nullselectlab.prove('five scans: usage count 5, the maximum',(selectcount(*)fromcachedwhererelname='accounts'andusagecount=5)=9);
select*frompg_buffercache_evict_all();selectcount(*)fromaccounts;-- five times
selectpg_prewarm('events','buffer','main',0,19999);selectrelname,count(*)asbuffers,min(relblocknumber)asfirst_block,max(relblocknumber)aslast_block,min(usagecount)asmin_usage,max(usagecount)asmax_usagefromcachedwhererelnamein('accounts','events')groupbyrelname;
select*frompg_buffercache_evict_all()\g/dev/null-- ... five scans of accounts ...
selectpg_prewarm('events','buffer','main',0,19999)asprewarmed\gsetselectlab.prove('the flood evicted thousands of its own pages; its newest 2,000 are all still cached',(selectcount(*)fromcachedwhererelname='events')<20000-3000and(selectcount(*)fromcachedwhererelname='events'andrelblocknumber>=18000)=2000);selectlab.prove('the 9 accounts pages at usage count 5 survived the flood, losing a point per pass',(selectcount(*)fromcachedwhererelname='accounts')=9and(selectmax(usagecount)fromcachedwhererelname='accounts')<5);
updateaccountssetname=namewhereid<=50;selectlab.prove('an update dirties the pages it changes in memory; nothing is written to the table file yet',(selectcount(*)fromcachedwhererelname='accounts'andisdirty)>=1);selectbufferidasdirty_buffromcachedwhererelname='accounts'andisdirtylimit1\gsetselectlab.prove('to reuse a dirty buffer, the evicting process must write the page out first',(selectbuffer_evictedandbuffer_flushedfrompg_buffercache_evict(:dirty_buf)));
select*frompg_buffercache_evict_relation('events')\g/dev/nullselectcount(kind)fromevents\g/dev/nullselectcount(*)asevents_cachedfromcachedwhererelname='events'\gsetselectlab.prove('a full scan of events leaves at most one ring (288 buffers) of it in shared_buffers',:events_cached<=288);selectlab.prove('COPY leaves at most its 16 MB ring (2,048 buffers) of its pages in the cache',:copy_pagesbetween8850and8850+64and:copy_cached<=2048+64);selectlab.prove('INSERT ... SELECT gets no ring: it extends the table through the normal buffer pool',(:'bw3'::jsonb->>'extends')::bigint=(:'bw2'::jsonb->>'extends')::bigintand(:'nw3'::jsonb->>'extends')::bigint-(:'nw2'::jsonb->>'extends')::bigint>0);
ERROR: invalid value for parameter "temp_buffers": 8192
DETAIL: "temp_buffers" cannot be changed after any temporary tables have been
accessed in the session.
在一个先执行 set temp_buffers = '32MB' 的新会话中,同样的扫描会显示 Buffers: local hit=1792。
动手验证 · lab 04-buffers
selectlab.plan($$selectcount(kind)fromrecent$$)astp\gsetselectlab.prove('every scan of it reads all 1,792 pages again: local buffers are per session and too small',(:'tp'::jsonb->'Plan'->>'Local Read Blocks')::int=1792andcoalesce((:'tp'::jsonb->'Plan'->>'Shared Hit Blocks')::int,0)+coalesce((:'tp'::jsonb->'Plan'->>'Shared Read Blocks')::int,0)=0);selectlab.prove('temp_buffers cannot be changed once the session has used a temp table',pg_temp.try_set_temp_buffers()like'invalid value for parameter "temp_buffers"%');selectlab.prove('with temp_buffers = 32MB set first, the same table is read entirely from local buffers',(:'t_p'::jsonb->>'Local Hit Blocks')::int=1792andcoalesce((:'t_p'::jsonb->>'Local Read Blocks')::int,0)=0);
shared_preload_libraries='pg_stat_statements,pg_prewarm' # restart requiredpg_prewarm.autoprewarm=on # the default once the library is loadedpg_prewarm.autoprewarm_interval=300s # how often to save the list (default)
select*frompg_buffercache_evict_relation('users')\g/dev/nullselectpg_prewarm('users')asusers_prewarmed\gsetselectlab.prove('pg_prewarm(''users'') loads all 1,126 pages and says so',:users_prewarmed=1126and(selectcount(*)fromcachedwhererelname='users')>=1100);selectlab.prove('after prewarming, a full scan of users is all hits',(lab.plan($$selectcount(email)fromusers$$)->'Plan'->>'Shared Read Blocks')::int=0);
seteffective_cache_size='1MB';explainselect*fromusersorderbyaccount_id,emaillimit30000;reseteffective_cache_size;-- the default, 4GB
explainselect*fromusersorderbyaccount_id,emaillimit30000;
Limit (cost=10062.34..10137.34 rows=30000 width=55)
-> Sort (cost=10062.34..10312.34 rows=100000 width=55)
Sort Key: account_id, email
-> Seq Scan on users (cost=0.00..2126.00 rows=100000 width=55)
Limit (cost=0.42..2720.65 rows=30000 width=55)
-> Index Scan using users_account_id_email_key on users (cost=...)
selectlab.prove('one query, two Sort nodes, two work_mem allowances: each sort holds about 4 MB',:sorts=2and:smallestbetween3.5*1024*1024and4.5*1024*1024and:largestbetween3.5*1024*1024and4.5*1024*1024);
CacheMemoryContext and its children
-----------------------------------
fresh connection 731 kB
after touching 1,000 tables 10 MB
after DISCARD ALL 10 MB
new connection 731 kB
createschematenants;-- create tenants.t1 .. tenants.t1000, each:
-- (id bigint primary key, account_id bigint not null,
-- body text, created_at timestamptz)
do$$beginforiin1..1000loopexecuteformat('select count(*) from tenants.t%s where id = 1',i);endloop;end$$;
selectlab.prove('touching 1,000 small tables grows them by more than 5 MB',:touched-:fresh>5*1024*1024);selectlab.prove('DISCARD ALL gives none of it back',:discarded>=:touched*0.95);selectlab.prove('a new connection starts small again',:reconnected<2*1024*1024);