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.
Respuestas:
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.
fuente
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).
fuente
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:
Ahora...
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:
(utilizando
?
como enlaces de parámetros)Una biblioteca de acceso a la base de datos hipotética podría permitirle consultar como (pseudocódigo):
O usando el enfoque de vista de base de datos:
Luego puede consultar jugadores durante ese período como:
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
INSERT
dos filas similares en sus bases de datos operativas e históricas cada una, y luego ejecuta unaDELETE
en 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:
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:
¡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:
¿" 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?
fuente
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.
fuente
El soporte de versiones de datos en bases de datos es un tema bien establecido y algunos DBMS admiten esta característica. Recuerdo haber leído que MariaDB admite el control de versiones de datos ( https://mariadb.com/resources/blog/automatic-data-versioning-in-mariadb-server-10-3/ ) y una búsqueda rápida descubrió algo llamado OrpheusDB ( https: / /medium.com/data-people/paless-data-versioning-for-collaborative-data-science-90cf3a2e279d )
fuente