I/O 模式怎么选,ScyllaDB 教会你

标题:Different I/O Access Methods for Linux, What We Chose for ScyllaDB, and Why

日期:2022/05/19

作者:Avi Kivity

链接:https://www.youtube.com/watch?v=27Xz21RJoUA

注意:此为 AI 翻译生成 的中文转录稿,详细说明请参阅仓库中的 README 文件。


谢谢你,Marisa。那么我们开始吧。简单介绍一下我自己。我叫 Avi。我是 Linux 虚拟机管理程序 KVM 的最初维护者。我是 CSTAR 的联合维护者,CSTAR 是一个 I/O 和异步编程框架;也是 ScyllaDB 的联合维护者,ScyllaDB 是一个数据库,一个能够管理数 TB 或更大规模数据的大数据数据库。我也是公司的联合创始人。因此,在担任 Linux KVM 和 ScyllaDB 这两个角色期间,以及之前的角色中,我都必须处理大量的 I/O。我想分享一些我在这个主题上的经验。

简单介绍一下 ScyllaDB。它是一个分布式 NoSQL 数据库,适用于拥有海量数据(从 TB 到 PB)且需要每秒数十万或数百万次操作的应用,所有这些操作都要求低延迟。我们有很多用户,他们能够报告延迟的降低(尤其是在 99 百分位)、吞吐量的提升以及节点数量的减少,这直接转化为成本的降低。它提供开源版本、企业版和云附加组件。它与 Apache Cassandra 协议和 Amazon DynamoDB 兼容。因此,从现有数据库迁移过来相当容易,并且它加入了一个庞大的生态系统。

我们的一些客户。如果你认出这些组成整行或整列的徽标中的任何一个,只要喊“Bingo”,你可能会赢得奖品,但可能不会。但你可以看到我们有来自各种不同类型应用的客户。连接他们所有人的共同点是,他们都需要管理海量数据,并且延迟对他们来说非常重要。

因此,对于一个数据库来说,访问数据、访问磁盘上的数据非常重要,而 Linux 提供了多种访问数据的方式。在本演讲中,我将讨论四大类方法,它们之间的区别,以及为你的应用选择哪种方法。

那么这四种方法是什么?

  1. 传统的系统调用 readwrite,与 opencloseseek 等一起使用。这些方法来自 1970 年代。大多数应用程序都是这样编写的。

  2. 更现代的 mmap,被一些较新的应用使用。

  3. 直接 I/O (Direct I/O),它绕过 Linux 页面缓存,提供了一些好处,但也带来了一些复杂性。

  4. 最后,是最现代的方法,异步 I/O (Asynchronous I/O),如今流行的 io_uring 也加入了这一行列。我们将最后讨论它。

为了评估这些方法,我们将看两个不同的工作负载示例。

  • 一个示例是桌面工作负载。它不使用海量数据,但仍然是数据密集型的,比如 Git。Git 处理的仓库大小可能达到 GB 或更大。所以它是一个很大的工作集,但通常能放入内存。Git 是一个短生命周期的应用。它实际上以极其快速而著称。执行一次 Git 操作可能只需要几毫秒。同步要求是宽松的。你不太担心数据是否真正写入磁盘,因为你通常信任你的台式机或笔记本电脑不会崩溃。即使它崩溃了,也不是世界末日。你通常可以恢复你的工作。它是部分多线程的。所以一些操作运行在多个线程中,但大多数操作只运行在单个线程中。

  • 让我们将其与数据库工作负载进行对比。对于数据库,工作集通常超过内存。这是因为数据库需要管理数 TB 的数据,但你不想购买数 TB 的 RAM。数据库是一个长生命周期的应用。你启动它一次,希望它能持续运行数月或数年。当然,你必须不时地更新它,但它不是一个频繁启动和关闭的应用。它通常有严格的同步要求。因此,当你确认一个写入时,你希望确保它确实落盘了,并且崩溃或断电不会导致数据丢失。你想保证数据的持久性。数据库通常是重度多线程的。它在大型多台机器上运行,客户端有很多线程。所以同时有许多用户与数据库交互。

