El mejor diseño de base de datos y tabla para miles de millones de filas de datos [cerrado]

74

Estoy escribiendo una aplicación que necesita almacenar y analizar grandes cantidades de datos eléctricos y de temperatura.

Básicamente, necesito almacenar grandes cantidades de mediciones de consumo de electricidad por hora durante los últimos años y durante muchos años para decenas de miles de ubicaciones y luego analizar los datos de una manera no muy compleja.

La información que necesito almacenar (por ahora) es Identificación de ubicación, Marca de tiempo (Fecha y hora), Temperatura y uso de electricidad.

Acerca de la cantidad de datos que deben almacenarse, esto es una aproximación, pero algo así: más de
20 000 ubicaciones, 720 registros por mes (mediciones por hora, aproximadamente 720 horas por mes), 120 meses (durante 10 años) ) y muchos años en el futuro. Los cálculos simples arrojan los siguientes resultados:

20 000 ubicaciones x 720 registros x 120 meses (10 años atrás) = 1 728 000 000 registros .

Estos son los registros anteriores, los nuevos registros se importarán mensual, por lo que es de aproximadamente 20 000 x 720 = 14 400 000 nuevos registros al mes .

Las ubicaciones totales también crecerán constantemente.

En todos esos datos, se deberán ejecutar las siguientes operaciones:

  1. Recupere los datos para una fecha y un período de tiempo determinados: todos los registros para una identificación de ubicación determinada entre las fechas 01.01.2013 y 01.01.2017 y entre 07:00 y 13:00.
  2. Operaciones matemáticas simples para un determinado rango de fecha Y hora, por ejemplo, temperatura MIN, MAX y AVG y uso de electricidad para una determinada ID de ubicación durante 5 años entre las 07:00 y las 13:00.

Los datos se escribirán mensualmente, pero serán leídos por cientos de usuarios (al menos) constantemente, por lo que la velocidad de lectura es significativamente más importante.

No tengo experiencia con las bases de datos NoSQL, pero por lo que he reunido, son la mejor solución para usar aquí. Leí en las bases de datos NoSQL más populares, pero como son bastante diferentes y también permiten una arquitectura de tabla muy diferente, no he podido decidir cuál es la mejor base de datos para usar.

Mis opciones principales fueron Cassandra y MongoDB, pero desde que tengo un conocimiento muy limitado y no tengo experiencia real cuando se trata de datos grandes y NoSQL, no estoy muy seguro. También leí que PostreSQL también maneja bien tales cantidades de datos.

Mis preguntas son las siguientes:

  1. ¿Debo usar una base de datos NoSQL para cantidades tan grandes de datos? Si no, ¿puedo seguir con MySQL?
  2. ¿Qué base de datos debo usar?
  3. ¿Debo mantener la fecha y la hora en columnas separadas e indexadas (si es posible) para recuperar y procesar los datos rápidamente durante ciertos períodos de tiempo y fecha, o puede hacerlo manteniendo la marca de tiempo en una sola columna?
  4. ¿Es apropiado un enfoque de modelado de datos de series de tiempo aquí, y si no, podría darme consejos para un buen diseño de tabla?

Gracias.

Gecata
fuente
29
2017. Aunque no es pequeño, esta no es particularmente una GRAN cantidad de datos para el hardware adecuado. Y odio decírtelo, pero hasta ahora lo que tienes allí suena como datos relacionales.
TomTom
66
He almacenado tablas de múltiples TB con decenas de miles de millones de filas en MS SQL Server 2008-2014 utilizando una buena clave (fecha de época), compresión, particionamiento y asegurando que mis consultas / índices estén alineados. Tuve que pasar a NoSQL (Hadoop) cuando comencé a obtener petabytes de datos para analizar e indexar de manera diferente. NoSQL debería tener otras consideraciones y, en este caso, no parece encajar.
Ali Razeghi
3
@AliRazeghi Hadoop no tiene nada que ver con SQL o NoSQL: es solo un motor de almacenamiento. Hay muchas interfaces SQL respaldadas por Hadoop.
mustaccio
3
¿Cuáles son sus limitaciones con respecto al dinero para gastar en software / licencias?
user3067860
1
Cuando tenga dinero infinito, sugeriría comprar un dispositivo SAP HANA. Es ideal para agregaciones en grandes conjuntos de datos. Pero es probable que no tengas dinero infinito.
Philipp

Respuestas:

90

