Skip to content

评估 Garnet 的性能优势

我们已经在各种部署模式下对 Garnet 进行了彻底的测试:

  • 同一台本地设备用于客户端和服务器
  • 两台本地设备- 一个客户端和一个服务器
  • Azure Windows
  • Azure Linux

以下,我们重点关注几个选择性的关键结果。

设置

我们配置了两台 Azure Standard F72s v2 虚拟机(每台 72 个 vcpu,每个 144 GiB 内存),运行 Linux(Ubuntu 20.04),启用了加速的 TCP。 这个 SKU 的好处是我们可以确保不与另一台虚拟机共存以优化性能。一台机器运行不同的缓存存储服务器,另一台专用于发出工作负载。我们使用自己的基准测试工具: Resp.benchmark,生成所有结果。我们将 Garnet 与撰写时的最新开源版本的 Redis(v7.2)、KeyDB(v6.3.4)和 Dragonfly(v6.2.11)进行比较。 在实验中,我们使用均匀随机分布的键(Garnet 的共享内存设计在倾斜的工作负载下获益更多)。在这些实验中,所有数据都配置了适合的内存。基线系统根据可用信息进行了尽可能的调整和优化。以下,我们总结了每个系统在我们的实验中使用的初始配置。

Garnet ```bash dotnet run -c Release --framework=net8.0 --project Garnet/main/GarnetServer -- \ --bind $host \ --port $port \ --no-pubsub \ --no-obj \ --index 1g ```
Redis 7.2 ```bash ./redis-server \ --bind $host \ --port $port \ --logfile "" \ --save "" \ --appendonly no \ --protected-mode no \ --io-threads 32 ```
KeyDB 6.3.4 ```bash ./keydb-server \ --bind $host \ --port $port \ --logfile "" \ --protected-mode no \ --save "" \ --appendonly no \ --server-threads 16 \ --server-thread-affinity true ```
Dragonfly 6.2.11 ```bash ./dragonfly \ --alsologtostderr \ --bind $host \ --port $port \ --df_snapshot_format false \ --dbfilename "" \ --max_client_iobuf_len 10485760 ```

基本命令性能

我们通过变化有效负载大小、批处理大小和客户端线程数来测量基本 GET/SET 操作的吞吐量和延迟。 对于我们的吞吐量实验,在运行实际工作负载之前,我们将小型数据库(1024 个键)和大型数据库(256M 个键)预加载到 Garnet 中。 相反,我们的延迟实验是在空数据库上执行的,针对的是操作小键空间(1024 个键)的 GET/SET 命令的组合工作负载。

吞吐量 GET

对于图 1 所示的实验,我们使用大批量的 GET 操作(每批 4096 个请求)和小负载(8 字节的键和值)来最小化网络开销。 随着客户端会话数的增加,我们观察到 Garnet 比 Redis 或 KeyDB 具有更好的可伸缩性。 Dragonfly 展示了类似的扩展特性,但仅限于 16 个线程。此外,请注意,DragonFly 是一个纯内存系统。 总体而言,Garnet 相对于其他系统的吞吐量始终更高,即使数据库大小(即预加载的不同键的数量)比处理器缓存的大小更大(达到 2.56 亿个键)。

变化的客户端会话数或批处理大小(GET) ```bash dotnet run -c Release --framework=net8.0 --project Garnet/benchmark/Resp.benchmark \ --host $host \ --port $port \ --op GET \ --keylength 8 \ --valuelength $valuelength \ --threads 1,2,4,8,16,32,64,128 \ --batchsize $batchsize \ --dbsize $dbsize ```
tpt-get-threads.png
图 1: 吞吐量(对数刻度),变化的客户端会话数,数据库大小为 (a) 1024 个键,和 (b) 2.56 亿个键

即使对于小批处理大小,Garnet 也能够达到更高的吞吐量,如图 2 所示。 这种情况发生无论实际数据库大小如何。

tpt-get-batchsize.png.png
图 2: 吞吐量(对数刻度),变化的批处理大小,数据库大小为 (a) 1024 个键,和 (b) 2.56 亿个键

GET/SET 延迟

接下来,我们通过发出 80% GET 和 20% SET 请求的混合,测量了各种系统的客户端端到端延迟,并将其与 Garnet 进行了比较。 由于我们关心延迟,我们保持了小型数据库的大小,同时调整了工作负载的其他参数,如客户端线程、批处理大小和有效负载大小。

