Я создал такую систему для приложения около 8 лет назад, и я могу поделиться несколькими путями, по мере того, как она росла, с ростом использования приложения.
Я начал с записи каждого изменения (вставки, обновления или удаления) с любого устройства в таблицу «истории». Так, если, например, кто-то изменит свой номер телефона в таблице «контакт», система отредактирует поле contact.phone, а также добавит запись истории с действием = обновление, поле = телефон, запись = [идентификатор контакта], значение = [новый номер телефона]. Затем, когда устройство синхронизируется, оно загружает элементы истории с момента последней синхронизации и применяет их к своей локальной базе данных. Это похоже на шаблон «репликации транзакций», описанный выше.
Одной из проблем является сохранение уникальных идентификаторов, когда элементы могут быть созданы на разных устройствах. Когда я начал это, я не знал об идентификаторах UUID, поэтому я использовал автоматически увеличивающиеся идентификаторы и написал несколько извилистых кодов, которые выполняются на центральном сервере для проверки новых идентификаторов, загруженных с устройств, изменения их на уникальные идентификаторы в случае конфликта и скажите исходному устройству изменить идентификатор в своей локальной базе данных. Простое изменение идентификаторов новых записей было не так уж плохо, но если я, например, создаю новый элемент в таблице контактов, затем создаю новый связанный элемент в таблице событий, теперь у меня есть внешние ключи, которые мне также нужны проверить и обновить.
В конце концов я узнал, что UUIDs могли бы избежать этого, но к тому времени моя база данных стала довольно большой, и я боялся, что полная реализация UUID создаст проблему производительности. Поэтому вместо использования полных UUID я начал использовать случайно сгенерированные 8-символьные буквенно-цифровые ключи в качестве идентификаторов и оставил свой существующий код на месте для обработки конфликтов. Где-то между моими нынешними 8-символьными клавишами и 36 символами UUID должно быть хорошее место, которое устраняло бы конфликты без ненужного раздувания, но, поскольку у меня уже есть код разрешения конфликтов, экспериментировать с этим не было приоритетом. ,
Следующая проблема заключалась в том, что таблица истории была примерно в 10 раз больше, чем вся остальная база данных. Это делает хранение дорогим, и любое обслуживание таблицы истории может быть болезненным. Сохранение всей этой таблицы позволяет пользователям откатывать любые предыдущие изменения, но это начинало казаться излишним. Поэтому я добавил подпрограмму в процесс синхронизации, где, если элемент истории, который было загружено последним устройством, больше не существует в таблице истории, сервер не передает ему последние элементы истории, а вместо этого предоставляет файл, содержащий все данные для этот аккаунт. Затем я добавил cronjob, чтобы удалить элементы истории старше 90 дней. Это означает, что пользователи по-прежнему могут откатывать изменения менее чем за 90 дней, и если они синхронизируются хотя бы раз в 90 дней, обновления будут, как и прежде, инкрементными. Но если они ждут дольше 90 дней,
Это изменение уменьшило размер таблицы истории почти на 90%, поэтому теперь ведение таблицы истории только увеличивает базу данных в два раза, а не в десять раз больше. Еще одним преимуществом этой системы является то, что синхронизация может по-прежнему работать без таблицы истории при необходимости - например, если мне нужно выполнить какое-то обслуживание, которое временно отключило ее. Или я мог бы предложить разные периоды отката для учетных записей в разных ценовых категориях. И если для загрузки требуется более 90 дней изменений, полный файл обычно более эффективен, чем инкрементный формат.
Если бы я начинал сегодня, я бы пропустил проверку конфликта ID и просто нацелился на длину ключа, достаточную для устранения конфликтов, с какой-то проверкой ошибок на всякий случай. Но таблица истории и комбинация дополнительных загрузок для последних обновлений или полной загрузки, когда это необходимо, работала хорошо.