Esto es exactamente lo que hago todos los días, excepto que en lugar de usar los datos por hora, uso los datos de 5 minutos. Descargo unos 200 millones de registros todos los días, por lo que la cantidad de la que habla aquí no es un problema. Los datos de 5 minutos tienen un tamaño de aproximadamente 2 TB y tengo datos meteorológicos que retroceden 50 años a un nivel por hora por ubicación. Déjame responderte preguntas basadas en mi experiencia:

  1. No use NoSQL para esto. Los datos están altamente estructurados y se ajustan perfectamente a una base de datos relacional.
  2. Personalmente uso SQL Server 2016 y no tengo problemas para aplicar cálculos en ese volumen de datos. Originalmente estaba en una instancia de PostgreSQL cuando comencé mi trabajo y no podía manejar el volumen de datos ya que estaba en una pequeña instancia de AWS.
  3. Me altamente recomendar la extracción de la porción horas de la fecha y el almacenamiento separado de la misma fecha. ¡Créeme, aprende de mis errores!
  4. Almaceno la mayoría de los datos en forma de lista (FECHA, HORA, DATAPOINT_ID, VALOR) pero no es así como la gente querrá interpretar los datos. Esté preparado para algunas consultas horrendas contra los datos y grandes cantidades de pivote. No tenga miedo de crear una tabla desnormalizada para conjuntos de resultados que son demasiado grandes para computar sobre la marcha.

Consejo general: almaceno la mayor parte de los datos entre dos bases de datos, la primera es una serie de datos directos y está normalizada. Mi segunda base de datos está muy desnormalizada y contiene datos agregados previamente. Tan rápido como es mi sistema, no estoy ciego al hecho de que los usuarios ni siquiera quieren esperar 30 segundos para que se cargue un informe, incluso si personalmente creo que 30 segundos para procesar 2 TB de datos es extremadamente rápido.

Para explicar por qué recomiendo almacenar la hora por separado de la fecha, aquí hay algunas razones por las que lo hago de esa manera:

  1. La forma en que se presentan los datos eléctricos es por hora final- por lo tanto, 01:00 es en realidad el promedio de la energía eléctrica de la hora anterior y 00:00 es la hora final 24. (Esto es importante porque realmente tiene que buscar dos fechas para incluir el valor de 24 horas: el día que están buscando más la primera marca del día siguiente.) Sin embargo, los datos meteorológicos se presentan de manera directa (real y pronosticada para la próxima hora). En mi experiencia con estos datos, los consumidores desean analizar el efecto que tiene el clima en el precio / demanda de energía. Si tuviera que usar una comparación de fecha directa, en realidad estaría comparando el precio promedio de la hora anterior con la temperatura promedio de la hora siguiente, a pesar de que las marcas de tiempo son las mismas.DATETIME columna.
  2. Actuación. Diría que al menos el 90% de los informes que genero son gráficos, que normalmente representan el precio en función de la hora, ya sea para una sola fecha o para un rango de fechas. Tener que dividir el tiempo de la fecha puede atascar la velocidad de la consulta utilizada para generar el informe dependiendo del rango de fechas que desea ver. No es raro que los consumidores quieran ver una sola fecha, año tras año durante los últimos 30 años (de hecho, para el clima, esto es necesario para generar los 30 años normales), esto puede ser lento. Por supuesto, puede optimizar su consulta y agregar índices, y confíe en mí. Tengo algunos índices locos que preferiría no tener, pero hace que el sistema funcione rápidamente.
  3. Productividad. Odio tener que escribir el mismo código más de una vez. Solía ​​almacenar la fecha y la hora en la misma columna, hasta que tuve que escribir la misma consulta una y otra vez para extraer la parte de la hora. Después de un tiempo, me cansé de tener que hacer esto y lo extraje en su propia columna. Cuanto menos código tenga que escribir, menos posibilidades hay de que haya un error. Además, tener que escribir menos código significa que puede enviar sus informes más rápido, nadie quiere esperar informes todo el día.
  4. Usuarios finales. No todos los usuarios finales son usuarios avanzados (es decir, saben cómo escribir SQL). Tener los datos ya almacenados en un formato que puedan llevar a Excel (u otra herramienta similar) con un mínimo esfuerzo lo convertirá en un héroe en la oficina. Si los usuarios no pueden acceder o manipular los datos fácilmente, no utilizarán su sistema. Créeme, diseñé el sistema perfecto hace un par de años y nadie lo usó por este motivo. El diseño de la base de datos no se trata solo de adherirse a un conjunto predefinido de reglas / pautas, se trata de hacer que el sistema sea utilizable.

Como dije anteriormente, todo esto se basa en mi experiencia personal, y déjenme decirles que han sido unos años difíciles y muchos rediseños para llegar a donde estoy ahora. No haga lo que hice, aprenda de mis errores y asegúrese de involucrar a los usuarios finales de su sistema (o desarrolladores, autores de informes, etc.) al tomar decisiones sobre su base de datos.

