Cómo usar el índice para acelerar la clasificación en postgres

10

Estoy usando postgres 9.4.

El messagestiene el siguiente esquema: los mensajes mostrados pertenecen a FEED_ID, y tiene posted_at, también los mensajes pueden tener un mensaje de los padres (en caso de respuestas).

                    Table "public.messages"
            Column            |            Type             | Modifiers
------------------------------+-----------------------------+-----------
 message_id                   | character varying(255)      | not null
 feed_id                      | integer                     |
 parent_id                    | character varying(255)      |
 posted_at                    | timestamp without time zone |
 share_count                  | integer                     |
Indexes:
    "messages_pkey" PRIMARY KEY, btree (message_id)
    "index_messages_on_feed_id_posted_at" btree (feed_id, posted_at DESC NULLS LAST)

Quiero devolver todos los mensajes ordenados por share_count, pero para cada uno parent_id, solo quiero devolver un mensaje. es decir, si varios mensajes tienen lo mismo parent_id, solo posted_atse devuelve el último ( ). El parent_idpuede ser nulo, los mensajes con nula parent_iddebe toda la vuelta.

La consulta que utilicé es:

WITH filtered_messages AS (SELECT * 
                           FROM messages
                           WHERE feed_id IN (7) 
                           AND (posted_at >= '2015-01-01 04:00:00.000000') 
                           AND (posted_at < '2015-04-28 04:00:00.000000'))
    SELECT *
    FROM (SELECT DISTINCT ON(COALESCE(parent_id, message_id)) parent_id,
                          message_id, 
                          posted_at, 
                          share_count
          FROM filtered_messages
          ORDER BY COALESCE(parent_id, message_id), posted_at DESC NULLS LAST
         ) messages
    ORDER BY share_count DESC NULLS LAST, posted_at DESC NULLS LAST;

Aquí está el http://sqlfiddle.com/#!15/588e5/1/0 , en el violín de SQL, he definido el esquema, la consulta exacta y el resultado esperado.

Pero el rendimiento de la consulta es lento una vez que la tabla de mensajes se hace grande. Intenté agregar múltiples índices de clasificación, pero no parece usar el índice. Aquí está la explicación: http://explain.depesz.com/s/Sv2

¿Cómo puedo crear un índice correcto?

Zhaohan Weng
fuente
A primera vista, el ORDER BYen la subconsulta es totalmente inútil. Además, el plan vinculado no puede ser el resultado de la consulta publicada metadata; por ejemplo, no se menciona nada .
dezso
Su descripción no cubre el rol de feed_idy posted_aty no mencionó metadataen absoluto, ¿cuál parece ser un tipo JSON? Repare su pregunta para que sea coherente. Selecciona> 500k filas en el CTE ... ¿Cuántas filas hay en la tabla? ¿Qué porcentaje de filas normalmente selecciona en el CTE? ¿Qué porcentaje de filas tiene parent_id IS NULL? Considere la información en la etiqueta [postgresql-performance] para preguntas de rendimiento.
Erwin Brandstetter
También importante: ¿Cuántas filas para cada una parent_id? (min / avg / max)
Erwin Brandstetter
lo siento, estaba tratando de aclarar la pregunta reduciendo algunas de las columnas, share_count estaba realmente en hstore metadata. Actualmente, la tabla de mensajes tiene 10 millones de datos, pero aumenta rápidamente. Creo que se separa en tablas de partición para cada feed_id. Como solo estoy buscando por ID de feed. El porcentaje de parent_id nulo vs no nulo es aproximadamente 60% / 40%. una búsqueda típica es alrededor del 1-2% de la tabla. (alrededor de 100K mensajes) El rendimiento para 100K es de alrededor de 1s, pero una vez que llega a 500K + usa el índice de mapa de bits y normalmente toma 10s.
Zhaohan Weng

Respuestas:

9

Consulta

Esta consulta debería ser sustancialmente más rápida en cualquier caso:

SELECT parent_id, message_id, posted_at, share_count
FROM   messages
WHERE  feed_id = 7
AND    posted_at >= '2015-01-01 4:0:0'
AND    posted_at <  '2015-04-28 4:0:0'
AND    parent_id IS NULL  -- match index condition
UNION ALL
(
SELECT DISTINCT ON(parent_id)
       parent_id, message_id, posted_at, share_count
FROM   messages
WHERE  feed_id = 7
AND    posted_at >= '2015-01-01 4:0:0'
AND    posted_at <  '2015-04-28 4:0:0'
AND    parent_id IS NOT NULL  -- match index condition
ORDER  BY parent_id, posted_at DESC NULLS LAST
)
ORDER  BY share_count DESC NULLS LAST, posted_at DESC NULLS LAST;
  • El CTE no hace nada aquí que una subconsulta simple no pueda entregar también. Y un CTE introduce una barrera de optimización, ya que se ejecuta por separado y su resultado se materializa.

  • Tiene un nivel de subconsulta más de lo que realmente necesita.

  • La expresión (COALESCE(parent_id, message_id)no es compatible con un índice simple, necesitaría un índice en esa expresión. Pero eso puede no ser muy útil tampoco, dependiendo de la distribución de datos. Siga mis enlaces a continuación para obtener información detallada.

  • Dividir el caso simple de parent_id IS NULLen un separado SELECTpuede o no entregar el óptimo. Especialmente no, si ese es un caso raro de todos modos, en cuyo caso una consulta combinada con un índice (COALESCE(parent_id, message_id)puede funcionar mejor. Se aplican otras consideraciones ...

Índices

Especialmente cuando se admite con estos índices:

CREATE INDEX messages_idx_null ON messages (
  feed_id
, posted_at DESC NULLS LAST
, share_count DESC NULLS LAST
, parent_id, message_id
)
WHERE parent_id IS NULL;

CREATE INDEX messages_idx_notnull ON messages (
  feed_id
, posted_at DESC NULLS LAST
, share_count DESC NULLS LAST
, parent_id, message_id
)
WHERE parent_id IS NOT NULL;

Los dos índices parciales cubren toda la tabla juntos y tienen aproximadamente el mismo tamaño que un índice total único.

Las últimas dos columnas parent_id, message_idsolo tienen sentido si obtiene escaneos de solo índice . De lo contrario, retírelos de ambos índices.

SQL Fiddle.

Dependiendo de los detalles que faltan, DISTINCT ONpuede o no ser la mejor técnica de consulta para este propósito. Lea la explicación detallada aquí:

Y posiblemente alternativas más rápidas aquí:

Erwin Brandstetter
fuente