大约 8 年前,我为一个应用程序构建了一个这样的系统,我可以分享它随着应用程序使用量的增长而演变的几种方式。
我首先将来自任何设备的每个更改(插入、更新或删除)记录到“历史”表中。因此,例如,如果有人更改了“联系人”表中的电话号码,系统将编辑 contact.phone 字段,并添加一条历史记录 action=update, table=contact, field=phone, record=[联系人 ID],值 = [新电话号码]。然后,每当设备同步时,它都会下载自上次同步以来的历史记录项并将它们应用到其本地数据库。这听起来像上面描述的“事务复制”模式。
一个问题是当项目可以在不同的设备上创建时保持 ID 的唯一性。当我开始这个时我不知道 UUID,所以我使用自动递增 ID 并编写了一些在中央服务器上运行的复杂代码来检查从设备上传的新 ID,如果有冲突,将它们更改为唯一 ID,并且告诉源设备更改其本地数据库中的 ID。只是更改新记录的 ID 并没有那么糟糕,但是如果我在联系人表中创建一个新项目,然后在事件表中创建一个新的相关项目,现在我有我还需要的外键检查和更新。
最终我了解到 UUID 可以避免这种情况,但是到那时我的数据库变得非常大,我担心完整的 UUID 实现会产生性能问题。因此,我没有使用完整的 UUID,而是开始使用随机生成的 8 个字符的字母数字键作为 ID,并保留现有代码以处理冲突。在我当前的 8 个字符键和 UUID 的 36 个字符之间的某个地方,一定有一个最佳点可以消除冲突而不会产生不必要的膨胀,但由于我已经有了冲突解决代码,所以没有优先尝试这个.
下一个问题是历史表大约是数据库其余部分的 10 倍。这使得存储变得昂贵,并且对历史表的任何维护都可能很痛苦。保留整个表允许用户回滚之前的任何更改,但这开始感觉像是矫枉过正。所以我在同步过程中添加了一个例程,如果设备上次下载的历史项目不再存在于历史表中,服务器不会给它最近的历史项目,而是给它一个包含所有数据的文件那个帐户。然后我添加了一个 cronjob 来删除超过 90 天的历史记录项。这意味着用户仍然可以回滚不到 90 天的更改,如果他们至少每 90 天同步一次,更新将像以前一样是增量的。但如果他们等待的时间超过 90 天,
这一更改将历史记录表的大小减少了近 90%,因此现在维护历史记录表只会使数据库大两倍而不是十倍。这个系统的另一个好处是,如果需要,同步仍然可以在没有历史记录表的情况下工作——就像我需要做一些暂时离线的维护一样。或者我可以为不同价位的账户提供不同的回滚时间段。而且如果要下载超过 90 天的更改,完整的文件通常比增量格式更有效。
如果我今天重新开始,我会跳过 ID 冲突检查,只针对一个足以消除冲突的密钥长度,并进行某种错误检查以防万一。(看起来 YouTube 使用 11 个字符的随机 ID。)历史记录表以及最近更新的增量下载或需要时的完整下载的组合运行良好。