谁需要实时数据库?
摘要
Data Intellect 的一篇技术博客文章认为,kdb+ 系统中不应默认使用实时数据库(rdbs),并提供了比较内存和磁盘查询性能的基准测试。
<p>大部分背景都是关于 kdb+ 作为 <a href="https://code.kx.com/q/architecture/" rel="ugc">tickerplant</a> 的使用</p>
<p><a href="https://lobste.rs/s/advkuj/who_needs_real_time_database">评论</a></p>
查看缓存全文
缓存时间: 2026/07/20 21:29
# 谁还需要rdb? · Data Intellect
来源:https://dataintellect.com/blog/who-needs-an-rdb/
马特·多尔蒂
kdb\+ 以其对循环的厌恶而闻名(没有讨厌的循环 (https://nsl.com/))。作为 kdb\+ 开发者,我们在可用的架构方面拥有很大的灵活性,今天我想说服你们从系统中移除另一件东西:rdb。没有讨厌的 rdb!
在 kdb\+ 领域,数据存储主要有两种方式:内存中和磁盘上。数据本身是相同的。这是一个关键的见解,现在在列式数据领域相当常见(特别是参见Apache Arrow (https://arrow.apache.org/docs/format/Intro.html)),因为它消除了在数据移动时序列化的需求,并支持内存映射和非常快速地从磁盘读取数据。kdb\+ 支持一系列不同的架构,但标准或默认模型仍然主要使用两种进程类型:实时数据库(rdb),数据完全位于进程自己的内存空间中;以及历史数据库(hdb),数据序列化在磁盘上并通过内存映射映射到进程的内存空间中。
我的开场白是有意挑衅并且带点玩笑。我并不是真的认为我们根本不应该使用 rdb。kdb\+ 作为一项技术的一大优势是其灵活性,而且kdb-x (https://code.kx.com/kdb-x/)承诺会带来更多灵活性。我在这里要提出的论点是,rdb 不应该被视为所有 kdb 系统中的默认选项。它们一直是kdb中的核心进程类型之一 (https://code.kx.com/q/learn/startingkdb/tick/),但我认为对于许多问题,它们可能不是必需的,你至少应该考虑不使用它们。这种方法并不新鲜,并且已经部署在许多安装中,以及 KX 自身产品 Insights 的一些组件中,但关于何时何地适用它的细节并不总是很清楚。
## 为什么要用 rdb?
### 更快的查询
对内存中的数据查询当然更快,不是吗?我在相当标准的硬件上用相当标准的数据做了一个简单的基准测试:
- 一个 i7i.xlarge ec2 实例,配备 32GB 内存和快速 NVMe SSD 存储
- 单日典型的(相对较小的)NYSE TAQ 交易数据。大约 5000 万笔交易,磁盘上占用 10GB
- 比较对内存中数据(rdb)、磁盘上数据(hdb)以及缓存未命中时的磁盘上数据(hdb 冷缓存)的查询性能
- 使用一组涵盖一些典型工作负载的小查询
bench1
在进一步讨论这些结果之前,关键背景信息是:hdb 进程通常实际上不会直接访问磁盘来获取数据。它们会内存映射磁盘上的文件,当访问时,操作系统会首先检查页面缓存,如果某个特定文件存在于该缓存中,则不会实际去磁盘读取。所以在上面的所有hdb结果中,查询命中的是页面缓存而不是磁盘,即缓存是热的。因此,当缓存热时,hdb 与 rdb 差别不大:它的数据在内存中,但在操作系统级别的页面缓存中,而不是在进程自己的内存空间中。我还添加了第三组缓存“冷”时的基准测试,以便我们看到操作系统必须实际访问磁盘时的差异。
那么这些基准测试的关键要点是什么:
- 让我们直接说重点:对于所有基准测试,**内存中** 是最快的
- 如果查询可以使用属性并且结果集很小,**内存中** 数据比**磁盘** 数据快得多。特别是最后一个查询,当数据在内存中并具有属性时,速度要快得多。这些查询本质上是对哈希映射的查找,因此是 O(1) 且非常快。我们也可以为磁盘上的数据添加属性,但在 kdb\+ 中,磁盘上的属性是不可追加的,所以我们无法维护它们。我稍后会回到这一点。
- 如果查询不使用属性,或者使用属性但结果集较大,**rdb** 和 **hdb** 之间的性能差距很小。根据查询的不同,差距在 0-20% 之间。这种小幅差异可能来自于访问页面缓存时的轻微页面错误、TLB 压力以及其他不太需要担心的技术问题。操作系统页面缓存中的内存数据*并不完全*像 rdb 堆中的数据那样快,但也接近了。上面的聚合和大型扫描(带属性过滤)比较可能是最有趣的:这两个查询都受益于属性,但没有属性的 hdb 仍然落后不远,因为查询仍然需要访问大量数据,所以属性的好处要小得多。因此,实际上 hdb 可以被认为是另一种类型的内存数据库,其内存位于共享空间中:操作系统页面缓存
- 即使查询必须一直访问**磁盘**,速度差异仍然不是很大。最坏情况下是 2 倍的差异。这就是现代 NVMe SSD 发挥巨大作用的地方。如果是机械硬盘,这个差异会大得多。
### 跟上数据插入
除了读取数据,我们还必须能够添加新数据。白天会有新的交易流入,我们需要能够添加它们。这里是另一个比较两种设置的基准测试:
bench2
**内存中** 进程在这里有更明显的优势。但可能没有你想象的那么大。肯定比你的存储是慢速机械硬盘时要小得多。另一个关键点是:插入内存是一个串行过程。当 rdb 插入新数据时,它**无法同时提供查询服务**。这与后面的情况不同,我们可以向磁盘上的分片表插入数据,同时其他进程可以对该数据提供查询服务。计算和内存是分离的。
## 为什么不用 rdb?
### 更快的查询
我知道上面我展示了 rdb(稍微)更快,但那是针对一个查询。如果你有 2 个或 10 个查询呢?在 rdb 上,你只能串行运行查询。它无法并发运行查询。然而,如果你的数据是共享在磁盘上(并通过操作系统页面缓存共享在内存中),那么你可以并行处理。每个单独的查询可能需要稍长一点时间,但你可以在单独的进程上同时运行所有 10 个查询。在 hdb 上慢 10% 的查询,如果你需要运行两次,由于可以并行,它会快 45%。例如,两个查询各需 110ms,并行运行总共需要 110ms,而两个各需 100ms 的查询串行运行则需要 200ms。如果你想在同一系统上支持不同类型的用例,这也是一个很大的架构优势。例如,研究查询和对延迟更敏感的交易查询,它们不会互相干扰。
### 默认共享所有内存
我们可以基本上免费地进行分片和扩展。rdb 有固定的内存成本,如果我们想要更多进程,就需要更多内存,并且新进程启动缓慢,因为它们需要将所有当前数据读入内存。当你的数据只存储一次,在磁盘上和操作系统页面缓存中时,你可以在此基础上运行 10 个进程,或者为什么不是 100 个?这些进程几乎可以瞬间启动。这从根本上分离了内存和计算。
### 无需网关
当然,如果你愿意,你仍然可以有网关进程,但**不需要**它们来连接 rdb 和 hdb 数据。可以在一个进程中访问所有数据。用户可以启动他们自己的单一进程来访问所有数据,包括实时数据和历史数据。
### 更简单的架构
在处理数据被“实时”追加时,重新加载和映射磁盘上的数据有一些细节需要考虑,但总的来说,你有更少的进程和更少的进程类型。rdb 和 hdb 以及网关之间没有协调问题,所以你的系统可能会更简单。
那么让我们再多谈谈这个...
## 替代默认架构
标准的 kdb\+架构 (https://code.kx.com/q/architecture/)之所以如此,是有充分理由的。如果你处在一个内存昂贵得多、所有非易失性存储都是机械硬盘的世界里,将数据库一分为二更有意义。向磁盘上的分片表插入数据会比内存慢得多,可能慢几个数量级,而任何未命中操作系统页面缓存(冷缓存)的查询也会慢得多,同样可能慢几个数量级。但这已经不再是我们所处的世界了。
我提出的另一种默认架构看起来更像这样:
arch
tickerplant 进程与之前完全相同,但所有数据通过“写入器”进程(本质上是 TorQ wdb 进程)直接进入磁盘,所有数据通过挂载磁盘上数据的数据库进程(TorQ hdb)读取。
让我明确一点,这绝不是革命性的想法。我相信目前正在运行的 kdb\+ 系统中,有很多采用了类似的架构。我的论点与其说是这是新的,不如说是我们应该更认真、更广泛地考虑这个替代方案。
下面是一个更清晰的图示,显示磁盘上的数据分为“实时分区”和所有标准历史数据:
arch1.5
我相信这种方法为我们带来了一些显著的优势:
- **更简单。** 进程更少,问题更少。
- **更灵活和可扩展。** 计算和内存分离。如果我们需要更多的数据库进程,我们可以简单地添加它们,它们没有直接的内存开销,并且会非常快地启动(不同于传统的 rdb)。我们实际上是在利用操作系统页面缓存作为“免费”的内存管理,这是经过实战考验的核心操作系统代码,因此非常稳定。
- **数据集中在一处。** 如果你需要今天和之前的数据,无需对两个不同的进程运行两个查询并处理连接等操作。所有数据都在一个地方。
当然,软件工程中一切都是权衡取舍,这里我们要做的权衡是:
- **与 rdb 相比,查询性能稍慢。** 如上所述,差异比你想象的要小得多,但仍存在实际差异。
- **在正在被实时追加的分区中,我们不能有属性。** 这意味着对于某些“小型查找”类型的查询,性能差异是巨大的。然而,这些查询可能不是你工作流程中的重要部分,或者如果你希望获得上述优势同时保持这些类型的查询快速,你可以为你需要的内容添加专用的缓存进程。也就是说,根据需求添加专门的缓存,而不是一个庞大而昂贵的通用缓存进程(即 rdb)。
对于“实时追加”数据库(db,不再需要称它们为 hdb 和 rdb 了),还有一些关键技术考虑:
- 我们希望将“实时分区”锁定到操作系统页面缓存中,以确保查询很少/永远不会访问磁盘。如果数据被频繁使用,这自然会发生,因为操作系统会尝试将频繁访问的数据保留在缓存中。然而,我们也可以使用vmtouch (https://hoytech.com/vmtouch/) 或 tmpfs (https://www.kernel.org/doc/html/latest/filesystems/tmpfs.html)(RAM 磁盘)来直接控制这一点。这样我们可以保证始终获得上面热缓存的基准测试结果,并且永远不需要访问磁盘。设置起来相对简单。
- 数据库进程与 symfile(枚举)和正在实时追加的列之间存在协调问题。这并不像看起来那么糟糕。我认为解决这个问题的最佳方法是使用 `.Q.MAP (https://code.kx.com/q/ref/dotq/#map-maps-partitions)` 来避免每次查询的 mmap 开销,并使用 `\\l` 定期重新加载 symfile 并重新映射最新数据。原则上这可以非常快(在我的测试设置中,运行 `\\l` 需要 0.018 毫秒),并且根据我们在系统中想要在可用性和速度之间做出的权衡,我们有几种执行方式的选择:
- 如果成本足够低,你可以在任何查询之前简单地运行它
- 或者你可以让写入器在每次插入时触发所有 db 重新加载,这样只在有新数据时才重新加载
- 或者可以基于定时器
- symfile 必须保持较小,因为我们会频繁重新加载它。不过我们可以聪明一点,乐观地重新加载它,即使用校验和快速检查它是否已更改,只在需要时重新加载。
- 在“实时”分区中我们不能有属性。传统上,hdb 分区会在最常被过滤的列上设置 p 属性,它类似于查找的索引。kdb\+ 中磁盘上的属性根本上是不可以追加的,所以我们不得不放弃它们。然而,当数据移动到历史分区并离开“实时分区”时,我们仍然可以在滚动时像往常一样排序和添加它们。
根据你特定系统的需求,上述架构可以通过实时引擎和/或缓存进程进行扩展,以计算分析结果(例如 VWAP、K 线等)。这些值然后可以发布回 tickerplant 并从数据库进程读取,或者直接访问。
arch2
我不想过多讨论这些技术细节,所以我打算后续跟进关于如何使用 TorQ 设置这种架构的详细信息:
- 如何尽可能快地重新加载和重新映射数据
- 滚动机制
- 使其尽可能容易设置和运行
我不喜欢长篇结论,但希望至少让你考虑一个 kdb\+ 的替代默认架构。“无 rdb” 架构可以更简单、更具可扩展性,并且几乎同样快。我当然不是说这是“正确”的方式,只是说我们应该更认真地考虑它。如果有人有其他想法或想进一步讨论这些想法,我们总是很乐意听取你的意见!
相似文章
任何 text-to-SQL 基准测试都应应对真实数据存储的难点
本文认为,text-to-SQL 基准测试必须考虑到真实世界数据存储的复杂性和挑战,而不仅仅是理想化的数据集。
SurrealDB 3.x 与 Postgres、Mongo、Neo4j 和 Redis 的基准测试(含Fsync)
SurrealDB 发布了基准测试,在生产级持久化设置下将其 3.x 版本与 Postgres、Mongo、Neo4j 和 Redis 进行对比,结果显示其性能较之前版本大幅提升,且与其他数据库相比具有竞争力。
从测试时扩展到可复用记忆:衡量文本到SQL中的结晶化
本文引入了“结晶化问题”,用于评估文本到SQL系统中的可复用记忆,表明将经过验证的修正查询存储在每个数据库的库中,可以将BIRD上留出集的首次尝试准确率提高4.34个百分点,捕获按需修复所提供的44.4%的提升空间。受控干预措施识别出数据库特定内容是主要驱动因素。
@PrajwalTomar_: 你的向量数据库正在悄无声息地拖垮你的AI智能体,而你完全不知道。陷阱就在这里。每个人都选了那个……
一条推文警告说,仅根据速度基准测试选择向量数据库可能会对AI智能体造成陷阱,因为AI智能体具有持续的写入负载,这与RAG以读取为主的模式不同。它根据用例推荐了特定的数据库,例如用于智能体记忆的Qdrant,以及用于PostgreSQL上低于1000万向量的pgvector。
SQLite:持久化工作流的全部所需
这篇博文认为,SQLite 结合 Litestream 进行异步备份,为许多工作流系统(尤其是 AI 智能体)提供了一种简单而有效的持久化执行方法,无需单独编排层或网络数据库。