Skip to content

增量同步

增量同步是 Luker 对 SillyTavern 数据传输架构的根本性改进,用增量 patch 替代全量覆盖,大幅降低带宽消耗并消除并发写入冲突。

问题背景

SillyTavern 的数据保存采用全量传输模式:每次修改(哪怕只是编辑一条消息的一个字),都会将整个聊天记录序列化后发送到后端覆盖写入。这带来了几个严重问题:

  • 带宽浪费 — 一个包含数百条消息的聊天记录可能有数百 MB(尤其是有些插件会在 chat metadata 里存储大量数据),每次操作都要传输全量数据
  • 写入冲突 — 多个标签页或设备同时操作时,后写入的会覆盖先写入的,导致数据丢失
  • 性能瓶颈 — 大型聊天记录的序列化和传输本身就是性能负担
  • 保存延迟 — 全量写入的 I/O 开销使得保存操作无法做到实时

增量端点

Luker 新增了三个增量端点,覆盖聊天数据的不同修改场景:

追加消息(append)

当用户发送新消息或 AI 生成新回复时,只需将新消息追加到聊天文件末尾,而非重写整个文件。这是最常见的操作路径,也是性能收益最大的场景。

后端收到请求后,直接将新消息追加到文件末尾,时间复杂度为 O(1),与聊天记录的总长度无关。后端还会对最后一条已存储消息进行去重检查,防止因网络重试导致的重复追加。

修补消息(patch)

当用户编辑已有消息(如修改内容、切换 swipe、更新消息元数据)时,按消息索引修补指定行,只更新变更的消息。

支持在一次请求中批量修补多条消息,并具有幂等性——自动检测已应用的操作,跳过重复提交。

更新元数据(patch-metadata)

当聊天的元数据(如标题、标签、聊天设置等)发生变化时,使用深度合并(deep merge)更新,而非替换整个元数据对象。

深度合并意味着只有请求中包含的字段会被更新,未提及的字段保持不变。这对于包含大量扩展元数据的聊天尤为重要。

Integrity Hash 并发冲突检测

每次写入操作完成后,后端会生成一个新的 UUID,写入聊天状态文件,同时在响应中返回。前端缓存该值,并在后续写入请求中携带:

  1. 匹配 — 正常执行写入,返回新的 integrity UUID
  2. 不匹配 — 返回 409 Conflict,表示文件在上次操作后已被其他来源修改

这从根本上防止了并发写入冲突——无论是多标签页、多设备还是多用户场景。

冲突处理

收到 409 Conflict 时,前端需要重新获取最新数据并基于最新状态重试。这确保了数据一致性,但也意味着在极端并发场景下用户可能需要等待重试完成。

设计参考

元数据更新端点的设计参考了 RFC 6902(JSON Patch)的理念,通过结构化操作实现增量更新。消息修补端点则针对行式存储做了简化——使用行索引定位,更适合聊天记录的线性结构。

设置数据的增量 Patch 保存

除了聊天数据,Luker 的设置数据(用户偏好、扩展配置、预设参数等)同样支持增量 patch 保存。设置变更通过深度合并应用到服务端的设置文件中,同样带有 integrity hash 冲突检测,避免并发修改导致设置丢失。

这意味着修改一个采样参数不再需要传输整个设置对象——只需发送变更的字段即可。

延迟触发机制

为了避免频繁的小修改产生过多的网络请求,增量同步实现了延迟触发(debounce)机制:

  • 短时间内的多次修改会被合并为一次 patch 请求发送
  • 用户连续编辑消息时,只有在停顿后才会触发实际的网络请求
  • 设置变更同样适用延迟合并,避免滑动条拖动等操作产生大量请求

延迟触发在减少网络开销的同时,不影响数据安全——因为后端实时存储确保了数据到达服务端后立即持久化。

与后端实时存储的协作

增量同步与后端实时存储紧密配合,形成完整的数据安全链路:

  1. 前端检测到数据变更
  2. 延迟触发合并短时间内的多次修改
  3. 发送增量 patch 请求(携带 integrity UUID)
  4. 前端通过序列化聊天写入流程将本地写任务串行发送
  5. 验证 integrity → 应用 patch → 持久化到磁盘 → 更新状态文件
  6. 返回新的 integrity UUID 供下次请求使用

这个流程确保了数据变更从前端到磁盘的完整链路,既高效又安全。

性能收益

对于一个包含 500 条消息的聊天记录,编辑一条消息时:SillyTavern 需要传输约 2-5 MB 的全量数据,而 Luker 只需传输约 1-2 KB 的 patch 数据。在移动网络或高延迟环境下,差异尤为明显。

相关页面

Built upon SillyTavern