Я створив подібну систему для додатка близько 8 років тому, і я можу поділитися кількома способами, які розвивалися в міру зростання використання додатків.
Я почав із реєстрації кожної зміни (вставлення, оновлення чи видалення) з будь-якого пристрою в таблицю "історії". Так, якщо, наприклад, хтось змінить свій номер телефону в таблиці "контакт", система редагує поле contact.phone, а також додасть запис історії з діями = оновлення, поле = телефон, запис = [контакт ID], value = [новий номер телефону]. Потім кожен раз, коли пристрій синхронізується, він завантажує елементи історії з часу останньої синхронізації та застосовує їх до своєї локальної бази даних. Це звучить як описаний вище шаблон "реплікації транзакції".
Одне питання полягає у збереженні унікальних ідентифікаторів, коли елементи можна створювати на різних пристроях. Коли я починав це, я не знав про UUID, тому я використав автоматичні посилення ідентифікаторів і написав зведений код, який працює на центральному сервері, щоб перевірити нові ідентифікатори, завантажені з пристроїв, змінити їх на унікальний ідентифікатор, якщо є конфлікт, і скажіть пристрою-джерелу змінити ідентифікатор у його локальній базі даних. Просто зміна ідентифікаторів нових записів було не так вже й погано, але якщо я створив, наприклад, новий елемент у таблиці контактів, то створив новий пов’язаний елемент у таблиці подій, тепер у мене є зовнішні ключі, які мені також потрібно перевірити та оновити.
Врешті-решт я дізнався, що UUID можуть цього уникнути, але до того моєї бази даних стало досить багато, і я побоювався, що повне впровадження UUID створить проблему з ефективністю. Тому замість використання повних UUID я почав використовувати випадково згенеровані 8-символьні буквено-цифрові ключі як ідентифікатори, і я залишив свій існуючий код на місці для вирішення конфліктів. Десь між моїми поточними 8-символьними клавішами та 36 символами UUID повинно бути приємне місце, яке б усувало конфлікти без зайвого нахилу, але оскільки у мене вже є код вирішення конфлікту, експеримент із цим не було пріоритетним завданням .
Наступною проблемою було те, що таблиця історії була приблизно в 10 разів більша, ніж уся решта бази даних. Це робить зберігання дорогим, і будь-яке обслуговування в таблиці історії може бути болючим. Зберігання цілої таблиці дозволяє користувачам відмовляти будь-які попередні зміни, але це почало відчувати себе надмірними. Тому я додав процедуру до процесу синхронізації, коли, якщо елемент історії, який останній завантажений пристрій більше не існує в таблиці історії, сервер не дає йому останніх елементів історії, а натомість надає йому файл, що містить усі дані для цей рахунок. Потім я додав cronjob, щоб видалити предмети історії, старші 90 днів. Це означає, що користувачі все ще можуть відмовляти зміни, які не досягли 90 днів, і якщо вони синхронізуються щонайменше раз на 90 днів, оновлення будуть поступовими, як і раніше. Але якщо вони чекають довше 90 днів,
Ця зміна зменшила розмір таблиці історії майже на 90%, тому тепер підтримка таблиці історії робить базу даних лише вдвічі більшою, а не в десять разів більшою. Ще одна перевага цієї системи полягає в тому, що синхронізація все ще може працювати без таблиці історії, якщо це потрібно - наприклад, якщо мені потрібно було виконати деяке обслуговування, яке тимчасово відключило її. Або я можу запропонувати різні періоди відкату рахунків за різними ціновими точками. Якщо для завантаження є більше 90 днів змін, повний файл, як правило, є більш ефективним, ніж інкрементальний формат.
Якби я починав сьогодні, я би пропустив перевірку конфліктів ідентифікаторів і просто націлювався на ключову довжину, достатню для усунення конфліктів, з певною перевіркою помилок на всякий випадок. Але таблиця історії та комбінація поступових завантажень для останніх оновлень або повного завантаження при необхідності працює добре.