<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>nfs on Monsoon's Blog</title><link>https://monsoon-cs.moe/zh/tags/nfs/</link><description>Recent content in nfs on Monsoon's Blog</description><generator>Hugo</generator><language>zh-CN</language><lastBuildDate>Sat, 17 Feb 2024 00:00:00 +0000</lastBuildDate><atom:link href="https://monsoon-cs.moe/zh/tags/nfs/index.xml" rel="self" type="application/rss+xml"/><item><title>NFS Performance Tuning</title><link>https://monsoon-cs.moe/zh/2024-02-16-nfs-tuning/</link><pubDate>Fri, 16 Feb 2024 00:00:00 +0000</pubDate><guid>https://monsoon-cs.moe/zh/2024-02-16-nfs-tuning/</guid><description>&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;本文是我在实践中总结出的生产场景下 10 Gbps 网络下的 NFS 性能调优指南，特别是针对&lt;strong&gt;大量小文件&lt;/strong&gt;（Lots of Small Files, LOSF）读写的优化。&lt;/p&gt;
&lt;h2 id="调优"&gt;调优&lt;/h2&gt;
&lt;h3 id="硬件"&gt;硬件&lt;/h3&gt;
&lt;p&gt;网络硬件方面，&lt;strong&gt;带宽&lt;/strong&gt;和&lt;strong&gt;延迟&lt;/strong&gt;两者都很重要。&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="前言">前言</h2>
<p>本文是我在实践中总结出的生产场景下 10 Gbps 网络下的 NFS 性能调优指南，特别是针对<strong>大量小文件</strong>（Lots of Small Files, LOSF）读写的优化。</p>
<h2 id="调优">调优</h2>
<h3 id="硬件">硬件</h3>
<p>网络硬件方面，<strong>带宽</strong>和<strong>延迟</strong>两者都很重要。</p>
<p>要保证 NFS 的性能，高带宽网络是必要的，10 Gbps 对于生产场景来说是基础要求，更高速的 InfiniBand 或者 RoCE 网络则可按照需求和预算进行选择。</p>
<p>对于<strong>大量小文件</strong>（Lots of Small Files, LOSF）场景来说，<strong>延迟比带宽更重要</strong>。很多性能调优教程都忽略了这一点，只关注了连续读写的性能，即使测试了 4K 随机读写，也使用了<strong>错误的测试方法</strong>（下文给出了正确的测试方法）。</p>
<p>延迟的重要性体现在，如果程序对于小文件的访问是<strong>内秉串行化</strong>的，<strong>延迟会决定串行化 IOPS 的上限</strong>。0.1 ms 的延迟决定了串行化的 IOPS 上限是 10k，而 1 ms 的延迟对应的上限则是 1k。</p>
<p>内秉串行化访问的场景非常多。例如，把家目录放置于 NFS 上，oh-my-zsh 的加载、python 包的加载都是内秉串行化的。1ms 的网络延迟会让这些程序慢到不可接受（例如 <code>import torch</code> 的执行需要 30s 以上）。</p>
<p>使用合格的企业级交换机、恰当配置的网络拓扑，可以尽量降低延迟。同时，光模块、光转电口模块的质量也有可能极大影响延迟（我原来使用的中科光电光转电口模块会引入 0.1ms 的额外延迟，导致 IOPS 下降了 2/3）。</p>
<p>需要注意的是，RDMA 尽管理论上能降低延迟，但实际测试中发现 10 Gbps 以太网和 100 Gbps InfiniBand 的串行化 IOPS 差距并不大，预算有限时只使用以太网也足够。</p>
<p>TODO: 巨型帧</p>
<h3 id="linux-kernel">Linux Kernel</h3>
<p>内核网络参数需要进行调整，以适应高速网络：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="c1"># Ref: https://gist.github.com/mizanRahman/40ba603759bfb5153189ccdc9dbbd1e4</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Disable TCP slow start on idle connections</span>
</span></span><span class="line"><span class="cl"><span class="na">net.ipv4.tcp_slow_start_after_idle</span> <span class="o">=</span> <span class="s">0</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Increase Linux autotuning TCP buffer limits</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Set max to 16MB for 1GE and 32M (33554432) or 54M (56623104) for 10GE</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Don&#39;t set tcp_mem itself! Let the kernel scale it based on RAM.</span>
</span></span><span class="line"><span class="cl"><span class="na">net.core.rmem_max</span> <span class="o">=</span> <span class="s">56623104</span>
</span></span><span class="line"><span class="cl"><span class="na">net.core.wmem_max</span> <span class="o">=</span> <span class="s">56623104</span>
</span></span><span class="line"><span class="cl"><span class="na">net.core.rmem_default</span> <span class="o">=</span> <span class="s">56623104</span>
</span></span><span class="line"><span class="cl"><span class="na">net.core.wmem_default</span> <span class="o">=</span> <span class="s">56623104</span>
</span></span><span class="line"><span class="cl"><span class="na">net.core.optmem_max</span> <span class="o">=</span> <span class="s">40960</span>
</span></span><span class="line"><span class="cl"><span class="na">net.ipv4.tcp_rmem</span> <span class="o">=</span> <span class="s">4096 87380 56623104</span>
</span></span><span class="line"><span class="cl"><span class="na">net.ipv4.tcp_wmem</span> <span class="o">=</span> <span class="s">4096 65536 56623104</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># TCP Congestion Control</span>
</span></span><span class="line"><span class="cl"><span class="na">net.ipv4.tcp_congestion_control</span> <span class="o">=</span> <span class="s">bbr</span>
</span></span><span class="line"><span class="cl"><span class="na">net.core.default_qdisc</span> <span class="o">=</span> <span class="s">cake</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>在服务端和客户端都需要应用这套设置，可以写入 <code>/etc/sysctl.conf</code> 中以持久化。</p>
<h3 id="server-side">Server Side</h3>
<p>NFS server 的线程数可以尽量调大点，服务器负载比较高时可以提升性能，我直接设成了服务器的线程数。修改 <code>/etc/nfs.conf</code>：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[nfsd]</span>
</span></span><span class="line"><span class="cl"><span class="na">threads</span><span class="o">=</span><span class="s">128</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>以下几个 NFS server 参数需要调整：</p>
<ul>
<li><code>async</code>：将同步 IO 操作视为异步。同步读写为主的负载可以大幅提升性能，但服务器崩溃时可能造成数据丢失，对数据完整性有极高要求的情况下不推荐使用；</li>
<li><code>no_subtree_check</code>：对性能没有大影响，但在某些情况下可以提升可靠性（同时有轻微的安全风险）。参见 [1]。</li>
</ul>
<h3 id="client-side">Client Side</h3>
<p>没有特殊的理由时应该默认使用最新的 NFSv4.2，NFSv3 使用 UDP 作为底层传输方式时，在高速网络下会因为 UDP 包序列号问题导致数据损坏，参见 [2]。</p>
<p>以下几个 NFS client 参数需要调整：</p>
<ul>
<li><code>proto=rdma</code>：网络支持 RDMA 时设置；</li>
<li><code>nocto</code>：关闭 close-to-open 缓存一致性语义。NFS 默认行为是关闭文件时会把所有更改写回到服务器。如果对于多客户端之间的文件一致性要求比较高，不推荐使用此选项；</li>
<li><code>ac</code>：启用属性缓存（attribute caching），客户端会缓存文件属性。同样。对于数据一致性要求较高的集群，不推荐使用此选项；</li>
<li><code>fsc</code>：使用 FS-Cache 缓存数据到本地。需要同时<a href="https://github.com/jnsnow/cachefilesd">配置 cachefilesd</a>。奇怪的是我在测试中并没有发现数据被缓存到本地，这可能需要进一步的探究；</li>
<li><code>nconnect=16</code>：设置 NFS client 和 server 间建立 16 条 TCP 连接。NFS client 默认只建立一条 TCP 连接，所有 RPC 复用这条连接。在某些情况下这会限制连续读写的带宽。增大 <code>nconnect</code>（最大值 16）可以解决这个问题。</li>
</ul>
<p>特别的，<code>noatime</code> / <code>relatime</code> 的设置对于 NFS 并无影响 [3]，NFS client 始终会缓存 atime 的更改。</p>
<p>有些教程中会推荐修改 <code>rsize</code> 和 <code>wsize</code>，这两个值在 NFSv4.2 默认协商出的即是最大值 <code>1048576</code>，因而无需手动更改，只需检查一下是否协商正确即可。</p>
<p>根据 [4]，<code>sunrpc.tcp_max_slot_table_entries</code> 可能会影响性能，可以适当调大（默认 <code>2</code>）。在我的测试中，我发现当遇到千万数量级的持续小文件访问负载时，NFS 有时候会卡住。当我把这个参数调大时，此问题得以解决。设置 <code>/etc/modprobe.d/sunrpc.conf</code>：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="na">options sunrpc tcp_slot_table_entries</span><span class="o">=</span><span class="s">16384</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>有时我会遇到 <code>nfsd</code> 占用大量 CPU 且性能急剧下降的问题，同时记录到大量 <code>delegreturn</code> RPC calls。根据 [5]，可以通过禁用 <code>fs.leases-enable</code> 解决，设置 <code>/etc/sysctl.conf</code>：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="na">fs.leases-enable</span> <span class="o">=</span> <span class="s">0</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>当 <code>nfsd</code> 因为种种原因重启后，默认会有 90s 的 grace period 用于锁恢复，这段时间内 <code>nfsd</code> 会拒绝所有 <code>open</code> 请求，在内核日志中显示：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-txt" data-lang="txt"><span class="line"><span class="cl">[1073511.138061] NFSD: starting 90-second grace period (net f0000000)
</span></span></code></pre></td></tr></table>
</div>
</div><p>实践中发现这段时间可以适当调小，以减少 <code>nfsd</code> 重启带来的影响。设置 <code>/etc/default/nfs-kernel-server</code>：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Options for rpc.svcgssd.</span>
</span></span><span class="line"><span class="cl"><span class="nv">RPCSVCGSSDOPTS</span><span class="o">=</span><span class="s2">&#34;--lease-time 10 --grace-time 10&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="测试">测试</h2>
<p>TODO</p>
<h2 id="总结">总结</h2>
<p>TODO</p>
<h2 id="参考">参考</h2>
<p>[1] <a href="https://man.archlinux.org/man/exports.5.en#no_subtree_check">https://man.archlinux.org/man/exports.5.en#no_subtree_check</a></p>
<p>[2] <a href="https://man.archlinux.org/man/nfs.5.en#Using_NFS_over_UDP_on_high-speed_links">https://man.archlinux.org/man/nfs.5.en#Using_NFS_over_UDP_on_high-speed_links</a></p>
<p>[3] <a href="https://man.archlinux.org/man/nfs.5.en#File_timestamp_maintenance">https://man.archlinux.org/man/nfs.5.en#File_timestamp_maintenance</a></p>
<p>[4] <a href="https://learn.microsoft.com/en-us/azure/azure-netapp-files/performance-linux-concurrency-session-slots">https://learn.microsoft.com/en-us/azure/azure-netapp-files/performance-linux-concurrency-session-slots</a></p>
<p>[5] <a href="https://docs.gitlab.com/ee/administration/nfs.html#disable-nfs-server-delegation">https://docs.gitlab.com/ee/administration/nfs.html#disable-nfs-server-delegation</a></p>
]]></content:encoded></item></channel></rss>