Valkey 9.1 内存到底省在哪里:按版本、编码与每键字节做一次可复现测量

2026-09-29 22 预计阅读时间: 1 分钟
来源: percona.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:10 分钟

对于大量使用 String 键值的 Valkey 集群,内存优化会直接影响单节点容量、扩容频率和故障切换成本。相关测试显示,Valkey 9.1 在以 String 为主的环境中,相比 7.2 最多观察到约 37.5% 的内存下降。这一数字很有吸引力,但真正决定生产收益的,是键长度、值长度、对象编码、分配器碎片以及你选择的测量口径。

与其只看一个百分比,不如回答三个更具体的问题:每个键省了多少字节?哪些编码获得了收益?这些节省最终有没有反映到进程 RSS 和机器账单上?

37.5% 不是所有工作负载的固定折扣

String 密集型场景广泛存在于缓存、会话与鉴权、实时应用、电商、广告技术和游戏系统中。此类系统往往拥有数百万乃至数十亿个较小对象,因此即使每个对象只减少十几个字节,累积效果也可能非常可观。

不过,“内存下降 37.5%”必须连同测试边界一起理解:

  • 它是特定测试环境中 Valkey 9.1 相对 7.2 的结果,不是所有数据模型的承诺值。
  • 小键、小值通常更容易体现元数据优化,因为对象头、指针和分配器对齐在总占用中的比例更高。
  • 大值主要由有效载荷本身决定,固定开销减少后,百分比可能没有那么显眼。
  • used_memory、used_memory_dataset、used_memory_rss 和云主机实际占用不是同一个指标。
  • 复制缓冲区、客户端缓冲区、持久化、主动碎片整理和后台任务都可能改变结果。

因此,版本升级评估应同时报告总量、每键字节数和 RSS,而不是只摘取最漂亮的百分比。

编码决定了“同样是 String”到底占多少内存

Valkey 会根据对象内容和操作方式选择内部编码。可以使用以下命令检查单个对象:

valkey-cli SET session:1 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
valkey-cli OBJECT ENCODING session:1
valkey-cli MEMORY USAGE session:1

对 String 而言,整数形式、较短字符串和较长字符串可能采用不同编码。具体阈值和布局可能随版本变化,因此不应只凭业务字段类型推断编码,而应在目标版本上通过 OBJECT ENCODING 验证。

设计基准数据时,至少应覆盖以下几组:

数据组 示例 需要观察的内容
整数值 SET counter:1 12345 是否使用整数编码、每键占用
短字符串 32~64 字节会话令牌 紧凑编码和固定元数据占比
长字符串 1 KiB 以上 JSON 或二进制数据 有效载荷占比、分配器对齐
不同键长 12、32、128 字节键名 键名本身及其分配开销
更新后的字符串 创建后执行 APPEND 或覆盖 编码变化和额外容量

Hash、List 和 Set 等复合结构还可能使用 listpack 等紧凑编码。它们值得单独测试,但不能拿复合结构的结果直接代表 String 密集型集群。

用同一份数据比较 Valkey 7.2 与 9.1

下面是一套可以直接改造的本地测试。它使用官方容器、关闭持久化,并向两个版本装载完全相同的 20 万个键。运行前需要安装 Docker 和 Python 3,并确认所需镜像标签可用;正式报告中还应固定镜像 digest 和 CPU 架构。

先创建数据生成器 load.py:

#!/usr/bin/env python3
import sys

count = int(sys.argv[1]) if len(sys.argv) > 1 else 200_000
value_size = int(sys.argv[2]) if len(sys.argv) > 2 else 64
out = sys.stdout.buffer
value = b"x" * value_size

for i in range(count):
    key = f"user:{i:08d}".encode()
    command = (
        b"*3\r\n"
        b"$3\r\nSET\r\n"
        + f"${len(key)}\r\n".encode() + key + b"\r\n"
        + f"${len(value)}\r\n".encode() + value + b"\r\n"
    )
    out.write(command)

再创建 bench.sh:

#!/usr/bin/env bash
set -euo pipefail