图 3 展示了随着客户端会话数的增加,Garnet 的延迟(以微秒计)在各个百分位数上始终较其他系统更低且更稳定。请注意,此实验未使用批处理。

变化的客户端会话数或批处理大小(GET/SET)的延迟基准 ```bash dotnet run -c Release --framework=net8.0 --project Garnet/benchmark/Resp.benchmark --host $host \ --port $port \ --batchsize 1 \ --threads $threads \ --client GarnetClientSession \ --runtime 35 \ --op-workload GET,SET \ --op-percent 80,20 \ --online \ --valuelength $valuelength \ --keylength $keylength \ --dbsize 1024 \ --itp $batchsize ```
lat-get-set-threads.png
图 3: 在(a)中位数、(b)99 百分位数和(c)99.9 百分位数处变化的客户端会话数的延迟

Garnet 的延迟经过优化,适应了自适应的客户端批处理,并有效处理了查询系统的多个会话。 对于我们的下一组实验,我们将批处理大小从 1 增加到 64,并在下面不同的百分位数处绘制延迟,其中有 128 个活动客户端连接。 正如图 4 所示,当批处理大小增加时,Garnet 保持稳定,并实现了比其他系统更低的总体延迟。

lat-get-set-batchsize.png
图 4: 在(a)中位数、(b)99 百分位数和(c)99.9 百分位数处变化的批处理大小的延迟

复杂数据结构性能

Garnet 支持大量不同的复杂数据结构,如 Hyperloglog、Bitmap, Sorted Sets, Lists 等。 下面,我们展示了其中的一些性能指标。

Hyperloglog

Garnet 支持自己内置的 Hyperloglog(HLL)数据结构。 这是使用 C# 实现的,支持更新(PFADD)、计算估计值(PFCOUNT)和合并(PFMERGE)两个或多个不同的 HLL 结构。 HLL 数据结构通常在内存占用方面进行了优化。 我们的实现也不例外,在非零计数的数量较低时利用稀疏表示,在给定的固定阈值之后利用稠密表示,其中内存节省和用于解压缩的附加工作之间的权衡不再具有吸引力。 对于类似 Garnet 这样的并发系统,实现有效的 HyperLogLog(HLL)结构更新至关重要。 因此,我们的实验专注于 PFADD 的性能,有意设计为针对以下情况对我们的系统进行压力测试:

  1. 大量高争用更新(即 batchsize 为 4096,1024 个键的 DB)以及增加线程数或增加有效负载大小 插入几次后,构建的 HyperLogLog(HLL)结构将过渡到使用稠密表示。
  2. 大量低争用更新(即 batchsize 为 4096,256M 个键的 DB)以及增加线程数或增加有效负载大小 此调整将增加构建的 HyperLogLog(HLL)结构利用稀疏表示的可能性。 因此,我们的测量将考虑使用压缩数据或逐步为非零值分配更多空间的额外开销。

在图 5 中,我们呈现了第一个实验场景的结果。 在高争用的情况下,Garnet 的扩展性非常好,对于增加的线程数,原始吞吐量始终优于其他每个系统。 类似地,对于增加的有效负载大小,Garnet 的总吞吐量高于其他系统。 在所有测试系统中,随着有效负载大小的增加,我们注意到吞吐量明显减少。 这种行为是因为固有的 TCP 网络瓶颈所致。

在少量键上操作时,变化的客户端会话数或有效负载大小 ```bash dotnet run -c Release --framework=net8.0 --project Garnet/benchmark/Resp.benchmark \ --host $host \ --port $port \ --op PFADD \ --keylength 8 \ --valuelength $valuelength \ --threads 1,2,4,8,16,32,64,128 \ --batchsize 4096 \ --dbsize 1024 \ --skipload ```
tpt-pfadd-few-keys.png
图 5: (对数刻度)的吞吐量,对于(a)增加的客户端会话数,和(b)增加的有效负载大小,对于 1024 个键的数据库。

图 6 显示了如上述第二个实验场景所述的结果。 即使在操作 HLL 稀疏表示时,Garnet 的性能也优于任何其他系统,并且在增加的客户端会话数时保持一致更高的吞吐量。 类似地,对于增加的有效负载大小,Garnet 的整体吞吐量高于竞争对手。 请注意,在这两种情况下,吞吐量与之前的实验相比较低,这是由于在压缩数据上操作的开销。

