Mais uma pergunta da minha descoberta das ótimas novas EXPLAIN
opções do PostgreSQL. Este se concentra na BUFFERS
opção.
Aqui está EXPLICAR :
EXPLAIN (ANALYZE, BUFFERS) SELECT event_time FROM ui_events_v2 WHERE page ~ 'foo' LIMIT 1;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------
Limit (cost=0.00..1539.68 rows=1 width=8) (actual time=0.858..0.858 rows=1 loops=1)
Buffers: shared read=10
I/O Timings: read=0.735
-> Seq Scan on ui_events_v2 (cost=0.00..3313394.58 rows=2152 width=8) (actual time=0.857..0.857 rows=1 loops=1)
Filter: (page ~ 'foo'::text)
Rows Removed by Filter: 112
Buffers: shared read=10
I/O Timings: read=0.735
Planning Time: 6.455 ms
Execution Time: 0.877 ms
É bastante rápido - e essa consulta é muito lenta em uma inicialização a frio. Esta é uma tabela de 30 milhões de linhas e sete linhas page
contêm substring foo
. A primeira linha que corresponde a esta consulta parece mostrar 604 mil páginas, o que seria ~20% das ~3 milhões de páginas mencionadas em pg_class
:
SELECT min(ctid) FROM ui_events_v2 WHERE page ~ 'foo';
min
-------------
(604435,10)
Minha suposição era que, mesmo que cada página desta tabela estivesse no cache do PostgreSQL ou do sistema operacional, ela ainda precisaria percorrer uma lista linear de páginas para uma varredura sequencial. A seção Buffers: shared read=10
e o 0.877 ms
tempo de execução me sugerem que de alguma forma está "pegando de onde parou" e começando em páginas que estão longe do início. Eu pensei que ele se movia através de IDs de página e espiava em um cache tentando cada página enquanto se movia, mas talvez comece a partir do próprio cache? Se for esse o caso, como ele descobre as páginas não armazenadas em cache?
Eu sei que não é garantido que as linhas sejam encontradas em nenhuma ordem específica, mas imaginei que, dentro da mesma estratégia de consulta, o caminho seguido seria relativamente semelhante.
Se vários processos estiverem tentando seq varrer a mesma tabela, ele tentará alinhá-los para que todos leiam as mesmas páginas aproximadamente ao mesmo tempo, para maximizar os acertos do cache. Ele faz isso fazendo com que os que chegam mais tarde comecem no meio (onde quer que os existentes estejam) e enrole no final para terminar a mesa na página antes de onde começou. Um efeito colateral disso é que, se uma varredura seq parar cedo, a próxima será iniciada na mesma página em que a última parou. Portanto, se todo o LIMIT puder ser satisfeito por linhas de uma página, uma varredura como a sua apenas varrerá a mesma página várias vezes.
Você pode desativar essa otimização com
set synchronize_seqscans=off
, então todas elas começam no primeiro bloco da tabela.