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