2011-01-13 13 views
11

Tengo una tabla con alrededor de 20 millones de filas. Por razones de argumentos, digamos que hay dos columnas en la tabla: una identificación y una marca de tiempo. Estoy tratando de contar el número de artículos por día. Esto es lo que tengo en este momento.Agilizando una consulta de grupo por fecha en una gran mesa en postgres

SELECT DATE(timestamp) AS day, COUNT(*) 
    FROM actions 
    WHERE DATE(timestamp) >= '20100101' 
    AND DATE(timestamp) < '20110101' 
GROUP BY day; 

Sin ningún índice, esto toma alrededor de 30 segundos para funcionar en mi máquina. Aquí está la salida explicar analizar:

GroupAggregate (cost=675462.78..676813.42 rows=46532 width=8) (actual time=24467.404..32417.643 rows=346 loops=1) 
    -> Sort (cost=675462.78..675680.34 rows=87021 width=8) (actual time=24466.730..29071.438 rows=17321121 loops=1) 
     Sort Key: (date("timestamp")) 
     Sort Method: external merge Disk: 372496kB 
     -> Seq Scan on actions (cost=0.00..667133.11 rows=87021 width=8) (actual time=1.981..12368.186 rows=17321121 loops=1) 
       Filter: ((date("timestamp") >= '2010-01-01'::date) AND (date("timestamp") < '2011-01-01'::date)) 
Total runtime: 32447.762 ms 

Desde que estoy viendo un recorrido secuencial, he tratado de índice en la fecha agregada

CREATE INDEX ON actions (DATE(timestamp)); 

que corta la velocidad en un 50%.

HashAggregate (cost=796710.64..796716.19 rows=370 width=8) (actual time=17038.503..17038.590 rows=346 loops=1) 
    -> Seq Scan on actions (cost=0.00..710202.27 rows=17301674 width=8) (actual time=1.745..12080.877 rows=17321121 loops=1) 
     Filter: ((date("timestamp") >= '2010-01-01'::date) AND (date("timestamp") < '2011-01-01'::date)) 
Total runtime: 17038.663 ms 

Soy nuevo en este negocio de optimización de consultas, y no tengo ni idea de qué hacer a continuación. ¿Alguna pista de cómo puedo hacer que esta consulta se ejecute más rápido?

--edit--

Parece que estoy golpeando los límites de los índices. Esta es prácticamente la única consulta que se ejecuta en esta tabla (aunque los valores de las fechas cambian). ¿Hay alguna forma de particionar la tabla? ¿O crear una tabla de caché con todos los valores de conteo? O alguna otra opción?

+2

¿Nos está contando toda la historia aquí? Parece que ha cambiado la configuración de memoria entre el primero y el segundo plan. Cuál habría sido mi consejo. ;-) –

+0

Sin cambios en la configuración de la memoria, aunque he estado haciendo un análisis de vacío entre cada cambio de esquema. No sé si se supone que debo hacer eso, pero sí afecta seriamente los resultados. – zaius

Respuesta

5

¿Hay alguna manera de particionar la tabla?

Sí:
http://www.postgresql.org/docs/current/static/ddl-partitioning.html

o crear una tabla de caché con todos los valores de recuento? O alguna otra opción?

Crear una tabla de "caché" ciertamente es posible. Pero esto depende de la frecuencia con la que necesite ese resultado y de la precisión que necesita.

 
CREATE TABLE action_report 
AS 
SELECT DATE(timestamp) AS day, COUNT(*) 
    FROM actions 
    WHERE DATE(timestamp) >= '20100101' 
    AND DATE(timestamp) < '20110101' 
GROUP BY day; 

A continuación, un SELECT * FROM action_report le dará lo que quiere en el momento oportuno. Luego programaría un trabajo cron para recrear esa tabla de forma regular.

Este enfoque, por supuesto, no ayudará si el rango de tiempo cambia con cada consulta o si esa consulta solo se ejecuta una vez al día.

+0

Oh bien.Crear una tabla de caché definitivamente resolverá el problema. Las selecciones de esa tabla son (obviamente) súper rápidas. Tener datos del día anterior está bien, y tener una actualización de 20 segundos una vez al día tampoco es problema. ¿Hay alguna manera de actualizar la tabla con la semana más reciente, en lugar de dejar caer y recrear cada vez? – zaius

+1

No necesita crear cada vez. Una vez creado, simplemente haga un 'TRUNCATE action_report' seguido por un 'INSERT INTO action_report SELECT ....' –

