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

推荐订阅源

Cloudbric
Cloudbric
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
量子位
A
About on SuperTechFans
宝玉的分享
宝玉的分享
小众软件
小众软件
T
Tor Project blog
The Hacker News
The Hacker News
WordPress大学
WordPress大学
IT之家
IT之家
L
LINUX DO - 热门话题
大猫的无限游戏
大猫的无限游戏
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
酷 壳 – CoolShell
酷 壳 – CoolShell
NISL@THU
NISL@THU
D
Darknet – Hacking Tools, Hacker News & Cyber Security
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Latest news
Latest news
Martin Fowler
Martin Fowler
F
Full Disclosure
爱范儿
爱范儿
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Application and Cybersecurity Blog
Application and Cybersecurity Blog
W
WeLiveSecurity
C
Cisco Blogs
Recorded Future
Recorded Future
C
CXSECURITY Database RSS Feed - CXSecurity.com
博客园 - Franky
美团技术团队
N
Netflix TechBlog - Medium
Know Your Adversary
Know Your Adversary
Hacker News - Newest:
Hacker News - Newest: "LLM"
H
Help Net Security
雷峰网
雷峰网
G
Google Developers Blog
人人都是产品经理
人人都是产品经理
Microsoft Azure Blog
Microsoft Azure Blog
Security Latest
Security Latest
M
MIT News - Artificial intelligence
J
Java Code Geeks
Project Zero
Project Zero
Jina AI
Jina AI
P
Palo Alto Networks Blog
Vercel News
Vercel News
腾讯CDC
N
News | PayPal Newsroom
V
Visual Studio Blog
Cisco Talos Blog
Cisco Talos Blog
V
Vulnerabilities – Threatpost
AWS News Blog
AWS News Blog

暗无天日

