Creé un sistema como este para una aplicación hace aproximadamente 8 años, y puedo compartir un par de formas en que ha evolucionado a medida que el uso de la aplicación ha crecido.
Comencé registrando cada cambio (insertar, actualizar o eliminar) de cualquier dispositivo en una tabla de "historial". Entonces, si, por ejemplo, alguien cambia su número de teléfono en la tabla "contacto", el sistema editará el campo contact.phone y también agregará un registro de historial con action = update, field = phone, record = [ID de contacto], valor = [nuevo número de teléfono]. Luego, cuando un dispositivo se sincroniza, descarga los elementos del historial desde la última sincronización y los aplica a su base de datos local. Esto suena como el patrón de "replicación de transacciones" descrito anteriormente.
Un problema es mantener las identificaciones únicas cuando se pueden crear elementos en diferentes dispositivos. No conocía los UUID cuando comencé esto, así que usé ID de incremento automático y escribí un código complicado que se ejecuta en el servidor central para verificar las nuevas ID cargadas desde los dispositivos, cambiarlas a una ID única si hay un conflicto, y dígale al dispositivo fuente que cambie la ID en su base de datos local. Simplemente cambiar las ID de los nuevos registros no fue tan malo, pero si creo, por ejemplo, un nuevo elemento en la tabla de contactos, luego creo un nuevo elemento relacionado en la tabla de eventos, ahora tengo claves externas que también necesito verificar y actualizar.
Finalmente, aprendí que los UUID podrían evitar esto, pero para entonces mi base de datos se estaba volviendo bastante grande y temía que una implementación completa de UUID creara un problema de rendimiento. Entonces, en lugar de usar UUID completos, comencé a usar claves alfanuméricas de 8 caracteres generadas aleatoriamente como ID, y dejé mi código existente para manejar conflictos. En algún lugar entre mis claves actuales de 8 caracteres y los 36 caracteres de un UUID debe haber un punto óptimo que elimine los conflictos sin una hinchazón innecesaria, pero como ya tengo el código de resolución de conflictos, no ha sido una prioridad experimentar con eso. .
El siguiente problema fue que la tabla de historial era aproximadamente 10 veces más grande que el resto de la base de datos. Esto hace que el almacenamiento sea costoso y cualquier mantenimiento en la tabla de historial puede ser doloroso. Mantener toda esa tabla permite a los usuarios revertir cualquier cambio anterior, pero eso comenzó a parecer excesivo. Así que agregué una rutina al proceso de sincronización donde si el elemento de historial que un dispositivo descargó por última vez ya no existe en la tabla de historial, el servidor no le proporciona los elementos de historial recientes, sino que le da un archivo que contiene todos los datos para esa cuenta. Luego agregué un cronjob para eliminar elementos del historial de más de 90 días. Esto significa que los usuarios aún pueden revertir los cambios de menos de 90 días de antigüedad, y si se sincronizan al menos una vez cada 90 días, las actualizaciones serán incrementales como antes. Pero si esperan más de 90 días,
Ese cambio redujo el tamaño de la tabla de historial en casi un 90%, por lo que ahora mantener la tabla de historial solo hace que la base de datos sea dos veces más grande en lugar de diez veces más grande. Otro beneficio de este sistema es que la sincronización aún podría funcionar sin la tabla de historial si fuera necesario, como si tuviera que hacer un mantenimiento que lo desconectó temporalmente. O podría ofrecer diferentes períodos de reversión para cuentas a diferentes puntos de precio. Y si hay más de 90 días de cambios para descargar, el archivo completo suele ser más eficiente que el formato incremental.
Si comenzara de nuevo hoy, omitiría la comprobación de conflictos de ID y solo apuntaría a una longitud de clave que sea suficiente para eliminar conflictos, con algún tipo de comprobación de errores por si acaso. Pero la tabla de historial y la combinación de descargas incrementales para actualizaciones recientes o una descarga completa cuando es necesario ha funcionado bien.