Estoy tratando de crear índices parciales para una tabla estática grande (1.2TB) en Postgres 9.4.
Mis datos son completamente estáticos, por lo que puedo insertar todos los datos y luego crear todos los índices.
En esta tabla de 1.2TB, tengo una columna llamada run_id
que divide limpiamente los datos. Hemos obtenido un gran rendimiento al crear índices que cubren un rango de run_id
s. Aquí hay un ejemplo:
CREATE INDEX perception_run_frame_idx_run_266_thru_270
ON run.perception
(run_id, frame)
WHERE run_id >= 266 AND run_id <= 270;
Estos índices parciales nos dan la velocidad de consulta deseada. Desafortunadamente, la creación de cada índice parcial lleva unos 70 minutos.
Parece que estamos limitados por la CPU ( top
se muestra 100% para el proceso).
¿Hay algo que pueda hacer para acelerar la creación de nuestros índices parciales?
Especificaciones del sistema:
- 18 núcleos Xeon
- 192 GB de RAM
- 12 SSD en RAID
- Las aspiradoras automáticas están apagadas
- maintenance_work_mem: 64GB (¿Demasiado alto?)
Especificaciones de la mesa:
- Tamaño: 1,26 TB
- Número de filas: 10.537 mil millones
- Tamaño de índice típico: 3.2 GB (hay una variación de ~ .5 GB)
Definición de tabla:
CREATE TABLE run.perception(
id bigint NOT NULL,
run_id bigint NOT NULL,
frame bigint NOT NULL,
by character varying(45) NOT NULL,
by_anyone bigint NOT NULL,
by_me bigint NOT NULL,
by_s_id integer,
owning_p_id bigint NOT NULL,
obj_type_set bigint,
seq integer,
subj_id bigint NOT NULL,
subj_state_frame bigint NOT NULL,
CONSTRAINT perception_pkey PRIMARY KEY (id))
(No lea demasiado los nombres de las columnas, los he ofuscado un poco).
Información de fondo:
- Tenemos un equipo separado en el sitio que consume estos datos, pero en realidad solo hay uno o dos usuarios. (Todos estos datos se generan a través de una simulación). Los usuarios solo comienzan a analizar los datos una vez que las inserciones están terminadas y los índices están completamente integrados. Nuestra principal preocupación es reducir el tiempo requerido para generar datos utilizables, y en este momento el cuello de botella es el tiempo de creación del índice.
- La velocidad de consulta ha sido completamente adecuada cuando se usan parciales. De hecho, creo que podríamos aumentar el número de ejecuciones que cubre cada índice y aún así mantener un rendimiento de consulta lo suficientemente bueno.
- Supongo que tendremos que dividir la tabla. Estamos tratando de agotar todas las demás opciones antes de tomar esa ruta.
run_id
? ¿Distribuidos equitativamente? Tamaño del índice resultante en el disco? Los datos son estáticos, ok. ¿Pero eres el único usuario?completely static
, entonces, ¿qué quiere decir conWe have a separate team onsite that consumes this data
? ¿Acabas de indexar el rangorun_id >= 266 AND run_id <= 270
o toda la tabla? ¿Cuál es la esperanza de vida de cada índice / cuántas consultas lo usarán? ¿Para cuántos valores diferentesrun_id
? Suena como ~ 15 millones. filas porrun_id
, ¿cuál sería alrededor de 800 valores diferentes pararun_id
? ¿Por qué sonobj_type_set
,by_s_id
,seq
no definido NO NULO? ¿Qué porcentaje aproximado de valores NULL para cada uno?Respuestas:
Índice BRIN
Disponible desde Postgres 9.5 y probablemente justo lo que estás buscando. Creación de índice mucho más rápida, índice mucho más pequeño. Pero las consultas no suelen ser tan rápidas. El manual:
Sigue leyendo, hay más.
Depesz realizó una prueba preliminar.
El óptimo para su caso: Si usted puede escribir filas agrupadas en
run_id
, el índice se vuelve muy pequeña y la creación mucho más barato.Incluso podría indexar toda la tabla .
Diseño de la mesa
Independientemente de lo que haga, puede guardar 8 bytes perdidos en el relleno debido a los requisitos de alineación por fila al ordenar columnas como esta:
Hace que su tabla sea 79 GB más pequeña si ninguna de las columnas tiene valores NULL. Detalles:
Además, solo tiene tres columnas que pueden ser NULL. El mapa de bits NULL ocupa 8 bytes para 9 - 72 columnas. Si solo una columna entera es NULL, hay un caso de esquina para una paradoja de almacenamiento: sería más barato usar un valor ficticio: 4 bytes desperdiciados pero 8 bytes guardados al no necesitar un mapa de bits NULL para la fila. Más detalles aquí:
Índices parciales
Dependiendo de sus consultas reales, podría ser más eficiente tener estos cinco índices parciales en lugar del anterior:
Ejecute una transacción para cada uno.
Eliminar
run_id
de esta manera como columna de índice ahorra 8 bytes por entrada de índice, 32 en lugar de 40 bytes por fila. Cada índice también es más barato de crear, pero crear cinco en lugar de uno solo lleva mucho más tiempo para una tabla que es demasiado grande para permanecer en caché (como comentaron @ Jürgen y @Chris). Entonces eso puede o no ser útil para usted.Fraccionamiento
Basado en la herencia : la única opción hasta Postgres 9.5.
(La nueva partición declarativa en Postgres 11 o, preferiblemente, 12 es más inteligente).
El manual:
El énfasis en negrita es mío. En consecuencia, estimando 1000 valores diferentes para
run_id
, haría particiones que abarcan alrededor de 10 valores cada una.maintenance_work_mem
Eché de menos que ya te estás adaptando
maintenance_work_mem
en mi primera lectura. Dejaré una cita y un consejo en mi respuesta como referencia. Por documentación:Solo lo establecería tan alto como sea necesario, lo que depende del tamaño del índice desconocido (para nosotros). Y solo localmente para la sesión de ejecución. Como explica la cita, una configuración general demasiado alta puede privar al servidor de lo contrario, porque el vacío automático también puede reclamar más RAM. Además, no lo establezca mucho más de lo necesario, incluso en la sesión de ejecución, la RAM libre podría utilizarse para almacenar datos en caché.
Podría verse así:
Acerca de
SET LOCAL
:Para medir tamaños de objeto:
El servidor generalmente debe configurarse razonablemente de lo contrario, obviamente.
fuente
Tal vez esto solo está sobre diseñado. ¿Realmente has intentado usar un solo índice completo? Los índices parciales que cubren toda la tabla juntos no proporcionan mucha ganancia, si es que hay alguna, para las búsquedas de índice, y de su texto deduzco que tiene índices para todos los run_ids. Puede haber algunas ventajas para indexar exploraciones con índices parciales, pero aún así primero compararía la solución simple de un índice.
Para cada creación de índice, necesita una exploración completa de E / S a través de la tabla. Por lo tanto, la creación de varios índices parciales requiere mucho más IO leyendo la tabla que para un solo índice, aunque la clasificación se derramará en el disco para el índice grande único. Si insiste en índices parciales, puede intentar construir todos (o varios) índices al mismo tiempo en paralelo (si la memoria lo permite).
Para una estimación aproximada de maintenance_work_mem requerida para ordenar todos los run_ids, que son bigints de 8 bytes, en la memoria necesitaría 10.5 * 8 GB + algo de sobrecarga.
fuente
También puede crear los índices en otros espacios de tabla que no sean los predeterminados. Estos espacios de tabla podrían apuntar a discos que no son redundantes (solo recrear los índices si fallan), o están en matrices más rápidas.
También puede considerar particionar la tabla usando los mismos criterios que sus índices parciales. Esto permitiría la misma velocidad que el índice al realizar consultas, sin crear ningún índice en absoluto.
fuente