读:AI Agent 安全日志——从可见性与隐私的两难说起 - 暗无天日 AI写作的语言指纹——如何让文字不那么像机器 - 暗无天日 读:50 条 Claude Code 技巧——一个工程经理的六个月使用心得 读:AI 辅助开发为什么让 E2E 测试更有价值 - 暗无天日 读:在Emacs中使用Claude Code(Spacemacs适配版) - 暗无天日 Claude Code 背后的工程哲学——读 Agent Harness Engineering 读:Agent Harness Engineering——AI 智能体不只是模型,还有套件 - 暗无天日 browser-harness:让 AI 直接接管你的浏览器 - 暗无天日 读:Security-First CI/CD —— DevSecOps 自动化实践指南 TIL: 数字小键盘的小数点陷阱与行内算术求值 - 暗无天日 读:Immutability 不是万能药,它是一种权衡 - 暗无天日 Conducty:给 Claude Code 加上项目记忆和并行执行能力 - 暗无天日 读 — GitHub Trending 里的 Claude Code 技能包 读 — Prompt Caching 省钱指南 TIL: Emacs 中那些跟鼠标配合的冷门快捷键 - 暗无天日 读:Anvil——把 Emacs 变成 AI 的工具服务器 读:Emacs 代码折叠终极指南 - 暗无天日 读:Clojure 搭车客指南 - 暗无天日 git推送失败后恢复仓库损坏的完整记录 - 暗无天日 多智能体系统的两个有效模式——以及对 Claude Code 用户的启示 - 暗无天日 用 Org Babel 写 Literate 博文:扩展执行 + 定制导出 proced:Emacs 内置的进程查看器 - 暗无天日 从 proced 定制中学到的 Elisp 模式 读:让 Emacs proced 在 macOS 上显示 CPU 和内存 异步编程的函数着色税 - 暗无天日 链式调用的代价:JavaScript 和 Clojure 的共同教训 - 暗无天日 hyperfine:命令行基准测试工具 - 暗无天日 管道中的变量去哪了?——子 shell 作用域陷阱 - 暗无天日 开源包装器的信任陷阱:四个危险信号 - 暗无天日 程序员愿意为 AI 写文档,却不愿为同事写 - 暗无天日 mktemp: Shell 脚本中临时文件的安全陷阱与最佳实践 - 暗无天日 WSL9x —— 在 Windows 9x 里跑 Linux 内核 6.19 用 ox.el 做你想做的事 —— org-export 高级编程指南 读:Hot-wiring the Lisp Machine —— 用纯 Elisp 构建零依赖的 Org 静态站点生成器 Elisp 性能优化的六个实战教训 - 暗无天日 fcitx5 下 Emacs 无法切换输入法的排查 - 暗无天日 ERT 测试交互命令的三种方式 - 暗无天日 SEM Assistant: 当 Elisp 守护进程遇上 LLM 用 dmsg 给 Elisp 加上结构化调试日志 用 org-habit 追踪非每日习惯 - 暗无天日 Clojure X-Men:当编程语言特性变成超能力 - 暗无天日 TIL: 用 diff-hl 在 fringe 中显示 git 变更 读:llm-test —— 用 LLM agent 驱动 Emacs 测试 TIL: AI 时代的橡皮鸭调试 - 暗无天日 fcitx 启动后键盘输入卡顿的排查 - 暗无天日 TIL: 早期网页的图片热区导航 - 暗无天日 读 Seeing the Whole System 用 Emacs 自动生成每周链接推荐 - 暗无天日 读:ASCII control characters in my terminal 读 What to learn - 暗无天日 Lisp 的括号之痛——一个愚人节玩笑揭开的老伤疤 - 暗无天日 一本书该"线性读"还是"并行读" - 暗无天日 读 How to Monetize a Blog:一篇伪装成变现指南的讽刺文 Python Mock 第三方依赖的四种策略 - 暗无天日 Emacs Lisp 热重载实用指南 - 暗无天日 Prot 的 Emacs 配置哲学 - 暗无天日 TIL: 从直播对谈中学到的三个 Emacs 技巧 - 暗无天日 TIL: 自动使用项目虚拟环境的 Python - 暗无天日 TIL: 让 Help buffer 自动获得焦点 一条命令让本地开发用上 HTTPS —— slim 工具介绍 用 fsck 检查和修复 Linux 文件系统 排查Linux进程"卡死"实战:从strace到gdb全流程 - 暗无天日 PostgreSQL 索引:从基础到你可能不知道的高级用法 - 暗无天日 用 .pdbrc 自定义 Python 调试器 ANSI 转义码的标准化现状 - 暗无天日 终端程序的潜规则 - 暗无天日 PARA Org-mode 测试配置 - 暗无天日 AI越强越辣鸡?控制论说这是必然的 - 暗无天日 AI 越强越需要你盯着——反馈循环实操指南 - 暗无天日 你的AI代理正在偷你的密钥——四种你没想到的泄露通道 - 暗无天日 LLM 在 DevOps 中的三种角色 - 暗无天日 写作风格的反建议 - 暗无天日 反驳本质复杂性——Dan Luu 论为什么《没有银弹》错了 - 暗无天日 文件充满了危险——Dan Luu 谈文件系统的可靠性陷阱 - 暗无天日 AI 时代的 PARA 方法:用 Org-mode 和 AI 打造个人知识管理系统 Linux 数据去重学习笔记 - 暗无天日 创建跨平台 ZIP 文件的隐藏陷阱:Extra Field - 暗无天日 X11 Forwarding 排障指南 - 暗无天日 IP欺骗端口扫描:当别人冒充你去扫描别人 - 暗无天日 Linux 输入栈全景解析:从硬件按键到屏幕响应 - 暗无天日 Unix 系统中那些被埋没的配置开关——以 FontConfig 为例 - 暗无天日 在Linux上限制儿童使用电脑 - 暗无天日 GIF不仅仅是一种图片格式——用GIF流做些奇怪的事 - 暗无天日 Leiningen 学习笔记:Clojure 项目构建与管理从入门到实战配置 - 暗无天日 Google SRE Book 读书笔记 - 暗无天日 yes 管道 head 发生了什么 - 暗无天日 为什么 nohup 在 crontab 中不起作用 Bash中的Indirection与Nameref - 暗无天日 Linux PAM 简介 - 暗无天日 从Linux ISO文件启动计算机 - 暗无天日 用 Bash 打造一个Screen Locker 用GitHub Actions自动构建EGO博客 - 暗无天日 blocking I/O 的作用 - 暗无天日 mobileog 手机端同步提示Error:2 No such file 的解决方法 回收 WSL2 VHDX 文件占用空间 使用 org-mode columnview 生成任务列表 - 暗无天日 Emacs 作为 MPD 客户端 - 暗无天日 移动文件路径却不破坏org file link的方法 - 暗无天日 如何合理的导出help link 成HTML - 暗无天日 笑话理解之Biology - 暗无天日
读:Event Sourcing——让你的数据库记住每一次变更 - 暗无天日
2026-05-16 · via 暗无天日

