Arquitectura de dos bases de datos: operativa e histórica

8

Pensé en una estructura de base de datos poco común y me pregunto si alguien la ha visto en uso antes. Básicamente está usando 2 bases de datos:

  • La primera base de datos contiene solo los datos actualmente válidos
  • La segunda base de datos contiene el historial de todo lo que se ha ingresado, actualizado o eliminado en la primera base de datos

Guión

Estoy trabajando en un proyecto donde debo registrar todo lo que sucede y donde los datos cambian con frecuencia.

Ejemplo (no el real)

Tienes que hacer el diseño de la base de datos para una liga de fútbol. En esta liga hay jugadores y equipos. Los jugadores a menudo cambian de equipo.

  • Primer requisito : la base de datos debe contener la información necesaria para jugar el próximo partido. Esto significa una lista de todos los jugadores, equipos y en qué equipo está actualmente cada jugador.
  • Segundo requisito : la base de datos debe contener valores históricos que usaremos para generar estadísticas. Esto significa la lista de todos los jugadores que han sido parte de un equipo o la lista de todos los equipos de los que un jugador ha sido parte.

El problema

Estos dos requisitos son un poco opuestos entre sí. He intentado hacer todo en la misma base de datos pero no tiene sentido. El primer requisito solo se preocupa por "jugar el próximo partido", mientras que el segundo requisito solo se preocupa por "generar estadísticas".

Para hacer todo en la misma base de datos, utilicé una especie de base de datos "insertar solo" usando la eliminación suave obvia para eliminar / actualizar información ...

Lo que inicialmente parecía una tarea fácil, tener una lista de jugadores, equipos y el equipo actual de cada jugador, de repente se vuelve mucho más difícil. La lógica de la aplicación requerida para jugar la próxima partida ya es lo suficientemente complicada, pero ahora la base de datos tiene un diseño muy poco útil en el que se requiere que la aplicación agregue una verificación "se elimine" en cada consulta solo para jugar la próxima partida.

¿Te gustaría ser ese entrenador que grita "todos los jugadores del equipo, ven a mí" y luego 2000 jugadores vienen a ti. En ese momento, probablemente gritarás "todos los jugadores que no están eliminados en el equipo, ven a mí" (mientras juras sobre este estúpido diseño).

Mi conclusión

Llegué a preguntarme por qué necesitas poner todo en la misma base de datos. La eliminación suave no solo hace un mal trabajo al registrar todo a menos que agregue muchas columnas (time_created, who_created_it, time_deleted, who_deleted_it) sino que también complica todo. Complica el diseño de la base de datos y complica el diseño de la aplicación.

Además, recibo estos 2 requisitos como parte de una sola aplicación que no se puede dividir, pero sigo pensando: se trata de 2 aplicaciones completamente distintas. ¿Por qué estoy tratando de hacer todo juntos?

Fue entonces cuando pensé en dividir la base de datos en dos. Una base de datos operativa que se usa solo para jugar el próximo partido y solo contiene la información que es actualmente válida y una base de datos histórica que contiene toda la información que existió alguna vez, cuando se creó, se eliminó y quién lo hizo.

El objetivo es mantener la primera base de datos (operativa) y la aplicación lo más simple posible mientras se tiene tanta información como sea posible en la segunda base de datos (histórica).

Preguntas

  • ¿Has visto ese diseño antes? ¿Tiene nombre?
  • ¿Hay algún obstáculo obvio que me estoy perdiendo?



EDITAR 2015-03-16

Arquitectura actual

Básicamente, puede pensar en toda la arquitectura como un proceso de 2 pasos.

Paso 1 :

  • La aplicación se está ejecutando y los usuarios están realizando algunas acciones.
  • Cada vez que ocurre un evento, se registra automáticamente (solución de auditoría) en una tabla de eventos
  • Luego se actualiza la fila correcta, en la base de datos operativa

Paso 2 :

  • Un trabajo lee la última inserción en la tabla de eventos e inserta estos nuevos datos en la base de datos histórica.
  • Los usuarios consultan la base de datos histórica para recuperar la información que necesitan.

Solo desde la tabla de eventos, puede reconstruir la información en cualquier momento. El problema es que esta tabla de eventos no es fácilmente consultable. Aquí es donde entra en juego la base de datos histórica; presentar los datos de manera que sea fácil recuperar exactamente lo que queremos.

