Excalidraw数据库存储结构解析:PostgreSQL与MongoDB的工程权衡
在远程协作工具日益成为技术团队“数字工作台”的今天,Excalidraw 这类轻量级、手绘风的虚拟白板正悄然改变着产品设计和系统架构的沟通方式。它看似简单——几笔线条、几个方框就能勾勒出复杂的系统拓扑或流程逻辑——但支撑其流畅协作体验的背后,是一套对数据一致性、实时同步和存储灵活性极高要求的技术体系。
尤其是随着 AI 辅助绘图、历史版本回溯、跨设备状态同步等功能的引入,底层数据持久化方案的选择不再只是“用哪个数据库”的问题,而演变为一场关于一致性 vs. 可扩展性、事务完整性 vs. 写入吞吐、结构稳定 vs. 模式演化的深度权衡。在这其中,PostgreSQL 与 MongoDB 成为两种最具代表性的技术路径。
PostgreSQL:以结构驾驭复杂性
当一个团队需要确保每一次画布更新都准确无误地被记录,并能与其他权限系统、审计日志无缝集成时,PostgreSQL 往往是首选。它不是最“快”的,但它足够“稳”。
它的核心优势不在于能否存下 JSON 数据,而在于如何在保持关系模型严谨性的同时,优雅地容纳非结构化内容。这正是JSONB类型的价值所在——它不是简单的字符串字段,而是可索引、可查询、可压缩的二进制 JSON 存储格式。
比如,在 Excalidraw 中,每个图形元素(element)都是一个包含type、x/y坐标、样式属性的 JSON 对象,多个元素组成数组。传统做法可能需要一张独立的elements表并关联外键,但在 PostgreSQL 中,我们可以直接将整个状态序列化为JSONB字段:
CREATE TABLE excalidraw_board ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), room_id VARCHAR(255) NOT NULL UNIQUE, title TEXT, elements JSONB NOT NULL, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), owner_id UUID REFERENCES users(id) );关键来了:我们并不希望每次查询都要扫描所有 JSON 内容。为此,PostgreSQL 提供了 GIN(Generalized Inverted Index)索引,能够对 JSONB 字段中的任意嵌套属性建立倒排索引。例如,若想快速找出所有包含矩形(rectangle)的画板:
CREATE INDEX idx_elements_type ON excalidraw_board USING GIN ((elements -> 'type')); SELECT id, title FROM excalidraw_board WHERE elements @? '$[*].type ? (@ == "rectangle")';这条语句利用了 SQL/JSON 路径表达式,结合@?操作符实现存在性判断。配合 GIN 索引后,即使elements数组长达数千项,也能在毫秒级完成匹配。
但这还不是全部。如果你的应用需要严格的访问控制,Row-Level Security(RLS)允许你基于当前用户身份动态过滤结果集;如果要追踪变更历史,WAL(Write-Ahead Log)不仅保障崩溃恢复,还可用于构建 CDC 流水线;甚至通过pg_trgm扩展,还能支持模糊搜索标题或标签。
从工程角度看,这种“混合模型”特别适合那些已有成熟用户系统、权限体系和运维规范的组织。你可以继续使用 Prisma 或 TypeORM 进行开发,借助 Flyway 管理迁移脚本,同时享受 JSONB 带来的灵活性。本质上,PostgreSQL 在告诉你:不必为了灵活性牺牲结构,也不必为了事务放弃半结构化数据。
MongoDB:让数据流动起来
如果说 PostgreSQL 是一位注重章法的建筑师,那 MongoDB 更像是一位即兴创作的画家——它不在乎你明天会不会改结构,只关心今天的协作是否够快。
它的文档模型天然契合 Excalidraw 的数据形态:一个画板就是一个文档,里面内嵌了一个元素数组。没有 JOIN,没有外键约束,写入即生效。更重要的是,它的 WiredTiger 存储引擎支持文档级锁,意味着两个用户同时修改不同房间的状态几乎不会产生竞争。
更吸引人的是Change Streams——这是 MongoDB 在实时协作场景下的“杀手锏”。无需额外部署 Kafka 或 Redis Pub/Sub,应用可以直接监听数据库层面的数据变更事件:
const changeStream = Board.watch(); changeStream.on('change', (change) => { if (change.operationType === 'update') { console.log(`Board ${change.documentKey._id} was updated`); // 推送至 WebSocket 客户端 } });这个机制极大简化了服务端推送逻辑。每当有人拖动一个形状,数据库更新触发 change event,后端立即广播给房间内其他成员,延迟可控制在百毫秒以内。相比之下,PostgreSQL 要实现类似功能,必须依赖LISTEN/NOTIFY或引入中间件,架构复杂度显著上升。
此外,MongoDB 的动态 schema 让迭代变得轻松。假设某天新增了 AI 自动生成图表的功能,需要在elements中加入generatedByAI: true字段?没问题,只需在代码中添加即可,无需执行ALTER TABLE或担心旧数据兼容性。
还有 TTL Index,对于临时协作房间尤其有用:
updatedAt: { type: Date, default: Date.now, expires: '7d' }只要设置过期时间,MongoDB 会自动清理陈旧文档,省去定时任务的维护成本。
当然,自由是有代价的。MongoDB 默认提供的是最终一致性,多文档事务虽已支持但性能开销较大;聚合查询能力虽强,但不如 SQL 直观;而且一旦涉及跨集合关联分析(如统计某用户创建的所有画板),就会暴露出 NoSQL 的短板。
因此,MongoDB 最适合那种以实时性为核心、数据结构尚不稳定、追求快速上线验证的项目。它降低了初期开发门槛,也让水平扩展(sharding)成为可能——当你预计单实例无法承载百万级活跃画板时,分片集群几乎是必然选择。
架构抉择:不只是数据库之争
让我们把视角拉回到整体系统流程:
[前端] ↔ [WebSocket 网关] ↔ [应用服务] ↔ [数据库]用户的每一次点击、拖拽都会生成增量 patch 发送到服务端。无论是 PostgreSQL 还是 MongoDB,都需要处理以下共性问题:
- 如何高效合并并发操作?(OT / CRDT)
- 如何减少网络传输负载?
- 如何实现撤销/重做?
- 如何管理历史快照?
但在数据库层,两者给出了截然不同的解法:
| 场景 | PostgreSQL 解法 | MongoDB 解法 |
|---|---|---|
| 小幅高频更新 | UPDATE ... SET elements = $1+ WAL 批刷优化 | 原地文档更新 + WiredTiger 高并发写入 |
| 实时同步 | 需配合 LISTEN/NOTIFY 或消息队列 | 内置 Change Streams,直连监听 |
| 版本管理 | 单独 history 表 + temporal 查询 | 使用 time-series collection 或定期 snapshot |
| 数据清理 | cron job 删除过期记录 | TTL index 自动过期 |
| 查询能力 | 支持复杂 SQL、JOIN、全文检索 | Aggregation Pipeline 强大,适合统计 |
可以看到,PostgreSQL 更像是“主动出击”——你需要自己设计通知机制、规划备份策略、优化索引结构;而 MongoDB 则倾向于“内置解决方案”,很多功能开箱即用,但也意味着你得接受它的运行范式。
工程建议:没有银弹,只有适配
那么,到底该选谁?
选 PostgreSQL 当:
- 团队熟悉 SQL 和事务模型;
- 系统需与现有用户中心、权限系统深度集成;
- 强调数据审计、合规性和长期可维护性;
- 已有基于 PostGIS、全文搜索等扩展需求;
- 偏好使用 ORM 加快开发节奏。
它更适合中大型企业、内部协作平台或需要长期运营的产品。你付出的是一定的架构复杂度,换来的是极高的可控性与稳定性。
选 MongoDB 当:
- 产品处于早期阶段,数据模型频繁变动;
- 实时协同是核心卖点,希望最小化推送延迟;
- 团队偏好 JS/Node.js 技术栈,倾向“文档即对象”的编程模型;
- 预期未来数据量巨大,需提前规划分片;
- DevOps 能力较强,能管理副本集与配置服务器。
它更适合初创项目、敏捷团队或面向公众的开放协作工具。你获得的是开发效率和横向扩展潜力,但也要准备好应对最终一致性的挑战。
高阶思路:混合持久化并非幻想
有趣的是,这两种路线并非完全对立。在一些高级架构中,已经开始出现“混合持久化”的实践:
- 使用PostgreSQL存储元信息:房间归属、用户权限、创建时间、访问日志;
- 使用MongoDB存储高频变更的画布状态和操作日志;
- 通过 CDC 工具(如 Debezium)将 MongoDB 的变更流导入分析系统;
- 或反过来,用 PostgreSQL 的 WAL 输出作为事件源。
这种方式实现了职责分离:关系型数据库管“人”和“权限”,文档数据库管“状态”和“动作”。既保留了事务完整性,又获得了高吞吐的写入能力。
甚至可以进一步设想:将最近 7 天的活跃画板放在 MongoDB 实例中,超过时限的自动归档到 PostgreSQL 的压缩表中用于审计查询——这就是真正的冷热分离。
结语:技术选择的本质是团队哲学
Excalidraw 的魅力,在于它用极简的形式承载复杂的思维。而它的数据库选型,则映射出团队对系统的根本认知。
选择 PostgreSQL,是你相信秩序带来可靠,愿意为一致性投入更多设计精力;
选择 MongoDB,是你相信速度驱动创新,愿意用一定的不确定性换取更快的迭代节奏。
无论哪条路,都没有绝对的对错。真正重要的,是清楚知道自己在为什么而优化:是为了让每一次头脑风暴都被精准记录?还是为了让每一场即兴共创都能即时发生?
在这个意义上,数据库不仅是存储引擎,更是工程价值观的体现。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考