Veamos como hacer memoria transaccional: si queremos realizar una operación, iniciamos una transacción, dentro de la transacción no modificamos datos, en su lugar se escriben un log de lectura y un log de escritura de datos... una vez termina la operación, terminamos la transacción, y se puede verificar si alguno de los datos datos del log de lectura cambió. Si hubo cambios, la transacción falla, en caso contrario se pueden escribir los datos del log de escritura (lo que puede significar que otras transacciones fallen). Mucho más interesante, podemos asignar un instante a la transacción, luego utilizando el log de escritura y este momento asignado a la transacción decidimos cual transacción interactua primero... lo que permite saber de forma temprana si una transacción va a fallar. Este modelo permite realizar transacciones en sistemas que no permiten realizar bloqueos en datos específicos.
Nota: No podemos utilizar el momento asignado a la transacción para serializarlas y ejecutarlas todas en orden, la razón es que no sabemos el resultado de las transacciones que no han terminado, y por tanto no podemos leer los datos que aun no han escrito. Intentar hacer eso implicaría: 1) Mover el código de las transacciones a la base de datos, 2) Que este código funcione independientemente de otros sistemas, 3) No tener conocimiento del momento en que se ejecutan. 4) Ejecutar las transacciones de forma secuencial, perdiendo el paralelismo. Es posible hacer eso, y lograr transacciones que no fallan... sin embargo no lo considero una buena idea porque: 1) Reduce la versatilidad de las transacciones, y 2) se ejecutan de forma secuencial. Esto seguirá siendo cierto si usamos operaciones idempotentes.
Lo interesante de este sistema (que fue diseño para funcionar en RAM) es que trabaja con logs de operaciones sobre los datos. Que es precisamente lo que tenemos en un marco de trabajo de eventos. Eso significa que es posible implementar transacciones sobre bases de datos distribuidas, con características similares a las que tenemos en bases de datos convencionales.
Digamos que asignamos un nodo cada tabla (o conjunto de tablas), pero una de estas tablas crece mucho y decidimos agregar un nuevo nodo. Luego, la tabla existe en dos nodos. Ahora, si uno d estos nodos se desconecta, datos dejan de existir desde el punto de vista de la aplicación. Esto puede ser una violación de integridad (una relación foránea, por ejemplo). Si luego agregamos datos al nodo que queda, que ya existían en el otro nodo, y ese otro nodo vuelve a conectarse... terminamos con otra violación de integridad (de la llave primaria).
Ya que algo puede terminar existiendo dos veces... asumamos que las cosas pueden existir dos veces. Manteniendo copias redundantes de todos los datos, evitamos que los datos desaparezcan si se desconecta un nodo. Por otro lado, es mejor tener una especificación clara de que nodo tiene que datos, debido a esto, al conectar un nuevo nodo, lo primero que debe hacer es copiar los datos que le corresponde copiar.
Un buen sistema de base de datos distribuido debe - aprender de git y - tener un recolector de basura. Si cada operación que se escribe es una modificación (por ejemplo, para eliminar se escribe una eliminación) se genera basura. Además, decidir si es mejor guardar un registro de modificaciones o copias de cada versión depende de los datos en particular (por ejemplo, para un registro grande, será más eficiente guardar diferencias - hasta cierto punto - para uno pequeño será mejor guardar copias) y además los registros de cambios muy antiguos no serán usados y se pueden eliminar.
Comentarios
Publicar un comentario