Language:Chinese VersionEnglish Version

在过去的十五年里,我们将一切都迁移到了云端。文档、照片、代码、笔记、电子表格、设计文件、任务列表——所有这些数据都存储在别人的服务器上,通过浏览器访问,依赖于网络连接以及 SaaS 提供商的持续支持。

对于许多应用来说,这种架构是完全合理的。但越来越多的开发者、研究人员和公司开始提出一个”异端”问题:如果数据首先存在于用户的设备上,而云只是一个便捷的同步层,会怎样?

这就是本地优先运动,它由冲突解决算法、同步引擎和离线架构等实际技术进步所推动,使得构建以用户设备为数据源头的软件变得切实可行。

本地优先原则

“本地优先”这一术语由 Ink & Switch 的 Martin Kleppmann、Adam Wiggins、Peter van Hardenberg 和 Mark McGranaghan 在 2019 年的一篇研究论文中确立。他们为本地优先软件概述了七项原则:

  • 没有加载指示器。 应用程序即时响应,因为操作在本地数据上进行。网络延迟永远不会成为用户交互的关键路径。
  • 您的工作不会被困在单一设备上。 数据在您的所有设备间无缝同步。
  • 网络是可选的。 应用程序完全离线时也能正常工作。网络连接可以增强体验(同步、协作),但不是核心功能所必需的。
  • 无缝协作。 多个用户可以同时处理相同的数据,冲突会自动解决。
  • 长期可用。 您的数据可以访问数十年,不依赖于公司维护服务器。
  • 默认的安全与隐私。 数据可以进行端到端加密,因为服务器不需要读取它——只需要中继加密的同步消息。
  • 用户保留所有权和控制权。 数据不会被服务商的服务条款或商业可行性所挟持。

这些原则之所以引起共鸣,是因为它们描述了我们希望从软件中获得但很少能体验到的体验。大多数云应用程序违反了多项这些原则:它们显示加载指示器,需要互联网访问,并将您的数据困在您无法控制的专有服务器格式中。

CRDTs:核心技术

本地优先软件的基本技术挑战是冲突解决。当两个用户在不同设备上离线编辑同一文档,然后重新连接时会发生什么?传统方法要么锁定文档(悲观并发),要么需要手动解决冲突(git 式合并冲突),要么使用最后写入获胜的语义(丢失数据)。

无冲突复制数据类型(CRDTs)从数学上解决了这个问题。CRDT 是一种数据结构设计,使得任何接收到相同更新集的两个副本都会收敛到相同状态,无论接收更新的顺序如何。无需协调,无需锁定,无需手动解决冲突。

CRDT 如何工作(简述)

CRDT 的直观理解是某些操作自然可交换。如果 Alice 在购物清单中添加”牛奶”,Bob 添加”鸡蛋”,无论哪个操作先应用,结果都应该包含这两项。基于集合的 CRDT(称为 G-Set 或仅增长集合)捕捉了这一点:添加总是可以安全地合并。

对于更复杂的数据类型,CRDT 使用唯一标识符、逻辑时间戳和因果顺序的巧妙组合来确保收敛。例如,文本 CRDT 为每个字符分配一个唯一的位置标识符,该标识符确定其相对于其他字符的顺序。两个用户在不同位置输入都可以插入字符,CRDT 保证一致的合并结果。

// 概念示例:一个简单的计数器 CRDT
// 每个节点维护自己的计数器
// 全局值是所有节点计数器的总和

class GCounter {
  constructor(nodeId) {
    this.nodeId = nodeId;
    this.counts = {};  // nodeId -> count
  }

  increment() {
    this.counts[this.nodeId] = (this.counts[this.nodeId] || 0) + 1;
  }

  value() {
    return Object.values(this.counts).reduce((sum, n) => sum + n, 0);
  }

  merge(other) {
    // 取每个节点计数器的最大值
    for (const [nodeId, count] of Object.entries(other.counts)) {
      this.counts[nodeId] = Math.max(this.counts[nodeId] || 0, count);
    }
  }
}

关键见解是 merge 函数:它取每个节点计数器的最大值。这个操作是可交换的(顺序无关)、可结合的(分组无关)和幂等的(应用相同的合并两次没有额外效果)。这三个属性保证了收敛。

文本 CRDT 的演进