Mr.Brownstone
fuente
Tuve buena suerte usando la fecha de época pero su recomendación es interesante para su caso de uso. Gracias por compartir.
Ali Razeghi
44
No estoy de acuerdo con mucho de esto. Nada de esto es una preocupación real con una base de datos moderna como se demuestra con números reales aquí . Si los usuarios de los datos son demasiado estúpidos para usar el sql, entonces debes crearles una interfaz, no debes confundir el esquema. Extraer la hora es una mala idea
Evan Carroll
1
¿Cómo es tu hardware?
Kennes
1
@kennes física, 16 núcleos, 256 GB de RAM, unidad de sistema operativo de 100 GB, SSD local de 500 GB con datos TempDB, SAN híbrido con caché de SSD de 8 TB y 40 TB de discos de husillo con capacidad para 100,000 iops / seg. La implementación de la base de datos utiliza ColumnStore, compresión, tablas en memoria, particionamiento y una instancia tabular SSAS.
Mr.Brownstone
1
Ese es un hardware increíble dependiendo de cuántos usuarios atiendas. Dado que esta es una respuesta de pseudo-optimización, creo que es útil incluir su tecnología. Me sorprendió completamente escuchar que puedes procesar 2TB en 30 segundos, eso es increíblemente rápido. Dejando a un lado mi propio criterio personal, creo que sería útil para las personas futuras que buscan optimizar los datos de series temporales.
Kennes
57

Índices PostgreSQL y BRIN

Pruébalo por ti mismo. Esto no es un problema en una computadora portátil de 5 años con un ssd.

EXPLAIN ANALYZE
CREATE TABLE electrothingy
AS
  SELECT
    x::int AS id,
    (x::int % 20000)::int AS locid,  -- fake location ids in the range of 1-20000
    now() AS tsin,                   -- static timestmap
    97.5::numeric(5,2) AS temp,      -- static temp
    x::int AS usage                  -- usage the same as id not sure what we want here.
  FROM generate_series(1,1728000000) -- for 1.7 billion rows
    AS gs(x);

                                                               QUERY PLAN                                                               
----------------------------------------------------------------------------------------------------------------------------------------
 Function Scan on generate_series gs  (cost=0.00..15.00 rows=1000 width=4) (actual time=173119.796..750391.668 rows=1728000000 loops=1)
 Planning time: 0.099 ms
 Execution time: 1343954.446 ms
(3 rows)

Por lo tanto, tardó 22 minutos en crear la tabla. En gran parte, porque la mesa es un modesto 97GB. Luego creamos los índices,

CREATE INDEX ON electrothingy USING brin (tsin);
CREATE INDEX ON electrothingy USING brin (id);    
VACUUM ANALYZE electrothingy;

Tomó bastante tiempo crear los índices también. Aunque debido a que son BRIN, solo tienen 2-3 MB y se almacenan fácilmente en ram. Leer 96 GB no es instantáneo, pero no es un problema real para mi computadora portátil en su carga de trabajo.

Ahora lo consultamos.

explain analyze
SELECT max(temp)
FROM electrothingy
WHERE id BETWEEN 1000000 AND 1001000;
                                                                 QUERY PLAN                                                                  
---------------------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=5245.22..5245.23 rows=1 width=7) (actual time=42.317..42.317 rows=1 loops=1)
   ->  Bitmap Heap Scan on electrothingy  (cost=1282.17..5242.73 rows=993 width=7) (actual time=40.619..42.158 rows=1001 loops=1)
         Recheck Cond: ((id >= 1000000) AND (id <= 1001000))
         Rows Removed by Index Recheck: 16407
         Heap Blocks: lossy=128
         ->  Bitmap Index Scan on electrothingy_id_idx  (cost=0.00..1281.93 rows=993 width=0) (actual time=39.769..39.769 rows=1280 loops=1)
               Index Cond: ((id >= 1000000) AND (id <= 1001000))
 Planning time: 0.238 ms
 Execution time: 42.373 ms
(9 rows)

Actualizar con marcas de tiempo

Aquí generamos una tabla con diferentes marcas de tiempo para satisfacer la solicitud de indexar y buscar en una columna de marca de tiempo, la creación tarda un poco más porque to_timestamp(int)es sustancialmente más lenta que now()(que se almacena en caché para la transacción)