Problemas adicionales al poner todo en las mismas tablas

Ya he expresado mi preocupación por la complejidad adicional de marcar "se elimina" en cada consulta. Pero hay otro problema: la integridad .

Hago un uso intensivo de la clave externa y la restricción para asegurarme de que en cualquier momento, los datos que están en mi base de datos sean válidos.

Veamos un ejemplo :

Restricción: solo puede haber un arquero por equipo.

Es fácil agregar un índice único que verifique si solo hay un arquero por equipo. Pero entonces, ¿qué sucede cuando cambias el portero? Aún necesita conservar la información sobre la anterior, pero ahora tiene 2 porteros en los mismos equipos, uno activo y otro inactivo, lo que contradice su restricción.

Claro que es fácil agregar un cheque a su restricción, pero es otra cosa para administrar y pensar.

Gudradain
fuente
Eche un vistazo a las dimensiones que cambian lentamente para algunos enfoques estándar
Turch
1
Vale la pena verificar: abastecimiento de eventos . Una de sus tablas es el registro de eventos, que proporciona una pista de auditoría completa; parte de ella se puede archivar más tarde. Otra tabla es el estado de resumen actual creado al aplicar los cambios de cada evento. Puede contener puntos de control anteriores para permitir la restauración de solo una parte de la secuencia de eventos.
9000

Respuestas:

7

Ocurre con bastante frecuencia, aunque el historial (a veces conocido como registros de auditoría) se mantiene en la misma tabla o en uno separado en la misma base de datos.

Por ejemplo, solía trabajar con un sistema en el que cualquier actualización de una tabla se implementaría como una inserción, el antiguo registro 'actual' tendría un indicador establecido que decía que era un registro histórico y la marca de tiempo cuando se actualizó escrito a una columna.

Hoy trabajo en un sistema donde cada cambio se escribe en una tabla de auditoría dedicada y luego la actualización se produce en la tabla.

Este último es más escalable, pero no tan fácil de implementar de manera genérica.

La forma más fácil de lograr su objetivo de simplificar las consultas y no requerir agregar el indicador 'es actual' es permitir solo las consultas de lectura a través de una vista o procedimiento almacenado. Luego, hace una llamada para decir "obtener todos los jugadores" y el proceso almacenado devolverá solo los jugadores actuales (puede implementar un segundo procedimiento para devolver jugadores con más control sobre cuáles son devueltos). Esto también funciona bien para escribir. Un procedimiento almacenado para actualizar un reproductor puede escribir los detalles del historial necesarios y actualizar el reproductor, sin que el cliente sepa cuál es el mecanismo del historial. Por esta razón, los procedimientos almacenados son mejores que una vista que solo devuelve los reproductores actuales, ya que mantiene todo el mecanismo de acceso a la base de datos igual para leer y escribir: todo pasa por un proceso.

gbjbaanb
fuente
Actualmente, prefiero lo posterior. Puede que no parezca un gran problema ejecutar todas sus consultas a través de procedimientos almacenados o vistas, pero agrega mucha complejidad para mantenerlas todas. La vista y el procedimiento almacenado también presentan muchas limitaciones. blog.sqlauthority.com/2010/10/03/… stackoverflow.com/questions/921190/…
Gudradain
1
Las limitaciones son buenas, es como decir "las variables locales en los objetos me limitan el uso de globales". Restringir su superficie de acceso a datos significa que es más seguro y que tiene que diseñar mejor. Estas son cosas buenas. Los enlaces: las vistas son limitantes, no están diseñadas para ser tablas de reemplazo. Un sproc al que no puede unirse significa que necesita un sproc diferente. Escribir su SQL en sprocs no es peor que escribirlos en código del lado del cliente, lo mantiene perfectamente bien para que pueda mantener su código sproc con la misma facilidad.
gbjbaanb
1
@Gudradain estoy con gbjbaanb. Basando el odio a las buenas prácticas de servidor cliente en una publicación de blog mal escrita que se refiere a un RDBMS y un enlace irrelevante a una pregunta sobre el desbordamiento de la pila donde alguien está tratando de hacer algo antinatural con un procedimiento almacenado y no puede. Básicamente, lees las vistas y escribes a través de procesos almacenados. Sí, debe tener un proceso en torno al control y la versión de la versión, pero de todos modos lo necesita.
mcottle
@mcottle Pasar por view y sproc parece poco práctico cuando tienes un proyecto que ya usa un ORM y una capa de acceso a datos. Además, si coloca todo en la base de datos, hace un mal trabajo encapsulando la lógica del programa. A menudo, tiene más de una aplicación que usa la misma base de datos. Simplemente no tiene sentido tener todos esos procedimientos almacenados relacionados con muchas aplicaciones diferentes en el mismo lugar. Si tuviera que adoptar este enfoque de agregar "se elimina" en todas partes, lo haría en mi capa de acceso a datos (en mi aplicación).
Gudradain
@Gudradain sí, ya que "muchas aplicaciones diferentes" utilizan felizmente las mismas tablas y datos. Piense en un sproc como una forma diferente de tabla en lugar de la lógica del cliente pegada en la capa DB. Todavía puede llamarlos a través de su ORM, y si realmente desea que muchas aplicaciones diferentes los usen, use esquemas para aislarlos, lo que hace que su aplicación de base de datos sea realmente buena para la seguridad y el mantenimiento (ya que 1 aplicación puede cambiar su API de datos sin afectar otras aplicaciones). Las Sprocs son la mejor práctica, el código del cliente puede olvidar agregar "se elimina", sproc lo asegura.
gbjbaanb
4

