kdb+ 以讨厌循环而闻名(不要讨厌的循环)。作为 kdb+ 开发者,我们在可使用的架构上有很大的灵活性,今天我将尝试说服你,我们应该从系统中移除另一种东西:rdbs。不要讨厌的 rdbs!
在 kdb+ 领域,本质上有两种存储数据的方式:内存中和磁盘上。数据本身是相同的。这是一个关键的洞见,现在在列式数据领域已经相当普遍(特别是参见 Apache Arrow),因为它消除了在数据从一处移动到另一处时进行序列化的需要,并支持内存映射和从磁盘非常快速地读取数据。kdb+ 支持一系列不同的架构,但标准或默认模型仍然是将两种主要类型的进程一起使用:数据完全位于进程自身内存空间中的实时数据库(rdbs),以及数据序列化在磁盘上并内存映射到进程内存空间中的历史数据库(hdbs)。
我的开场白是有意挑衅且有点开玩笑的。我并不真的认为我们根本不应该使用 rdbs。kdb+ 作为一项技术的一大优势是它的灵活性,而 kdb-x 承诺会增加更多灵活性。我在这里要提出的论点是,rdbs 不应被视为所有 kdb 系统的默认选项。它们一直是 kdb 的核心进程类型之一,但我认为对于许多问题,它们可能不是必需的,你至少应该考虑不使用它们。这种方法并不新颖,已部署在许多安装中,以及 KX 在其 Insights 产品中的一些自有组件中,但关于何时何地可能适用的细节并不总是清晰的。
为什么要有 rdb?
更快的查询
对内存中数据的查询当然要快得多,不是吗?我在一台相当标准的硬件上用相当标准的数据做了一个简单的基准测试:
- 一台 i7i.xlarge ec2 实例,配备 32GB 内存和连接的快速 NVMe SSD 存储
- 纽约证交所 TAQ 交易数据的典型(相对较小)一天。大约 5000 万笔交易,磁盘上 10GB
- 比较对 内存中数据(rdb)、磁盘上数据(hdb) 和 缓存冷时的磁盘上数据(hdb cold cache) 的查询性能
- 使用一小组查询,涵盖一些典型工作负载
所以在讨论这些结果之前,关键的背景信息是,hdb 进程通常实际上不会去磁盘获取数据。它们会内存映射磁盘上的文件,当访问时,操作系统会首先检查页缓存,如果特定文件存在于该缓存中,它就不会真正去磁盘获取它。所以在上面的所有 hdb 结果 中,查询命中的是页缓存而不是磁盘,即缓存是热的。所以当缓存热时,hdb 与 rdb 没有太大区别:它的数据在内存中,但是在操作系统级别的页缓存中,而不是进程自身的内存空间中。我还添加了 第三组基准测试,当缓存“冷”时,这样我们就能看到操作系统必须真正去磁盘时的差异。
那么,这些基准测试的关键要点是什么:
- 让我们不要埋没要点:内存中 在所有基准测试中都是最快的
- 如果查询可以使用属性且结果大小较小,内存中 数据比 磁盘 胜出很大优势。特别是最后一个查询,当数据在内存中且有属性时,速度快得多。这些查询实际上是对哈希映射的查找,因此是 O(1) 且非常快。我们也可以在磁盘数据上添加属性,但磁盘属性在 kdb+ 中不可追加,所以我们无法维护它们。我稍后会回到这一点。
- 如果查询不使用属性,或者使用了但结果大小较大,rdb 和 hdb 之间的性能差距很小。根据查询的不同,它徘徊在 0-20% 之间。这个小差异可能来自访问页缓存时轻微的缺页和 TLB 压力以及其他技术细节,不值得过多担心。操作系统页缓存中的内存数据 并不完全 像 rdb 堆中的数据那么快,但已经很接近了。上面的聚合和带属性的过滤器的大扫描比较可能最有趣:这两个查询都受益于属性,但没有属性的 hdb 仍然不落后太多,因为查询仍然需要访问大量数据,所以属性的好处要小得多。所以实际上,hdb 可以被视为另一种类型的内存数据库,内存位于共享空间:操作系统页缓存
- 即使查询必须一直 到磁盘,速度差异仍然不是很大。最坏的情况下我们谈论的是 2 倍。这就是现代 NVMe SSD 带来巨大差异的地方。在旋转磁盘上,这个差异会大得多。
跟上插入
除了读取数据,我们还需要能够添加新数据。新的交易在一天中不断流入,我们需要能够添加它们。下面是比较这两种设置的另一个基准测试:
内存中 进程在这里有更明显的优势。但它可能不像你想象的那么大。它肯定比你的存储是慢速旋转磁盘时要小得多。另一个关键点是:内存插入是一个串行过程。当 rdb 插入新数据时,它 不能同时提供查询服务。在后一种情况下则不是这样,我们可以同时插入到磁盘上的 splayed 表,并且进程可以同时提供对数据的查询服务。计算和内存是分离的。
为什么不要 rdb?
更快的查询
我知道上面我展示了 rdb(稍微)更快,但那是对一个查询。如果你有 2 个或 10 个查询呢?在 rdb 上,你只能串行运行查询。它不能并发运行查询。但是,如果你的数据共享在磁盘上(并通过操作系统页缓存位于内存中),你就可以并行运行。每个单独的查询可能稍微慢一点,但你可以在单独的进程上同时运行所有 10 个查询。一个在 hdb 上慢 10% 的查询,如果你需要运行两次,就会快 45%,因为你可以并行运行,例如两个查询各 110ms 并行运行总共 110ms,相比之下两个查询各 100ms 串行运行需要 200ms。如果你想在同一系统上支持不同类型的用例,这也是一个很大的架构优势。例如研究查询和对延迟更敏感的交易查询,而不会相互干扰。
默认情况下所有内存都是共享的
我们基本上可以免费分片和扩展。rdbs 有固定的内存成本,如果我们想要更多进程,我们就需要更多内存,新进程启动很慢,因为它们需要将所有当前数据读入内存。当你的数据只存储一次,在磁盘上和操作系统页缓存中时,你可以在其上运行 10 个进程,或者为什么不运行 100 个?这些进程可以几乎立即启动。这从根本上分离了内存和计算
无需网关
如果你愿意,你当然仍然可以有网关进程,但你 不需要 它们来连接 rdb 和 hdb 数据。可以让所有数据在一个进程中都可访问。用户可以启动自己的单个进程,访问所有数据,包括实时和历史数据。
更简单的架构
在实时追加数据时重新加载和映射磁盘数据有一些细节需要考虑,但总体而言,你会有更少的进程和更少的进程类型。没有 rdb、hdbs 和网关之间的协调问题,所以你的系统可能会更简单。
那么,让我们再来谈谈这个……
替代默认架构
标准 kdb+ 架构 之所以如此是有充分理由的。如果你处于内存贵得多,而所有非易失性存储都是旋转磁盘的世界中,将数据库分成两部分就更有意义。对磁盘上 splayed 表的插入会比内存慢得多,可能慢几个数量级,而任何未命中操作系统页缓存(冷缓存)的查询都会慢得多,可能又慢几个数量级。但这已经不是我们生活的世界了。
我提出一种替代的默认架构,看起来更像这样:
tickerplant 进程与之前完全相同,但所有数据都通过“写入器”进程(本质上是 TorQ wdb 进程)直接写入磁盘,所有数据都通过挂载磁盘数据的数据库进程(TorQ hdbs)读取。
在这里我要明确说明,这绝不是一个革命性的想法。我相信今天正在运行的 kdb+ 系统中有一些与此类似的架构。我在这里的论点不是说这是新的,而是说我们应该更认真、更广泛地考虑这种替代方案。
下面是一张更清晰的图片,展示了我们如何将磁盘数据分成“实时分区”和所有标准历史数据:
我相信这种方法给我们带来了几个显著的优势:
- 更简单。 更少的进程意味着更少的问题
- 更灵活且可扩展。 计算和内存是分离的。如果我们需要更多数据库进程,我们可以简单地添加它们,它们没有直接的内存开销,并且会非常快速地启动(不像传统的 rdbs)。我们实际上是将操作系统页缓存用作“免费”的内存管理,而这是经过实战考验的核心操作系统代码,因此非常可靠。
- 数据集中在一处。 如果你需要今天和之前几天的数据,就不需要对两个不同的进程运行两个查询并处理连接等。所有数据都在一处
当然,软件工程中的一切都是权衡,这里我们做的权衡是:
- 与 rdb 相比,查询性能稍慢。如上所述,差异比你想象的要小得多,但确实存在实际差异。
- 在正在实时追加的分区中,我们不能有属性。 这意味着对于某些“小查找”类型的查询,性能差异是巨大的。然而,这些查询可能不是你工作流的重要组成部分,或者如果你想要上述优势同时保持这些类型查询的速度,你可以为所需内容添加专用缓存进程,即根据需要添加专门的缓存,而不是大型昂贵的一般用途缓存进程(即 rdb)
对于“实时追加”数据库(dbs,不再需要称之为 hdb 和 rdb),还有几个关键的技术考虑:
- 我们希望将“实时分区”锁定到操作系统页缓存中,以确保查询很少/从不必须去磁盘。如果数据被大量使用,这自然会发生,因为操作系统会尝试将频繁访问的数据保留在缓存中。但是,我们也可以使用 vmtouch 或 tmpfs(RAMdisk)来直接控制这一点。所以我们可以保证始终获得上面的热缓存基准测试结果,而不必去磁盘。这相对容易设置。
- 数据库进程与 symfiles(枚举)和正在实时追加的列之间存在协调问题。这并不像看起来那么糟糕。我相信解决这个问题的最佳方法是使用 .Q.MAP 来避免每次查询的 mmap 开销,并使用 \l 定期重新加载 symfile 并重新映射最新数据。原则上这可以非常快(在我的测试设置中,运行 \l 需要 0.018ms),我们有几种选择,具体取决于我们希望在系统中做出的可用性和速度之间的权衡:
- 如果足够便宜,你可以在任何查询之前简单地运行它
- 或者你可以在每次插入时让写入器触发所有 dbs 的重新加载,这样我们只在新数据到来时重新加载
- 或者它可以基于定时器
- symfile 必须保持较小,因为我们会经常重新加载它。虽然我们可以在这方面变得聪明,并乐观地重新加载它,即使用 chksum 快速查看它是否已更改,仅在需要时重新加载。
- 我们不能在“实时”分区中拥有属性。传统上,hdb 分区在最常过滤的列上放置 p 属性,这充当查找的索引。磁盘属性在 kdb+ 中本质上不可追加,所以我们必须没有它们。但是,我们仍然可以在数据 rollover 时正常排序并添加它们,使数据进入历史记录并移出“实时分区”
根据你的特定系统的要求,上述架构随后可以扩展为带有实时引擎和/或缓存进程,以计算分析(例如 VWAPs、bars 等)。这些值随后可以发布回 tickerplant 并从数据库进程读取,或直接访问
我不想过多纠结于这些技术细节,所以我将尝试跟进如何使用 TorQ 设置这种架构的细节:
- 如何尽可能快速地重新加载和重新映射数据
- 翻转
- 使其尽可能容易设置和开始使用
我不是一个喜欢长篇结论的人,但希望我至少让你考虑了 kdb+ 的替代默认架构。“无 rdb”架构可以更简单、更可扩展且几乎一样快。我当然不是说这是“正确”的方式,只是说这是我们应该更强烈考虑的事情。如果其他人有想法或想进一步讨论这些想法,我们总是很乐意听取你的意见!
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.