EXPLAIN ANALYZE
CREATE TABLE electrothingy
AS
  SELECT
    x::int AS id,
    (x::int % 20000)::int AS locid,
    -- here we use to_timestamp rather than now(), we
    -- this calculates seconds since epoch using the gs(x) as the offset
    to_timestamp(x::int) AS tsin,
    97.5::numeric(5,2) AS temp,
    x::int AS usage
  FROM generate_series(1,1728000000)
    AS gs(x);

                                                               QUERY PLAN                                                                
-----------------------------------------------------------------------------------------------------------------------------------------
 Function Scan on generate_series gs  (cost=0.00..17.50 rows=1000 width=4) (actual time=176163.107..5891430.759 rows=1728000000 loops=1)
 Planning time: 0.607 ms
 Execution time: 7147449.908 ms
(3 rows)

Ahora podemos ejecutar una consulta en un valor de marca de tiempo en su lugar ,,

explain analyze
SELECT count(*), min(temp), max(temp)
FROM electrothingy WHERE tsin BETWEEN '1974-01-01' AND '1974-01-02';
                                                                        QUERY PLAN                                                                         
-----------------------------------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=296073.83..296073.84 rows=1 width=7) (actual time=83.243..83.243 rows=1 loops=1)
   ->  Bitmap Heap Scan on electrothingy  (cost=2460.86..295490.76 rows=77743 width=7) (actual time=41.466..59.442 rows=86401 loops=1)
         Recheck Cond: ((tsin >= '1974-01-01 00:00:00-06'::timestamp with time zone) AND (tsin <= '1974-01-02 00:00:00-06'::timestamp with time zone))
         Rows Removed by Index Recheck: 18047
         Heap Blocks: lossy=768
         ->  Bitmap Index Scan on electrothingy_tsin_idx  (cost=0.00..2441.43 rows=77743 width=0) (actual time=40.217..40.217 rows=7680 loops=1)
               Index Cond: ((tsin >= '1974-01-01 00:00:00-06'::timestamp with time zone) AND (tsin <= '1974-01-02 00:00:00-06'::timestamp with time zone))
 Planning time: 0.140 ms
 Execution time: 83.321 ms
(9 rows)

Resultado:

 count |  min  |  max  
-------+-------+-------
 86401 | 97.50 | 97.50
(1 row)

Entonces, en 83.321 ms podemos agregar 86.401 registros en una tabla con 1.7 mil millones de filas. Eso debería ser razonable.

Hora final

Calcular el final de la hora también es bastante fácil, truncar las marcas de tiempo y luego simplemente agregar una hora.

SELECT date_trunc('hour', tsin) + '1 hour' AS tsin,
  count(*),
  min(temp),
  max(temp)
FROM electrothingy
WHERE tsin >= '1974-01-01'
  AND tsin < '1974-01-02'
GROUP BY date_trunc('hour', tsin)
ORDER BY 1;
          tsin          | count |  min  |  max  
------------------------+-------+-------+-------
 1974-01-01 01:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 02:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 03:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 04:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 05:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 06:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 07:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 08:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 09:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 10:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 11:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 12:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 13:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 14:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 15:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 16:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 17:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 18:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 19:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 20:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 21:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 22:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-01 23:00:00-06 |  3600 | 97.50 | 97.50
 1974-01-02 00:00:00-06 |  3600 | 97.50 | 97.50
(24 rows)

Time: 116.695 ms

Es importante tener en cuenta que no está utilizando un índice en la agregación, aunque podría hacerlo. Si esa es su consulta típica, probablemente desee un BRIN date_trunc('hour', tsin)allí, ya que date_truncexiste un pequeño problema que no es inmutable, por lo que primero debe envolverlo para que sea así.

Fraccionamiento

Otro punto importante de información sobre PostgreSQL es que PG 10 trae particiones DDL . Entonces, por ejemplo, puede crear particiones fácilmente para cada año. Desglosando su modesta base de datos en pequeñas que son pequeñas. Al hacerlo, debería poder usar y mantener índices btree en lugar de BRIN, lo que sería aún más rápido.

CREATE TABLE electrothingy_y2016 PARTITION OF electrothingy
    FOR VALUES FROM ('2016-01-01') TO ('2017-01-01');

O lo que sea.

Evan Carroll
fuente
13

Me sorprende que nadie aquí haya mencionado el benchmarking , ¡hasta que @EvanCarroll llegó con su excelente contribución!

Si yo fuera usted, pasaría algún tiempo (y sí, ¡sé que es un bien precioso!) Configurando sistemas, ejecutando lo que cree que será (¡obtenga la entrada del usuario final aquí!), Digamos, sus 10 consultas más comunes.

Mis propios pensamientos:

Las soluciones NoSQL pueden funcionar muy bien para casos de uso particulares, pero con frecuencia son inflexibles para consultas ad-hoc. Para una versión divertida de NoSQL por Brian Aker, ex arquitecto jefe de MySQL, ¡mira aquí !

¡Estoy de acuerdo con @ Mr.Brownstone en que sus datos son muy adecuados para una solución relacional (y esta opinión ha sido confirmada por Evan Carroll )!

Si tuviera que comprometerme con algún gasto, ¡sería con la tecnología de mi disco! ¡Gastaría todo el dinero que tuviera a mi disposición en NAS o SAN o tal vez en algunos discos SSD para almacenar mis datos agregados raramente escritos!

Primero miraría lo que tengo disponible ahora . Ejecute algunas pruebas y muestre los resultados a los tomadores de decisiones. ¡Ya tienes un proxy en forma de trabajo de EC ! Pero, una prueba rápida o dos realizadas en su propio hardware sería más convincente.

¡Entonces piensa en gastar dinero! Si va a gastar dinero, primero mire el hardware en lugar del software. AFAIK, puede contratar tecnología de disco por un período de prueba, o mejor aún, hacer un par de pruebas de concepto en la nube.

Mi primer puerto de escala personal para un proyecto como este sería PostgreSQL. ¡Eso no quiere decir que descartaría una solución patentada, pero las leyes de la física y los discos son las mismas para todos! "Ya no puedes remover las leyes de la física Jim" :-)

Vérace
fuente
6

Si aún no lo ha hecho, eche un vistazo a un DBMS de serie temporal, ya que está optimizado para almacenar y consultar datos donde el foco principal es el tipo de fecha / hora. Por lo general, las bases de datos de series temporales se utilizan para registrar datos en los intervalos de minuto / segundo / sub-segundo, por lo que no estoy seguro de si aún es apropiado para incrementos por hora. Dicho esto, parece que vale la pena analizar este tipo de DBMS. Actualmente, InfluxDB parece ser la base de datos de series de tiempo más establecida y ampliamente utilizada.

División de piso
fuente
1
¿Cuál es un ejemplo de una serie temporal de DBMS?
obispo
2
Echa un vistazo aquí .
Vérace
4

Claramente, este no es un problema de NoSQL, pero sugeriría que si bien una solución RDBMS funcionaría, creo que un enfoque OLAP se ajustará mucho mejor y dados los muy limitados rangos de datos involucrados, sugeriría encarecidamente investigar el uso de un DB basado en columnas en lugar de uno basado en filas. Piénselo de esta manera, puede tener 1.700 millones de datos, pero aún necesita 5 bits para indexar cada valor posible de hora o día del mes.

Tengo experiencia con un dominio de problemas similar en el que Sybase IQ (ahora SAP IQ) se utiliza para almacenar hasta 300 millones de contadores por hora de datos de gestión de rendimiento de equipos de telecomunicaciones, pero dudo si tiene el presupuesto para ese tipo de solución. En el campo del código abierto, MariaDB ColumnStore es un candidato muy prometedor, pero recomendaría también investigar MonetDB.

Dado que el rendimiento de las consultas es un motor importante para usted, considere cómo se formularán las consultas. Aquí es donde OLAP y RDBMS muestran sus mayores diferencias: - con OLAP usted normaliza el rendimiento de la consulta, no para reducir la repetición, reducir el almacenamiento o incluso para garantizar la coherencia. Entonces, además de la marca de tiempo original (¿recordó capturar su zona horaria, espero?) Tenga un campo separado para la marca de tiempo UTC, otras para la fecha y hora, y aún más para el año, mes, día, hora, minuto y desplazamiento UTC. Si tiene información adicional sobre ubicaciones, siéntase libre de guardarla en una tabla de ubicación separada que se pueda buscar a pedido y siéntase libre de mantener la clave de esa tabla en su registro principal, pero mantenga el nombre completo de la ubicación en su tabla principal como bueno, después de todo

Como sugerencia final, use tablas separadas para datos agregados populares y use trabajos por lotes para rellenarlos, de esa manera no tendrá que repetir el ejercicio para todos y cada uno de los informes que usan un valor agregado y hacen consultas que comparan actual con histórico o histórico a histórico mucho más fácil y mucho, mucho más rápido.

Paul Smith
fuente
¡También podría considerar a Greenplum como una tienda columnar si está viendo esos! Como "bono", ¡se basa en PostgreSQL!
Vérace
He tenido una buena experiencia con HP Vertica. Teníamos una sola tabla con 9 columnas que tenían 130 mil millones de filas, sin mucha afinación. Simplemente funcionó.
ThatDataGuy