惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Microsoft Azure Blog
Microsoft Azure Blog
博客园 - 三生石上(FineUI控件)
WordPress大学
WordPress大学
人人都是产品经理
人人都是产品经理
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 聂微东
Jina AI
Jina AI
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
T
Tailwind CSS Blog
罗磊的独立博客
爱范儿
爱范儿
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Apple Machine Learning Research
Apple Machine Learning Research
博客园 - Franky
阮一峰的网络日志
阮一峰的网络日志
雷峰网
雷峰网
博客园 - 叶小钗
美团技术团队
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
月光博客
月光博客
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
大猫的无限游戏
大猫的无限游戏
The Cloudflare Blog
Last Week in AI
Last Week in AI
S
SegmentFault 最新的问题
博客园 - 【当耐特】
小众软件
小众软件
Hugging Face - Blog
Hugging Face - Blog
量子位
宝玉的分享
宝玉的分享
V
Visual Studio Blog
博客园_首页
IT之家
IT之家
V
V2EX
腾讯CDC
aimingoo的专栏
aimingoo的专栏
博客园 - 司徒正美
Microsoft Security Blog
Microsoft Security Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Blog — PlanetScale
Blog — PlanetScale
I
InfoQ
有赞技术团队
有赞技术团队
J
Java Code Geeks
Recorded Future
Recorded Future
Engineering at Meta
Engineering at Meta
Vercel News
Vercel News
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
H
Help Net Security

See you soon

哟,好久不见,无线打印 | See you soon 使用 Quadlet 将 Podman 中的 Postgres 当作 systemd 服务运行 | See you soon 大他者,那个无时无刻都在盯着你的东西 | See you soon Laws of Software Engineering,软件工程定律 | See you soon 浅记多因素身份认证 | See you soon Linux 内核中的度量单位 | See you soon 重置 GPG 智能密钥 | See you soon 向 NAS 引入 samba | See you soon 无法重复键入的 Fcitx5 | See you soon ZFS 降级事故 | See you soon 记被 XanMod Kernel 和 AppArmor 联合坑的一次踩坑 | See you soon agent 的 skill 与 toolcall | See you soon 记一次服务器被挂恶意挖矿二进制 | See you soon 活着的 Arc | See you soon 令 acme.sh 使用 Cloudflare 的 DNS API 签发与续签证书 | See you soon 于 Tokio 中卸载 CPU Bound 任务 | See you soon 如我所见,梦破碎的时候 | See you soon 74LS 家族手册 | See you soon JDK Projects 备忘录 | See you soon 关于历史 | See you soon 用 curl 下载 OnePlus 的 ROM | See you soon 实用命令切片 | See you soon 再见,Oh My Zsh。 | See you soon 你不应该复用 strings.Builder | See you soon 博客的明日 | See you soon 被 AppArmor 击杀的 Dockge | See you soon AI 时代的自我 | See you soon 支持删除的布隆过滤器 | See you soon 基于栈的虚拟机与基于寄存器的虚拟机 | See you soon RVA23 包含了什么 | See you soon Rust 的边界检查是否已经有很大的进步 | See you soon 使用 Rust 编写操作系统:Barebones | See you soon 使用 Rust 编写操作系统:引言 | See you soon 编译器笔记:rope | See you soon 编译器笔记:CST | See you soon Paradoxical 札记 | See you soon 简单的相似去重算法(基于向量) | See you soon 更加现代的 PaperMC Minecraft 插件设计指南 | See you soon 简单的 CFG 语法分析方法 | See you soon 简单地使用 Caddy 实现 CORS 配置 | See you soon 让 OpenCV 可以被静态链接 | See you soon 图片搜索笔笺 | See you soon 电路板设计笔记-保护 | See you soon 使用 Rust 实现 SnowflakeId | See you soon 农夫乐事 FaQ 启示 | See you soon 使用 Rust 实现拓展系统札记 | See you soon 遗传学定律的代码实现 | See you soon EN:The Journey of Rust and Procps | See you soon ZH:Rust 与 Procps 之旅 | See you soon 使用 debootstrap 与 schroot 构建一个纯净环境 | See you soon GPG添加新的用户信息 | See you soon GSoC2024 笔记:使用 Rust 重新实现 procps | See you soon JWT 小册 | See you soon 使用密钥登录 SSH | See you soon Guava:Cache | See you soon 编译器笔记:增量编译 | See you soon Gradle秘境:添加一个类似modCompileOnly的依赖块 | See you soon Minecraft:原始经济系统设计简述 | See you soon TinyRemapper笔记 | See you soon MicroOS:进阶 | See you soon MicroOS:起步 | See you soon Swapfile 指北 | See you soon GPG 物理密钥的安装与密钥的迁移 | See you soon 用GPG签名告诉大家这就是我的提交 | See you soon DFS:深度优先搜索 | See you soon 文章翻译:从init.vim到init.lua | See you soon 目录遍历攻击 | See you soon 如何挂载.img文件,以及如何使用QEMU模拟arm64环境 | See you soon 在树莓派上编译 OpenCV | See you soon 关于卸载BlueStacks后遇到的问题 | See you soon
试试将文章版本化管理吧 | See you soon
Krysztal Huang · 2026-07-12 · via See you soon

