¿Por qué es array_agg () más lento que el constructor ARRAY () no agregado?

13

Estaba revisando un código antiguo escrito para PostgreSQL anterior a 8.4 , y vi algo realmente ingenioso. Recuerdo que una función personalizada hizo algo de esto en el día, pero olvidé cómo se array_agg()veía previamente. Para su revisión, la agregación moderna se escribe así.

SELECT array_agg(x ORDER BY x DESC) FROM foobar;

Sin embargo, una vez, se escribió así,

SELECT ARRAY(SELECT x FROM foobar ORDER BY x DESC);

Entonces, lo probé con algunos datos de prueba ...

CREATE TEMP TABLE foobar AS
SELECT * FROM generate_series(1,1e7)
  AS t(x);

Los resultados fueron sorprendentes. La forma #OldSchoolCool fue enormemente más rápida: una aceleración del 25%. Además, al simplificarlo sin ORDEN, se mostró la misma lentitud.

# EXPLAIN ANALYZE SELECT ARRAY(SELECT x FROM foobar);
                                                         QUERY PLAN                                                          
-----------------------------------------------------------------------------------------------------------------------------
 Result  (cost=104425.28..104425.29 rows=1 width=0) (actual time=1665.948..1665.949 rows=1 loops=1)
   InitPlan 1 (returns $0)
     ->  Seq Scan on foobar  (cost=0.00..104425.28 rows=6017728 width=32) (actual time=0.032..716.793 rows=10000000 loops=1)
 Planning time: 0.068 ms
 Execution time: 1671.482 ms
(5 rows)

test=# EXPLAIN ANALYZE SELECT array_agg(x) FROM foobar;
                                                        QUERY PLAN                                                         
---------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=119469.60..119469.61 rows=1 width=32) (actual time=2155.154..2155.154 rows=1 loops=1)
   ->  Seq Scan on foobar  (cost=0.00..104425.28 rows=6017728 width=32) (actual time=0.031..717.831 rows=10000000 loops=1)
 Planning time: 0.054 ms
 Execution time: 2174.753 ms
(4 rows)

Entonces, ¿qué está pasando aquí? ¿Por qué es array_agg , una función interna mucho más lenta que el vudú SQL del planificador?

Usando " PostgreSQL 9.5.5 en x86_64-pc-linux-gnu, compilado por gcc (Ubuntu 6.2.0-5ubuntu12) 6.2.0 20161005, 64 bits"

Evan Carroll
fuente

Respuestas:

17

No hay nada de "vieja escuela" o "anticuado" sobre un constructor de ARRAY (Eso es lo que ARRAY(SELECT x FROM foobar)es). Es moderno como siempre. Úselo para una agregación de matriz simple.

El manual:

También es posible construir una matriz a partir de los resultados de una subconsulta. De esta forma, el constructor de matriz se escribe con la palabra clave ARRAYseguida de una subconsulta entre paréntesis (no entre corchetes).

La función de agregadoarray_agg() es mucho más versátil, ya que se puede integrar en una SELECTlista con más columnas, posiblemente más agregaciones en la misma SELECT, y se pueden formar grupos arbitrarios GROUP BY. Mientras que un constructor ARRAY solo puede devolver una sola matriz de una SELECTcolumna única que regresa.

No estudié el código fuente, pero parece obvio que una herramienta mucho más versátil también es más costosa.