Al dividir una base de datos en dos bases de datos, perderá todos los beneficios de las referencias relacionales y la verificación de integridad referencial. Nunca he intentado algo así, pero supongo que se convertiría en una gran pesadilla.

Creo que todo el conjunto de datos que describe un determinado sistema pertenece a una sola base de datos. Los problemas de conveniencia para acceder a los datos casi nunca son una buena razón para tomar decisiones de organización de datos.

Las cuestiones de conveniencia para acceder a sus datos deben manejarse haciendo uso de las funciones de conveniencia que ofrece su RDBMS.

Por lo tanto, en lugar de tener una base de datos 'actual' y una base de datos 'histórica', solo debe tener una base de datos, y todas las tablas deben tener el prefijo 'histórico'. Luego, debe crear un conjunto de vistas, una para cada tabla que desee ver como 'actual', y hacer que cada vista filtre las filas históricas que no desea ver, y deje pasar solo las actuales.

Esta es una solución adecuada a su problema porque hace uso de una función de conveniencia del RDBMS para abordar una preocupación de conveniencia del programador, dejando intacto el diseño de la base de datos.

Un ejemplo de un problema con el que es probable que se encuentre (demasiado tiempo para un comentario)

Suponga que está mirando una pantalla que muestra información actual sobre un equipo, por ejemplo, team.id = 10, team.name = "Manchester United", y hace clic en el botón que dice "mostrar historial". En ese momento, querrá cambiar a una pantalla que muestre información histórica sobre ese mismo equipo. Entonces, tomará la identificación de 10, que sabe que en la base de datos "actual" significa "Manchester United", y tendrá que esperarque este número de identificación de 10 también significa "Manchester United" en la base de datos histórica. No existe una regla de integridad referencial que imponga que el ID se refiera a la misma entidad exacta en ambas bases de datos, por lo que, esencialmente, tendrá dos conjuntos de datos completamente disjuntos con conexiones implícitas que solo conoce, honra y promete mantener. por, código fuera de la base de datos.

Y, por supuesto, esto es válido no solo para las mesas principales, como la mesa de "Equipos", sino incluso para la pequeña mesa más pequeña que tendrá a un lado, como "Posiciones de jugador: Delantero, Centrocampista, Portero, etc."

Lograr historicidad dentro de la misma base de datos

Existen varios métodos para mantener la historicidad, y aunque están más allá del alcance de esta pregunta, que es básicamente lo que podría tener esta idea particular tuya, aquí hay una idea:

Puede mantener una tabla de registro que contenga una entrada para cada cambio que se haya realizado en la base de datos. De esta manera, todas las tablas "actuales" se pueden borrar por completo de los datos y reconstruirse por completo mediante la reproducción de los cambios registrados. Por supuesto, si puede reconstruir las tablas "actuales" mediante la reproducción de los cambios desde el principio de los tiempos hasta ahora, también puede construir un conjunto temporal de tablas para obtener una vista de la base de datos en una coordenada de tiempo específica mediante la reproducción los cambios desde el principio del tiempo hasta ese tiempo específico coordinan.

Esto se conoce como "Abastecimiento de eventos" (artículo de Martin Fowler).

