Nuestro sistema escribe muchos datos (tipo de sistema Big Data). El rendimiento de escritura es lo suficientemente bueno para nuestras necesidades, pero el rendimiento de lectura es realmente demasiado lento.
La estructura de la clave primaria (restricción) es similar para todas nuestras tablas:
timestamp(Timestamp) ; index(smallint) ; key(integer).
Una tabla puede tener millones de filas, incluso miles de millones de filas, y una solicitud de lectura suele ser para un período específico (marca de tiempo / índice) y etiqueta. Es común tener una consulta que devuelve alrededor de 200k líneas. Actualmente, podemos leer aproximadamente 15k líneas por segundo, pero debemos ser 10 veces más rápidos. ¿Es posible? y si lo es, cómo?
Nota: PostgreSQL está empaquetado con nuestro software, por lo que el hardware es diferente de un cliente a otro.
Es una VM utilizada para pruebas. El host de la VM es Windows Server 2008 R2 x64 con 24.0 GB de RAM.
Especificaciones del servidor (máquina virtual VMWare)
Server 2008 R2 x64
2.00 GB of memory
Intel Xeon W3520 @ 2.67GHz (2 cores)
postgresql.conf
optimizaciones
shared_buffers = 512MB (default: 32MB)
effective_cache_size = 1024MB (default: 128MB)
checkpoint_segment = 32 (default: 3)
checkpoint_completion_target = 0.9 (default: 0.5)
default_statistics_target = 1000 (default: 100)
work_mem = 100MB (default: 1MB)
maintainance_work_mem = 256MB (default: 16MB)
Definición de tabla
CREATE TABLE "AnalogTransition"
(
"KeyTag" integer NOT NULL,
"Timestamp" timestamp with time zone NOT NULL,
"TimestampQuality" smallint,
"TimestampIndex" smallint NOT NULL,
"Value" numeric,
"Quality" boolean,
"QualityFlags" smallint,
"UpdateTimestamp" timestamp without time zone, -- (UTC)
CONSTRAINT "PK_AnalogTransition" PRIMARY KEY ("Timestamp" , "TimestampIndex" , "KeyTag" ),
CONSTRAINT "FK_AnalogTransition_Tag" FOREIGN KEY ("KeyTag")
REFERENCES "Tag" ("Key") MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
WITH (
OIDS=FALSE,
autovacuum_enabled=true
);
Consulta
La consulta tarda unos 30 segundos en ejecutarse en pgAdmin3, pero nos gustaría tener el mismo resultado en menos de 5 segundos si es posible.
SELECT
"AnalogTransition"."KeyTag",
"AnalogTransition"."Timestamp" AT TIME ZONE 'UTC',
"AnalogTransition"."TimestampQuality",
"AnalogTransition"."TimestampIndex",
"AnalogTransition"."Value",
"AnalogTransition"."Quality",
"AnalogTransition"."QualityFlags",
"AnalogTransition"."UpdateTimestamp"
FROM "AnalogTransition"
WHERE "AnalogTransition"."Timestamp" >= '2013-05-16 00:00:00.000' AND "AnalogTransition"."Timestamp" <= '2013-05-17 00:00:00.00' AND ("AnalogTransition"."KeyTag" = 56 OR "AnalogTransition"."KeyTag" = 57 OR "AnalogTransition"."KeyTag" = 58 OR "AnalogTransition"."KeyTag" = 59 OR "AnalogTransition"."KeyTag" = 60)
ORDER BY "AnalogTransition"."Timestamp" DESC, "AnalogTransition"."TimestampIndex" DESC
LIMIT 500000;
Explicar 1
"Limit (cost=0.00..125668.31 rows=500000 width=33) (actual time=2.193..3241.319 rows=500000 loops=1)"
" Buffers: shared hit=190147"
" -> Index Scan Backward using "PK_AnalogTransition" on "AnalogTransition" (cost=0.00..389244.53 rows=1548698 width=33) (actual time=2.187..1893.283 rows=500000 loops=1)"
" Index Cond: (("Timestamp" >= '2013-05-16 01:00:00-04'::timestamp with time zone) AND ("Timestamp" <= '2013-05-16 15:00:00-04'::timestamp with time zone))"
" Filter: (("KeyTag" = 56) OR ("KeyTag" = 57) OR ("KeyTag" = 58) OR ("KeyTag" = 59) OR ("KeyTag" = 60))"
" Buffers: shared hit=190147"
"Total runtime: 3863.028 ms"
Explicar 2
En mi última prueba, ¡tardé 7 minutos en seleccionar mis datos! Vea abajo:
"Limit (cost=0.00..313554.08 rows=250001 width=35) (actual time=0.040..410721.033 rows=250001 loops=1)"
" -> Index Scan using "PK_AnalogTransition" on "AnalogTransition" (cost=0.00..971400.46 rows=774511 width=35) (actual time=0.037..410088.960 rows=250001 loops=1)"
" Index Cond: (("Timestamp" >= '2013-05-22 20:00:00-04'::timestamp with time zone) AND ("Timestamp" <= '2013-05-24 20:00:00-04'::timestamp with time zone) AND ("KeyTag" = 16))"
"Total runtime: 411044.175 ms"
fuente
Entonces, de los planes veo una cosa: su índice está hinchado (luego junto con la tabla subyacente) o simplemente no es realmente bueno para este tipo de consulta (intenté abordar esto en mi último comentario anterior).
Una fila del índice contiene 14 bytes de datos (y algunos para el encabezado). Ahora, calculando a partir de los números dados en el plan: obtuviste 500,000 filas de 190147 páginas, eso significa, en promedio, menos de 3 filas útiles por página, es decir, alrededor de 37 bytes por página de 8 kb. Esta es una relación muy mala, ¿no? Dado que la primera columna del índice es el
Timestamp
campo y se usa en la consulta como un rango, el planificador puede, y elige, el índice para encontrar filas coincidentes. Pero no seTimestampIndex
menciona en lasWHERE
condiciones, por lo que el filtradoKeyTag
no es muy efectivo ya que esos valores supuestamente aparecen aleatoriamente en las páginas de índice.Entonces, una posibilidad es cambiar la definición del índice a
(o, dada la carga de su sistema, cree este índice como uno nuevo:
La otra posibilidad es que una gran proporción de las páginas de índice esté ocupada por filas muertas, que podrían eliminarse aspirando. Creó la tabla con configuración
autovacuum_enabled=true
, pero ¿alguna vez ha comenzado a realizar el vacío automático? ¿O correrVACUUM
manualmente?fuente