在许多键上操作时,变化的客户端会话数或有效负载大小(PFADD) ```bash dotnet run -c Release --framework=net8.0 --project Garnet/benchmark/Resp.benchmark \ --host $host \ --port $port \ --op PFADD \ --keylength 8 \ --valuelength $valuelength \ --threads 1,2,4,8,16,32,64,128 \ --batchsize 4096 \ --dbsize 1048576 \ --totalops 1048576 \ --skipload ```
tpt-pfadd-few-keys.png
图 6: (对数刻度)的吞吐量,对于(a)增加的客户端会话数,和(b)增加的有效负载大小,对于 1M 个键的数据库。

在图 7 中,我们执行了与前述相同类型的实验,将客户端会话数固定为 64,并将有效负载固定为 128 字节,同时增加了批处理大小。 请注意,即使批处理大小为 4,Garnet 的吞吐量增益也明显高于我们测试的任何其他系统。 这表明,即使对于小批处理大小,我们的性能仍然优于竞争系统。

tpt-pfadd-batchsize.png
图 7: (对数刻度)的吞吐量,对于增加的批处理大小,通过 64 个客户端会话在具有(a)1024 个键的 DB,和(b)1M 个键的 DB。

Bitmap

Garnet 支持一组针对字符串数据类型的面向位的操作。这些操作可以在常量时间(如 GETBIT、SETBIT)或线性时间(如 BITCOUNT、BITPOS、BITOP)内进行处理。为了加速处理,对于线性时间操作,我们使用了硬件和 SIMD 指令。下面我们展示了这些操作的一部分的基准测试结果,涵盖了这两种复杂性类别。与之前类似,我们使用了一个小型的数据库大小(1024 个键)来评估每个系统在高争用情况下的性能,同时通过增加有效载荷大小(1MB)来避免所有数据都驻留在 CPU 缓存中。

在图8中,我们展示了 GETBIT 和 SETBIT 命令的性能指标。在这两种情况下,Garnet 在客户端会话数量增加时始终保持较高的吞吐量和更好的可伸缩性。

不同数量的客户端会话 (GETBIT/SETBIT/BITOP_NOT/BITOP_AND) ```bash dotnet run -c Release --framework=net8.0 --project Garnet/benchmark/Resp.benchmark \ --host $host \ --port $port \ --op GETBIT \ --keylength 8 \ --valuelength 1048576 \ --threads 1,2,4,8,16,32,64,128 \ --batchsize 4096 \ --dbsize 1024 ```
tpt-getbit-setbit-threads.png
图 8: 吞吐量(对数刻度),不同数量的客户端会话,数据库大小为 1024 个键和 1MB 的有效载荷。

在图9中,我们评估了 BITOP NOT 和 BITOP AND(带有两个源键)的性能,其中客户端会话数量增加并且有效载荷大小为 1MB。Garnet 在客户端会话数量增加时保持了整体较高的吞吐量,相比我们测试过的其他系统,它也在高争用情况下表现得非常好,考虑到我们的数据库大小相对较小(仅有 1024 个键)。

tpt-bitop-threads.png
图9:吞吐量(对数刻度),不同数量的客户端会话,数据库大小为 1024 个键和 1MB 的有效载荷。

如图10和图11所示,即使对于小的批处理大小,Garnet 也能获得比我们测试的其他任何系统更高的吞吐量。事实上,即使在批处理大小为 4 时,也能观察到明显的吞吐量差异。

不同批处理大小 (GETBIT/SETBIT/BITOP_NOT/BITOP_AND) ```bash dotnet run -c Release --framework=net8.0 --project Garnet/benchmark/Resp.benchmark \ --host $host \ --port $port \ --op GETBIT \ --keylength 8 \ --valuelength 1048576 \ --threads 64 \ --batchsize $batchsize \ --dbsize 1024 ```
tpt-bitop-batchsize.png
图10:吞吐量(对数刻度),通过 64 个客户端会话增加批处理大小,数据库大小为 1024 个键和 1MB 的有效载荷。
tpt-bitop-batchsize.png
图11:吞吐量(对数刻度),通过 64 个客户端会话增加批处理大小,数据库大小为 1024 个键和 1MB 的有效载荷。