+0

¡Genial! Gracias por la ayuda. – zaius

1

Parece que la gama cubre casi todos los datos disponibles.

Esto podría ser un problema de diseño. Si va a ejecutar esto a menudo, es mejor que cree una columna timestamp_date que contenga solo la fecha. Luego, cree un índice en esa columna y cambie la consulta en consecuencia. La columna debe mantenerse insertada + activadores de actualización.

SELECT timestamp_date AS day, COUNT(*) 
FROM actions 
WHERE timestamp_date >= '20100101' 
    AND timestamp_date < '20110101' 
GROUP BY day; 

Si estoy equivocado sobre el número de filas del intervalo de fechas se encuentra (y es sólo un pequeño subconjunto), entonces usted puede intentar un índice en solo la columna de marca de tiempo en sí, la aplicación de la cláusula WHERE a poco la columna (que le dio el rango funciona igual de bien)

SELECT DATE(timestamp) AS day, COUNT(*) 
FROM actions 
WHERE timestamp >= '20100101' 
    AND timestamp < '20110101' 
GROUP BY day; 
+0

Sí, el filtro es para un gran subconjunto de los datos. Las consultas de un subconjunto más pequeño generalmente son lo suficientemente rápidas; solo las grandes consultas son lentas. Intenté agregar la columna timestamp_date y un índice. Lamentablemente, ninguna de las opciones ofrece una velocidad significativa. Aquí está la salida: http://pastie.org/1454799 – zaius

+2

¿Por qué marcaría la diferencia? Un índice sobre 'date (timestamp)' con una consulta que incluya 'date (timestamp)' tendrá el mismo efecto que un índice en 'timestamp_date' con una consulta que involucre la columna' timestamp_date'. –

+1

@ Peter Eisentraut debes haber emitido el voto en negativo. Independientemente de lo que pienses, mira el resultado real de la prueba de zaius. Salió marginalmente más rápido. Es decir, si el 10% sigue siendo marginal y no significativo. – RichardTheKiwi

0

intente ejecutar explain analyze verbose ... para ver si el agregado está utilizando un archivo temporal. Tal vez aumentar work_mem para permitir que se haga más en la memoria?

+0

Aquí está el resultado de verbose: http://pastie.org/1455439 Lo que no parece muy diferente. (Tenga en cuenta que agregué más datos porque lo actualicé desde la producción db) – zaius

+0

¿qué versión de postgresql está usando? – araqnid

+0

Estoy ejecutando todas estas pruebas en mi entorno de desarrollo, que es postgres 9.0.1 en mac. La producción (donde el rendimiento realmente cuenta) se ejecuta en 8.3 en Ubuntu. Sin embargo, puedo actualizar la producción si es necesario. – zaius

2

En general, la mayoría de las bases de datos ignorarán los índices si el número esperado de filas devuelto va a ser alto. Esto se debe a que para cada hit de índice, deberá encontrar la fila también, por lo que es más rápido realizar un escaneo completo de la tabla. Este número está entre 10,000 y 100,000. Puedes experimentar con esto al reducir el rango de fechas y ver dónde postgres cambia a usar el índice. En este caso, postgres planea escanear 17.301.674 filas, por lo que su tabla es bastante grande. Si lo haces realmente pequeño y todavía sientes que postgres está haciendo una elección incorrecta, intenta ejecutar un análisis sobre la mesa para que postgres obtenga sus aproximaciones correctas.

-1

Establezca work_mem para decir 2GB y vea si eso cambia el plan. Si no es así, es posible que no tenga opciones.

0

Lo que realmente desea para tales consultas de tipo DSS es una tabla de fechas que describe los días. En el lenguaje de diseño de bases de datos se llama una dimensión de fecha. Para rellenar dicha tabla, puede usar el código que publiqué en este artículo: http://www.mockbites.com/articles/tech/data_mart_temporal

Luego, en cada fila de su tabla de acciones, coloque la fecha_clave adecuada.

Su consulta se convierte entonces en:

SELECT 
    d.full_date, COUNT(*) 
FROM actions a 
JOIN date_dimension d 
    ON a.date_key = d.date_key 
WHERE d.full_date = '2010/01/01' 
GROUP BY d.full_date 

Suponiendo índices en las teclas y FULL_DATE, esto será muy rápida debido a que opera en las teclas INT4!

Otra ventaja es que ahora puedes dividir y cortar por cualquier otra columna de date_dimension (s).

Cuestiones relacionadas