CRUD 的问题:数据库会"失忆"

Chris May 在 Talk Python To Me 播客第 548 期 里举了一个例子:假设一个购物车,用户加了三件商品,又退掉一件,最后结账。在传统的 CRUD(Create-Read-Update-Delete)模式下,数据库里最终只剩一行数据:购物车 ID、用户 ID、状态 = 已结账。

两个用户都买了三件商品,结账后他们的数据库行一模一样。谁先加的哪件、谁中途改过主意、谁的购物路径更曲折,这些信息 CRUD 不会主动保留。除非你额外建一张历史表,否则数据库只记得"现在是什么样",不记得"怎么变成这样的"。

这对多数应用没问题。但在需要审计追踪、需要回答"过去发生了什么"、或者需要从历史数据中挖掘新洞察的场景里,CRUD 的这种"失忆"就成了硬伤。金融交易、合规审计、用户行为分析,这些场景都需要完整的历史记录。

Event Sourcing 的核心:不存状态,存事件

Event Sourcing(事件溯源)的思路是:数据库只记录每次状态变更的事件,不保存当前状态。

购物车的例子换成 Event Sourcing:

事件1: ShoppingCartCreated(cart_id=1, user_id=42)
事件2: ItemAdded(cart_id=1, item="键盘", price=299)
事件3: ItemAdded(cart_id=1, item="鼠标", price=99)
事件4: ItemRemoved(cart_id=1, item="鼠标", price=99)
事件5: CheckoutCompleted(cart_id=1, total=299)

当前状态(购物车里有一把键盘,已结账,总价 299)靠重放这些事件推导出来,不单独存储。就像 Git 不存文件的最终状态,存的是一系列 diff(变更记录),靠 base + diffs 重建出当前文件。

数据库变成了一个只追加(append-only)的事件日志,每条记录都是不可变的。没有 UPDATE,没有 DELETE,只有 INSERT。如果你对 Clojure 的不可变数据结构有所了解,这里是同一个思路:数据不可变,"修改"只是产生新版本,所有历史都保留。只不过 Event Sourcing 把这种思路从内存中的数据结构延伸到了持久化层。这带来了几个实际变化:

  • 完整的审计追踪 :每个状态变化都有记录,不需要额外建一张"历史表"
  • 时间旅行 :可以回溯到任意时间点的系统状态
  • 事后分析 :可以回答当初没想到要问的问题

播客里 Chris May 举了个实际的例子:上线三天后,另一个团队需要从他的服务拉数据到 BigQuery 做分析。他不需要额外开发数据导出功能,因为所有历史事件都在,直接回灌了几个月的数据。对方的反应是"震惊",因为他们以为只会拿到三天的数据。

你的表里有没有"状态"字段?

如果你的表里有一个 status 列,说明同一行数据会在不同状态间切换,每个状态可能有不同的业务规则。比如订单只能从"待付款"变成"已付款",不能直接跳到"已完成"。这些"谁能变成谁"的约束就是状态转换规则。纯 CRUD 不管这些,你必须在业务代码里自己判断,而 Event Sourcing 天然为状态转换建模。

以下场景适合 Event Sourcing:

  1. 需要审计追踪 :金融交易、医疗记录、法律合规(PCI、HIPAA、GDPR),要求每一步操作都有据可查
  2. 需要从历史数据中发现新洞察 :业务方可能提出你当初没想到的问题
  3. 状态转换有复杂规则 :订单从"待付款"到"已付款"到"已发货"到"已完成",每个状态允许的操作不同