Erwin Brandstetter
fuente
array_aggdebe realizar un seguimiento del orden de sus entradas donde el ARRAYconstructor parece estar haciendo algo aproximadamente equivalente a a UNIONcomo una expresión interna. Si tuviera que aventurarme a adivinar, array_aggprobablemente requeriría más memoria. No pude probar exhaustivamente esto, pero en PostgreSQL 9.6 que se ejecuta en Ubuntu 16.04 la ARRAY()consulta ORDER BYutilizó una fusión externa y fue más lenta que la array_aggconsulta. Como dijiste, antes de leer el código, tu respuesta es la mejor explicación que tenemos.
Jeff el
@ Jeffrey: ¿Encontraste un caso de prueba donde array_agg()es más rápido que el constructor de matrices? Por un caso simple? Muy improbable, pero si es así, probablemente porque Postgres basó su decisión para un plan de consulta en estadísticas inexactas de la configuración de costos. Nunca he visto array_agg()superar a un constructor de matrices y lo he probado muchas veces.
Erwin Brandstetter
1
@ Jeffrey: ¿No hay efectos de almacenamiento en caché engañosos? ¿Ejecutó cada consulta más de una vez? Necesitaría ver la definición de la tabla, las cardinalidades y la consulta exacta para decir más.
Erwin Brandstetter
1
Esta no es una respuesta real. Muchas herramientas versátiles pueden funcionar tan bien como herramientas más específicas. Si ser versátil es lo que lo hace más lento, ¿qué pasa con su versatilidad?
Gavin Wahl el
1
@Jeffrey: Parece que Postgres elige un algoritmo de clasificación diferente para cada variante (basado en estimaciones de costos y estadísticas de tabla). Y termina eligiendo un método inferior para el constructor ARRAY, que indica que uno o más factores en el cálculo del costo estimado están demasiado lejos. Esto está en una mesa temporal? ¿Lo hiciste VACUUM ANALYZEantes de ejecutar las consultas? Considere: dba.stackexchange.com/a/18694/3684
Erwin Brandstetter
5

Creo que la respuesta aceptada por Erwin podría agregarse con lo siguiente.

Por lo general, estamos trabajando con tablas regulares con índices, en lugar de tablas temporales (sin índices) como en la pregunta original. Es útil tener en cuenta que las agregaciones, como ARRAY_AGG, no pueden aprovechar los índices existentes cuando la clasificación se realiza durante la agregación .

Por ejemplo, suponga la siguiente consulta:

SELECT ARRAY(SELECT c FROM t ORDER BY id)

Si tenemos un índice activado t(id, ...), el índice podría usarse, a favor de una exploración secuencial tseguida de una clasificación activada t.id. Además, si la columna de salida que se envuelve en la matriz (aquí c) es parte del índice (como un índice activado t(id, c)o un índice de inclusión activado t(id) include(c)), esto podría incluso ser una exploración de solo índice.

Ahora, reescribamos esa consulta de la siguiente manera:

SELECT ARRAY_AGG(c ORDER BY id) FROM t

Ahora, la agregación no usará el índice y tiene que ordenar las filas en la memoria (o peor aún para conjuntos de datos grandes, en el disco). Esto siempre será una exploración secuencial tseguida de agregación + ordenación .

Hasta donde yo sé, esto no está documentado en la documentación oficial, pero puede derivarse de la fuente. Este debería ser el caso para todas las versiones actuales, v11 incluido.

pbillen
fuente
2
Buen punto. Pero para ser justos, las consultas con array_agg()o similares funciones agregadas todavía puede índices de apalancamiento con una subconsulta como: SELECT ARRAY_AGG(c) FROM (SELECT c FROM t ORDER BY id) sub. La ORDER BYcláusula por agregado es lo que impide el uso del índice en su ejemplo. Un constructor de matriz es más rápido que array_agg()cuando cualquiera puede usar el mismo índice (o ninguno). Simplemente no es tan versátil. Ver: dba.stackexchange.com/a/213724/3684
Erwin Brandstetter el
1
Bien, esa es una distinción importante que hacer. Modifiqué ligeramente mi respuesta para dejar en claro que este comentario solo se cumple cuando la función de agregación tiene que ordenar. De hecho, aún podría beneficiarse del índice en el caso simple, porque PostgreSQL parece garantizar que la agregación ocurrirá en el mismo orden que se define en la subconsulta, como se explica en el enlace. Eso es genial Sin embargo, me pregunto si esto aún se cumple en el caso de tablas particionadas y / o tablas FDW y / o trabajadores paralelos, y si PostgreSQL puede mantener esta promesa en futuras versiones.
pbillen
Para el registro, de ninguna manera tuve la intención de dudar sobre la respuesta aceptada. Solo pensé que era una buena adición a la razón sobre la existencia y el uso de índices en combinación con la agregación.
pbillen
1
Que es una buena adición.
Erwin Brandstetter