一个 PHP 大佬,用 C# 手搓了一个分布式数据库

最近翻 GitHub 的时候挖到一个挺有意思的玩意儿:CamusDB,一个用 C# 写的分布式 SQL 数据库。

你可能没听过。它只有 14 个 star。正常来说这种项目我扫一眼就划走了,但这次我多看了两眼,结果一看就是一下午------因为作者的玩法跟一般开源项目完全不一样。

作者是谁

先说人。作者叫 Andrés Gutiérrez,PHP 圈的老炮应该认识------他是 Phalcon 框架的作者,就是那个用 C 扩展写的、号称最快的 PHP 框架,GitHub 上一万多 star。后来还搞了个 Zephir,一门专门用来写 PHP 扩展的语言。

这几年他转行搞分布式系统了。起因也简单:他在游戏行业干了很多年后端,用 Google Spanner 用出了感情,觉得强一致 + 水平扩展这东西太香了。然后有一天他问自己:我自己能不能造一个?

正常人问完这个问题就去睡觉了。他没有。

为了造这个数据库,他先把底下的零件一个个手搓了出来:一个 Actor 运行时(Nixie),一个 Raft 共识库(Kommander),一个对标 etcd 的分布式协调服务(Kahuna),最后才是 SQL 引擎本体。"这哥们儿为了做一个 .NET 版 etcd,先把 Actor 框架和 Raft 库都自己造了一遍。"

2021 年立项,闷头写了五年。真正的爆发是今年夏天------四个月时间,成本优化器、分布式查询、数据库分支、时间旅行,全都冒出来了。

它有什么不一样

功能层面,CamusDB 有几个设计我觉得挺有性格。

第一,默认可串行化。 主流数据库的默认隔离级别都是打折的------PostgreSQL 默认 Read Committed,MySQL 默认 Repeatable Read,想要正确性你得自己加钱。CamusDB 反过来:所有事务默认 Serializable,你想要弱隔离?可以,自己显式声明。悲观两阶段锁、范围锁防幻读、wait-die 防死锁,教科书怎么写它就怎么做。

第二,数据库可以像代码一样开分支。 一行 SQL:

javascript 复制代码
CREATE DATABASE staging BRANCH FROM prod;

瞬间给你fork出一份生产库的完整副本,写时复制,不拷贝数据。分支上随便折腾,父库完全无感。想清楚了,这对测试环境、迁移预演、AI agent 并行试错意味着什么。Neon 靠这个功能撑起了一门生意,CamusDB 把它做成了内建能力。

第三,DROP 删了能找回来。 误删的表和库不是立刻物理删除,先进"孤儿院"挂着,SHOW ORPHAN TABLES 能看,RELINK TO 能捞。再加上 AS OF SYSTEM TIME 时间旅行查询,误删误改基本变成了可逆操作。

干过运维的都懂这三个功能的含金量。它们不性感,但每一个都在救人的命。

真正让我坐直的,是那条龙

功能谁都会抄。这个项目真正稀缺的东西是它的测试方式。

作者给他的数据库造了一个混沌工程工具,起名叫 Caraxes------《龙之家族》里那条血红色的龙。它的工作只有一个:攻击自家数据库。

注入的故障全是来真的。杀进程就是真 SIGKILL,网络分区就是真防火墙规则,慢磁盘是用内核 I/O 控制器把块设备限流到每秒只能写一个 4KB。作者的原话是:"模拟只能测试我们已经想象到的行为。"

然后是最核心的校验,简单到无法反驳:银行转账测试。让一堆账户互相转账,同时往死里折腾集群,最后对账------所有余额加起来,必须跟开始一模一样。钱只能搬家,不能消失。

就这么个朴素的校验,抓出过一个让人头皮发麻的 bug。

一次两小时的混沌测试跑完,总账少了 3 块钱。背景是三十亿。三十亿分之三。任何日志都看不出异常,但对不上就是对不上。查了好几天,根因是 SQL 引擎把底层"没响应"(超时、正在复制)当成了"这行数据不存在",于是一条 UPDATE 静默地什么都没改,事务却高高兴兴地提交了。一笔转账只执行了一半。

修完之后,整个引擎立下一条铁律:读不到确定答案的时候,必须报错,永远不许假装数据不存在。

还有更夸张的。某次测试里,集群健康检查显示 3 个节点全部正常,一切安好------实际上一个分区已经彻底停摆了 83 分钟,一笔交易都过不去。从那以后,健康检查不再看"节点活没活着",只看"账还在不在走"。

今年 10 月,作者干脆发了篇文章,标题叫《What we can prove about consistency》(我们能证明什么),完全照 Jepsen 审计报告的格式写:系统声称了什么、怎么测的、测出了什么、修了什么、还有什么是证明不了的。

没证明完的部分他全列出来了。包括一条至今没找到根因的 Raft 层缺陷------"一条已确认的写入在所有副本上都不见了"。他原话是:这种 bug,银行不能接受;它俩月没复现了,但不复现不等于修好了。

张善友老师那句评价我很认同:"敢把没修完的 bug 公开挂出来,这比一万个 star 都有说服力。"

泼几盆冷水

夸完了,说点实在的。

这是个单人项目。 868 次提交,87% 是作者一个人写的。没有社区,没有讨论,英文世界几乎没人知道。bus factor 等于 1,这是最硬的伤。

它还是个半成品。 README 自己写着"部分功能 alpha,谨慎使用",存储格式可能随版本变,升级还得靠逻辑导出导入。那条没找到根因的写丢失 bug,也一直挂在那儿。它声称的"可串行化"目前也只验证到了 serializable,更严格的 strict serializable(真实时间顺序)还没测。

性能是未知数。 没有公开 benchmark,没有 TPC-C 数据,悲观锁 + 强一致的代价在高并发下什么样,只有作者自己知道。

所以结论很清楚:现在别拿它跑核心生产。 作者自己也是这么说的。

但这事为什么值得关注

我觉得 CamusDB 的价值不在它今天能不能用,而在两个地方。

一个是给 .NET 生态蹚路。长久以来大家默认基础设施就该用 Go、Rust、C++ 写,.NET 不配。这个项目证明了现代 .NET 完全扛得住这种活------引擎甚至能编译成 WebAssembly,在浏览器标签页里跑一个三节点 Raft 集群,你可以亲手杀掉 leader 看重新选举。这个 playground 值得每个对分布式感兴趣的人去玩五分钟。

另一个是它示范了一种工程态度。现在太多项目是"先宣传、后补票",benchmark 挑好看的跑,故障测试随缘。CamusDB 反过来:先养一条龙天天咬自己,咬出来的伤口晾在明面上,修好的、没修好的都记台账。 它的可信度不是喊出来的,是一次次对账对出来的。

数据库这东西,信任是在最糟糕的那天挣来的。而想知道那天长什么样的唯一办法,就是自己把那天造出来。

这个道理,做数据库的适用,写代码的适用,做任何需要别人信任的系统,都适用。


项目地址:github.com/camusdb/camusdb | 底层组件:github.com/kahunakv | 浏览器试玩:camusdb.github.io/playground