<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Amiriox&#39;s Storage</title>
  <icon>https://amiriox.github.io/images/favicon.ico</icon>
  <subtitle>Declaration does not declare anything.</subtitle>
  <link href="https://amiriox.github.io/atom.xml" rel="self"/>
  
  <link href="https://amiriox.github.io/"/>
  <updated>2026-04-03T16:34:42.267Z</updated>
  <id>https://amiriox.github.io/</id>
  
  <author>
    <name>折鸦夜明け前</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>【论文速读】Scalable Far Memory: Balancing Faults and Evictions</title>
    <link href="https://amiriox.github.io/2026/03/20/paper_MAGE/"/>
    <id>https://amiriox.github.io/2026/03/20/paper_MAGE/</id>
    <published>2026-03-20T00:19:06.000Z</published>
    <updated>2026-04-03T16:34:42.267Z</updated>
    
    <content type="html"><![CDATA[<h2 id="一-核心内容">一 核心内容</h2><p>论文分析了传统远端内存存储系统的性能瓶颈, 并依据这些瓶颈提出了 MAGE,在 Linux 和 LibOS 上实现了对远端内存系统的有效改进.</p><ul><li>严格限制驱逐线程的数量以避免 IPI 风暴导致的 TLB Flush 延迟过高</li><li>严格控制应用程序线程和驱逐线程的行为, 明确区分职责</li><li>利用批处理和流水线来掩盖 TLB Wait 和 RDMA Wait 的延迟</li><li>减少锁竞争的开销</li></ul><h2 id="二-研究动机">二 研究动机</h2><p>论文第三节提出了当前的 Far Memory System 的局限性和性能瓶颈.</p><h3 id="far-memory-system">Far Memory System</h3><p>在传统云计算的服务器架构中, 内存(DRAM)直接插在 CPU 旁的插槽中.然而这样的架构存在一些问题:</p><ol type="1"><li>搁浅(strand): 若一台物理服务器的内存卖完了而 CPU 还有空闲, 则空闲CPU 处于可用而未用的状态, 称为<strong>CPU搁浅</strong>; 相对地,若一台服务器的 CPU 卖完了但是内存仍然存在空闲,对应内存处于可用而未用的状态, 称为<strong>内存搁浅</strong>.核心问题是搁浅的内存无法被其他物理机器上搁浅的 CPU 使用, 搁浅的 CPU也没有内存用于计算.</li><li>容量不足: 对于现代较大规模的应用(如数据库系统),单机的内存是显然不够用的, 通常通过 buffer pool manager等方式从磁盘载入和换出, 但这样会有 I/O 的开销.</li></ol><p>由此产生了远端内存(Far Memory)的概念. 从存储层级来看, Far Memory 处于Local DRAM 和 本次磁盘之间, Local DRAM 称作 Near Memory,而与之相对的远端内存则是通过高速网络(RDMA)或专用协议(如 CXL)被访问的远端服务器内存. (当然会比本地内存慢一点, DRAM 访存在 100ns 左右;而 CXL 大约在 250ns 左右, RDMA 会更慢一些)</p><h3 id="现有远端内存系统存在的问题">现有远端内存系统存在的问题</h3><p>论文通过对 DiLOS 和 Hermit 进行了一些实验,验证了两者在多核场景下性能崩溃(可扩展性较差, 随着线程数增加,吞吐量在短暂提升后很快持平甚至降低),并进一步确认了其性能崩溃并非来源于应用层的 Thrashing而是分页机制的软件同步和全局锁竞争.</p><h4 id="宏观测试">宏观测试</h4><p>通过对多种应用程序内存访问模式下不同内存卸载比例(有多少内存在本地缺页而需要通过RDMA 从远端访问)的测试. 首先设定了一个理想的基线(所有内存都在本地,相当于完全没有远端内存参与)用作参照. 基本运行时长 <spanclass="math inline">\(T_0\)</span>, 加上某一核心 Page Fault的次数乘以单次 Page Fault 延迟 <span class="math inline">\(L \timesF_{c,x}\)</span> 则为核心 <span class="math inline">\(c\)</span>上的该任务运行时间; 任务完成取决于最慢(产生页错误最多)的核心,则取最小后为单次任务所需时长, 除 <spanclass="math inline">\(3600\)</span> 则为一小时的任务数:</p><p><span class="math display">\[\text{Thp}_{\text{ideal}}(x) =\underset{c\in C}{\text{min}}(\frac{3600}{T_0 + L\times F_{c,x}}) \text{jobs/hour} \]</span></p><ol type="1"><li>在随机访问的访问模式下, 仅仅 10% 的内存卸载就造成了 70%左右的性能下降</li><li>在局部性较好的规则/顺序访问模式下, 即使有 prefetch机制减少了随机访问的 Page Fault,也仍然因为空闲页耗尽触发同步驱逐而限制吞吐量</li><li>工作集阶段切换(如 MapReduce): 新老页面交替时造成长达数秒的拥堵</li></ol><p>Fault-in 路径和 Eviction 路径都存在无法多核线性扩展的问题.为确认瓶颈在哪个路径, 论文采用的测试方案是:</p><p>每个线程在充足网络带宽下进行页的顺序读取, 区分两种场景:</p><ol type="1"><li>仅处理缺页, 设定 100% 的本地内存数据配额,但是预先驱逐所有页(本地没有数据, 数据都在远端), 导致每次访问都会 PageFault, 且只统计 Page Fault 的开销)</li><li>处理缺页并且主动换出内存. 设定 50% 的数据在本地内存配额,这导致每从远端拉下来一页, 就会导致本地的一个页被驱逐</li></ol><p>情况 2 比情况 1 出现大量的性能损失, 证明驱逐路径的崩溃是主要瓶颈</p><h4 id="tlb-shootdown-和-ipi-风暴">TLB Shootdown 和 IPI 风暴</h4><p>当从 local memory 中驱逐一个数据页到 far memory 时,不仅需要写回远端内存并释放数据页, 还要清理本地页表和 TLB 避免再次通过TLB 或页表访问到已经被驱逐的数据页.</p><p>MMU 一般在 CPU 芯片上, 每个 CPU 核心都有自己的 TLB, 因此 TLB Flush必须通知其他核心也清理自己 TLB 中对应地址的映射缓存. 这就需要 IPI(Inter-Processor Interrupt) 来通知其他核心. 而 IPI 毕竟是个中断, 对于CPU 来说还是非常慢的.</p><p>在单个 Host 上, Linux kernel 对同一地址空间的 TLB Flush 请求有一个<code>mmu_gather</code> 的过程进行聚集, 即使有多个 TLB Flush,也不会朴素地发送多个 IPI. 但是即使存在 <code>mmu_gather</code>,当大量应用程序线程同时发 IPI, 硬件的中断队列会很快被 <spanclass="math inline">\(N \times N\)</span> 的中断风暴填满,从而导致特定某一个 TLB Flush 的延迟极高(因为还要等待之前的 TLB Flush完成).</p><h4 id="lru-链表的锁竞争">LRU 链表的锁竞争</h4><p>在支持远端内存的系统中, 需要判断页面热度来决定页面的去留.被访问次数较多的页称作热页,按照存储层级的看法应当被放在较快但是容量相对较小的 local DRAM 层;与之相对的称作冷页, 显然应该放在容量更大(虽然较慢)的远端内存.而判断页面冷热通常通过 LRU/LRU-K/ARC 等页面置换算法实现,这依赖(显然应当)共享的数据结构如 LRU 链表. 由于不同 Host判断页面冷热的需求较频繁, 对 LRU 链表的共享操作造成了较严重的锁竞争.</p><h4id="页面迁移导致的全局分配器的锁竞争">页面迁移导致的全局分配器的锁竞争</h4><p>由于不同页面频繁地在本地 DRAM 和远端内存之间迁移,且这个操作一定需要对全局内存分配器上锁, 因此对 allocator共享操作的锁竞争同样拖慢了吞吐效率.</p><h2 id="三-mage-系统设计">三 MAGE 系统设计</h2><p>针对以上三个问题, 论文分别提出了解决方案.</p><h3 id="设计原则">设计原则</h3><p>MAGE 有三条设计原则.</p><ol type="1"><li>永远解耦后台驱逐线程和前台应用程序线程,前台应用程序绝不亲自下场驱逐页面. 当内存不足时,应用程序就只是等着驱逐线程”开路”, 腾出新的页面后继续进程.</li><li>用批处理+流水线技巧来掩盖延迟(话说这就是我进组时候面试的代码任务).既然原先发 TLB Flush 是同步的, 即需要 <code>发 TLB 请求</code> -&gt;<code>TLB Wait</code> -&gt; <code>接受 ACK</code>, 那干脆就在 TLB Wait期间去做其他事情而不是干等着(当然, 对于本次驱逐, 必须在 TLB ACK之后才能去发 RDMA 具体驱逐页面, 否则可能会有线程通过旧的 TLB访问到已经不在本地内存的页面, 一般是进行下一次驱逐的 TLB Flush Send或者上一次驱逐的 RDMA Send). RDMA 请求也类似, 在 RDMA Wait期间可以做其他事情.</li><li>由于统计页面冷热时对共享数据结构的操作造成了锁竞争, 那么干脆设立Per-CPU 的统计数据结构, 避免冲突. 这样会降低对冷热页的判断精度,但是这样的 trade-off 是合理的.</li></ol><h3 id="其他优化">其他优化</h3><p>为了减少 TLB Shootdown 造成的 IPI 风暴, MAGE 严格限制驱逐线程数量,论文认为 4 个驱逐线程是一个 sweet pot, 甚至能跑满 200Gbps网卡的上限.</p><p>为了避免全局 bitmap 或者 free list 的锁竞争, MAGE直接采用了线性映射的方式, 即物理地址唯一对应一个偏移的虚拟地址,避免了锁竞争. 但是坏处是连续虚拟地址无法映射在不连续的物理内存,远端内存的释放也变得没意义了(因为不会有人再用这一物理页), MAGE的观点是”反正远端内存又大又便宜,牺牲一点空间去增加吞吐量降低延迟会更好”</p><h2 id="五-阅读总结">五 阅读总结</h2><p>验证对比实验比较耳目一新, 这个 profiling的过程感觉很厉害我要是也能有这个水平就好了</p><p>处理方式的话感觉比较经典, 异步, 批处理流水线</p><p>不过这篇总结其实写得不太认真, 我还有两篇要读,感觉最近早睡之后效率实在有点太低了</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;一-核心内容&quot;&gt;一 核心内容&lt;/h2&gt;
&lt;p&gt;论文分析了传统远端内存存储系统的性能瓶颈, 并依据这些瓶颈提出了 MAGE,
在 Linux 和 LibOS 上实现了对远端内存系统的有效改进.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;严格限制驱逐线程的数量以避免 IPI 风暴导致的 TLB Flush 延迟过高&lt;/li&gt;
&lt;li&gt;严格控制应用程序线程和驱逐线程的行为, 明确区分职责&lt;/li&gt;
&lt;li&gt;利用批处理和流水线来掩盖 TLB Wait 和 RDMA Wait 的延迟&lt;/li&gt;
&lt;li&gt;减少锁竞争的开销&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;二-研究动机&quot;&gt;二 研究动机&lt;/h2&gt;
&lt;p&gt;论文第三节提出了当前的 Far Memory System 的局限性和性能瓶颈.&lt;/p&gt;</summary>
    
    
    
    <category term="学术" scheme="https://amiriox.github.io/categories/%E5%AD%A6%E6%9C%AF/"/>
    
    
    <category term="文献" scheme="https://amiriox.github.io/tags/%E6%96%87%E7%8C%AE/"/>
    
    <category term="文件系统" scheme="https://amiriox.github.io/tags/%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F/"/>
    
    <category term="SOSP" scheme="https://amiriox.github.io/tags/SOSP/"/>
    
    <category term="远端内存" scheme="https://amiriox.github.io/tags/%E8%BF%9C%E7%AB%AF%E5%86%85%E5%AD%98/"/>
    
  </entry>
  
  <entry>
    <title>【论文阅读】Tigon: A Distributed Database for a CXL Pod</title>
    <link href="https://amiriox.github.io/2026/02/01/paper_tigon/"/>
    <id>https://amiriox.github.io/2026/02/01/paper_tigon/</id>
    <published>2026-02-01T00:00:00.000Z</published>
    <updated>2026-03-27T12:06:59.084Z</updated>
    
    <content type="html"><![CDATA[<h2 id="一-核心内容">一 核心内容</h2><p>本论文提出了名为 Tigon，一种基于 CXL Pod 的分布式内存数据库。</p><p>论文作者注意到 CXL 标准在强缓存一致性实现上 Snoop Filter记录的缓存行信息过大、现有 CXL硬件实现通常仅仅支持一小部分内存的缓存一致性，充分利用这一点划分了 HWcc和 SWcc 区域，通过在 SWcc 中存储松散一致性的实际数据、在 HWcc中存储高频同步原语的方式，高效地在 CXL Pod架构下管理了跨机活跃元组（Cross-host Active Tuple,CAT）在不同主机间的迁移，利用 CLOCK 算法管理有限的 HWcc空间，驱逐非热点数据。</p><p>在数据库本身的特性方面，Tigon 采用 2PL 两阶段锁（元数据保存在HWcc）策略，结合 CXL 共享内存的特点改进了 Next-Key Locking协议解决幻读问题，利用 CXL 共享内存的特性避免了 2PC 两阶段提交。</p><h2 id="二-研究背景">二 研究背景</h2><p>关于 CXL 可以看 <ahref="https://computeexpresslink.org">computeexpresslink.org</a> 或 <ahref="https://zheya.cc/2026/01/29/paper_CxTnL/">这篇论文笔记</a>的背景介绍.</p><p>在本篇论文的语境下，将通过 CXL 总线共享 CXL 内存的机器集群称为 CXLPod.</p><p>传统的分布式数据库通过网络或者 RDMA支持超远距离的分布式计算节点之间的通信，但基于 TCP/IP的通信会产生较大的协议栈和上下文切换开销，RDMA也依然具有较大的延迟。基于 CXL链路的分布式节点虽然没有基于网络的分布式节点那么远的通信物理距离，但是CXL 相对前两者来说延迟更低、吞吐量更高。然而，CXL 链路和 DRAM的引脚相比依然延迟更高、带宽更低。DRAM 的顺序访问带宽可以达到 250GB/s左右，而 CXL 只有 50GB/s 左右。CXL 标准的缓存一致性代价也较高，SnoopFilter需要为每个缓存行维护元信息，导致当前许多硬件只实现了一小部分内存的缓存一致。</p><p>另外，在分布式数据库中，尽管数据库本身的规模可能很大，但是在不同主机上运行的事务并发读写的数据元组集合规模可能很小：每个事务只包含较小数量的元组，而并发运行的事务一般与核心数相当。以论文中的介绍的TPC-C 测试为例，单个事务平均访问 39 个元组，总数据量约 7KB。假设系统配备1000 个核心且每个核心执行一个事务，则系统至多存在 3.9万个活跃元组，对应约 7MB数据。我们将此类元组集合称为”跨主机活跃元组”（简称 CAT）。这启发了使用CXL 共享内存只迁移和共享这些 CAT 的方法。</p><h2 id="三-tigon-系统设计">三 Tigon 系统设计</h2><h3 id="分区与共享内存">分区与共享内存</h3><p>Tigon 采用 Pasha 架构。Pasha 架构将计算与存储解耦，由 CPU 计算节点和CXL 共享内存内存存储节点构成，将数据库索引（B+Tree等）存储在 CXL共享内存中。</p><p><img src="/images/Pasted%20image%2020260202160531.png" /></p><p>整体数据库中的数据先在不同的 CXL Pod节点中分区，初始阶段每个主机是其分区数据的所有者（Owner），数据最初在主机的Local DRAM 中，此时访问具有低延迟和高带宽。</p><p>当节点需要操作远端节点中的数据时，该数据就由那个节点的 Local DRAM转移到 CXL 共享内存中，成为 CAT 的一部分。后续的跨主机同步操作都可以靠对CXL内存的原子操作实现，而无需昂贵的网络消息开销。当然，如果某个节点想要在远端节点的数据分区中插入数据，还是需要网络消息通知那个远端节点，但仅仅只是一个插入通知：远端节点将一个占位元组插入并转移到CXL 共享内存，由当前节点通过原子操作修改这个占位元组中的数据。</p><p>当某个节点想访问的 CAT 已经被其他节点获取了 latch 时,这个节点必须自旋等待. 但是在 Tigon 的设计中, 自旋等待无需每次都通过 CXL链路访问 CXL 中的 HWcc latch 导致昂贵的开销. 第一次自旋访问 HWcc latch时会 cache miss 并加载到 CPU 高速缓存中, 则之后的每次自旋都是 cache hit,称为本地自旋. 当持有锁的节点释放锁后, CXL HWcc latch 状态改变, 由于latch 在 HWcc 区域, Snoop Filter 会根据 CXL 3.0的缓存一致性协议更新自旋节点的 CPU 缓存, 自旋结束, 此节点获取到锁.</p><p>传统的分布式数据库实现需要基于网络通信的两阶段提交，而 Tigon只需要基于 CXL 链路的 CXL 原子操作即可。由于涉及跨主机访问的元组都在 CXL内存中，单机就可以锁定所有资源、决定事务的提交与终止、写入日志等，极大便利了数据库实现，避免了2PC 的开销。</p><h3 id="数据结构设计">数据结构设计</h3><h4 id="cxl-共享内存中">CXL 共享内存中</h4><p>Tigon 将高频同步元数据放在 HWcc 内存区域中，包括索引（CXL Index）和HWcc Record; 将实际元组数据以 SWcc Row 的形式放在 SWcc内存区域，包含用于判断是否已经成为墓碑（见下文）的 <code>is-valid</code>标记、用于判断何时能将墓碑空间释放的<code>epoch-version</code>、以及元组数据本身 <code>tuple</code>。</p><p>HWcc Record 的主要字段以及用途如下：</p><ul><li><code>HWcc-latch</code>: 数据库中对元组数据的 latch <spanclass="math inline">\(^{[1]}\)</span>,防止多主机或多线程同时访问造成不一致性</li><li><code>2pl-lock</code>: 用于记录两阶段锁状态</li><li><code>has-next-key</code>: Next-Key Locking 的改进。由于 Next-KeyLocking 需要锁住 next key 来避免幻读问题，而 CXL共享内存可能并未加载这个 next key，所以通过这个标志位记录当前 CXL Index的下一个 key 是不是在 Owner 那里时真实的下一个 key（因为 CXL 只加载CAT，很可能当前 key 在 CXL Index 里的下一个 key 是其他的CAT）。这个维护由 Owner 在获取 CXL Index B+ Tree锁后修改索引时顺手维护，在 Next-Key Locking 时需要检查。若<code>has-next-key</code> 为真则无需额外操作，否则需要从节点的 LocalDRAM 里再加载到 CXL 内存中。</li><li><code>is-dirty</code>: 用于记录 CXL 内存中的数据是否被修改过（是否和Owner Local DRAM 里的数据相同），如果未被修改则 Owner 访问时可以不读SWcc 中的数据而是在 Local DRAM 中访问，节省 CXL 带宽。</li><li><code>clock-bit</code>: Tigon 采用 CLOCK 算法而不是 LRU 算法来管理HWcc 中数据的进出。</li><li><code>SWcc-bitmap</code>: 缓存有效性位图，若当前主机对应的位是<code>1</code> 则说明本地 CPU 缓存有效，否则则需要 flush，强制从 CXL读取最新数据并重设位 1。当有主机需要更新 SWcc Row时会清零所有缓存有效位，通知它们主机的本地缓存已经无效了。</li><li><code>SWcc-row-ptr</code>: 指向在 SWcc 中的 SWccRow，这部分记录实际数据。</li></ul><blockquote><p><span class="math inline">\([1]\)</span> :数据库的语境中将管理较高逻辑层次的事务的同步原语称作锁（lock），将保护数据库系统实现中本身对象的同步原语称为闩锁（latch）。区别主要在于，latch更接近 OS 语境下的锁，需要实现者手动控制、手动防止死锁；而 lock由事务管理器控制，有死锁也会有单独的方案自动恢复。</p></blockquote><h4 id="local-dram-中">Local DRAM 中</h4><p>Local DRAM 中由 Local Index 索引的 Local Row同一般数据库中的元组及其元信息一致，如两阶段锁标记、epoch版本、实际元组数据。</p><p>但除此之外还包含了一个 <code>shortcut-ptr</code> 指向 HWcc Record. 当Local DRAM 中的元组成为 CAT 并移动到 CXL 后，CXL共享内存中的这个元组才是真实最新的数据，本地只是一个即将被替换的副本，所以理论上即使是Owner 自己要访问时也必须要查 CXL Index 然后访问 CXL 中的数据，但这对于Owner 来说还要查一下 CXL Index 实在没有必要，因此 Tigon在这里做了一个小优化，通过 <code>shortcut-ptr</code> 直接记录本地元组在CXL 内存中的位置（HWcc Record），无需再查询 CXL Index.</p><h3 id="并发与事务一致性管理">并发与事务一致性管理</h3><h4 id="防止-use-after-free-is-valid">防止 Use After Free<code>is-valid</code></h4><p><code>is-valid</code> 为 <code>false</code>的通常作为墓碑或占位符，其数据无意义，但能代表某些语义</p><p><strong>占位符</strong>：当某个主机想在远端主机所拥有的分区插入数据时，需要让远端主机先插入一个<code>is-valid = false</code> 的占位符并加载到CXL，然后当前主机再抢锁并修改数据。</p><p><strong>墓碑</strong>：CXL 中的某个元组被 CLOCK 算法判定应写回主机Local DRAM 后，这个元组在 CXL Index中被删除，但此时还不能直接释放空间，由于这里的设计必须是先获取 HWccRecord 的裸指针再通过原子操作 <code>HWcc-latch</code>获取锁，所以即便写回数据需要获取锁，也可能有一些主机提前拿到了裸指针，这里只是将其设置为<code>is-valid = false</code> 的墓碑，避免其他主机拿到裸指针后 CAT被写回原主机导致解引用时 Use After Free.当确认没有任何主机引用这个对象时，这个对象才能被安全释放空间。而判断是否还有主机引用这个对象则需要EBR.</p><h4 id="版本标记-epoch-version">版本标记 <code>epoch-version</code></h4><p>和传统数据库中的版本管理类似，epoch 类似一个主机的 timestamp (约 10ms更新一次，作为一个 epoch), 标志当前主机在运行哪个 epoch的代码。读操作只能读被标记为 <code>epoch &lt;= current_epoch</code>的元组，垃圾回收可以回收 <code>epoch &lt;= current_epoch</code>的旧版本快照。</p><p>上文说到判断是否有主机引用等待释放的对象需要 EBR (Epoch-BasedReclamation), 如果某个墓碑的 epoch-version 在所有主机的 epoch之前，即它是在所有主机当前运行的版本之前被删除的，那么可以放心释放空间。换言之，即使存在一个主机运行在比墓碑更早的epoch 上，那这个元组也并不会被释放。</p><h3 id="日志与恢复">日志与恢复</h3><p>Tigon 的日志采用并行记录、并行重放的机制. 每个 worker都写入自己的日志到 log buffer, 根据 epoch (10ms) 进行组提交 (写入SSD).只有一个 epoch (及其之前的 epoch) 的全部日志都被提交落盘后, 这个 epoch才算被成功提交.</p><h2 id="四-性能评估">四 性能评估</h2><p>使用 Intel 虚拟化技术模拟 CXL 硬件.</p><ul><li>对比 Shared-nothing 架构 (数据分片, 各节点不共享数据)的数据库吞吐量提升了 2.5 倍</li><li>对比 RDMA 数据库吞吐量提升了 18.5 倍</li></ul><p>HWcc 空间大小:</p><p>当硬件的缓存一致预算不足时, 哪怕只有 50MB, Tigon 的性能下降也只有5.8%, 可见 CLOCK 算法能精准识别热点 CAT 以管理 HWcc 空间的驱逐. 当 HWcc空间只有 10 MB 时, 出现比较严重的缓存抖动, 吞吐量下降较大</p><p><img src="/images/Pasted%20image%2020260203122859.png" /></p><h2 id="五-阅读总结">五 阅读总结</h2><p>主要思路是一反其他研究方向将 CXL 透明化的思路 (但其实之前 CtXnL对用户态也不是透明的, 毕竟还是有 CTLib allocator), 将 CXL内存视为不透明的通信介质 “中转站”, 或者说是一个共享缓存.总之充分利用了数据库的负载特性 (CAT 较小等) 设计了符合 CXL Pod架构特性的数据库系统.</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;一-核心内容&quot;&gt;一 核心内容&lt;/h2&gt;
&lt;p&gt;本论文提出了名为 Tigon，一种基于 CXL Pod 的分布式内存数据库。&lt;/p&gt;
&lt;p&gt;论文作者注意到 CXL 标准在强缓存一致性实现上 Snoop Filter
记录的缓存行信息过大、现有 CXL
硬件实现通常仅仅支持一小部分内存的缓存一致性，充分利用这一点划分了 HWcc
和 SWcc 区域，通过在 SWcc 中存储松散一致性的实际数据、在 HWcc
中存储高频同步原语的方式，高效地在 CXL Pod
架构下管理了跨机活跃元组（Cross-host Active Tuple,
CAT）在不同主机间的迁移，利用 CLOCK 算法管理有限的 HWcc
空间，驱逐非热点数据。&lt;/p&gt;
&lt;p&gt;在数据库本身的特性方面，Tigon 采用 2PL 两阶段锁（元数据保存在
HWcc）策略，结合 CXL 共享内存的特点改进了 Next-Key Locking
协议解决幻读问题，利用 CXL 共享内存的特性避免了 2PC 两阶段提交。&lt;/p&gt;
&lt;h2 id=&quot;二-研究背景&quot;&gt;二 研究背景&lt;/h2&gt;</summary>
    
    
    
    <category term="学术" scheme="https://amiriox.github.io/categories/%E5%AD%A6%E6%9C%AF/"/>
    
    
    <category term="文献" scheme="https://amiriox.github.io/tags/%E6%96%87%E7%8C%AE/"/>
    
    <category term="CXL" scheme="https://amiriox.github.io/tags/CXL/"/>
    
    <category term="OSDI" scheme="https://amiriox.github.io/tags/OSDI/"/>
    
    <category term="分布式数据库" scheme="https://amiriox.github.io/tags/%E5%88%86%E5%B8%83%E5%BC%8F%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    
  </entry>
  
  <entry>
    <title>【论文速读】CtXnL: A Software-Hardware Co-designed Solution  for Efficient CXL-Based Transaction Processing</title>
    <link href="https://amiriox.github.io/2026/01/29/paper_CxTnL/"/>
    <id>https://amiriox.github.io/2026/01/29/paper_CxTnL/</id>
    <published>2026-01-29T00:00:00.000Z</published>
    <updated>2026-03-27T12:06:30.526Z</updated>
    
    <content type="html"><![CDATA[<h2 id="一-核心内容">一 核心内容</h2><p>论文作者提出了一种名为 CtXnL的软硬件协同方案，通过区分元数据与记录数据不同程度的缓存一致性要求，降低了分布式处理系统中的开销。</p><p>CtXnL 通过 View Shim机制将同一份数据的副本安排在不同的地址，从而巧妙骗过 CXL 的 Snoop Filter避免其启动广播，再通过 GSync 一次性提交写回。同时，CtXnL 还设计了 VF/VBF过滤器来避免无谓的访存操作 CXL 链路通知。</p><h2 id="二-研究背景">二 研究背景</h2><p>Compute Express Link (CXL) 是一种总线协议，采用了 PCIe的物理结构，但是其目的是为了达成一个逻辑上的共享内存池，CPU和外设设备都能通过 Load/Store（不经过 RDMA这种的数据搬运，相对更快）访问到这片共享内存上的数据。共享内存池通常通过接入CXL 总线的 CXL 内存设备（如 CXL Type-3）实现，在不同的节点中，HPA 到 CXL内存的 DPA 映射是一致的<spanclass="math inline">\(^{[2]}\)</span>。外设设备/节点如 GPU（带有缓存和VRAM）、CPU（带有缓存和 DRAM）等。</p><ul><li>为了发现不同设备，设计了 CXL.io 协议</li><li>为了允许 CPU 能以可缓存的字节级寻址方式访问远端内存或 GPUVRAM，设计了 CXL.mem</li><li>为了解决由共享内存带来的缓存不一致问题，设计了 CXL.cache<br />当 GPU 想在 G-FAM 这个共享内存池中找数据时可以去和 CPU Cache沟通，如果最新数据在 CPU Cache 里，GPU 就直接把数据从 CPU Cache放到自己的 GPU Cache 里，从而保持了强缓存一致性；CXL 硬件会设置一个Snoop Filter 监听过滤器，记录哪些缓存行被哪些 CXL设备缓存了，从而避免询问其他设备缓存中是否有修改过的数据时向所有设备进行广播。SnoopFilter 通常在 G-FAM（远端内存）上，并在地址被初次访问时记录某个主机独占了这一地址上的数据，其他主机在访问这一地址上的数据时可以直接从独占这个地址的主机缓存中拿数据。</li></ul><p>然而，为了保证 CXL的缓存一致性，读写时需要进行复杂的广播去通知其他节点，而多次节点间通信经过的CXL链路开销较大；同时论文作者敏锐地发现软件层面的应用程序通常用一系列原语手动控制了一致性（如两阶段锁、MVCC等），因此硬件层面的强一致性往往是冗余的。因此，硬件完全可以只保证关键元数据（如锁等）的强一致性，允许记录数据在事务提交前是不一致、私有的。</p><blockquote><p><span class="math inline">\([2]:\)</span>事实上由于不同节点的物理地址布局不同，在某一个节点上可行的地址在另一个节点上可能已经被占用了，所以通常会给每一个节点设置一个HPA Base，每个节点的 HPA 为 Base + Offset，是 CXL IP负责将不同节点不同的 HPA 减去 BaseHPA 得到偏移量作为统一的 DPA.</p></blockquote><h2 id="三-ctxnl-方案设计">三 CtXnL 方案设计</h2><p>CtXnL 主要包括软件层面的用户态库CTLib（以及相应内核驱动）和硬件层面的 CTHW 硬件控制器。其中 CTLib绕过页缓存（DAX），同时为应用程序提供特殊的 allocator用于显示区分元数据的分配和记录数据的分配，从而区分两者的一致性语义；CTHW拦截 CXL总线上的读写操作，用于在一定情况下避免昂贵的节点间数据同步。</p><p>CtXnL 所管理的内存是堆内存分配的，且都在 CXL G-FAM上（无论是下文将要提到的 EMS 还是 VMS），而 Local DRAM 仅用于存储 OS /私有栈等数据。</p><h3 id="view-shim-机制">View Shim 机制</h3><p>CtXnL 将内存分为公共可见的 EMS 和私有可见的 VMS.<br />EMS 存放元数据和已提交的记录数据，VMS是节点的缓存无法容纳修改（写操作）的数据时用于存放这些数据。</p><p>为了实现灵活的地址段控制，CtXnL将每和节点的主机物理地址（HPA，即操作系统中 VA 映射到的 PA<spanclass="math inline">\(^{[1]}\)</span>）分为三部分逻辑地址空间：CXLVanilla, CtXnL Primitive 和配置空间。</p><ul><li>CXL Vanilla的语义是缓存强一致性，元数据都会被分配在这个段。对这个地址段的访问直通EMS.</li><li>CtXnL Primitive的语义是缓存松散一致性，记录数据会被分配在这个段。CtXnL 中的数据是 L-Ld和 L-St 语义（即读写只对当前节点私有可见），内容在当前节点缓存或 VMS中（由 VAT 哈希表管理映射），在 GSync操作提交后才会公共可见。对这个地址段进行访问时，如果这个地址上的数据未被当前节点修改过，则直通EMS；若已被当前节点修改过，则查找 VAT 并前往 VMS.</li><li>配置空间用于直接访问 VMS 和 CXLib 自身的数据结构。</li></ul><p>虽然 VMS 仍然在 G-FAM 中，但 VMS中写入数据写入的是不同的数据地址（副本，其实有点类似 CoW 的原理），SnoopFilter认为没有节点缓存了这个地址的数据，因此不会触发广播。比起广播多次经过 CXL链路，GSync 的一次 memcpy 反而是延迟更低的。另外，虽然数据经过节点达到G-FAM 上的 VMS 区域这一过程依然经过 CXL 链路，但是大部分的数据其实都会在CPU 缓存中，根本不会溢出到 VMS.</p><p>由于 VAT 在 G-FAM 上且需要记录不同节点的 VMS 写入，因此 VAT的查询也包含节点的 id. 在 CtXnL 的设计方案中，VAT 的数据结构采用 CuckooHashing.</p><blockquote><p><span class="math inline">\([1]：\)</span> VA 也可能映射到虚拟机的GPA，再映射到 HPA，HPA 是CPU（或其他主机）发往总线的地址，最后是设备上的地址 DPA</p></blockquote><h3 id="写操作-一致写或-l-st">写操作： 一致写或 L-St</h3><p>用户程序通过 CTLib 提供的 allocator将元数据和记录数据分配在不同的语义段，用户态虚拟地址经 MMU 映射为HPA，HPA 经 CXL IP 减去 Base 的到 DPA 后并不会直接进行硬件地址访问，CTHW会拦截并检查这个 DPA （或者说偏移量）对应在主机物理地址空间中的 CTLVanilla 段还是 CxTnL Primitive段。 如果是前者则写到 EMS 区域，正常广播或Snoop Filter;如果是后者则保持写入的数据私有，只在当前节点可见，避免污染公共区域，即保持在当前节点缓存或者写入VMS 并在视图地址表 VAT 中记录。</p><h3 id="读操作一致读或-l-ld">读操作：一致读或 L-Ld</h3><p>当一个读请求通过 CXL 总线达到 CTHW 时，CTHW 会通过 DPA 判断在 CTLVanilla 还是 CxTnL Primitive 段上，如果是前者则直通 EMS 并正常走 CXL 的Snoop Filter 正常缓存同步；如果是后者说明是需要绕过 CXL强一致性语义的数据，则拦截下来，查阅 VAT 表，如果有记录说明 VMS中有这个数据，重定向到 VMS 中的地址；如果没有记录则映射到EMS，不过完全绕过了广播或者 Snoop Filter.</p><h3 id="提交操作-gsync">提交操作： GSync</h3><p>在 GSync 之前，节点自身缓存和 VMS都是本节点私有、内部可见的，为了提交这些数据到 EMS，GSync操作首先通知除本 Requester 节点以外的所有 Peer节点作废这些数据的缓存、删除 VAT 中这些数据的记录；然后 Requester节点把这些数据写入 EMS. 如果在缓存中就直接写回 EMS，如果在 VMS 中则直接<code>memcpy</code> 并删除 VAT 项。</p><p>在这个过程中其他节点对这个数据的修改会丢失，这是正常的，上层逻辑比如数据库应用程序会采用合适的类似无锁/乐观锁的方案检查元数据（如版本号）及时发现并重试。（因此，元数据本身必须在硬件层面保证强一致性）</p><h3 id="vf-和-vbf">VF 和 VBF</h3><p>由于 VAT 在配置空间里，同样存在于 G-FAM 上；每次对 Primitive段的读写都要查询 DRAM 中的 VAT，相当于双倍了内存 I/O 次数。由于 MMU 查DRAM 页表太慢所以设计出了 TLB，但是在 CXL的共享内存架构中增设额外的缓存依然会涉及一致性问题（而且缓存不一定够大），因此CtXnL 设计了 VMS Filter. VMS Filter是一个布隆过滤器，布隆过滤器的特点是假阳性，也就是真阴性——当 filter报告为“不存在”时一定不存在，报告“存在时”可能存在也可能不存在。由于大量访存可能都在缓存中或者没有被修改过（在EMS 中），因此 VF 可以过滤极大一部分无需查表的情况。</p><p>VBF 则是对 SF的简洁优化，本身是计数布隆过滤器，对一系列哈希值计数，记录哈希值相同的一个桶中所有地址在缓存行中的个数。若某一地址哈希后到VBF 中查询计数为 0 则说明当前节点绝无可能包含这一地址的缓存，则 GSync 将VMS 的数据提交到 EMS 时就无须通知这个节点。因为 CXL系统中可能有较多节点，比如一个 CXL G-FAM 为共享存储中心，一个 CPU节点、一个 GPU 节点，所以 G-FAM 中为每个节点设立单独的 VBF，如<code>VBF_CPU0</code>, <code>VBF_GPU1</code>. 由于不像 SF是中心化的，CtXnL 的 VBF 完全可以并行地操作每个节点的独立 VBF. 另外，VBF的更新维护是由 CTHW 在 L-Ld/L-St 查询 VF 时同时旁路并行的，提前为 GSync做准备，因此并不在读写操作的关键路径上。</p><h2 id="四-性能评估">四 性能评估</h2><p>// TODO(amiriox):</p><h2 id="五-阅读总结">五 阅读总结</h2><p>// TODO(amiriox):</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;一-核心内容&quot;&gt;一 核心内容&lt;/h2&gt;
&lt;p&gt;论文作者提出了一种名为 CtXnL
的软硬件协同方案，通过区分元数据与记录数据不同程度的缓存一致性要求，降低了分布式处理系统中的开销。&lt;/p&gt;
&lt;p&gt;CtXnL 通过 View Shim
机制将同一份数据的副本安排在不同的地址，从而巧妙骗过 CXL 的 Snoop Filter
避免其启动广播，再通过 GSync 一次性提交写回。同时，CtXnL 还设计了 VF/VBF
过滤器来避免无谓的访存操作 CXL 链路通知。&lt;/p&gt;
&lt;h2 id=&quot;二-研究背景&quot;&gt;二 研究背景&lt;/h2&gt;
&lt;p&gt;Compute Express Link (CXL) 是一种总线协议，采用了 PCIe
的物理结构，但是其目的是为了达成一个逻辑上的共享内存池，CPU
和外设设备都能通过 Load/Store（不经过 RDMA
这种的数据搬运，相对更快）访问到这片共享内存上的数据。共享内存池通常通过接入
CXL 总线的 CXL 内存设备（如 CXL Type-3）实现，在不同的节点中，HPA 到 CXL
内存的 DPA 映射是一致的&lt;span class=&quot;math inline&quot;&gt;&#92;(^{[2]}&#92;)&lt;/span&gt;。外设设备/节点如 GPU（带有缓存和
VRAM）、CPU（带有缓存和 DRAM）等。&lt;/p&gt;</summary>
    
    
    
    <category term="学术" scheme="https://amiriox.github.io/categories/%E5%AD%A6%E6%9C%AF/"/>
    
    
    <category term="文献" scheme="https://amiriox.github.io/tags/%E6%96%87%E7%8C%AE/"/>
    
    <category term="ASPLOS" scheme="https://amiriox.github.io/tags/ASPLOS/"/>
    
    <category term="分布式事务处理系统" scheme="https://amiriox.github.io/tags/%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1%E5%A4%84%E7%90%86%E7%B3%BB%E7%BB%9F/"/>
    
    <category term="CXL" scheme="https://amiriox.github.io/tags/CXL/"/>
    
    <category term="速读" scheme="https://amiriox.github.io/tags/%E9%80%9F%E8%AF%BB/"/>
    
  </entry>
  
  <entry>
    <title>【论文阅读】Strata: A Cross Media File System</title>
    <link href="https://amiriox.github.io/2026/01/27/paper_strata/"/>
    <id>https://amiriox.github.io/2026/01/27/paper_strata/</id>
    <published>2026-01-27T00:00:00.000Z</published>
    <updated>2026-03-27T12:06:50.250Z</updated>
    
    <content type="html"><![CDATA[<p>本文中的 NVM (Non-Volatile Memory) 和 上一篇 NOVA 中的 NVMM(Non-Volatile Main Memory) 是相同的概念。</p><h2 id="一-核心内容">一 核心内容</h2><p>论文作者提出了一种名为 Strata 的多层次文件系统，同时管理 NVM/SSD/HDD等多种存储系统结构层次。Strata为用户态、内核态、存储层次进行了职责划分，进一步为 NVM/SSD/HDD的多层存储系统上的应用程序降低延迟、提高吞吐量。</p><p>Strata 主要分为用户态的 LibFS 和内核态的 KernelFS。LibFS 负责记录per-process 的 Update log 以及进行热点数据缓存，KernelFS 主要负责 Digest操作。NVM 中的 Update log 能够保证写入的同步语义；Digesting通过日志合并将随机写转化为顺序写，契合 SSD 的硬件特性。</p><p>为了保证 LibFS 的 Kernel Bypass不会导致并发安全问题且避免大量轻度写操作的大量锁开销，Strata采用租约（Lease）操作防止两个线程绕过内核同时修改同一个文件。</p><p>Strata还提供了对程序员更友好的同步写入、顺序可持久化语义，并且通过合并 log改善了传统 LFS 的写放大。</p><h2 id="二-研究背景">二 研究背景</h2><p>大部分传统文件系统只针对特定存储设备，面对复杂的存储设备层次结构时不能充分利用其硬件特性降低开销。另外，现代应用的性能瓶颈逐步由计算转移到I/O上，因此对现代应用（如键值存储、数据库）对性能和功能的需求远超传统文件系统的舒适区：</p><ul><li>一些程序的业务场景需要大量的小型分散式更新，反复进内核<code>copy_from_user</code>拷贝数据开销太大，需要常态内核旁路来实现盲写的零拷贝</li><li>RPC Server 必须在响应前持久化数据，异步的 <code>write()</code>会在写入 DRAM 页缓存后就返回，需要手动调用<code>fsync()</code>，对编程不友好。开发者倾向于有符合直觉的同步顺序写入语义</li><li>部分文件系统牺牲强一致性保证性能，或为了保证性能牺牲了强一致性。应用程序需要高效可靠的崩溃一致性</li></ul><h2 id="三-strata-系统设计">三 Strata 系统设计</h2><p><img src="/images/Pasted%20image%2020260128172402.png" /></p><p>为了减少内核中介的开销，如上文所言，Strata将文件系统的职责在用户态和内核态之间进行了拆分，划分为用户态的 LibFS和内核态的 KernelFS。</p><h3 id="libfs-update-log">LibFS: Update Log</h3><p>LibFS 层面为每个线程设计用户态的私有的 NVM Updatelog。对于写操作，LibFS 将写入操作和数据同步地记录在 NVM 中的 Update log中。由于 NVM 支持字节寻址，可以直接通过 MMU 把 NVM 物理页映射到 LibFS虚拟地址空间，从而绕过内核，实现零拷贝。由于 Update log 在 NVM中，这意味着写入是直接同步可持久化的，而传统的 write通常是先写入易失性的 DRAM 缓存就直接返回（write 的持久化是异步的），在<code>fsync()</code> 被显示调用时写回脏页。Strata的这一同步可持久化特性满足了现代 RPC应用在响应前必须已经快速持久化了数据的需求，尤其能够降低大量 small write的延迟。</p><p>与 NOVA 相比，NOVA 的 Inode log 是真正的数据形式，而 Strata 的 Updatelog 是等待被 KernelFS 通过 Digesting操作消费掉的中间形式。之所以要有这个中间形式，是为了大量 small write的低延迟以及高效的崩溃一致性。</p><h3 id="kernelfs-shared-area">KernelFS: Shared Area</h3><p>Strata 是一种跨介质的文件系统，提供了统一的接口管理不同的存储介质。Update log 在达到一定大小后会由 KernelFS 异步地 <strong>Digest</strong>到 SharedArea，形成实际的、读优化形式（Read-optimized）的文件数据。KernelFS合并日志中的操作，将随机访问合并为顺序写入。由于 Digesting是异步的，所以 KernelFS 可以批量处理 Strata事务。（如果是同步的，那只有到一个事务处理完一个事务然后才能接下一个事务）</p><p>NVM/SSD/HDD 分别有各自的 Shared Area，文件数据块和 Inode数据块根据热度存放在不同层次结构，充分利用了不同层次结构的速度/容量特点和时间局部性。</p><p>Shared Area 对用户态 LibFS 是可读且只读的，可读是为了 LibFS能快速读取数据，只读是防止多线程写冲突。NVM 中的 Shared Area会进行页对齐，从而利用 MMU 的页级别访问控制权限。 SSD 的 Kernel Bypass安全控制是利用硬件功能如 NVMe Namespace，HDD 则是软件模拟。</p><p>Shared Area 保留了传统文件系统的 Superblock、Bitmap、Inode、Blocks，但是提出了一定的优化：</p><ul><li>传统的文件系统分配空闲块需要获取 Bitmap 锁并且扫描空闲位，造成对Bitmap 的竞争。而 Strata 的 Free block bitmap 一次抓取一个 Erasure Block的大小（与 SSD 有关，包含多个空闲块），在这个 EB中为每个请求顺序地分配空闲块。当 KernelFS的多个线程同时申请空闲块，他们并不会申请这个 EB的整体锁，而是用无锁操作抢到 <code>[offset, offset+N)</code>的一系列空闲块，没抢到的继续无锁重试。由于每个线程拿到的都是不同的空闲块位置，所以直接翻转对应bit 就好，无需加锁。当 <code>offset</code> 超出了一个 EB大小，就从空闲池中找一个新的 EB。当一个 EB 中的空间都被回收，则这个 EB本身进入空闲池等待下一次被复用。</li><li>Strata 将 Inode 当做一个普通的 File，使得 Strata 可以支持热点文件的Inode 存在于较快存储器层次的 Shared Area 上。由于 Inode 本身也是一个File，找这个 File 需要找这个 Inode File 的 Inode，但是这个 Inode ofInode File 也需要找 Inode File。为避免 Inode File 本身的 Inode 只存在于Inode File 上， Inode File 本身的 Inode 被硬编码存放在 Superblock中。</li><li>Strata 会为文件数据块和 Inode 块建立 Extent Tree索引，每一存储层次的 Shared Area 都有自己的 Extent TreeRoot，NVM/SSD/HDD 共三个 Root。</li></ul><h2 id="三-strata-实现机制">三 Strata 实现机制</h2><h3 id="读写操作">读写操作</h3><p>对于读操作，LibFS 会先在 DRAM 中的 File data cache 寻找，若 cachemiss 则查看 Update log pointer。Update log pointer记录了每个文件上一次被更改的位置，从而使得“先写了一点然后读取”的操作更简短迅速。若Update log pointer 中也不存在，则依次查找每个 layer 的 ExtentTree，同时更新缓存。</p><p>对于写操作，则是上述 LibFS 获取租约、记录 Update log 到 KernelFS 启动Digest，Free block bitmap 分配空闲块并更新 Extent Tree 索引的过程。</p><h3 id="strata-事务">Strata 事务</h3><p>LibFS 的持久化单元是事务（Transaction）。Strata 事务提供 ACID语义，对最大达到 Update log大小的写入提供原子化保障。若写入数据过多，则拆分为多个 Strata 事务。一个Strata 事务包括：Header, Update log, Commit Record.</p><p>执行（或者说记录） Strata 事务时，先使用 CAS 操作分配 log 空间，写入Header 和 Update log，等写入持久化完毕后最后持久化写入 Commit Record. 以Commit Record 结尾的日志才算有效。事务 log 头部包含指向下一个事务 log的指针，所以 Strata LibFS 的 Update log 实际上是一个事务的链表。</p><p>对比 NOVA 的 log 链表，NOVA 的 log 是每个文件的 Inode里都有一个针对本文件的 4KB 页日志链表，修改或恢复时不冲突；而 Strata是对所有文件更新事务的链表，需要异步 Digesting 操作来把它们改为读优化的Blocks + Extent Tree 视图。</p><h3 id="租约机制">租约机制</h3><p>当两个线程都想写一个文件时，Strata设计了租约机制（Lease）。租约的语义类似于读写锁，但略有不同。相似点在于，当一个线程试图读写一个文件时，若当前文件没有被其他线程持有租约则直接获取到租约（获取租约的线程可以不经过内核就对该文件进行可持久化写入，即Updatelog），其他线程再想写入这个文件不能直接写入，但多个线程可共享读取租约；区别点在于，当线程想写入一个已被其他线程持有租约的文件，必须通知那个线程（在本论文中是Socket 实现），当前持有租约的线程收到解约通知后会触发 KernelFSDigesting，待 Update log 全部被 Digest后，原线程正式解约，新线程获取到这个文件的租约。同时，若原线程超时，也会被直接解约，由于LibFS 的 Update log 通过 Strata 事务保证 ACID 语义，Strata可以在必要时因租约而撤销任何进行中的读写操作。</p><p>传统的锁是锁临界区，每次 write 都需要加锁解锁，如果有 1000 次连续的write，就需要 1000 次获取锁和释放锁。租约是锁对象（文件）所有权，只要这1000 次都是同一线程write，那就只有一次租约申请。二者区别在于何时获取和何时释放，至于读共享写互斥的语义是一样的。</p><p>租约机制仍然存在性能瓶颈，但这基于 Strata认为大部分文件不会被共享访问的设计哲学。</p><h3 id="数据转移">数据转移</h3><p>Strata 同时管理 NVM/SSD/HDD 三层的 Shared Area，KernelFS 会根据 LibFS提供 LRU信息以根据热度决定哪些数据被放在哪一个层次上，即热度更高的数据放在更快的存储层级上。同时，一个layer 达到 95% 的容量后就会触发 KernelFS 将其 Digest 到下一层级。对于SSD 而言，KernelFS 采用 Erasure Block 的大小迁移数据以达到最大效率。</p><h2 id="四-性能评估">四 性能评估</h2><p>测试环境上，NVM 依然是模拟的。论文作者在 DRAM上用软件增加延迟并限制带宽（NVM 比 DRAM 慢），其他层次采用 Intel 750PCIe SSD 和 Seagate 1TB HDD. 对比对象选取了 NVM 层的 EXT4-DAX, PMFS,以及<a href="https://zheya.cc/2026/01/24/paper_NOVA/">上一篇</a>读的NOVA, SSD 层的 F2FS, HDD 层的 EXT4.</p><h3 id="microbenchmark">Microbenchmark</h3><p>Zipfian分布的随机写入(一小部分热点被反复写，占比越大的部分写入越少，符合实际需求场景)，比较写入效率（写放大的倒数），在较小数据规模下，Strata的写放大极小，因为这种大量 small write 正是 LibFS Update log用武之地</p><p><img src="/images/Pasted%20image%2020260128205310.png" /></p><p>NVM 上的平均 IO 延迟，依然是在 small write 上有优势，因为 LibFS的部分是零拷贝的，Digest 是后台任务。其他情况大部分持平。Error Bar代表的是 P99 Latency，Strata 的稳定性也优于对比对象。</p><p><img src="/images/Pasted%20image%2020260128205428.png" /></p><p>NVM 多线程吞吐量扩展性测试，在随机写的情况下 8 线程时 Strata的吞吐量比 NOVA 高出 28%（这个图太难绷了，看着都在 2.0附近，但其实其一单位是 GB/s,其二这个图的纵轴比例很有误导性，实际上确实有 28%的吞吐量提升，我不知道为什么图 2 为什么还守着那个 0 2 5 7的纵轴，就为了塞个图例进去吗），但看上去 NOVA 的 throughput scalability要更线性更好一些。毕竟 NOVA 是 per-inode的锁，粒度比较小，并发性很好。不过 NVM本来也比较快，依然拉不开差距。</p><p><img src="/images/Pasted%20image%2020260128210120.png" /></p><p>SSD 多线程吞吐量扩展性测试。在写入场景下 Strata无疑最出色，几乎是顶着 SSD 带宽来的，通过 log把随机写转换为顺序写非常成功。但是读操作表现比较一般。随机读的那张图没什么意义，因为这里测的Strata是单线程的。其实我觉得有一点“懒加载”写操作，把写操作的负载均摊一些到读上了</p><p><img src="/images/Pasted%20image%2020260128210820.png" /></p><h3 id="macrobenchmark">Macrobenchmark</h3><p>宏基准测试成绩比微基准测试好很多，在 Varmail, Fileserver上吞吐量领先。邮件服务器产生临时文件，先创建后删除，这些临时文件产生的log 都在 NVM 的 Update log 里被抵消了，无需 Digest.</p><p><img src="/images/Pasted%20image%2020260128212212.png" /></p><p>在 LevelDB Benchmark 的写操作上 Strata也领先（尤其在数据较小时），主要是同步 write 带来的好处，不用每次都<code>fsync()</code></p><p>在 Redis SET 测试上吞吐量领先但差距不大。</p><p><img src="/images/Pasted%20image%2020260128212410.png" /></p><h2 id="五-阅读总结">五 阅读总结</h2><p>看力竭了，比 NOVA复杂很多，很多机制的意义和目的要想一会才能明白，不像 NOVA那样很明显就能联系起来。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文中的 NVM (Non-Volatile Memory) 和 上一篇 NOVA 中的 NVMM
(Non-Volatile Main Memory) 是相同的概念。&lt;/p&gt;
&lt;h2 id=&quot;一-核心内容&quot;&gt;一 核心内容&lt;/h2&gt;
&lt;p&gt;论文作者提出了一种名为 Strata 的多层次文件系统，同时管理 NVM/SSD/HDD
等多种存储系统结构层次。Strata
为用户态、内核态、存储层次进行了职责划分，进一步为 NVM/SSD/HDD
的多层存储系统上的应用程序降低延迟、提高吞吐量。&lt;/p&gt;
&lt;p&gt;Strata 主要分为用户态的 LibFS 和内核态的 KernelFS。LibFS 负责记录
per-process 的 Update log 以及进行热点数据缓存，KernelFS 主要负责 Digest
操作。NVM 中的 Update log 能够保证写入的同步语义；Digesting
通过日志合并将随机写转化为顺序写，契合 SSD 的硬件特性。&lt;/p&gt;
&lt;p&gt;为了保证 LibFS 的 Kernel Bypass
不会导致并发安全问题且避免大量轻度写操作的大量锁开销，Strata
采用租约（Lease）操作防止两个线程绕过内核同时修改同一个文件。&lt;/p&gt;</summary>
    
    
    
    <category term="学术" scheme="https://amiriox.github.io/categories/%E5%AD%A6%E6%9C%AF/"/>
    
    
    <category term="文献" scheme="https://amiriox.github.io/tags/%E6%96%87%E7%8C%AE/"/>
    
    <category term="NVMM非易失性主存" scheme="https://amiriox.github.io/tags/NVMM%E9%9D%9E%E6%98%93%E5%A4%B1%E6%80%A7%E4%B8%BB%E5%AD%98/"/>
    
    <category term="混合存储系统" scheme="https://amiriox.github.io/tags/%E6%B7%B7%E5%90%88%E5%AD%98%E5%82%A8%E7%B3%BB%E7%BB%9F/"/>
    
    <category term="文件系统" scheme="https://amiriox.github.io/tags/%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F/"/>
    
    <category term="精读" scheme="https://amiriox.github.io/tags/%E7%B2%BE%E8%AF%BB/"/>
    
    <category term="SOSP" scheme="https://amiriox.github.io/tags/SOSP/"/>
    
  </entry>
  
  <entry>
    <title>【论文阅读】NOVA: A Log-structured File System for Hybrid Volatile/Non-volatile Main Memories</title>
    <link href="https://amiriox.github.io/2026/01/24/paper_NOVA/"/>
    <id>https://amiriox.github.io/2026/01/24/paper_NOVA/</id>
    <published>2026-01-23T16:26:06.000Z</published>
    <updated>2026-03-27T12:06:41.304Z</updated>
    
    <content type="html"><![CDATA[<h2 id="一-核心内容">一 核心内容</h2><p>论文作者提出了一种名为 NOn- Volatile memory Accelerated (NOVA)的日志结构文件系统 (log- structured file system, LFS) 以最大化 NVMM 和DRAM 同时存在的混合存储系统的效率。</p><ul><li>NOVA 充分利用 NVMM 的随机访问特性，从传统 LFS中放宽了局部性限制，从而实现了日志数据分离、日志链表来避免传统 LFS的垃圾回收开销</li><li>NOVA 通过将空闲链表索引和 Inode 索引建立在 DRAM 中，规避了在 NVMM中由于一致性难以保证造成的数据结构难以实现的问题</li><li>除了本身是 log-structured 之外，NOVA 还在需要同时修改多个 inode的操作(如对路径相关的操作)中采用了 journaling 技术，同时依赖 VFS的目录锁，规避了影子分页系统的级联开销和因此强顺序数据依赖造成的性能损失</li></ul><p>NOVA 适配了 NVMM 以及 NVMM + DRAM 混合存储系统的特点，解决了传统 LFS中 GC 开销大的痛点，尤其在写密集的场景下表现极其优秀。</p><h2 id="二-研究动机">二 研究动机</h2><p>2016 年恰恰是 NVMM 较为火热的一年，但纯 NVMM的存储系统也有一些弊端。现有的软件技术栈几乎都是为了慢速磁盘设计的，对于几乎所有传统计算机科学学者而言，文件I/O要比内存访存慢几个数量级几乎是常识。此时 NVMM的出现就显得不合时宜，需要绕过内核(如 Direct Access, DAX模式来绕过操作系统的 Page Cache)，重写数据结构等</p><ul><li>优势未利用：为传统文件系统而生的软件技术栈如驱动程序并不能很好地利用NVMM 支持按字节寻址的特性，仍然读 4KB 修改再写回。此外，NVMM还有更好的随机访问性能，不必太过纠结空间局部性，这也为 NOVA 对传统 LFS的改造奠定了基础</li><li>假设不成立：从访存上来看，NVMM 更类似 DRAM，硬件层面只支持到 8 Byte的原子读写（CPU指令决定的)，而传统存储设备能保持扇区或闪存块的原子性的假设是不成立的</li><li>此外，内存重排也会影响 NVMM 的数据一致性</li></ul><p>一些相关研究在 NVMM上颇有局限性。影子分页在处理文件系统的树形结构时需要从叶子级联影子到根，这是强顺序要求的，导致CPU 无法进行乱序执行等优化降低并行度。传统 LFS则基于传统存储器的局部性，在尾部追加日志和数据，导致垃圾回收操作开销太大。</p><h2 id="三-nova-系统设计">三 NOVA 系统设计</h2><p>日志和文件数据分离。首先，NOVA 本身是 log-structure的，这意味着它需要 1存放实际数据 2记录日志。传统 LFS会将日志和数据放在一起直接追加到已用空间尾部，这是基于空间局部性考虑的。而NOVA 考虑到 NVMM可以放宽局部性限制，几乎想跳到哪个地址就跳到哪个地址，所以分离了日志和实际文件数据。这样，可以实现数据的copy-on-write (COW),在更改数据时可以找个空闲页写入，然后原子单纯追加日志记录那个空闲页的地址。</p><p>数据和索引分离。无论日志还是数据都有可持久化需求，必须在非易失性存储器上，但是索引很多时候是可以用完就丢的，反正重建速度也快。基于这一点，NOVA将空闲页索引 (RB Tree) 和 Inode 索引 (Radix Tree) 放在 DRAM 上。DRAM还是要比 NVMM快一些的，无论是数据结构实现还是效率上都比较有优势。此外，NOVA 还有 PerCPU 的空闲列表作为每个 CPU独立的页分配器以防止对全局空闲页索引的锁竞争。</p><p>日志链表。日志被实现为作为一个 4KB页的链表，在某个页都是无效日志（如都是“删除xxx”的记录）时，可以单纯只进行链表删除(Unlink), 指针操作效率极高，不需要像传统 LFS那样读取实际数据并且移动。</p><p>Journaling 技术。在面对多个 inode 的更改操作时，需要用到每个 CPU独立的 journal。若要进行 <code>mv A/file.txt B/file.txt</code>，首先对 A和 B 的 inode log 进行追加 <code>ADD file.txt</code> 和<code>DEL file.txt</code>，但此时还没有更新 log tail指针，此时这两条数据都是对文件系统不可见的。随后在 journaling 中记录“我要原子性地同时把 A inode log 的 tail pointer 向后移动一条记录，Binode log 的 tail pointer 向后移动一条记录”，若操作成功则<code>mv</code> 顺利完成，若断电故障失败则查 journaling 恢复。</p><p>细粒度锁。由于每个 inode 都有单独的 log，这允许 NOVA 为每个 inode上单独的锁，从而支持并发修改。</p><h2 id="四-nova-实现细节">四 NOVA 实现细节</h2><p><img src="/images/2026-01-25_11-37-35.png" /></p><h3 id="一致性保证">一致性保证</h3><p>将 NVMM 为每个 CPU 划分一个池 (pool)。对于每个pool，将索引数据结构（通常是内存友好的数据结构）存在 DRAM 中，而 Journal和 Inode table 存在 NVMM 中，其中每个 Inode 有自己的锁和 log 指针。Inode的 <code>Head</code>, <code>Tail</code> 指针分别指向 log链表的第一页首地址和最后一页已提交的地址，在 <code>Tail</code>指针之后的均为无效数据，等待被原子地 commit，即 <code>Tail</code>向后移动。一个文件的 Inode 仅仅存在于一个 CPU 核心的 Inode Table中，因此所有修改都是共享的，靠 CAS 的 log tail 更新处理并发问题。</p><p>NOVA 将 Inode table 设计为初始 2MB，128 字节对齐的块数组。每个 CPU有独立的 Inode 分配器，它们可以自由无锁地分配 Inode空间并填充数据，然后再获取 Inode 锁把刚分配空间的地址信息记入 Inode日志。Again，这里又是随机访问的优势。如果 NVMM的随机访问很糟糕，那任意分配 Inode显然会造成很差的空间局部性。值得注意的是，传统 LFS由于垃圾回收，除了带来开销之外还会导致数据的位置（地址）被频繁移动，而NOVA 分离日志和数据使得数据在通常情况下物理地址固定，为之后的<code>mmap</code> 语义打下基础。</p><p>Journal 被设计为 4KB的环形缓冲区，由队首和队尾指针控制，在其之间的就是还未被提交的事务。Journal的一条记录通常包含多次修改，如<code>(Inode A: DEL file, Inode B: ADD file)</code>,并且记录旧数据以用于恢复。</p><p>综合以上，NOVA 有三种原子性保证：</p><ul><li>CPU 的 8 字节（64位）指令原子性保证</li><li>日志结构（log-structured）实现的单个 Inode 原子性保证</li><li>Journaling 技术实现的多个 Inode 原子性保证</li></ul><h3 id="如何应对内存重排">如何应对内存重排</h3><p>CPU为了避免在访存时的空等待，有时会重排一些看上去没有相关性的指令。但是在我们的例子中，log的 tail pointer必须在所有操作确实完成后进行，否则就失去了其事务提交和回滚的意义。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">new_tail = append_to_log(inode-&gt;tail, entry); <span class="comment">// writes back the log entry</span></span><br><span class="line">cachelines <span class="title function_">clwb</span><span class="params">(inode-&gt;tail, entry-&gt;length)</span>;  </span><br><span class="line">sfence(); <span class="comment">// orders subsequent</span></span><br><span class="line">PCOMMIT <span class="title function_">PCOMMIT</span><span class="params">()</span>; <span class="comment">// commits entry to NVMM</span></span><br><span class="line">sfence(); <span class="comment">// orders subsequent store</span></span><br><span class="line">inode-&gt;tail = new_tail;</span><br></pre></td></tr></table></figure><p>其中 <code>clwb</code> 用于把数据从 CPU 缓存写回到内存控制器；<code>sfence()</code>用于保证其前面的指令一定比其后面的指令先可见（防止重排或乱序执行）；<code>PCOMMIT()</code>用于清空内存控制器写队列保证数据被写入 NVMM 芯片上。</p><h3 id="读写操作">读写操作</h3><p>对于 <code>write</code> 操作，NOVA 绕过 DAX，沿袭传统 LFS 的Copy-on-Write (CoW) 方案，先在空闲页写入更改后的数据，再追加 log entry并原子更改元数据指向的位置，旧页变为垃圾等待回收（或被作为一个版本的快照管理）。</p><p>回顾经典的<code>mmap</code>：最初，操作系统只是在虚拟内存区域中分配一片地址并标记为属于对应的文件，但不分配物理地址更不加载数据。当访问到这片地址时触发内核Page Fault，内核检查对应文件数据是否在页缓存中，如果不在就将文件数据写入DRAM页缓存并更改页表映射把相应物理地址映射到之前的虚拟地址上。此后的读写都只操作内存里这片页缓存，内核会异步地将脏页写回磁盘。</p><p>而在 NVMM 中，Page Cache 多此一举，因为 NVMM本来就能够作为内存使用，所以 NOVA 是 Direct Access (DAX) 模式。NOVA 对<code>mmap</code> 采用原地更新而不是像 <code>write()</code> 或传统 LFS那样先写空闲页再添加日志的 CoW 方案，这使得 NOVA 的文件数据不像传统 LFS那样频繁变动位置（NOVA 的垃圾回收只需要单纯 Unlink 掉日志链表节点并修改bitmap），NOVA 的 <code>mmap</code> 不必频繁修改页表的 VPN 到 PPN映射，提高了性能。当然，原地写入并不是原子的，但 <code>mmap</code>本身也不保证原子性，要保持强一致性的话，仍然需要 CoW，并且在<code>msync()</code> 时才会更改日志指针。</p><p>另外，在需要快照时，由于必须分为多个版本的页面快照、不能原地更新，也仍然需要CoW 的方案。</p><h3id="初始化内存保护恢复时懒加载与并行">初始化内存保护、恢复时懒加载与并行</h3><p>野指针对 DRAM危害较小，易失性存储器重启后就是新数据了；而一旦在野指针访问到了 NVMM中的地址（NVMM同样会在内核地址空间中！），可能会对本应持久化的数据造成破坏，所以通常在初始化阶段NVMM 的区域被标记为只读，在需要操作时临时禁用 CPU 的 Write Protect,看上去并非特别优雅的方案。</p><p>在重启恢复时，主要恢复空闲页索引和 Inode 索引。对于后者，NOVA只会在有需要时（如某个目录或文件的 Inode被访问）才对其进行懒加载建立索引；同时由于每个 Inode日志有独立的锁，NOVA 允许并行读取所有 Inode 日志进行恢复。而对于前者，则必须进行恢复，否则懒加载会导致页分配器没有足够的信息。</p><h2 id="五-性能评估">五 性能评估</h2><p>对于微基准测试，作者测试了简单的 <code>create</code><code>append</code> 和 <code>delete</code> 操作在 STT-RAM 和PCM（相变内存，二者均为 NVMM）上，其中 <code>create</code> 和<code>append</code> 操作，NOVA 本身的逻辑仅占总 latency 的 21%-28%，而<code>delete</code> 操作中 NOVA 逻辑的 latency占比较大由于释放数据和日志需要读取 Inode 日志。</p><p><img src="/images/Pasted%20image%2020260126223410.png" /></p><p>对于宏基准测试，作则测试了文件服务器、Web代理、Web服务器、邮件服务器四种情况，NOVA在文件服务器和邮件服务器这样写密集业务上的吞吐量均领先（NVMM的特性规避了写放大），而在 Web Server 和 Web Proxy这样读密集的业务上表现一般。</p><p><img src="/images/Pasted%20image%2020260126223518.png" /></p><p>作者还通过高负载写入测试 GC 压力，发现 NOVA 吞吐量平稳，几乎不受 GC影响。同样，恢复速度也在毫秒甚至微秒级别。</p><p><img src="/images/Pasted%20image%2020260126223649.png" /></p><p><img src="/images/Pasted%20image%2020260126223710.png" /></p><h2 id="六-阅读总结">六 阅读总结</h2><p>NOVA 似乎目前只在学术界地位很高， 没有被 Linux内核合并。我之前在初学的时候就有在想是否存在可持久化内存之类的东西，后来了解到好像被Intel 玩崩了，这么看 NOVA 也是生不逢时了。不过似乎后面还有非易失性的 CXL内存，待我往下读。<del>为什么三天只读了一篇啊。</del></p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;一-核心内容&quot;&gt;一 核心内容&lt;/h2&gt;
&lt;p&gt;论文作者提出了一种名为 NOn- Volatile memory Accelerated (NOVA)
的日志结构文件系统 (log- structured file system, LFS) 以最大化 NVMM 和
DRAM 同时存在的混合存储系统的效率。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;NOVA 充分利用 NVMM 的随机访问特性，从传统 LFS
中放宽了局部性限制，从而实现了日志数据分离、日志链表来避免传统 LFS
的垃圾回收开销&lt;/li&gt;
&lt;li&gt;NOVA 通过将空闲链表索引和 Inode 索引建立在 DRAM 中，规避了在 NVMM
中由于一致性难以保证造成的数据结构难以实现的问题&lt;/li&gt;
&lt;li&gt;除了本身是 log-structured 之外，NOVA 还在需要同时修改多个 inode
的操作(如对路径相关的操作)中采用了 journaling 技术，同时依赖 VFS
的目录锁，规避了影子分页系统的级联开销和因此强顺序数据依赖造成的性能损失&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;NOVA 适配了 NVMM 以及 NVMM + DRAM 混合存储系统的特点，解决了传统 LFS
中 GC 开销大的痛点，尤其在写密集的场景下表现极其优秀。&lt;/p&gt;
&lt;h2 id=&quot;二-研究动机&quot;&gt;二 研究动机&lt;/h2&gt;</summary>
    
    
    
    <category term="学术" scheme="https://amiriox.github.io/categories/%E5%AD%A6%E6%9C%AF/"/>
    
    
    <category term="文献" scheme="https://amiriox.github.io/tags/%E6%96%87%E7%8C%AE/"/>
    
    <category term="NVMM非易失性主存" scheme="https://amiriox.github.io/tags/NVMM%E9%9D%9E%E6%98%93%E5%A4%B1%E6%80%A7%E4%B8%BB%E5%AD%98/"/>
    
    <category term="混合存储系统" scheme="https://amiriox.github.io/tags/%E6%B7%B7%E5%90%88%E5%AD%98%E5%82%A8%E7%B3%BB%E7%BB%9F/"/>
    
    <category term="文件系统" scheme="https://amiriox.github.io/tags/%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F/"/>
    
    <category term="FAST" scheme="https://amiriox.github.io/tags/FAST/"/>
    
    <category term="精读" scheme="https://amiriox.github.io/tags/%E7%B2%BE%E8%AF%BB/"/>
    
  </entry>
  
</feed>