什么场景 不该 用:

  • 简单的表单应用 :一个联系人管理表、一个后台配置表,没有复杂的状态转换,CRUD 完全够用
  • 存储成本敏感 :事件日志比单行数据占用更多空间。不过 Chris 的观点是,存储是现代基础设施中最便宜的组件

Chris 还强调了一点:可以一点点引入。同一个项目里,有些模块用 Event Sourcing,其他模块继续 CRUD,两套模式可以共存。

性能:重放事件会不会太慢?

这是每个人第一次听说 Event Sourcing 时的第一反应。Chris 的导师 Martin Dilger 的回答是:"计算机很快。"

实际数据:事件流在约 2000 条以内,直接重放推导状态只需要毫秒级。大多数业务对象(一个购物车、一个订单、一个账户)的生命周期内不会产生那么多事件。

当事件流确实很长时,解决方案是 CQRS (Command Query Responsibility Segregation,命令查询职责分离):"命令"是会改变数据的操作(下订单、加商品),"查询"是只读数据的操作(查看购物车、生成报表)。两边走不同的路径后各自优化,不用互相将就。

写入路径: Command → Aggregate(业务对象) → Events → Event Store
读取路径: Event Store → Projector → Read Model → Query

Read Model(读模型)是一个独立的、预先计算好的视图。它订阅事件流,每次有新事件进来就增量更新自己。查询时直接读 Read Model,不需要重放全部事件。

这跟数据库的物化视图思路类似:用空间换时间。Read Model 可以是关系型数据库的一张表、Redis 里的缓存、或者 MongoDB 里的一个文档。

Chris 还提到一种借鉴会计学的技巧叫 Closing the Books (结账):在一个自然的业务边界(比如月末、年末)生成一个"摘要事件",然后开始一个新的事件流。重放时只需要从摘要事件开始,不用从第一天算起。这解决了"事件日志会不会无限增长"的担忧。

事件版本:Schema 怎么变?

传统数据库改字段,跑一个 ALTER TABLE 就行。Event Sourcing 不能这么做,因为历史事件已经写死了。

Chris 描述了三种策略:

  1. 只加字段,给默认值 :新版本的事件多了一个字段,旧事件读取时用默认值填充。大多数消费者忽略不认识的新字段,少数需要用到的按需处理
  2. Upcaster(升级器) :写一个小函数,把旧版事件在读的时候转成新版格式。这个函数只放在需要升级的那个功能模块里,不是全局的
  3. 全量拷贝重写 :把整个事件存储拷贝一份,转换过程中逐条改写事件,写到一个新的存储里。需要停写、耗时可能很长、任何转换错误都会污染整个新存储,所以只在没有其他办法时才用。Greg Young(Event Sourcing 的主要推广者)专门为此写了一本书

策略一够用时别用策略二,策略二够用时别用策略三。

审计、调试、合规中的意外收获

Chris 在播客中反复提到一个感受:"能精确回答过去的问题,让人上瘾。"

几个具体场景:

  • 调试 :一个字段莫名其妙是 null,CRUD 模式下只能猜"可能是某次更新清掉了"。Event Sourcing 模式下,找到产生那个 null 值的具体事件,看到当时的完整上下文
  • 合规 :PCI、HIPAA、GDPR 都要求审计追踪。传统做法是建一张"历史表",但 Chris 的观察是:"如果你从不测试备份策略,你就等于没有备份。"历史表也一样,团队经常忘记写,或者写了但没人验证。Event Sourcing 的事件日志天然就是审计日志,不需要额外维护
  • 按需生成新报表 :老板要一个过去 30 天的某项统计,写一个新的事件消费者,跑一遍历史事件,30 天的数据瞬间生成

这些好处在金融行业尤其明显。银行核心系统的记账方式天然就是 Event Sourcing 的存储模式:每笔交易(转账、存款、取款)一旦入账就不能修改或删除,只能做一笔冲正交易;账户余额不是直接存一个数字,而是所有交易记录的汇总结果。这其实就是复式记账法几百年来一直在做的事。Event Sourcing 把核心系统一直在用的思路搬到了整个应用架构里。也因为写操作只往事件存储追加一条记录,天然避免了 "写数据库成功但写消息队列失败"的不一致问题(双写问题)。