因此,这两种应用虽然都访问磁盘 I/O,但差异很大。这将决定它们执行 I/O 的方式。我们将根据不同的标准评估这四种方法。第一个当然是性能。但性能还有第二个维度,即最坏情况性能。另一种看待它的方式是可预测性。你不仅希望常规性能良好,还可能希望最坏情况性能也良好,这样你就不会有意外。在桌面工作负载中,这可能无关紧要。偶尔速度变慢,你等一分钟,也许去喝杯咖啡。没什么坏事发生。但对于数据库来说,拥有良好的最坏情况性能可能非常重要。我们不希望出现意外。最后是复杂性。我们希望避免复杂性。它代价高昂,是 bug 的来源。所以最好避免它。但有时无法避免。

让我们从传统的读写开始。希望它不需要太多解释。让我们看看 I/O 路径中发生了什么。

  • 读路径最佳情况:应用程序发出 read。如果你幸运,它命中了缓存。一切都很顺利。你执行一个系统调用,内核将数据从缓存中复制出来。这获得了极佳的性能并且极其简单。

  • 读路径缓存未命中情况:如果数据不存在于缓存中,情况就变得复杂了。接下来发生的是,内核告诉磁盘开始执行读取。然后它寻找其他事情来做。它不能返回给应用程序,因为这是一个同步系统调用。所以它需要找到另一个线程来切换,可能是同一应用中的线程,也可能是不同应用中的线程。它可能会切换到你的浏览器或另一个数据库线程。与此同时,那个线程执行自己的计算,磁盘执行寻道操作。如果我们找不到其他线程来执行,那么 CPU 将被内核置于睡眠状态。磁盘将数据传输到内核,然后中断内核。内核会注意到这一点,并切换回数据库线程或应用线程,复制数据。你可以看到这里涉及更多的复杂性。而且成本也更高,因为我们遇到了上下文切换。如果这是一个常见操作,我们将看到大量的上下文切换,性能将开始下降。这对于像 Git 这样的应用来说没问题,它期望大多数时候命中缓存。但对于数据库来说就不太好了,它预期会有很大比例的缓存未命中。

让我们看看写路径。

  • 写路径最佳情况:同样,通常很顺利。你复制数据。发出 write 系统调用,它将数据复制到内核并立即返回。应用程序可以继续处理。因此数据根本没有触及磁盘。它缓存在内存中。这里的理念是内核寻找机会合并多个写调用,甚至完全避免写入磁盘——如果你正在写入一个临时文件然后很快删除它。如果你删除得足够快,那么你可能完全不需要将临时文件写入磁盘。这样就节省了 I/O。如果你没有删除文件,那么最终内核会在应用程序处理的同时并行执行回写操作。这很好,因为它与常规处理并行发生。但这也意味着对于需要保证数据落盘的数据库来说,可预测性较差。有办法强制执行(如 fsync),但我现在不深入讨论。

  • 写路径内存压力情况:然而,写操作也可能变得更复杂。之前,内核为写操作分配了一些内存,对吧?只是把数据放在那里。但内存不是无限的——除非你姑姑拥有一个 DRAM 工厂。它是非常有限的,你可能会耗尽内存。如果内核耗尽内存,它会寻找之前写入的较旧数据,并开始回写这些旧数据。在它等待这个回写完成以释放内存期间,它会切换到另一个线程,类似于我们在读路径缓存未命中时的情况。当回写完成时,它会中断并通知内核可以继续处理我们最初的写操作。然后最初的写操作才会继续。稍后,我们自己的写操作才会被回写到磁盘。

所以你可以看到,通常写操作非常快。但在某些情况下,它会变得复杂而缓慢。

让我们评估传统的读写。

  • 性能:通常非常好。

  • 可预测性:差。因为你无法判断读是否会命中缓存。你当然也无法判断写操作是否有足够内存,或者是否需要减慢速度以等待之前的写入回写完成。

  • 复杂性:低。接口被充分理解且简单。很多应用都是用这种方式编写的。

让我们继续。这或多或少是本次演讲的基线。现在谈谈 mmap。我想大多数人都熟悉它。但无论如何我还是简单说一下。应用程序告诉内核为文件创建一个内存映射(memory mapping),一块与文件数据有一一对应关系的内存区域。任何使用常规机器指令对此内存的读写操作,都将透明地转换为对文件的读写操作。内核在后台管理它。分配给映射的内存量也是透明管理的。内核可以决定为映射分配大量内存以使其更快(代价是消耗更多内存),或者为映射分配较少内存(这会使其变慢,但允许将内存挪给其他应用)。通常内核非常擅长在这种情况下决定怎么做。在很多情况下,你可以忽略你正在这里做 I/O 的事实。你就像操作内存一样工作,内核会处理好一切。这非常好。

