如果你写过任何一点数据库代码,多半撞过这堵墙:给表加了个索引,查询从 800 毫秒掉到 2 毫秒,爽;但写入开始变慢、磁盘还悄悄膨胀了一截。 你以为是自己索引建得不够好、参数调得不够精。其实不是——这是底层一条铁律在作祟,你越用力压一头,另一头就翘得越高。
2016 年,哈佛大学 DASlab 实验室的 Stratos Idreos 团队在 EDBT 会议上发了一篇论文,把数据库工程师几十年来"凭手感"拿捏的那种纠结,提炼成了一句近乎宿命论的断言:一个存取方法(access method),不可能同时把读、写、存三件事都做到最优。 你能给其中两件设上限,第三件就必然触底——不是"比较差",是数学结构上被钉死的下限。他们管它叫 RUM Conjecture(RUM 猜想):R=Read 读、U=Update 写改、M=Memory 存储。
这期由 Arpit Bhayani(DiceDB 作者、前 Google GCP Memorystore 工程师)主讲的 Understanding Database Trade-offs — The RUM Conjecture,就是把这篇论文嚼碎了讲给你听。它的价值不在于教你某条 SQL,而在于给你一副"数据库选型"的透视镜——从此你看到 Redis、Cassandra、RocksDB、Postgres,看到的不再是品牌,而是它们各自站在 RUM 三角形的哪个角上,以及为此牺牲了什么。
| 项目 | 内容 |
|---|---|
| 视频标题 | Understanding Database Trade-offs - The RUM Conjecture (Paper Explained) |
| 频道 | Arpit Bhayani |
| 频道订阅 | 约 21 万 |
| 视频类型 | Paper Explained(顶会论文逐句拆解) |
| 所属合集 | Database Engineering 系列 |
| 核心主题 | 为什么没有任何一种数据结构能同时优化读、写、存 |
| 论文出处 | EDBT 2016 Designing Access Methods: The RUM Conjecture |
视频一上来就先立一个靶子:什么是理想的存取方法?答案是三全其美——读得最快、写得最省、还不占用任何额外内存。
听起来像废话,但只要你想清楚"快"从哪里来,就明白它不可能。读要快,就得有索引、有缓存、有冗余副本帮你秒定位;写要省,就得少动磁盘、批量攒着写、避免每次都重组数据;存要省,就绝不能搞副本、不能加索引、最好把数据压缩到极致。这三条优化方向,本质上是在互相拆台:你为了读快而建的索引,正是写入时要同步维护的负担,也正是吃掉内存的元凶。
RUM 猜想就是把这种"互相拆台"形式化:当你给读、写、存其中两件事都设了上限(让它俩别太差),第三件就被迫踩到一个再也降不下去的下限。注意措辞——是 conjecture(猜想),不是 theorem(定理)。它没有被严格证明,但被全世界无数系统反复观测到,和著名的 CAP 定理(一致性、可用性、分区容忍不能三全)是同一类的"工程铁律"。Arpit 在原文里直接点明:RUM 之于存储,就像 CAP 之于分布式系统。
光说"读得慢"太虚,论文给了三个可计算的指标,这是整期视频最该记的硬通货:
有了这三个比率,"Redis 比 Cassandra 快"这种含糊说法就能被拆解成:"Redis 在读放大上更优,Cassandra 在写放大上更优"。数据库之间的差异,本质是这三个比率的取舍差异——这是这期视频最值钱的一句潜台词。
第一类系统死磕读,把读放大压到极低。代表是 Hash Index(哈希索引,O(1) 定位) 和 B-Tree / B+Tree、SkipList(O(log n) 定位)。
它们为什么读得快?因为养了一支"辅助部队"——索引结构。但天下没有免费的索引:为了维护这支辅助部队,每次写入/更新主数据时,所有相关索引都要跟着改一遍,这就是 U(写)的代价;而这些索引本身要占内存和磁盘,这就是 M(存)的代价。B-Tree 还有个经典副作用叫页分裂:数据按顺序塞满一个页后,再插一条就要把页劈成两半、还要更新上层所有指针,单次写入可能牵动整条路径的重写。
所以读优化系统的命门很清楚——一旦写入变频繁,性能立刻跳水。这就是为什么纯读优化的设计扛不住高并发写入场景。Postgres、MySQL 的传统行存 B+Tree 索引、Redis 的字典,都属于这一族。
第二类系统反过来死磕写,代表是 LSM-Tree(日志结构合并树),以及 Partitioned B-Tree、FD-Tree。大名鼎鼎的 RocksDB、LevelDB、Cassandra、HBase、InfluxDB,底层全是 LSM。
LSM 的思路很巧:写入根本不就地改老数据,而是追加到内存里的 MemTable,攒够了再批量、顺序地刷成磁盘上的只读文件(SSTable)。顺序写、批量写——这正是磁盘和 SSD 最喜欢的姿势,所以单次写入的延迟极低。
但 RUM 的报应立刻来了:读放大爆炸。你要查一条数据,它可能藏在第 0 层、第 1 层……一直到第 N 层的某个 SSTable 里,引擎得逐层翻找、再把多版本合并去重才能给你答案。空间放大也爆炸:同一条数据的新旧版本散落在不同层,在 compaction(后台压缩合并)把它们清理掉之前,磁盘上挤满了冗余。
更妙的是,LSM 标榜自己"写省",可后台那个为了压制读放大和空间放大而存在的 compaction,会把老数据反复重写好几遍——业界实测 LSM 的写放大经常是 10~30 倍甚至更高。你以为省了写,其实只是把写的代价推迟并放大到了后台。这正是 RUM 张力的完美现场:你压住了"即时写延迟",U 的总量却被 compaction 偷偷抬了起来。SSD 寿命(TBW)焦虑,很大一块就来自这里。
第三类系统死磕存,把空间放大压到极致,思路是要么压缩、要么允许出错。代表是 Bloom Filter(布隆过滤器)、Count-Min Sketch、稀疏索引、各种有损编码。
这正好接上本系列之前那期 它故意撒谎:布隆过滤器永不漏报 —— 布隆过滤器就是 RUM 三角形里"存优化"那个角上的招牌居民。它用极少的比特(典型地,1% 误判率只需约 10 bit/元素)就能告诉你"这条数据八成不在",把昂贵的磁盘读拦在内存里。代价是什么?它会说谎(假阳性),还需要在写入时计算多个哈希、读出时多次校验——U 和 R 都被这层"概率判断"拖慢了。
Count-Min Sketch 同理:用固定的小内存做近似统计,牺牲精度换空间。这一派的核心哲学是:"够用就行,近似也是答案"。当你的业务能容忍一点点误差(缓存判断、热度排行、去重计数),存优化派就能帮你省下数量级的内存。
视频特意强调了一个细节,也是很多人会误解的地方:RUM 是 conjecture,不是 theorem。
定理需要严格的数学证明,给出"不可能"的硬下界。而 RUM 目前没有被这样证明过——它是大量工程实践的归纳总结。论文作者自己也很诚实:对于某些特定的比较型数据结构,已经有读-写权衡的下界证明,但"读、写、存三者同时"的完整下界,至今是个开放问题。
那它还有用吗?太有用了。 工程世界里,一条"几乎总是成立、且代价一目了然"的经验法则,往往比一个严苛但狭窄的定理更可操作。CAP 也是同理——它不是数学定理的完整形式,却成了分布式系统设计的指南针。RUM 给你的不是"不许你做什么",而是"当你决定优化某一项时,请主动预判另外两项会怎么恶化",并去量它、补它。
如果三角形的三个角都各有死穴,那有没有"尽量站中间"的设计?有。视频举了 Block-based Clustered Indexing(块式聚簇索引):它只建一棵很矮的辅助树(少量指向数据页的指针),而且因为数据本身聚簇地存在索引里(主数据即索引),引擎查到就能直接拿到,不必再回主存储取一次——读放大很低,写和存也还算克制。它是 RUM 三角形里靠近中心的一个妥协点。
但真正的圣杯是论文提出的 RUM Adaptive(RUM 自适应)存取方法:一个系统能根据当时的负载、应用、硬件,动态调整自己在三角形上的位置——读多就偏读优化,写洪峰就偏写优化,内存吃紧就开压缩。Arpit 把它总结成一句漂亮的话:"应该是应用、负载和硬件来决定我们怎么访问数据,而不是我们系统的约束去反过来限制应用。"这其实也是当下数据库演进的方向:从"一种结构打天下",走向"可调的、自适应的"混合引擎。
graph TD
RUM["🎯 RUM 猜想<br/>读 R · 写 U · 存 M<br/>三优不可兼得"]
RUM -->|"读-优"| R["R 读优化<br/>低读放大"]
RUM -->|"写-优"| U["U 写优化<br/>低即时写延迟"]
RUM -->|"存-优"| M["M 存优化<br/>低空间放大"]
R --> R1["Hash Index · B-Tree<br/>Postgres · MySQL · Redis"]
U --> U1["LSM-Tree<br/>RocksDB · Cassandra · HBase"]
M --> M1["Bloom Filter<br/>Count-Min Sketch · 稀疏索引"]
R -.代价.-> RC["写放大↑ + 空间↑<br/>页分裂/索引同步"]
U -.代价.-> UC["读放大↑ + 空间↑<br/>compaction 反噬写"]
M -.代价.-> MC["读放大↑ + 写放大↑<br/>假阳性/解压开销"]
BAL["⚖️ Block-based Clustered Indexing<br/>三角形中心 · 折中"]
ADAPT["🧭 RUM Adaptive<br/>按负载动态调位 · 终极理想"]
RUM --> BAL
BAL --> ADAPT
第一,选型前先问负载,别先问品牌。 拿到一个新系统,第一反应不是"它快不快",而是"它把 R/U/M 三者里的哪个压到了地板?又把哪个顶上了天?" 这样你才能预判它在你具体场景里会爆在哪。读多写少的用户中心、商品库 → 读优化(B-Tree 类);海量时序、日志、IM 消息流 → 写优化(LSM 类);内存吃紧、能容忍概率 → 上 Bloom Filter / Sketch。
第二,警惕"单索引治百病"的幻觉。 每加一个二级索引,你就在用 U(写入变慢)和 M(空间膨胀)去赎买 R(读变快)。索引越多不是越强,是债务越重。生产环境里"加个索引就好了"的提议,先算清楚它每天要被写入维护多少次、占多少 GB。
第三,真实系统都是"RUM 混合体",学会组合补刀。 这是最值钱的一条:没有任何顶级系统只站在一个角上。 RocksDB 是写优化的 LSM,但它会在每个 SSTable 上挂一个 Bloom Filter,用存优化的"小谎言"去把读放大从"翻遍所有层"砍回来——这就是在用 M 补 R。Redis 这种读优化的内存库,写多了也得靠 RDB/AOF 和淘汰策略去兜底。读不行就补 Bloom、写不行就上 LSM、存不够就压缩——三层思维,按需叠加,这才是工程师该有的工具箱,而不是迷信某一种结构。
第四,把三个放大系数挂上监控。 别等磁盘满了、SSD 写穿了才发现。读放大看 P99 延迟与回表次数,写放大看 SSD 写入量/用户实际写入量比值(云厂商的 IOPS 账单会诚实地告诉你 LSM compaction 有多狠),空间放大看实际占用/逻辑数据量。RUM 不是理论游戏,是直接对应你账单和故障的三个数。
没有任何数据结构能同时省下读、写、存——RUM 猜想把数据库选型从"比参数"提升为"量取舍",让你看懂每个系统站在三角形哪个角、又为此把哪一刀留给了你。
本文由「51学通信」技术团队出品。51学通信由通信行业18年老兵爱卫生(微信ID: gprshome201101)维护,聚焦5G/6G核心网、3GPP标准解读、通信前沿技术深度分析。 出版作品有《5G核心网原理与实践》《IMS原理与实践》等4本通信专业书籍。知识星球「51学通信」提供深度技术答疑和行业洞察。
[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。
