Il problema
Abbiamo una domanda come
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';
Dato che si verifica un timeout (dopo 10 minuti) il più delle volte, ho deciso di indagare sul problema.
L' EXPLAIN (ANALYZE, BUFFERS)
output è simile al seguente:
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
Il nodo del collo di bottiglia è ovviamente la scansione dell'indice. Quindi vediamo la definizione dell'indice:
CREATE INDEX reservation_r_article_id_idx1
ON reservation USING btree (r_article_id)
WHERE (r_status <> ALL (ARRAY['FULFILLED', 'CLOSED', 'CANCELED']));
Dimensioni e numeri di riga
La sua dimensione (riportata da \di+
o visitando il file fisico) è di 36 MB. Dato che le prenotazioni di solito trascorrono solo un tempo relativamente breve in tutti gli stati non elencati sopra, si verificano molti aggiornamenti, quindi l'indice è piuttosto gonfio (qui vengono sprecati circa 24 MB) - tuttavia, la dimensione è relativamente piccola.
La reservation
tabella ha una dimensione di circa 3,8 GB, contenente circa 40 milioni di righe. Il numero di prenotazioni non ancora chiuse è di circa 170.000 (il numero esatto è riportato nel nodo di scansione indice sopra).
Ora la sorpresa: la scansione dell'indice riporta il recupero di enormi quantità di buffer (ovvero pagine da 8 kb):
Buffers: shared hit=200193 read=48853 dirtied=450 written=8
I numeri letti dalla cache e dal disco (o dalla cache del sistema operativo) si sommano a 1,9 GB!
Nella peggiore delle ipotesi
D'altra parte, lo scenario peggiore, quando ogni tupla si trova su una pagina diversa della tabella, rappresenterebbe la visita (21644 + 151549) + 4608 pagine (righe totali recuperate dalla tabella più il numero di pagina dell'indice dal fisico taglia). Questo è ancora solo sotto i 180.000 - molto al di sotto dei quasi 250.000 osservati.
Interessante (e forse importante) è che la velocità di lettura del disco è di circa 2,2 MB / s, il che è abbastanza normale, immagino.
E allora?
Qualcuno ha idea di da dove possa venire questa discrepanza?
Nota: per essere chiari, abbiamo idee su cosa migliorare / cambiare qui, ma mi piacerebbe davvero capire i numeri che ho ottenuto: ecco di cosa si tratta.
Aggiornamento: controllo dell'effetto della memorizzazione nella cache o del microvacuuming
Sulla base della risposta di jjanes , ho verificato cosa succede quando eseguo di nuovo esattamente la stessa query. Il numero di buffer interessati non cambia davvero. (Per fare questo, ho semplificato la query al minimo indispensabile che mostra ancora il problema.) Questo è quello che vedo dalla prima esecuzione:
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 dopo il secondo:
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()
, quindi eseguire la query e quindi pg_statio_user_tables
controllare dove attribuisce i blocchi.
article
? Sembra che tutte le colonne coinvolte siano dallareservation
tabella e (supponendo) che ci sia un FK, il risultato dovrebbe essere lo stesso.