让我们看看一些 I/O 交互:

  • 缓存命中读:这甚至比我们之前用系统调用 (read) 的情况更好。这里我们甚至不需要系统调用。你可以直接从内存读取并继续。这耗时可能不到一纳秒。这速度快得惊人。

  • 缓存未命中读:情况类似于我们在系统调用中缓存未命中时的情况… 我们需要发起磁盘读取,并执行上下文切换到另一个应用或另一个线程。但略有不同的是,这里不是系统调用,而是缺页错误 (page fault)。这大致相似。这里的一个优势是,我们甚至避免了复制。内核和应用之间的内存映射是共享的。所以当磁盘执行 DMA(直接内存访问)到内核内存时,它也更新了映射到应用程序的同一块内存。我们不需要执行数据复制。这是另一个优势。一旦我们返回到应用程序,它执行读取操作就像什么都没发生过一样。因此,底层有很多复杂性,但从应用程序的角度来看,相当简单。尽管它确实降低了可预测性。

写操作同样很快。写操作不涉及任何额外操作。应用程序写入自己的内存,不需要告诉任何人。内核会在后台注意到内存已被更新,并执行回写操作。所以再次说明,非常理想。也存在你执行写操作时遇到内存压力的情况。但这相当复杂,所以我跳过了为此写幻灯片。这大致类似于我们在系统调用写操作时遇到的情况。

让我们评估 mmap

  • 性能:非常好,甚至比传统的读写更好。

  • 可预测性:相当差。当它工作时,效果非常好。但当应用程序开始使用越来越多的内存并管理更大量的数据时,它就变得不那么可预测了。你最终可能会遇到性能糟糕的情况。

  • 复杂性:稍微复杂一点。你需要处理一些细节,比如 I/O 是否发生在页边界上,还有其他一些事情需要处理。但对于能够利用它的应用来说,这是一个很好的权衡。你牺牲了一点复杂性,换来性能的显著提升。它特别适合短生命周期的应用,因为它们可以重用缓存中的文件,启动成本非常低。像 Git 这样的应用就使用 mmap,这对它们来说是一个很好的匹配。

现在看看直接 I/O (Direct I/O)。首先解释一下。“直接”部分意味着它绕过 Linux 的缓存机制,直接访问磁盘。这意味着缓存必须由应用程序自身完成,除非你根本不需要缓存。这立即意味着复杂性的增加。I/O 传输被限制在扇区(sectors)级别。扇区是对齐的 512 字节块。应用程序负责对齐访问。如果你想访问一个字节,你实际上需要访问整个扇区。所以你需要向上向下取整对齐。这增加了更多复杂性。一个好处是传输绕过了内核。不需要复制数据。这提高了性能。还有很多额外的限制,太多了无法在此涵盖。所以复杂性相当高。

工作流程如下(读写路径相同):

  • 有好消息也有坏消息。

  • 好消息是只有一条路径。实际上读写共用一张幻灯片,因为它们看起来完全一样。

  • 坏消息是,在所有情况下,性能都很低。因为在所有情况下,我们现在都没有命中缓存的情况(因为它绕过了缓存)。所以在所有情况下,我们都需要执行上下文切换,等待中断,然后再切换回来。DMA 意味着我们不复制数据,但也意味着会有大量的上下文切换。

评估直接 I/O:

  • 性能:差。因为大量的上下文切换。

  • 可预测性:好。因为你确切地知道会发生什么。你没有那种耗尽内存导致性能急剧下降的情况。

  • 复杂性:高。

所以这肯定不适合像 Git 这样的桌面应用,对于像数据库这样的东西也不是一个很好的匹配,因为性能不好。因此,直接 I/O 实际上只是通往异步直接 I/O (Asynchronous Direct I/O) 的垫脚石。

什么是异步直接 I/O?它类似于直接 I/O,但不是单个线程一次只管理一个传输。这个单线程可以发起多个传输,并且不仅可以在传输进行时发起更多传输,还可以继续做其他事情。这就是异步部分。内核会在 I/O 传输完成时通知应用程序。因此,我们不是使用同步系统调用,而是使用异步消息传递:应用程序发送消息给内核,代表它执行某些操作(比如读写数据),但它不需要等待消息被处理。它可以继续执行并发送更多消息(如果需要)。