最困难且商业上最重要的 CRDT 问题是协作文本编辑。早期的文本 CRDT(如 Logoot 和 LSEQ)能够工作,但在处理大文档时存在性能问题。该领域已取得显著进展:

  • Yjs 使用高度优化的 CRDT 实现,其性能可与操作转换(OT)等集中式方法相媲美。其内部编码足够紧凑,可用于大型文档的实时协作编辑。
  • Automerge 提供了一个通用 CRDT 库,支持文本、类 JSON 文档、列表和映射。最新版本(Automerge 2)通过 Rust 核心和语言绑定显著提高了性能。
  • Diamond Types 是 Joseph Gentle 的一个研究项目,已经证明 CRDT 性能可以匹配或超过 OT 实现,挑战了 CRDT 在文本编辑中固有较慢的长期假设。

同步引擎格局

CRDT 处理冲突解决,但一个完整的本地优先应用还需要同步——即在不同设备和用户之间传输数据的机制。已经出现了几种同步引擎,各有不同的权衡:

Automerge

Automerge 既是 CRDT 库也是同步协议。它提供基于类 JSON 数据模型的文档级 CRDT,使其适合处理结构化数据的应用程序。同步协议效率很高,只传输对方尚未看到的操作。

Automerge 非常适合数据模型自然映射到 JSON 文档的应用:笔记应用、项目管理工具和设计工具。其 Rust 实现带有 JavaScript、Python 和 Swift 绑定,使其成为跨平台解决方案。

Yjs

Yjs 已成为 Web 应用实时协作编辑的事实标准。Notion(部分)、AFFiNE、BlockSuite 和许多其他协作编辑器都使用了 Yjs。Yjs 提供共享类型(文本、数组、映射、XML)和基于提供者的架构,可以插入不同的网络传输方式。

import * as Y from 'yjs'
import { WebsocketProvider } from 'y-websocket'
import { IndexeddbPersistence } from 'y-indexeddb'

const doc = new Y.Doc()

// 本地持久化 - 支持离线工作
const indexeddbProvider = new IndexeddbPersistence('my-document', doc)

// 网络同步 - 在可用时
const wsProvider = new WebsocketProvider(
  'wss://sync.example.com', 'my-document', doc
)

// 具有 CRDT 冲突解决的共享文本类型
const ytext = doc.getText('content')
ytext.insert(0, 'Hello, local-first world!')

Yjs 的优势在于其生态系统:与 ProseMirror、TipTap、CodeMirror、Monaco、Quill 和其他流行编辑器都有集成。

ElectricSQL

ElectricSQL 采用了不同的方法,它将 PostgreSQL 数据同步到本地 SQLite 数据库。它不需要开发者采用新的数据结构(CRDTs),而是让他们使用熟悉的 SQL 并透明地处理同步。这对于已有 PostgreSQL 后端并希望添加离线功能的应用来说特别有吸引力。

权衡之处在于 ElectricSQL 的冲突解决比通用 CRDTs 更受限制。它在关系数据的语义范围内工作,这对许多应用来说很强大,但对于自由形式的协作编辑则不够灵活。

PowerSync

PowerSync 在理念上与 ElectricSQL 类似——将服务器端数据库同步到本地 SQLite——但它支持多种后端数据库(PostgreSQL、MongoDB、MySQL),并为 React Native、Flutter 和 Web 应用提供 SDK。它在移动开发社区特别受欢迎,因为离线功能通常是硬性要求。

使用本地优先架构的实际应用

Linear

Linear 已成为许多工程团队默认选择的项目管理工具,它基于本地优先原则构建。应用能够即时加载,因为数据缓存在本地。创建问题、更新状态和重组项目等操作在本地存储上立即执行,并在后台同步。结果是一个界面,感觉比传统的基于云的项目管理工具快得多。

Figma

虽然 Figma 主要基于云端,但其实时协作引擎使用了受 CRDT 启发的技术进行冲突解决。多个设计师可以同时编辑同一个文件而无需锁定,更改会自动合并。Figma 的方法表明,即使在不是纯粹本地优先的架构中,也可以应用 CRDT 概念。

Obsidian

Obsidian 是一个知识管理工具,它将笔记作为纯 Markdown 文件存储在用户的本地文件系统中。它可以完全离线工作,其可选的 Obsidian Sync 服务提供跨设备的端到端加密同步。这种架构让用户完全拥有自己的数据——笔记只是文件,可以用任何文本编辑器读取,独立于 Obsidian 的持续存在。

AFFiNE

AFFiNE 是 Notion 的开源替代品,它使用 Yjs 和 BlockSuite 构建在本地优先架构上。文档存储在本地并通过基于 CRDT 的协议同步,提供离线功能和实时协作,同时不牺牲数据所有权。