Mike Nakis
fuente
¿Se refiere a referencias relacionales e integridad referencial entre las 2 bases de datos o dentro de una base de datos? Utilizo claves y restricciones foráneas en todos los lugares apropiados porque solo los datos que respetan estas restricciones deben insertarse en la base de datos.
Gudradain
Bueno, que yo sepa, solo puede tener relaciones dentro de una sola base de datos. Es posible que existan RDBMS que permitan relaciones entre bases de datos, pero eso no es de esperar por otros productos, y en mi humilde opinión, ni depender de ellos ni siquiera con el producto que anuncia dicha característica.
Mike Nakis
Entonces, al dividir la base de datos en dos bases de datos, podrá tener relaciones dentro de cada base de datos por separado, pero no a través de las dos bases de datos. Lo que puede significar un desastre.
Mike Nakis
1
Los datos en la base de datos histórica provienen únicamente de la base de datos operativa y nunca hay actualización / eliminación en esa base de datos. Desde mi punto de vista, esta base de datos ni siquiera necesita ninguna relación o restricción. Simplemente contiene los datos que estaban en la base de datos operativa en algún momento. No hay manipulación ni nada que verificar sobre esos datos. Lo único que importa es: ¿existía en la otra base de datos (sí / no). No puedo ver cuál es el problema con eso ...
Gudradain
Como esto sería demasiado largo para un comentario, lo agregué como una enmienda a mi respuesta.
Mike Nakis
2

Lo primero es lo primero , ¿su base de código ya ofrece una separación clara de preocupaciones donde la lógica de negocios (de elegir jugadores para jugar en el próximo partido) se distingue de la lógica de acceso a la base de datos (una capa que simplemente se conecta a la base de datos y mapea sus estructuras de datos) en filas de la base de datos y viceversa)? La respuesta a esto será muy útil para explicar por qué está lidiando con esto:

Complica el diseño de la base de datos y complica el diseño de la aplicación.

Ahora...

La lógica de la aplicación requerida para jugar la próxima partida ya es lo suficientemente complicada, pero ahora la base de datos tiene un diseño muy poco útil en el que se requiere que la aplicación agregue una verificación "se elimine" en cada consulta solo para jugar la próxima partida.

Suponiendo que está hablando de RDBMS, aún puede tener una base de datos bitemporal que capture todos los datos válidos pasados, presentes y posiblemente futuros, y luego usar una estructura de biblioteca / acceso ORM suficientemente robusta para manejar la lógica de consulta de la base de datos por usted. Incluso puede usar una vista de base de datos para ayudar en su selección. Entonces, las partes de lógica de negocios de su código no deberían necesitar conocer los campos temporales subyacentes, lo que eliminará el problema que describió anteriormente.

Por ejemplo, en lugar de tener que codificar una consulta SQL en su aplicación:

SELECT * FROM team_players WHERE team_id = ? AND valid_from >= ? AND valid_to <= ? AND ...

(utilizando ?como enlaces de parámetros)

Una biblioteca de acceso a la base de datos hipotética podría permitirle consultar como (pseudocódigo):

dbConnection.select(Table.TEAM_PLAYERS).match(Field.TEAM_ID, <value>).during(Season.NOW)

O usando el enfoque de vista de base de datos:

-- Using MySQL dialect for illustration
CREATE VIEW seasonal_players AS SELECT * FROM team_players 
    WHERE valid_from >= ... AND valid_to <= ... AND ...

Luego puede consultar jugadores durante ese período como:

SELECT * FROM seasonal_players WHERE team_id = ?

Volviendo a su modelo de base de datos sugerido , ¿cómo piensa manejar la eliminación de datos históricos de su base de datos operativa? ¿Simplemente INSERTdos filas similares en sus bases de datos operativas e históricas cada una, y luego ejecuta una DELETEen la base de datos operativa cada vez que hay una acción del usuario para eliminar datos históricos?


Piensa en grande

Si está hablando del procesamiento de datos a gran escala en el que su 'base de datos' es una solución distribuida y escalable de agrupación de bases de datos / procesamiento de flujo, su enfoque sonará vagamente similar (probablemente solo en algunos de los términos definidos) al Lambda Arquitectura , en la que sus datos 'históricos' (es decir, en tiempo real pasado) se procesan por lotes por separado para realizar el tipo de estadísticas que está buscando, y sus datos 'operativos' (es decir, en tiempo real de transmisión) siguen siendo consultables un límite predefinido antes de que el procesamiento por lotes los persista. Sin embargo, la base de este enfoque se debe más a las ventajas y limitaciones de las implementaciones actuales de Big Data que a la simple simplificación de la lógica de la aplicación.


