为何我重新实现了LVM(尽管保证更差)
摘要
作者解释了为什么他们重新实现了LVM,尽管保证更差,以优化microVM在hypervisors上的启动性能,专注于使用Linux device-mapper的特定工作负载需求。
<p><a href="https://lobste.rs/s/khm6np/why_i_reimplemented_lvm_with_worse">评论</a></p>
查看缓存全文
缓存时间: 2026/08/18 16:21
# 为何我重新实现了LVM(牺牲了部分保证)
来源:https://depot.dev/blog/why-i-reimplemented-lvm
> 等等,你重写了LVM,还让它变得更差了?!
这是我开始重新实现LVM时脑海里听到的问题——毕竟LVM已经稳定运行了二十年。但我无法为我们的microVM裸机管理程序使用原生的LVM2:它功能过于庞杂,对我们的工作负载假设过于简单。
## 运行microVM很奇特
这一切始于我们转向在裸机管理程序上运行microVM来承载托管客户工作负载的沙箱。这意味着我们运行着昂贵的裸机,绝不应在客户工作负载之间浪费任何一毫秒——因此我们实现了亚秒级的microVM启动。
在本地SSD上实现亚秒级启动已是个不小的挑战,但我们还要使用高性能的网络存储做到这一点,因此我们不得不为自身(重新)发明一些工具。
**注意**:这一转变也意味着我们正在自动扩缩昂贵的裸机,因此启动管理程序并为microVM启动做准备所花费的每一秒都至关重要。
## LVM究竟在做什么?
我们欣赏LVM的原因在于它极其简洁,且因为数据平面基于Linux内核的设备映射器基础设施,它在使用最少“黑魔法”的同时实现了良好性能。
然而LVM的设计初衷是相当安全并提供保障,例如确保在创建和删除卷时崩溃不会导致数据消失。更进一步,如果你误删了数据,你有很大机会能恢复它!
这是以全局锁(准确说是卷组范围锁)为代价的,如果每周只执行一次操作,这完全可以接受。但若一次操作需持有卷组锁约100毫秒,而你想每秒并行执行多达200次这样的操作,这就几乎不可能了。这让人不禁怀疑是否选错了工具。
### LVM的保障 vs 我们实际需要(且愿意承担成本)的保障
锁定和数据安全确实是很棒的特性,但如果因此导致工作负载无法达到性能目标,你就无法使用该方案。我们面对LVM正是这种情况。
我们特别需要快速解决方案的场景之一是组装供管理程序启动microVM使用的块设备。本质上,它需要从远程主机获取网络块设备,在其上添加动态大小的缓存,最终生成单个块设备。
深入探讨本文最重要的部分:我们需要一种非常快速的方式从大型内存池中分配可变容量的RAM,然后通过设备映射器创建带缓存的块设备。
在此特定场景下,崩溃安全性并不能为我们带来太多价值。管理程序崩溃会导致临时microVM工作负载失败(因此被丢弃)以及易失性内存丢失(无论如何都会导致数据丢失),所以崩溃保护要么没有价值,要么没有需要保护的数据。
我们需要相当好的性能和高可靠性,因此我们使用了与LVM相同的设备映射器基础设施。只是我们的组装方式不同。
## 解决方案架构
作为第一层,我们使用提供多种块设备的网络存储。这部分内容超出了本文范围,但我们拥有自己的存储代理和存储守护进程,它们负责呈现可变大小的块设备。
第二层是Linux内核提供的内存块设备。实现这一点有多种机制和模式,但最终结果基本相同:一个由我们存储代理管理的内存池。
一旦拥有此内存池,存储代理中的进程内分配器将对其进行管理。分配器在内存中维护映射、切片及所有其他数据,能够处理自定义大小和形状的分配。
由于此关键路径在进程内完成,我们无需打开磁盘或进行任何“真实”的IO操作,因此分配速度极快。这种分配是我们唯一需要某种锁定机制的地方,且仅用于内存映射。
由于映射确保了内存块设备使用不存在竞争,我们可以享受内核的异步设备映射器创建,相比相同用例下使用LVM实现了约100倍的性能提升。
尽管第一个原型仍然使用`dmsetup`,它已经足够满足我们的启动时间目标。只是它并不那么优雅。
### 如果存储代理崩溃了怎么办?
最妙的是:我们实现了控制平面的智能部分(分配等),但我们不直接“编程数据平面”。内核保存着我们映射关系的关键部分。我们的代理会定期将映射保存到磁盘,但我们可以通过读取内核中的设备映射器映射来重建并验证它。这意味着我们的代理可以随时崩溃而不影响运行中的工作负载。即使恢复失败,现有的设备映射器设备仍会继续工作。
### 为何不使用(其他存储技术)?
在此之前,我们评估了多种存储解决方案(它们各有千秋!),但没有一个完全适合。这篇博文聚焦于我们存储栈的一部分,但我想稍作扩展。
我们期望的架构和性能目标使得ZFS不适合我们,即使经过调优。Btrfs和Stratis也是如此,且它们都无法提供块设备。虽然存在环回设备,但我们不想走文件系统的方向。
更好的替代方案是让我们拥有更多自己的存储栈,但这是另一篇文章的主题。
## 代价是什么?
开发过程比我预期的要快得多,但代价是调试一些与锁定、异步工作(指我们而非内核)、网络块设备协调(它们在本地呈现时可能随机耗时更长)以及各种奇怪行为相关的棘手场景。不过它很快就变得相当稳定了。
## 值得吗?
值得!我认为有时重新发明基础技术是值得的,尤其是当它开辟了新途径时。
例如,否则我们需要花费数倍于这100毫秒的锁时间,才能在不到一秒的时间内启动一个microVM。有趣的是,我们并未重新发明整个栈,我们只是替换了处理极端稀缺资源的那个非常缓慢的控制平面。
我们并没有止步于此。*少做事*更快,但*不做事*最快。我们将这个理念应用到了启动路径的其他几个部分,但这些内容值得单独撰文说明。
下一步是拥有更多自己的存储栈。
## 常见问题
**为何LVM对启动microVM来说太慢?**
我们启动microVM的启动时间目标非常紧张,必须权衡每一毫秒。LVM对卷操作需要卷组范围锁,在我们的环境中该锁大约持有100毫秒。每周一次操作时这微不足道。但当你想每秒并行执行多达200次操作来启动microVM时,单锁的串行化就会破坏你的延迟目标,而非底层数据路径。
**如果存储代理崩溃,运行中的microVM会怎样?**
它们会继续运行。内核保存着关键部分——实际的设备映射器映射,因此已组装的设备会继续处理IO,无论我们的代理状态如何。代理宕机期间你失去的是控制平面,所以在它恢复前不会有新分配。代理会定期将映射写入磁盘,也可以通过读取内核中的设备映射器状态来重建和验证映射。即使恢复失败,已组装的设备仍会继续工作。
**可以不使用LVM而直接使用设备映射器吗?**
可以。设备映射器是内核设施,你可以通过其ioctl接口直接编程,使用`dmsetup`这样的工具或从自己的进程操作。LVM是建立在它之上的控制平面,增加了元数据、锁定和恢复功能。这种分离对我们成为可能:我们保留了LVM使用的相同内核数据平面,只替换了其上的控制平面。我们的第一个原型使用`dmsetup`驱动设备映射器,已足够满足启动时间目标,只是不够优雅。
**在什么情况下放弃LVM的崩溃安全是错误的选择?**
当数据本应持久存在于崩溃时。我们的权衡之所以有效,是因为管理程序崩溃会同时导致临时microVM工作负载和支撑其设备的易失性内存失效,因此没有值得保护的剩余物。如果你的卷保存着持久数据,或者你想恢复意外删除的卷,LVM的元数据和锁定确实在为你做实际工作,这100毫秒是值得的。
Héja Péter (Vau)Héja Péter \(Vau\)
Depot 高级基础设施工程师
相似文章
如果你只是自己使用模型而不对外提供服务,vLLM 真的值得用吗?
一名用户讨论了在 AMD 硬件上进行本地单用户推理时,使用 vLLM 与 llama.cpp 之间的权衡,质疑在非企业级环境中 vLLM 的性能优势是否足以弥补其带来的复杂性。
或许我们该重新审视微内核
本文认为,现代硬件(特别是IOMMU)通过消除上下文切换的开销,使微内核再次变得可行,并建议重新审视微内核设计以提高安全性和可靠性。
proveKV – 诚实的36倍无损(vs f32,18倍 vs fp16)KV缓存压缩用于LLM(零PPL回归)
一个开源仓库proveKV展示了一种可复现的KV缓存压缩技术,在SmolLM2-1.7B上实现了36倍无损(vs f32)和68倍有损内存减少,且PPL回归为零,包括Rust示例和审计管道。
我们在 Apple Silicon 上重建了 Linux MicroVM 栈
Encore 已在 Apple Silicon 上重建 Linux microVM 栈,针对 Apple 的 M1 和 M2 处理器优化虚拟化。
为本地LLM提供大量答案的新旧基准测试
本文介绍了一个用于评估本地LLM配置的基准测试工具,重点关注显存使用情况、性能指标和硬件优化,以协助开发者优化配置。