本地优先应用的架构

构建本地优先应用需要重新思考几个架构假设:

本地存储作为事实来源

应用程序从本地数据库读取和写入数据(浏览器中为 IndexedDB,移动和桌面端为 SQLite)。所有用户交互都是本地操作,可立即完成。网络仅用于同步。

同步层

后台同步进程将本地更改发送到其他设备,并将远程更改应用到本地存储。同步协议必须处理间歇性连接、部分同步和无序交付。

冲突解决

CRDT(无冲突复制数据类型)或受 CRDT 启发的算法确保并发编辑能够确定性合并。应用程序必须经过设计,使得 CRDT 语义能够为用户的使用场景产生合理的结果。

身份验证和授权

由于服务器是同步中继而非应用后端,身份验证和授权模型也随之改变。服务器需要知道哪些用户可以同步哪些文档,但不需要验证业务逻辑——这些操作在本地完成。

权衡与挑战

本地优先并非没有代价。诚实的从业者会承认几个挑战:

  • 存储限制。本地设备存储空间有限。处理大型数据集(媒体库、数据分析)的应用可能无法在所有设备上舒适运行。
  • 初始同步。当用户设置新设备时,所有数据都需要同步。对于大型数据集,这可能既缓慢又消耗大量带宽。
  • 服务器端计算。某些操作(搜索所有文档、分析、机器学习)需要访问完整数据集,而这些数据可能仅在服务器端可用。
  • CRDT 复杂性。尽管使用 Yjs 和 Automerge 等库使得 CRDT 应用变得更容易,但理解其语义和调试合并行为比使用集中式数据库更困难。
  • 访问撤销。一旦数据在用户设备上,您无法单方面撤销访问。这既是功能(用户所有权)也是挑战(如 GDPR 删除权等合规要求)。
  • 模式演进。在分布式本地数据库之间迁移数据模式比在中央服务器上运行迁移更困难。

何时选择本地优先而非云优先

在以下情况下,本地优先最为合理:

  • 用户体验依赖低延迟(创意工具、笔记、项目管理)。
  • 离线访问很重要(移动应用、现场工作、旅行)。
  • 数据隐私是主要关注点(个人知识管理、健康数据、财务记录)。
  • 数据持久性很重要(个人档案、研究笔记、法律文件)。
  • 每个用户的数据集可管理(文档而非 PB 级数据湖)。

在以下情况下,云优先仍然是更好的选择:

  • 应用需要服务器端计算(机器学习推理、重型分析)。
  • 数据本质上是共享和集中的(社交媒体信息流、市场列表)。
  • 监管要求需要集中审计跟踪和访问控制。
  • 数据集太大,无法本地存储。
  • 需要实时协调且具有严格一致性(金融交易、库存管理)。

本地优先的未来

多种趋势正在加速本地优先的采用。设备存储和处理能力持续提升,使本地计算变得更加实用。WebAssembly 使在浏览器中高效运行复杂数据结构(包括基于 Rust 的 CRDT 实现)成为可能。边缘计算模糊了本地与云之间的界限,能够利用附近的边缘节点实现同步架构,从而降低延迟。

工具链也在迅速成熟。两年前,构建本地优先应用需要深厚的分布式系统专业知识。如今,像 Yjs、Automerge、ElectricSQL 和 PowerSync 这样的库已经将抽象层次提升到足够高的水平,应用开发者可以专注于产品本身,而不是同步机制。

结论

本地优先软件并非要拒绝云。它旨在将用户的设备重新置于体验的中心,并将云作为基础设施使用,而不是作为每次交互的强制中介。

技术基础——CRDT、同步引擎、离线存储——已经足够成熟,可以广泛应用于各类应用。用户体验的好处是实实在在的:即时交互、离线能力和数据所有权。不断发展的工具和库生态系统意味着你不再需要分布式系统博士学位就能构建本地优先软件。

并非每个应用都应该是本地优先的。但如果你的用户能从即时响应、离线访问和真正的数据所有权中受益,那么技术已经准备就绪。云并不总是答案,而我们终于有了证明这一点所需的工具。

By Michael Sun

Founder and Editor-in-Chief of NovVista. Software engineer with hands-on experience in cloud infrastructure, full-stack development, and DevOps. Writes about AI tools, developer workflows, server architecture, and the practical side of technology. Based in China.

Leave a Reply

Your email address will not be published. Required fields are marked *

You missed