试试将文章版本化管理吧

因为某个业务需求所以需要设计一个对于文章的版本管理,既然是版本管理…那是不是可以借鉴一下 git

参照实现

对于版本化,我们有个相当标准的参考实现:git

git 对一个 repository 的抽象为:repository 的各个变更为具体的 object,而使用一个版本指针来管理

简单来讲,就是 thin ref + immutable snapshot

Thin Ref

通过 Thin Ref 可以构建一个索引项的历史变更

Thin Ref 是内容的稳定身份标识,本身不保存业务数据,只记录:

  • 这个内容项是什么时候被创建的
  • 是谁创建了它
  • 它指向哪些历史快照

它的作用类似于 git 里的 branch/tag,或者文件系统里的 inode。外部引用永远只指向这个 Ref,Ref 下面的具体版本可以任意增减、切换状态,而不会因为版本的更替导致外部链接失效。

Immutable Snapshot

不可变快照应当是单调递增的——不可变快照的意思是:内容不能发生变化,但是元数据可以。

具体来说:

  • 内容字段不可变:标题、正文、描述、关联资源等一旦写入就不会再被修改
  • 元数据可变:版本的 workflow 状态(pendingpublishedarchived)可以流转

每个 Snapshot 都是一次编辑意图的完整封存。想要修改内容,必须创建一个新的 Snapshot,而不是覆盖旧的。

数据模型

因为这个数据模型略微抽象,因此我们引入一个简单的例子使得其更好理解:对于文章的版本化管理

  • 第一次保存时,系统为他创建一个文章身份(Ref),同时生成第一个草稿版本(Snapshot)
  • 他继续修改标题和正文,系统不会覆盖旧版本,而是新增第二个 Snapshot
    • 这是这个例子中最重要的行为假设
  • 他觉得第二个版本可以发布,于是把它标为 published;第一个版本仍保留为 pending 或后续可归档
  • 过几天他又修改了内容,新增第三个 Snapshot;发布第三个版本时,第二个版本自动降级为 archived
  • 自始至终,外部链接一直指向最开始创建的那个 Ref ID,不会因为版本切换而失效

这个场景里的两类对象,对应到数据模型就是两张核心表:

职责关键字段
refs / listing_refs稳定身份与历史入口id, created_by, created_at
snapshots / listings不可变的版本内容id, ref_id, title, description, content, resource_ids, changed_by, created_at, status

Ref 表

Ref 表非常薄,只负责内容的身份。

  • id:全局唯一,外部系统引用的就是这个 ID
  • created_by / created_at:记录内容项的诞生信息
  • 不保存任何业务内容

Snapshot 表

