La cuestión
Tenemos una 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 se agota el tiempo de espera (después de 10 minutos) la mayoría de las veces, decidí investigar el problema.
El EXPLAIN (ANALYZE, BUFFERS)
resultado se ve así:
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
El nodo del cuello de botella es obviamente el escaneo del índice. Entonces, veamos la definición del índice:
CREATE INDEX reservation_r_article_id_idx1
ON reservation USING btree (r_article_id)
WHERE (r_status <> ALL (ARRAY['FULFILLED', 'CLOSED', 'CANCELED']));
Tamaños y números de fila
Su tamaño (informado por \di+
o al visitar el archivo físico) es de 36 MB. Como las reservas generalmente pasan solo un tiempo relativamente corto en todos los estados no mencionados anteriormente, se están realizando muchas actualizaciones, por lo que el índice está bastante hinchado (alrededor de 24 MB se desperdician aquí), aún así, el tamaño es relativamente pequeño.
La reservation
tabla tiene un tamaño de aproximadamente 3,8 GB y contiene alrededor de 40 millones de filas. El número de reservas que aún no están cerradas es de aproximadamente 170,000 (el número exacto se informa en el nodo de exploración de índice anterior).
Ahora la sorpresa: el escaneo del índice informa que obtiene una gran cantidad de buffers (es decir, páginas de 8 kb):
Buffers: shared hit=200193 read=48853 dirtied=450 written=8
¡Los números leídos del caché y el disco (o el caché del sistema operativo) suman 1.9 GB!
Peor de los casos
Por otro lado, el peor de los casos, cuando cada tupla se sienta en una página diferente de la tabla, explicaría las visitas (21644 + 151549) + 4608 páginas (filas totales obtenidas de la tabla más el número de página de índice del físico) Talla). Esto sigue siendo solo inferior a 180,000, muy por debajo de los casi 250,000 observados.
Interesante (y tal vez importante) es que la velocidad de lectura del disco es de alrededor de 2.2 MB / s, lo cual es bastante normal, supongo.
¿Y qué?
¿Alguien tiene una idea de dónde puede venir esta discrepancia?
Nota: Para ser claros, tenemos ideas sobre qué mejorar / cambiar aquí, pero realmente me gustaría entender los números que obtuve, de eso se trata la pregunta.
Actualización: comprobar el efecto del almacenamiento en caché o microvacuuming
Según la respuesta de jjanes , he comprobado lo que sucede cuando vuelvo a ejecutar la misma consulta de inmediato. El número de memorias intermedias afectadas no cambia realmente. (Para hacer esto, simplifiqué la consulta al mínimo que todavía muestra el problema). Esto es lo que veo desde la primera ejecución:
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
y después del 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()
, y luego ejecutar la consulta, y luego mirar pg_statio_user_tables
para ver dónde atribuye los bloques.
article
? Parece que todas las columnas involucradas son de lareservation
tabla y (suponiendo) que hay un FK, el resultado debería ser el mismo.