O problema
Temos uma consulta como
SELECT COUNT(1)
FROM article
JOIN reservation ON a_id = r_article_id
WHERE r_last_modified < now() - '8 weeks'::interval
AND r_group_id = 1
AND r_status = 'OPEN';
Como ocorre um tempo limite (após 10 minutos) com mais frequência, decidi investigar o problema.
A EXPLAIN (ANALYZE, BUFFERS)
saída é assim:
Aggregate (cost=264775.48..264775.49 rows=1 width=0) (actual time=238960.290..238960.291 rows=1 loops=1)
Buffers: shared hit=200483 read=64361 dirtied=666 written=8, temp read=3631 written=3617
I/O Timings: read=169806.955 write=0.154
-> Hash Join (cost=52413.67..264647.65 rows=51130 width=0) (actual time=1845.483..238957.588 rows=21644 loops=1)
Hash Cond: (reservation.r_article_id = article.a_id)
Buffers: shared hit=200483 read=64361 dirtied=666 written=8, temp read=3631 written=3617
I/O Timings: read=169806.955 write=0.154
-> Index Scan using reservation_r_article_id_idx1 on reservation (cost=0.42..205458.72 rows=51130 width=4) (actual time=34.035..237000.197 rows=21644 loops=1)
Filter: ((r_group_id = 1) AND (r_status = 'OPEN') AND (r_last_modified < (now() - '56 days'::interval)))
Rows Removed by Filter: 151549
Buffers: shared hit=200193 read=48853 dirtied=450 written=8
I/O Timings: read=168614.105 write=0.154
-> Hash (cost=29662.22..29662.22 rows=1386722 width=4) (actual time=1749.392..1749.392 rows=1386814 loops=1)
Buckets: 32768 Batches: 8 Memory Usage: 6109kB
Buffers: shared hit=287 read=15508 dirtied=216, temp written=3551
I/O Timings: read=1192.850
-> Seq Scan on article (cost=0.00..29662.22 rows=1386722 width=4) (actual time=23.822..1439.310 rows=1386814 loops=1)
Buffers: shared hit=287 read=15508 dirtied=216
I/O Timings: read=1192.850
Total runtime: 238961.812 ms
O nó do gargalo é obviamente a varredura do índice. Então, vamos ver a definição do índice:
CREATE INDEX reservation_r_article_id_idx1
ON reservation USING btree (r_article_id)
WHERE (r_status <> ALL (ARRAY['FULFILLED', 'CLOSED', 'CANCELED']));
Tamanhos e números de linhas
O tamanho (relatado por \di+
ou visitando o arquivo físico) é de 36 MB. Como as reservas geralmente passam apenas um tempo relativamente curto em todos os status não listados acima, há muitas atualizações acontecendo, então o índice é bastante inchado (cerca de 24 MB são desperdiçados aqui) - ainda assim, o tamanho é relativamente pequeno.
A reservation
tabela tem cerca de 3,8 GB de tamanho, contendo cerca de 40 milhões de linhas. O número de reservas que ainda não foram fechadas é de cerca de 170.000 (o número exato é relatado no nó da varredura de índice acima).
Agora, a surpresa: os relatórios da varredura de índice buscam uma enorme quantidade de buffers (ou seja, 8 KB de páginas):
Buffers: shared hit=200193 read=48853 dirtied=450 written=8
Os números lidos no cache e no disco (ou no cache do SO) somam 1,9 GB!
Pior cenário
Por outro lado, o pior cenário, quando cada tupla fica em uma página diferente da tabela, seria responsável por visitar (21644 + 151549) + 4608 páginas (total de linhas buscadas na tabela mais o número da página de índice do físico Tamanho). Isso ainda está abaixo de 180.000 - muito abaixo dos quase 250.000 observados.
Interessante (e talvez importante) é que a velocidade de leitura do disco é de cerca de 2,2 MB / s, o que é bastante normal, eu acho.
E daí?
Alguém tem uma idéia de onde essa discrepância pode surgir?
Nota: Para ser claro, temos ideias sobre o que melhorar / alterar aqui, mas eu realmente gostaria de entender os números que obtive - é disso que se trata a questão.
Atualização: verificando o efeito do armazenamento em cache ou do microvacuum
Com base na resposta de jjanes , verifiquei o que acontece quando reexecuto exatamente a mesma consulta imediatamente. O número de buffers afetados não muda realmente. (Para fazer isso, simplifiquei a consulta ao mínimo necessário que ainda mostra o problema.) É o que vejo desde a primeira execução:
Aggregate (cost=240541.52..240541.53 rows=1 width=0) (actual time=97703.589..97703.590 rows=1 loops=1)
Buffers: shared hit=413981 read=46977 dirtied=56
I/O Timings: read=96807.444
-> Index Scan using reservation_r_article_id_idx1 on reservation (cost=0.42..240380.54 rows=64392 width=0) (actual time=13.757..97698.461 rows=19236 loops=1)
Filter: ((r_group_id = 1) AND (r_status = 'OPEN') AND (r_last_modified < (now() - '56 days'::interval)))
Rows Removed by Filter: 232481
Buffers: shared hit=413981 read=46977 dirtied=56
I/O Timings: read=96807.444
Total runtime: 97703.694 ms
e depois do segundo:
Aggregate (cost=240543.26..240543.27 rows=1 width=0) (actual time=388.123..388.124 rows=1 loops=1)
Buffers: shared hit=460990
-> Index Scan using reservation_r_article_id_idx1 on reservation (cost=0.42..240382.28 rows=64392 width=0) (actual time=0.032..385.900 rows=19236 loops=1)
Filter: ((r_group_id = 1) AND (r_status = 'OPEN') AND (r_last_modified < (now() - '56 days'::interval)))
Rows Removed by Filter: 232584
Buffers: shared hit=460990
Total runtime: 388.187 ms
pg_stat_reset()
-lo e, em seguida, executar a consulta e, em seguida, procurar pg_statio_user_tables
onde ele atribui os blocos.
article
? Parece que todas as colunas envolvidas são dareservation
tabela e (assumindo) que existe um FK, o resultado deve ser o mesmo.