Snapshot 表保存每一次变更后的完整内容。

  • id:版本自己的唯一标识
  • ref_id:属于哪个 Ref
  • title / description / content / resource_ids:这次版本的完整内容
  • changed_by:谁创建了这个版本
  • created_at:版本创建时间
  • status:当前 workflow 状态

注意:ref_id 同一个时刻下只允许存在一个 published 版本。新的版本发布时,旧的 published 版本会转为 archived

版本状态机

每个 Snapshot 都处于以下三种状态之一:

状态含义
pending草稿,未发布,仅创建者可见或用于审核
published已发布,是当前对外提供的内容
archived已归档,曾经是 published,被新版本替换后进入此状态

状态流转规则

stateDiagram-v2

[*] --> pending : 创建新版本

pending --> published : 发布

published --> archived : 新版本发布

archived --> published : 重新设为当前版本

  • pending → published:审核通过或创建者主动发布
  • published → archived:当同一个 Ref 下出现新的 published 版本时,旧的 published 自动降级为 archived
  • 不直接从 pending → archived,也不允许 published 回退到 pending
  • 不允许同一个用户同时存在多个 pending 版本,避免草稿堆积和并发冲突

读写语义

写入

所有写入操作都只会新增 Snapshot,不会修改已有 Snapshot。

  • 首次创建:在 Ref 表中插入一条新记录,同时生成第一个 pending 版本的 Snapshot
  • 更新内容:给已有 Ref 新增一个 pending 版本的 Snapshot
  • 发布版本:把指定的 pending 版本状态改为 published,并把该 Ref 下其它 published 版本降级为 archived

读取

读取时需要明确指定「通过 Ref 找哪个版本」:

  • 公开访问:默认读取 Ref 下最新的 published 版本;如果没有 published 版本,则视为不存在
  • 编辑/管理场景:可以列出 Ref 下的所有版本,查看 pendingarchived 的历史版本
  • 按版本读取:如果直接持有 Snapshot ID,可以读取该 Snapshot 的完整内容

不变性保证与并发控制

不变性

不变性由两层保证:

  1. 业务层约定:写入后不再修改 Snapshot 的内容字段,所有”修改”都是新增版本
  2. 数据库层兜底:内容字段只有在 INSERT 时写入,UPDATE 只允许修改 status 字段;必要时可以用触发器或权限约束限制 UPDATE 的列范围

并发控制

  • 同一用户单 pending 草稿:业务层先检查该 Ref 下是否已存在该用户的 pending 版本,避免重复创建草稿。数据库层面可加上部分唯一索引作为最终防线
  • 发布竞争:发布版本时,必须保证同一 Ref 下只有一个 published 版本。业务层先把旧 published 改为 archived,再把新的 pending 改为 published,整个过程放在同一个事务里
  • 读取一致性:读 published 版本时,使用 Ref + status 查询;由于 published 状态的切换是事务性的,读到的要么是最新的 published 版本,要么旧 published 已在事务中被降级

与 git 模型的对照

git 概念本方案对应说明
commitSnapshot不可变的内容快照
branch/tagRef稳定指针,指向某个当前版本
HEADpublished 版本当前对外生效的内容
commit historySnapshot 序列created_at 排列的历史版本
git checkout切换 published 版本可以重新指定哪个版本是 published

本方案和 git 的核心思想一致:用不可变对象保证历史可追溯,用薄指针保证外部引用的稳定性。

设计收益

  • 历史可追溯:每次修改都留下完整记录,可以回查任意时刻的内容
  • 回滚简单:重新把一个 archived 版本标回 published 即可恢复旧内容
  • 外部链接稳定:外部引用只依赖 Ref ID,不会因为内容更新而失效
  • 审核友好pending 版本天然支持”先编辑、后发布”的工作流
  • 并发安全:发布与草稿创建都有明确的事务边界,避免状态混乱

并且根据状态转换机制的设置,可以使用数据库作为最底层的约束来实现兜底,这样就可以畅快地无畏并发了