Serving Netflix Video at 400Gbps on FreeBSD 笔记
这几天 Netflix 的一份名为 “Serving Netflix Video at 400Gb/s on FreeBSD” 在 Hacker News 上引起了不少的关注。
作为一个基础架构工程师,我看了这份讲稿之后,也受益良多,对于目前 Epyc 平台下的硬件IO带宽有了更深入的理解。在这里记录一下这些收获,顺便对我之前的一些经验做一个总结。
预先声明一下,由于我完全不懂TLS加密相关的内容,所以这此讲稿中的这一部分我会写得非常粗略。
背景
Netflix 在2020年已经实现了在单台服务器上实现 200 Gbps的TLS加密视频的吞吐量,现在,他们想要做到 400Gbps。
服务器硬件架构
- CPU: AMD Epyc 7502P
- 内存: 256G, 8通道
- 2 张 Mellanox ConnectX-6 Dx (200GbE * 2)的网卡
- 18 张 WD SN720 2TB NVME SSD
400Gbps的目标就是根据网卡的吞吐量来确定的,毕竟InfiniBand网卡很贵。
最初的数据流(使用软件TLS)
数据流如上图所示,分为4步:
- 从磁盘读取数据到内存
- CPU读取内存数据进行加密
- CPU将加密结果写入内存
- 网卡将内存中的数据发送出去
每一步都恰好涉及一次内存操作,所以总的内存带宽消耗为400Gbps * 4 = 200GBps。
第一步优化 – NUMA aware
最初,Netflix在这一硬件平台上只能跑到240Gbps的吞吐量,根据AMDuProfPCM得到的结果和以往经验,他们判断瓶颈是在内存带宽上。
AMDuProfPCM在最近的几次更新中,加入了对PCIe,内存以及xGMI的实时带宽采样,真好用啊真好用。
背景知识——Epyc平台的NUMA Node
详情参阅Socket SP3 Platform NUMA Topology for AMD Family 17h Models 30h–3Fh
如上图所示 AMD Epyc Rome 的 CPU 典型架构 由4个四分块连接组成(俗称胶水),每个块下都有CPU核,两个内存通道和两个PCIe 4.0 * 16。在使用 AMD Epyc Rome 的 CPU时,BIOS中有一个选项叫NUMA Node Per Socket(NPS),即将每颗CPU分成几个NUMA Node,最常用的是NPS=4和NPS=1。
在NPS=1的时候,整个CPU会被视作一个NUMA node,对程序员会更为友好,但若想达到最佳的IO性能,则必须使用NPS=4以对内存操作和PCIE操作进行更精确的控制,让它们不要再各个四分块之间绕来绕去,如下图所示,在四分块之间互相访问是有一定延迟的,并且有Infinity Fabric的带宽限制。
举个最基础的例子,Epyc 7502P有8个内存通道,理论内存总带宽是200GBps,但实际上,使用STREAM Benchmark测试时,NPS=1的时候,总的内存带宽约为135GBps~150GBps,而在NPS=4的时候,控制让每个NUMA Node的内存都只被对应的cpu核访问的话,总的内存带宽能达到160GBps~175GBps(Netflix这里能测到175GBps,但不是所有3200MHz的内存都能到这个水平)。
所以我们首先的优化思路,就是尽可能让数据流不跨NUMA Node。
跨NUMA访问的最差Case
- 从NUMA0的磁盘读数据到NUMA1的内存
- NUMA2的CPU从NUMA1的内存读数据做加密
- NUMA2的CPU把加密好的数据写到NUMA3的内存里
- NUMA4的网卡把NUMA3的内存数据发送出去
对于每份数据,这里一共发生了4次跨NUMA操作,也需要200GBps的带宽,显然比上图所示的4条IF的带宽=4 * 47GBps大了。
所以接下来需要减少跨NUMA的操作
如何减少跨NUMA的操作
- 磁盘中心化隔离(Disk centric siloing) 努力把对某份数据做的所有操作都放在存储这份数据的NUMA Node上做。
- 网络中心化隔离(Network centric siloing) Try to do as much as we can on the NUMA node that the LACP partner chose for us,这里我其实没看懂,具体的意思可能是把尽可能多的操作放在网卡所在的NUMA Node?
具体操作
- 选择连接所在的网卡的NUMA node
- 从磁盘读数据的的时候,读到local的内存
- 加密的结果写到local的内存,同时使用local的cpu做计算
- kTLS worker 设置 NUMA node affinity
- Choose local lagg(4) egress port(没看懂)
实现以上操作后,在最差的情况下,对于每份数据,我们只会有一次跨NUMA node的操作,即从NUMA0的磁盘读数据到NUMA1,之后的操作都在NUMA1上进行。
注意,虽然前文没提,但这里默认每个NUMA Node上都有一张网卡。(我也不想这么叙事,但原slide就是这么干的)
那么在更真实的场景下,我们会遇到两个问题:
- 只有两张网卡,分别在两个NUMA Node上
- 每个NUMA Node上磁盘数量不同
对于第一个问题,Netflix hack了一下,假装每个NUMA Node上都有一张网卡,实际上是把NUMA node按距离分为了两组,每组对应一张网卡。
那么在这种更真实的场景下,我们以上的操作除了磁盘跨NUMA之外,网卡也可能跨NUMA,所以最差有两次跨NUMA的操作。但是此时在Infinity Fabric上的流量就降到最多100GBps了,比之前的200GBps削减了一半,而平均情况更是只有1.25次跨NUMA,也就是62.5GBps的Infinity Fabric流量。
所以做了这番操作之后,实际服务的平均吞吐量到达了280GBps。
看起来,在这种数据传输模式下,这就是最好的方案了。
补充问题:把连接直接建到数据所在的numa的网卡上可以吗?
(注:这里假设每个NUMA Node上都有一张网卡)
这样看起来就完全不需要跨NUMA的操作了,但有以下几个主要问题:
- 在知道请求内容位置之前就得建连接
- 请求的内容可能分布在几个不同的NUMA Node上
- 每个NUMA Node的磁盘数不一样,如果这样隔离得太好的话,那每个NUMA Node上分到的连接数就不平衡了
第二步优化 - NIC Based kTLS
NIC Based kTLS,即在网卡上做TLS加密,具体原理可以自己去看原slides,重点是他可以省掉之前数据传输模式中的内存->cpu和cpu->内存两次内存操作。 整个数据流就只剩下磁盘 -> 内存 -> 网卡,内存带宽和跨NUMA带宽的问题就这么迎刃而解了。
那为什么一开始没用这个呢? 因为一开始Mellnaox的固件不能用PCIE Relaxed Ordering,而且NIC Base kTLS性能也不行,后面更新了两次固件把这俩问题解决了,然后性能就起飞了,服务总吞吐能跑到380GBps了。
PCIE Relaxed Ordering 是在AMD Epyc平台上让InfiniBand达到最高性能的一个关键,具体原理可以自行了解。
好了,在AMD Epyc平台上跑到理想性能了,接下来测下其他平台。
其他平台的表现
Ampere Altra(Armv8.2)
内存带宽和PCIE带宽都和AMD一样,但是SW TLS(180 Gbps)和NIC TLS(240 Gbps)的初始性能都不如AMD。
然后他们观察到CPU利用率很低,而且网卡饱和了,看到很多output drops,所以怀疑是PCIe的问题。
最后发现是outstanding的DMA操作被PCIe tags限制,默认是5bit,也就是允许32个outstanding DMA操作,可以用8Bit的PCIE extended tags 8bit解决,允许 256个outstanding DMA 操作。Enable了PCIE extended tag之后性能从240Gbps -> 320 Gbps。
Intel Ice Lake
CPU做 TLS能跑到 230Gbps的吞吐量,被内存带宽卡了,值得注意的是Intel的内存插满只能跑在2933MHz,所以比AMD跑出来的结果要差。
Intel平台锁了PCIE Relaxed Ordering,所以没测NIC TLS的结果。
总结
总的来说,Netflix的这次分享让我收获最大的部分就是第一步优化中的跨NUMA Node的Infinity Fabric的带宽分析,解开了我之前的一些困惑,其次是关于PCIE Relaxed Ordering和extended tag的介绍。所以我在本文中主要记录了这两部分的内容。
但其原slides还有一些关于网络部分的内容,有兴趣的读者可以自行去了解。