Realmente no hay una 'mejor manera' para almacenar datos de series de tiempo, y honestamente depende de una serie de factores. Sin embargo, me enfocaré principalmente en dos factores, siendo ellos:
(1) ¿Qué tan serio es este proyecto que merece su esfuerzo para optimizar el esquema?
(2) ¿Cómo serán realmente sus patrones de acceso a consultas ?
Con esas preguntas en mente, analicemos algunas opciones de esquema.
Mesa plana
La opción de usar una tabla plana tiene mucho más que ver con la pregunta (1) , donde si este no es un proyecto serio o de gran escala, será mucho más fácil no pensar demasiado en el esquema, y solo use una mesa plana, como:
CREATE flat_table(
trip_id integer,
tstamp timestamptz,
speed float,
distance float,
temperature float,
,...);
No hay muchos casos en los que recomiende este curso, solo si se trata de un pequeño proyecto que no garantiza gran parte de su tiempo.
Dimensiones y hechos
Entonces, si ha superado el obstáculo de la pregunta (1) y desea un esquema de rendimiento más, esta es una de las primeras opciones a considerar. Incluye algo de normailización básica, pero extrae las cantidades 'dimensionales' de las cantidades 'factuales' medidas.
Esencialmente, querrás una tabla para registrar información sobre los viajes,
CREATE trips(
trip_id integer,
other_info text);
y una tabla para registrar marcas de tiempo,
CREATE tstamps(
tstamp_id integer,
tstamp timestamptz);
y finalmente todos sus hechos medidos, con referencias de claves externas a las tablas de dimensiones (es decir, meas_facts(trip_id)
referencias trips(trip_id)
y meas_facts(tstamp_id)
referencias tstamps(tstamp_id)
)
CREATE meas_facts(
trip_id integer,
tstamp_id integer,
speed float,
distance float,
temperature float,
,...);
Puede que esto no parezca tan útil al principio, pero si tiene, por ejemplo, miles de viajes simultáneos, entonces todos pueden estar tomando mediciones una vez por segundo, en el segundo. En ese caso, tendría que volver a grabar la marca de tiempo cada vez para cada viaje, en lugar de usar una sola entrada en la tstamps
tabla.
Caso de uso: este caso será bueno si hay muchos viajes simultáneos para los que está grabando datos, y no le importa acceder a todos los tipos de medición todos juntos.
Dado que Postgres lee por filas, cada vez que desee, por ejemplo, las speed
mediciones en un rango de tiempo determinado, debe leer toda la fila de la meas_facts
tabla, lo que definitivamente ralentizará una consulta, aunque si el conjunto de datos con el que está trabajando es no demasiado grande, entonces ni siquiera notarías la diferencia.
Dividiendo sus hechos medidos
Para extender la última sección un poco más, puede dividir sus medidas en tablas separadas, donde, por ejemplo, mostraré las tablas de velocidad y distancia:
CREATE speed_facts(
trip_id integer,
tstamp_id integer,
speed float);
y
CREATE distance_facts(
trip_id integer,
tstamp_id integer,
distance float);
Por supuesto, puede ver cómo esto podría extenderse a las otras mediciones.
Caso de uso: por lo tanto, esto no le dará una velocidad tremenda para una consulta, tal vez solo un aumento lineal de la velocidad cuando realice consultas sobre un tipo de medición. Esto se debe a que cuando desea buscar información sobre la velocidad, solo necesita leer las filas de la speed_facts
tabla, en lugar de toda la información adicional innecesaria que estaría presente en una fila de la meas_facts
tabla.
Por lo tanto, necesita leer grandes cantidades de datos sobre un solo tipo de medición, podría obtener algún beneficio. Con su caso propuesto de 10 horas de datos a intervalos de un segundo, solo estaría leyendo 36,000 filas, por lo que nunca encontraría un beneficio significativo al hacer esto. Sin embargo, si estuviera buscando datos de medición de velocidad para 5,000 viajes que duraron alrededor de 10 horas, ahora está leyendo 180 millones de filas. Un aumento lineal de la velocidad para una consulta de este tipo podría generar algún beneficio, siempre que solo necesite acceder a uno o dos de los tipos de medición a la vez.
Matrices / HStore / & TOAST
Probablemente no necesite preocuparse por esta parte, pero sé de casos en los que sí importa. Si necesita acceder a ENORMES cantidades de datos de series temporales, y sabe que necesita acceder a todos ellos en un bloque enorme, puede usar una estructura que utilizará las Tablas TOAST , que esencialmente almacenan sus datos en archivos más grandes y comprimidos. segmentos Esto conduce a un acceso más rápido a los datos, siempre que su objetivo sea acceder a todos los datos.
Un ejemplo de implementación podría ser
CREATE uber_table(
trip_id integer,
tstart timestamptz,
speed float[],
distance float[],
temperature float[],
,...);
En esta tabla, tstart
almacenaría la marca de tiempo para la primera entrada en la matriz, y cada entrada posterior sería el valor de una lectura para el siguiente segundo. Esto requiere que administre la marca de tiempo relevante para cada valor de matriz en una pieza de software de aplicación.
Otra posibilidad es
CREATE uber_table(
trip_id integer,
speed hstore,
distance hstore,
temperature hstore,
,...);
donde agrega sus valores de medición como (clave, valor) pares de (marca de tiempo, medición).
Caso de uso: esta implementación probablemente sea mejor para alguien que se sienta más cómodo con PostgreSQL, y solo si está seguro de que sus patrones de acceso deben ser patrones de acceso masivo.
Conclusiones?
Wow, esto se hizo mucho más de lo que esperaba, lo siento. :)
Esencialmente, hay una serie de opciones, pero probablemente obtendrá el mayor rendimiento de su inversión utilizando el segundo o el tercero, ya que se ajustan al caso más general.
PD: Su pregunta inicial implicaba que cargará en masa sus datos después de que se hayan recopilado. Si está transmitiendo los datos a su instancia de PostgreSQL, tendrá que hacer un trabajo adicional para manejar tanto la ingesta de datos como la carga de trabajo de consulta, pero lo dejaremos para otro momento. ;)
Es 2019 y esta pregunta merece una respuesta actualizada.
Tomando su ejemplo, primero cree una tabla simple en PostgreSQL
Paso 1
Paso 2
Esta mini tabla no es obvia cuando ejecuta consultas, aunque puede incluirla o excluirla en sus consultas
SELECCIONE create_hypertable ('viaje', 'ts', chunk_time_interval => intervalo '1 hora', if_not_exists => TRUE);
Lo que hemos hecho anteriormente es tomar nuestra tabla de viaje, dividirla en mini tablas de trozos cada hora en función de la columna 'ts'. Si agrega una marca de tiempo de 10:00 a 10:59, se agregarán a 1 fragmento, pero las 11:00 se insertarán en un nuevo fragmento y esto continuará infinitamente.
Si no desea almacenar datos infinitamente, también puede DROP trozos anteriores a 3 meses usando
SELECCIONE drop_chunks (intervalo '3 meses', 'viaje');
También puede obtener una lista de todos los fragmentos creados hasta la fecha mediante una consulta como
SELECCIONE chunk_table, table_bytes, index_bytes, total_bytes FROM chunk_relation_size ('trip');
Esto le dará una lista de todas las mini tablas creadas hasta la fecha y puede ejecutar una consulta en la última mini tabla si lo desea de esta lista
Puede optimizar sus consultas para incluir, excluir fragmentos u operar solo en los últimos N fragmentos, etc.
fuente