这个的流程图实际上没有一个固定的图,因为每次交互看起来都有些不同。在这个例子中,我有三个读和一个写的示例。应用程序首先在内存中准备这些读写操作。然后它指示内核开始执行这些请求,内核告诉磁盘执行这些请求并立即返回。磁盘开始处理,现在磁盘和应用程序在并行运行。最终,其中一些请求完成,磁盘通知内核,内核再通知应用程序。应用程序接收这些完成通知,并可以开始处理它们。完成可以乱序发生。所以这里有很多复杂性。另外,通常没有上下文切换,因为应用程序总是有机会继续处理。它永远不需要等待。只要有工作要做,它就可以利用 CPU。

评估异步 I/O:

  • 性能:极佳。这是峰值性能。

  • 可预测性:好。因为我们绕过了缓存系统。没有命中或未命中缓存的区别。我们知道磁盘 I/O 的特性。

  • 复杂性:高。因为你需要能够管理多个进行中的请求。你需要自己执行缓存。这就是为什么对于异步 I/O,通常你会使用一个框架来为你执行 I/O,而不是自己动手。

因此,对于像桌面应用这样的东西,这完全是杀鸡用牛刀。而且因为你无法依赖内核缓存,它的性能实际上会更差。每次启动这个桌面应用时,它都必须从头开始读取所有数据。但对于像数据库这样的东西,它运行数周或数月,并且能够管理自己的缓存,这确实是一个绝佳的匹配。

让我们从中得出一些结论:

  • 我们有很多访问 I/O 的方法。这里只描述了四种不同的方式,但实际上还有更多细微差别,甚至有更多方法。

  • 对于绝大多数应用程序,最好利用 Linux 在 read/writemmap 上投入的巨大优化努力。站在巨人的肩膀上,重用所有这些成果。

  • 但是,对于有更严格要求的应用,比如工作集非常大且超过内存;如果你有关于数据持久性的要求(你想确保数据何时落盘,并想在每个请求基础上控制它);那么额外的控制和额外的效率可以给你带来巨大的好处。

  • 这需要一些投入。通常你需要一个 I/O 框架。你需要更多了解磁盘的工作原理。你需要进行一些实验。但对于这样的应用来说,回报是非常丰厚的。

让我们用一个表格来总结一切:

方法

适用场景

原因

传统读写

应用非 I/O 密集型。大多数应用都不是。或者进行简单的流式处理,比如 Linux 中的常规管道工作 (grep, sort, uniq 等)。

优秀、简单、性能良好。无论数据是否在缓存中都能良好工作。性能虽然不是顶尖的,但非常好。通常是首选方法,尽管它们可追溯到 70 年代。

mmap

数据密集型应用,有大量随机 I/O。这是传统读写不擅长的领域(因为需要执行许多缓慢的系统调用)。磁盘空间占用率低(工作集 <= 内存)。

Git 属于此类。它需要对大文件(pack 文件)进行大量随机访问。数据集在几 GB 量级(通常更小)。内核不会被强制逐出页面并可能做出错误决定。读取的数据通常来自缓存。不关心数据是否立即写入(笔记本有电池,即使没有,偶尔断电也不是灾难)。

直接 I/O

理解其工作原理。因为它绕过了 Linux 缓存层。可用于探测磁盘对不同情况的反应。它相对简单,易于理解。

是通往 AIO 的良好垫脚石。本身不推荐使用(性能差,复杂性高)。

异步直接 I/O

数据库。以及类似类别的应用,如消息队列等。许多未被归类为数据库的 I/O 密集型应用也适用。

投入精力使用 AIO 是值得的。对于获得可预测的性能和低延迟非常有益。