COUNT=200000
VALUE_SIZE=64

run_version() {
  local tag="$1"
  local output="valkey-${tag}.txt"

  docker rm -f valkey-bench >/dev/null 2>&1 || true
  docker run -d --name valkey-bench -p 6379:6379 \
    "valkey/valkey:${tag}" \
    valkey-server --save '' --appendonly no >/dev/null

  until docker exec valkey-bench valkey-cli PING >/dev/null 2>&1; do
    sleep 0.2
  done

  python3 load.py "$COUNT" "$VALUE_SIZE" \
    | docker exec -i valkey-bench valkey-cli --pipe >/dev/null

  sleep 2

  {
    echo "version=${tag}"
    echo "count=${COUNT}"
    echo "value_size=${VALUE_SIZE}"
    docker exec valkey-bench valkey-cli INFO memory \
      | tr -d '\r' \
      | grep -E '^(used_memory:|used_memory_rss:|used_memory_dataset:|mem_fragmentation_ratio:)'
    echo -n 'sample_encoding='
    docker exec valkey-bench valkey-cli OBJECT ENCODING user:00000001
    echo -n 'sample_memory_usage='
    docker exec valkey-bench valkey-cli MEMORY USAGE user:00000001
  } | tee "$output"

  docker rm -f valkey-bench >/dev/null
}

run_version 7.2
run_version 9.1

执行:

chmod +x bench.sh
./bench.sh

输出会分别保存在 valkey-7.2.txt 和 valkey-9.1.txt。如果镜像仓库中的补丁版本标签不同,可以把 7.2、9.1 改成实际可用的完整标签。

建议重点计算两个指标:

每键数据集字节数 = used_memory_dataset / key_count
节省比例 = (7.2 的内存 - 9.1 的内存) / 7.2 的内存 × 100%

MEMORY USAGE 适合观察单个样本,used_memory_dataset 更适合比较已装载数据集,used_memory_rss 则更接近操作系统看到的进程占用。三者都应保留,因为对象级节省不一定立即转化为同等比例的 RSS 下降。

怎样避免得到一个“正确但没用”的结果

实验数据必须尽量贴近生产分布,而不是只使用一种固定长度。可以从脱敏后的统计信息中重建测试集,例如:

  • 键长度的 P50、P95 和 P99;
  • 值长度分桶及各桶占比;
  • 整数字符串、令牌、JSON 和二进制值的比例;
  • TTL 分布以及有无过期时间;
  • 原地更新、覆盖写和删除的频率;
  • 主从复制、持久化和碎片整理配置。

每个版本还应使用相同的配置、相同的容器内存限制、相同的 CPU 架构和相同的数据装载顺序。建议重复运行至少三次,并分别记录:

valkey-cli INFO memory
valkey-cli MEMORY STATS
valkey-cli DBSIZE
valkey-cli CONFIG GET maxmemory-policy

如果生产环境启用了 RDB、AOF、复制或主动碎片整理,应再做一轮开启对应功能的测试。关闭这些功能的实验适合隔离对象布局差异,却不足以预测完整生产进程的峰值内存。

升级前的决策清单

Valkey 9.1 对 String 密集型环境的优化可能带来显著收益,相关测试中相对 7.2 达到了约 37.5% 的下降。但是否能转化为节点缩容,需要完成以下检查:

  • 用真实键长和值长分布重放数据,而不是只测理想化样本;
  • 按编码拆分结果,并通过 OBJECT ENCODING 验证假设;
  • 同时比较 used_memory_dataset、used_memory 和 used_memory_rss;
  • 测量稳态内存,也测量快照、重写、复制和故障恢复期间的峰值;
  • 在容量规划中保留碎片、流量突增和故障切换余量;
  • 先做兼容性与性能回归,再根据实测结果调整 maxmemory 或实例规格。

真正有价值的结论通常不是“新版本节省了 37.5%”,而是“在我们的键和值分布下,每个对象减少了多少字节,整台机器最终释放了多少可用内存”。把测量细化到版本、编码和每键成本,升级收益才能进入可靠的容量模型。


相关推荐