Estoy usando Postgres 9.3 a través de Heroku.
Tengo una tabla, "tráfico", con 1M + registros que tiene muchas inserciones y actualizaciones todos los días. Necesito realizar operaciones SUM en esta tabla en diferentes intervalos de tiempo y esas llamadas pueden tomar hasta 40 segundos y me encantaría escuchar sugerencias sobre cómo mejorar eso.
Tengo el siguiente índice en su lugar en esta tabla:
CREATE INDEX idx_traffic_partner_only ON traffic (dt_created) WHERE campaign_id IS NULL AND uuid_self <> uuid_partner;
Aquí hay un ejemplo de declaración SELECT:
SELECT SUM("clicks") AS clicks, SUM("impressions") AS impressions
FROM "traffic"
WHERE "uuid_self" != "uuid_partner"
AND "campaign_id" is NULL
AND "dt_created" >= 'Sun, 29 Mar 2015 00:00:00 +0000'
AND "dt_created" <= 'Mon, 27 Apr 2015 23:59:59 +0000'
Y este es el EXPLICAR ANÁLISIS:
Aggregate (cost=21625.91..21625.92 rows=1 width=16) (actual time=41804.754..41804.754 rows=1 loops=1)
-> Index Scan using idx_traffic_partner_only on traffic (cost=0.09..20085.11 rows=308159 width=16) (actual time=1.409..41617.976 rows=302392 loops=1)
Index Cond: ((dt_created >= '2015-03-29'::date) AND (dt_created <= '2015-04-27'::date))
Total runtime: 41804.893 ms
http://explain.depesz.com/s/gGA
Esta pregunta es muy similar a otra en SE, pero esa utilizaba un índice en dos intervalos de marcas de tiempo de columna y el planificador de índice para esa consulta tenía estimaciones que estaban muy lejos. La sugerencia principal fue crear un índice ordenado de varias columnas, pero para los índices de una sola columna eso no tiene mucho efecto. Las otras sugerencias fueron usar los índices CLUSTER / pg_repack y GIST, pero aún no los he probado, ya que me gustaría ver si hay una mejor solución usando índices regulares.
Optimización de consultas en un rango de marcas de tiempo (dos columnas)
Como referencia, probé los siguientes índices, que no fueron utilizados por el DB:
INDEX idx_traffic_2 ON traffic (campaign_id, uuid_self, uuid_partner, dt_created);
INDEX idx_traffic_3 ON traffic (dt_created);
INDEX idx_traffic_4 ON traffic (uuid_self);
INDEX idx_traffic_5 ON traffic (uuid_partner);
EDITAR : Corrió EXPLICAR (ANALIZAR, VERBOSO, COSTOS, BUFFERS) y estos fueron los resultados:
Aggregate (cost=20538.62..20538.62 rows=1 width=8) (actual time=526.778..526.778 rows=1 loops=1)
Output: sum(clicks), sum(impressions)
Buffers: shared hit=47783 read=29803 dirtied=4
I/O Timings: read=184.936
-> Index Scan using idx_traffic_partner_only on public.traffic (cost=0.09..20224.74 rows=313881 width=8) (actual time=0.049..431.501 rows=302405 loops=1)
Output: id, uuid_self, uuid_partner, impressions, clicks, dt_created... (other fields redacted)
Index Cond: ((traffic.dt_created >= '2015-03-29'::date) AND (traffic.dt_created <= '2015-04-27'::date))
Buffers: shared hit=47783 read=29803 dirtied=4
I/O Timings: read=184.936
Total runtime: 526.881 ms
http://explain.depesz.com/s/7Gu6
Definición de tabla:
CREATE TABLE traffic (
id serial,
uuid_self uuid not null,
uuid_partner uuid not null,
impressions integer NOT NULL DEFAULT 1,
clicks integer NOT NULL DEFAULT 0,
campaign_id integer,
dt_created DATE DEFAULT CURRENT_DATE NOT NULL,
dt_updated TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
)
id es la clave principal y uuid_self, uuid_partner y campaign_id son claves foráneas. El campo dt_updated se actualiza con una función postgres.
fuente
explain (buffers, analyze, verbose) ...
podría arrojar más luz.traffic
. Además: ¿por qué el segundoEXPLAIN
muestra una caída de 42 segundos a 0,5 segundos? ¿Fue la primera ejecución con caché frío?id
? ¿Alguna otra restricción? Veo dos columnas que pueden ser NULL. ¿Cuál es el porcentaje de valores NULL en cada uno? ¿Qué obtienes por esto?SELECT count(*) AS ct, count(campaign_id)/ count(*) AS camp_pct, count(dt_updated)/count(*) AS upd_pct FROM traffic;
Respuestas:
Dos cosas que son muy extrañas aquí:
La consulta selecciona 300k filas de una tabla con 1M + filas. Para el 30% (o algo más del 5%, depende del tamaño de la fila y otros factores), generalmente no vale la pena usar un índice. Deberíamos ver una exploración secuencial .
La excepción serían los escaneos de solo índice, que no veo aquí. El índice de varias columnas que @Craig sugirió sería la mejor opción si obtiene escaneos de solo índice. Con muchas actualizaciones como las que mencionó, es posible que esto no funcione, en cuyo caso estará mejor sin las columnas adicionales, y solo el índice que ya tiene. Es posible que pueda hacer que funcione para usted con configuraciones de vacío automático más agresivas para la mesa. Puede ajustar los parámetros para tablas individuales.
Si bien Postgres utilizará el índice, ciertamente esperaría ver un escaneo de índice de mapa de bits para tantas filas, no un escaneo de índice simple, que generalmente es la mejor opción para un bajo porcentaje de filas. Tan pronto como Postgres espere múltiples visitas por página de datos (a juzgar por sus estadísticas en la tabla), normalmente cambiará a un escaneo de índice de mapa de bits.
A juzgar por eso, sospecharía que su configuración de costos es inadecuada (y posiblemente también las estadísticas de la tabla). Es posible que haya establecido
random_page_cost
y / o demasiado bajo , en relación con . Sigue los enlaces y lee el manual.cpu_index_tuple_cost
seq_page_cost
También encajaría con la observación de que el caché frío es un factor importante, como lo resolvimos en los comentarios. ¿Está accediendo a (partes de) tablas que nadie ha tocado en mucho tiempo o está ejecutando en un sistema de prueba donde el caché no está poblado (todavía)?
De lo contrario, simplemente no tiene suficiente RAM disponible para almacenar en caché la mayoría de los datos relevantes en su base de datos. En consecuencia, el acceso aleatorio es mucho más costoso que el acceso secuencial cuando los datos residen en la memoria caché. Dependiendo de la situación real, puede que tenga que ajustar para obtener mejores planes de consulta.
Se debe mencionar otro factor para la respuesta lenta en la primera lectura solamente: bits de pista . Lea los detalles en Postgres Wiki y esta pregunta relacionada:
O la tabla está extremadamente hinchada , en cuyo caso una exploración de índice tendría sentido y me referiría nuevamente a
CLUSTER
/pg_repack
en mi respuesta anterior que usted citó. (O simplementeVACUUM FULL)
e investiga tuVACUUM
configuración. Es importante con esomany inserts and updates every day
.Dependiendo de los
UPDATE
patrones, también considere un valorFILLFACTOR
inferior a 100. Si actualiza principalmente solo las filas recién agregadas, establezca el valor más bajoFILLFACTER
después de compactar su tabla, de modo que solo las páginas nuevas tengan margen de maniobra para las actualizaciones.Esquema
Ajuste la secuencia de columnas ligeramente, para guardar 8 bytes por fila (en el 99% de los casos donde
campaign_id
es NULL):Explicación detallada y enlaces a más:
Para medir:
fuente
Me parece que está consultando muchos datos en un índice grande, por lo que es lento. Nada notablemente malo allí.
Si está en PostgreSQL 9.3 o 9.4, puede intentar ver si puede obtener un escaneo de solo índice convirtiéndolo en una especie de índice de cobertura.
PostgreSQL no tiene verdaderos índices de cobertura o soporte para términos de índice que son solo valores, no parte del árbol b, por lo que esto es más lento y más costoso de lo que podría ser con esas características. Todavía podría ser una victoria sobre un escaneo de índice simple si el vacío se ejecuta con la frecuencia suficiente para mantener actualizado el mapa de visibilidad.
Idealmente, PostgreSQL admitiría campos de datos auxiliares en un índice como en MS-SQL Server ( esta sintaxis NO FUNCIONARÁ en PostgreSQL ):
fuente
random_page_cost
etc.). Además, para fines de prueba, solo vea siset enable_indexscan = off
yset enable_seqscan = off
luego volver a ejecutar fuerza un escaneo de solo índice, y si es así, cuáles son sus estimaciones de costos del análisis explicado.