这就是我的内容。我现在很乐意回答问题。Abby,Q&A 里已经有一些刚提交的问题了。好的。那么我来看一下。好的。那么第一个问题是:异步 I/O 操作不是按照它们被触发的顺序完成的。这种非确定性不会影响数据库的正确性吗?数据库当然必须考虑到这一点。数据库必须要么确保顺序无关紧要。一个简单的例子是读操作。对于读操作,你通常不关心它们完成的顺序。另一个顺序很重要的地方通常是写操作。有时数据库可以确定顺序无关紧要。如果写操作写入的是文件中不相关的区域,那么无论哪个顺序写都可以。也有顺序确实重要的例子。我想一个例子是先写入提交日志,然后再写入文件。你想确保在开始修改文件之前写入了实际的提交日志。对于这种情况,你可以自己排序。在收到第一个写操作完成之前,不要发起第二个写操作。这样你就实现了一个屏障(barrier)。通常这并不复杂。这里没有真正的复杂性。我想对于文件系统来说,这更多是个问题。文件系统希望在元数据更新之前不更新数据。有时情况相反。所以它增加了一些复杂性,但这就是现实。我必须说,其他方法并没有真正解决这个问题。因为当你使用 writemmap 时,一切看起来都是按顺序完成的。但实际上,在发生断电或崩溃时,可能只有部分写操作到达了磁盘,并且到达的写操作可能是后来的写操作而不是较早的写操作。所以无论你选择哪种方法,都必须处理这个问题。这可能很棘手。但如果你在处理数据库,那么你已经习惯处理棘手问题了。我希望这回答了问题。这是个好问题。如果你有更多疑问,尽管问。

我有一个问题:像 RocksDB 这样的存储引擎在这里属于哪一类?我想问题是:像 RocksDB 这样的存储引擎属于哪一类?我其实不知道 RocksDB 用什么。我希望他们为了自己好用的是异步 I/O,但我不真正了解代码。我知道大约 10 年前流行用 mmap,但我认为他们不那样做了。我真的不知道。我知道例如 MongoDB 曾经使用 mmap,但后来转向了 AIO,他们意识到 mmap 不是正确的解决方案。

下一个问题:关于应用程序处理自己的缓存。它指的是内存缓存,而不是 L1 或 L2 缓存吗?是的。我指的是使用主内存作为磁盘的缓存。反过来,CPU 会将主内存缓存在 CPU L2 缓存中,并将 L2 缓存在 L1 缓存中。所以我指的是内存(主存)级别的缓存。我想有了持久内存(Persistent Memory),你甚至可以添加另一层。你可以使用持久内存作为磁盘的缓存,使用内存作为持久内存的缓存。你可以选择多种拓扑结构。

另一个问题:使用直接 I/O 或 AIO 时,如何处理缺乏缓存的问题?这是一个有趣的问题,因为实际上有多个答案。一种执行缓存的方法是页面缓存 (page cache)。这意味着磁盘上的每个块都有一个与之关联的内存块,它只是该磁盘块的副本。我们确实对一些数据使用这种方法。哦,但我们也使用对象缓存 (object caches)。对象缓存不同,因为它们不是磁盘块的内存镜像。相反,它们是磁盘块的子集,并且可能已经被解析过。因此,一些计算,一些解析已经发生,我们缓存的是解析后的对象。这意味着我们节省了解析工作。我们不需要每次访问这个块时都重新解析,即使它被缓存了。我们只解析一次,然后重用解析后的对象。这样我们节省了一些计算开销。除此之外,有时一个逻辑记录(比如数据库中的一行)的来源是多个文件。为了生成一行数据,你需要合并来自多个文件的数据。与其单独缓存每个文件,我们缓存合并后的结果。这样我们就不需要在每次访问该行时都执行合并操作。相反,我们在第一次访问该行时执行合并。随后的访问,我们直接从内存访问。这节省了合并工作。所以这是一个相当复杂的缓存,但在你需要执行这种合并时(这在你有日志结构合并树 (Log-Structured Merge-Tree, LSM-Tree) 存储模型时很常见——这也是我们使用的模型,RocksDB 也是,并且现在越来越流行),当特定行的数据来自多个文件时,它可以非常高效。我希望这回答了问题。我们在我描述的单一缓存中使用了所有这些方法。

又一个问题。我得说,我预料到会有关于 io_uring 的问题,因为它太流行了。你们使用 io_uring 进行异步 I/O 吗?能多谈谈 io_uring 吗?目前,我们使用 Linux AIO(一个较旧的异步 I/O 接口),而不是 io_uring。这只是因为我们开始开发的时间远在 io_uring 存在之前。在性能方面,我们不期望它会实质性地改变性能,因为异步直接 I/O 的关键在于绕过(大部分)内核处理,所以内核不需要做很多工作。尽管如此,io_uring 有很多好处。其中之一是它正处于积极且密集的开发中,维护得非常好。所以最好选择活跃、有新功能和优化不断进行的项目。第二个优势是它的人体工程学更好。你可以在禁用缓存的情况下运行 io_uring(做直接 I/O),也可以在启用内核缓存的情况下运行它。这对于开发测试时更友好——开发时你可能不想执行直接 I/O,访问页面缓存就可以了。测试时,数据库实际上就像一个短生命周期的应用,不使用大型工作集,更像 Git 而不是数据库。在这种情况下使用页面缓存你会很高兴。而且它与网络有非常好的集成。当然,数据库涉及大量网络 I/O。这个演讲完全没谈网络,但拥有集成的网络和磁盘 I/O 意味着 I/O 的管理变得更好。所以我们正在开发 io_uring 接口(用于 Seastar)。我期待着切换到它。它是一个非常棒的接口。就我之前在图表中展示的模式而言,它实际上与 AIO 相同。它只是一个更令人愉悦的接口,并且在 CPU 消耗方面有一些优化。