editar (después de editar OP)

Debería haber respondido a esto antes, pero de todos modos:

Además, recibo estos 2 requisitos como parte de una sola aplicación que no se puede dividir, pero sigo pensando: se trata de 2 aplicaciones completamente distintas. ¿Por qué estoy tratando de hacer todo juntos?

Esto generalmente se debe a que los usuarios finales tienden a pensar en términos de características, no en la cantidad de bases de datos requeridas .

También mencionas que:

Paso 1:

  • Cada vez que ocurre un evento, se registra automáticamente (solución de auditoría) en una tabla de eventos.
  • Luego se actualiza la fila correcta, en la base de datos operativa.

Paso 2:

  • Un trabajo lee la última inserción en la tabla de eventos e inserta estos nuevos datos en la base de datos histórica.

¡Excelente! Entonces, ahora tiene una tabla de eventos, que supongo que realmente es el concepto de fuente de eventos mencionado por las otras respuestas. Sin embargo, ¿qué es lo que lee su tabla de eventos y actualiza la fila correcta en la base de datos operativa ? ¿Es lo mismo que el trabajo que lee el último evento y lo inserta en la base de datos histórica ?

Un punto más con respecto a su ejemplo de restricción:

Hago un uso intensivo de la clave externa y la restricción para asegurarme de que en cualquier momento, los datos que están en mi base de datos sean válidos.

Veamos un ejemplo :

Restricción: solo puede haber un arquero por equipo. ( activo en el campo durante un juego, solo una nota al margen )

¿" Algún punto en el tiempo " se refiere a tiempo válido o tiempo de transacción ?

¿Qué sucede cuando tenemos un tercer nuevo portero? ¿Crea una restricción única en los campos temporales en la base de datos histórica para mantener válidos los datos de dos "viejos" porteros?

hjk
fuente
Antes de insertar / actualizar / eliminar algo, creo un evento que contiene toda la información necesaria para reconstruir los datos en este momento y luego simplemente actualizo la base de datos operativa. Después, agrego la información del evento a la base de datos histórica. El proceso es principalmente automático y apenas tiene nada que mantener.
Gudradain
1
Los eventos son creados por disparadores directamente en las tablas. O el evento se guarda y la fila se crea / actualiza / elimina o no sucede nada. Entonces sucede al mismo tiempo. De esta manera, se asegura de que se crea el evento y se asegura de que el evento intente hacer algo que sea válido según lo especificado por la relación y la restricción en la base de datos operativa.
Gudradain
1

En realidad, esto es similar a la forma en que generalmente se implementan las transacciones de la base de datos, excepto que los datos históricos generalmente se descartan después de escribirse en la base de datos operativa. El patrón de programación más cercano que se me ocurre es el abastecimiento de eventos .

Creo que dividir estas dos bases de datos es el movimiento correcto. Más específicamente, consideraría la base de datos "operativa" como un caché, ya que los datos históricos serán suficientes para reconstruir la base de datos operativa en cualquier momento. Dependiendo de la naturaleza de su aplicación y los requisitos de rendimiento, puede ser innecesario mantener este caché como una base de datos separada si es razonable reconstruir el estado actual de los datos históricos en la memoria cada vez que se inicia el programa.

En cuanto a las dificultades, el problema principal con el que se puede encontrar es si necesita algún tipo de concurrencia (ya sea en el mismo programa o haciendo que varios clientes usen la base de datos al mismo tiempo). En ese caso, querrá asegurarse de que las modificaciones a las bases de datos históricas y operativas se realicen atómicamente. En el caso de concurrencia dentro del mismo programa, su mejor opción es probablemente algún tipo de mecanismo de bloqueo. Para varios clientes que interactúan con la misma base de datos, la forma más fácil sería mantener ambas tablas en la misma base de datos y utilizar transacciones para mantener la base de datos coherente.

John Colanduoni
fuente
1
El abastecimiento de eventos es en realidad bastante cercano a lo que tengo actualmente Se genera automáticamente una pista de auditoría de cada inserción / actualización / eliminación que se puede comparar con la lista de eventos.
Gudradain