继续下一个问题。mmap 节省 CPU 周期相比用户空间缓存?它由 MMU 处理,CPU 开销为零。像 LMDB 和一些实验性产品使用双 I/O 路径(读用 mmap,写用直接 I/O)。你为什么建议 mmap 只应用于小型数据集?问题在于大型数据集。首先,当然,mmap 被用于 LMDB 并且非常成功。但当数据集变大时,内核必须不断地将页面从文件的映射中解除映射(unmap),并将它们重新映射到新的页面。首先,内核没有很好的知识来决定选择哪个页面来释放。它可能选择错误的页面。当然,内核中投入了大量工作使 LRU 算法表现良好。但它仍然是一个通用系统,而数据库是一个专用系统。所以数据库对自己的算法和访问模式有更多的了解。其次,当你从 mmap 中移除一个页面时,突然你从,也许我可以移到图表上。所以突然你从,你所说的那条极其快速高效的路径(缓存命中),移到了需要执行上下文切换到另一个线程的复杂路径(缓存未命中)。这意味着你必须,你必须找到另一个线程。可能没有其他线程可用。所以,应用程序必须提供大量线程供内核选择。在使用异步 I/O 时,这变得更加复杂。你可以,拥有少量线程,匹配你拥有的核心数。这减少了上下文切换的次数。因此,只要 mmap 能够满足绝大部分从内存的读取,它的性能就会非常出色。但一旦 mmap 开始服务大量的未命中,性能就会下降。所以这取决于,数据集的大小,或者更准确地说,工作集的大小。数据量可以大于内存,但如果工作集能放入内存,那就非常好。但如果工作集变得比内存大,那么性能就会开始下降。

下一个问题。你提到了 Seastar。你能多谈谈 Seastar 是什么吗?好的。Seastar 是一个用于,异步网络、CPU 和磁盘 I/O 的框架。异步网络是很多框架在做的事情。所以每个人都在做异步网络。至少对于服务器来说,没人再做阻塞式网络了。但是 I/O 通常以同步方式完成,要么使用 mmap,要么使用传统的读写。多核通信、多核利用也通常以同步方式完成。所以通常,如果你有一个多线程应用,你需要锁来管理,共享内存结构。而 Seastar 以异步方式处理一切。它以异步方式处理磁盘(这是本次演讲的主题),以异步方式处理网络,也以异步方式处理多核系统,使用消息传递。所以,与其获取一个锁并阻塞一切直到锁被获得,不如,你向另一个核心发送一个异步消息。那个核心代表你执行访问操作,然后返回结果。这种统一的,处理 CPU、磁盘和网络的方式,都以相同的异步方式进行,可以带来,高度并发的应用,没有,没有并发瓶颈。你永远不会遇到,锁竞争。它也更复杂,但对于像数据库这样的高并发应用来说非常出色。所以我希望这回答了问题。顺便说一下,Seastar 是,开源的,采用 Apache 许可证,你可以在我们的 GitHub 页面上找到它。

下一个问题。SSD 垃圾回收在 ScyllaDB 的基准测试中可观察到吗?将这种硬件机制与数据库集成会让它更快或更可预测吗?这是一个,很好也很复杂的问题。答案取决于具体情况。使用更新的磁盘,SSD 垃圾回收的可观察性降低了。而且,ScyllaDB 也努力,非常注意以一种对 SSD 友好的方式布局数据。所以我们使用只追加(append-only)而不是覆盖(overwrite)。我们最近启用了在线丢弃(online discard),这是,一个文件系统机制,用于通知 SSD 特定文件已被删除且不再使用。这样它可以更早地执行垃圾回收算法而不是等到以后。我们在一些磁盘上,尤其是那些,使用了很长时间的磁盘上,确实看到了显著的性能下降和,更高的延迟。但是,通常使用更现代的磁盘,它,它是不可见的。有很多关于,一种叫做开放通道 SSD (Open-Channel SSD) 的讨论,它是一种暴露垃圾回收机制的 SSD。但事实是,它们并不,广泛可用,并且难以使用。所以我们,我们没有这种交互。肯定非常有趣。但在这种硬件变得广泛可用之前,我们可能不会,对它做任何事情。当然,它会让事情更可预测。虽然我觉得使用现代文件系统(顺便说一句,我们使用 XFS)以及我们在磁盘上布局数据的方式,我认为问题,是可控的。但如果你在开发自己的 I/O 引擎,这肯定是需要,牢记的事情。

下一个问题。异步 I/O 也是由内核通过系统调用提供的?是的,异步 I/O 是由系统调用提供的。旧的方法是 Linux AIO,系统调用是 io_submitio_getevents。对于 io_uring,有一个系统调用叫 io_uring_enter。所以尽管它使用系统调用,但涉及大量批处理。一个系统调用可以提交,几十个 I/O 请求,它们可以是磁盘 I/O,也可以是,网络 I/O。所以系统调用的成本被分摊到许多操作上,变得可以忽略不计。在某些条件下,也有机制可以绕过系统调用。所以是的,它是使用系统调用完成的,但系统调用的成本被缓解了。

好的。我看到,没有更多问题了。如果有人想让我详细说明前面的问题,或者,问一个新问题,我想我们还有一点时间。顺便说一下,在我们的 GitHub 上,你可以看到,Seastar 和 ScyllaDB 的代码库。也欢迎你加入我们的 Slack 频道并在那里提问。有一个专门讨论 Seastar 的频道,以及一个供 ScyllaDB 用户使用的通用频道。所以它既适合 Seastar 和 ScyllaDB 的开发者,也适合用户。

Abby:Q&A 里又来了一个问题。好的。我看看。考虑过为 ScyllaDB 使用 SPDK 吗?是的。SPDK 是,类似于 DPDK(数据平面开发套件)的存储对应物?也许是存储性能开发套件(Storage Performance Development Kit)。不确定这个缩写。它是用于存储的内核旁路(kernel bypass)。我们确实考虑过,甚至做了一些工作来尝试。但最终,由 XFS 提供的性能和可靠性,不是我们愿意轻易放弃的东西。需要投入的努力,去通过 SPDK 实现直接,I/O 的工作量太高了。所以我认为那里有边际改善,但对我们来说不值得付出这些努力。使用 io_uring 或 Linux AIO 的内核开销真的很小。所以尽管你在做,系统调用,这些系统调用的成本被分摊了。如果一个系统调用花费,一毫秒或一微秒左右,也许是两微秒,如果你把它们分摊到 10 或 20 个 I/O 上,它就变得可以忽略了。所以我们考虑过它,但是,成本性能权衡对我们来说不划算。

好的。另一个问题。那么其他非数据库应用使用 Seastar 吗?是的,CEPH,它是,由 Red Hat 和其他公司开发的分布式文件系统。分布式对象存储也正在迁移到 Seastar。还有,Redpanda,它是,一个兼容 Kafka 的消息队列,也使用 Seastar。它是完全基于 Seastar 构建的。还有几个其他的。我想,像 CEPH 这样的文件系统和像,像 Redpanda 这样的消息队列。它们类似于数据库,因为它们非常数据密集型。并且,所以它们是类似类别的应用,但,它们是非数据库应用。它,Seastar 确实非常适合文件系统和数据库类型的应用。任何需要管理海量数据的应用,但,超出这个,范围的应用,它真的,太复杂了。所以如果你,如果你想每秒执行 GB 级别的 I/O 并且 CPU 利用率低,就选择它。好的,我想我们时间快到了。希望,所有问题都得到了回答,如果有遗漏的,我,乐意,在我们的 Slack 频道或邮件列表上回答。那么那里见。谢谢大家。再见。

Marisa: 非常感谢 Avi 今天抽出时间。也感谢大家的参与和提出这么棒的问题。快速提醒一下,本次录播稍后会上传到 Linux 基金会的 YouTube 页面。再次感谢。希望大家参加我们未来的网络研讨会。祝大家度过愉快的一天。祝大家度过愉快的一天。祝大家度过愉快的一天。祝大家度过愉快的一天。谢谢。祝大家度过愉快的一天。