Valkey中数据的隐秘生活

Lobsters Hottest 工具

摘要

Valkey使用如listpack和hashtable等不同的编码方式来处理其数据类型,以平衡内存和性能;了解这些内部结构可以显著节省内存。

<p><a href="https://lobste.rs/s/2vuexn/secret_life_data_valkey">评论</a></p>
查看原文
查看缓存全文

缓存时间: 2026/07/27 01:38

# Valkey:Valkey 中数据的隐秘生活 技术深度剖析 2026-07-21 · *Kyle Davis* 如果你使用 Valkey 的时间较长,你可能已经了解核心中的不同主要数据类型:字符串(strings)、哈希(hashes)、列表(lists)、集合(sets)、有序集合(sorted sets)和流(streams)。每种数据类型都附带一组相应的命令来操作该数据类型。例如,你使用 `HSET` (https://valkey.io/commands/hset/) 来设置哈希的一个字段和值,但你不能用 HSET 来向列表添加元素。如果我告诉你,除非你使用特定的 `OBJECT` 命令,否则你永远看不到一个隐藏的层,并且除非你开始调整配置,否则你无法控制它呢?而且这可能对你的总可用存储产生深远影响:足够精心配置可以大幅节省基础设施成本。继续阅读,了解如何理解和调整*真正*的底层结构。 ## 工程权衡 紧凑的内存表示在 Valkey 中非常重要:大多数用户并非受限于性能,而是受限于可用内存。当你在 RAM 中表示数据时,通常没有一种技术能够在所有数据大小下同时最大化性能和紧凑性。就像任何工程项目一样,Valkey 试图在性能和紧凑性之间取得平衡。 在这种情况下,这种平衡是通过*编码*实现的。Valkey 中的所有数据都有一种编码,这将实际的内存表示与访问方式(即使用的命令)抽象开来。以这三个 HSET 为例(假设数据库为空): `` > HSET hash0 field "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" > HSET hash1 field "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" > HSET hash2 field "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" `` 乍看之下,你可能会注意到每个值比前一个多一个字符。然而,你可能没有看到的是,尽管只有一个字符的差异,所使用的内存却发生了显著变化。你可以通过在每个键上运行 `MEMORY USAGE` (https://valkey.io/commands/memory-usage/) 来看到这一点: | 键 | 值长度 | `MEMORY USAGE` | 与前一个相比的变化 | |---|---|---|---| | `hash0` | 63 | 104 | n/a | | `hash1` | 64 | 120 | +15.38% | | `hash2` | 65 | 212 | +76.67% | *注意:具体数值可能因平台和架构而异* 那么,在值长度为 64 和 65 字节之间,是什么变化导致了内存使用量增加了 75% 以上? 看看相关键的 `OBJECT ENCODING` (https://valkey.io/commands/object-encoding/) 命令结果: `` > OBJECT ENCODING hash0 "listpack" > OBJECT ENCODING hash1 "listpack" > OBJECT ENCODING hash2 "hashtable" `` 'hashtable' 结果或许在意料之中,毕竟你使用的是哈希命令,但什么是 'listpack'?listpack 是一种内存高效的数据结构,它将元素顺序存储在一块连续的内存区域中。当存储的数据量不大时,它通过最小化指针开销来节省内存。不过不必过于担心 'listpack' 编码的复杂性,关键在于理解你与数据交互的方式与实际存储方式是通过编码抽象出来的(如果你真的感兴趣,请参阅代码 (https://github.com/valkey-io/valkey/blob/unstable/src/listpack.c) 深入了解 listpack)。 让我们探索一些其他内容: `` > LPUSH list1 foo (integer) 1 > OBJECT ENCODING list1 "listpack" > SADD set1 "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" (integer) 1 > OBJECT ENCODING set1 "hashtable" `` 于是,我们有一个列表键使用了与哈希相同的编码,以及一个编码为哈希表的集合。简直是猫狗同笼 (https://www.youtube.com/watch?v=-9NMt42il4Q):这到底是怎么回事?传统观念认为,Valkey 会根据你使用的命令,使用经典数据结构(如哈希表、链表和集合)来存储数据。 结果发现:并非如此。 虽然 Valkey 呈现了一个基于经典数据结构的*数据模型*,但它在底层进行优化决策。这样你就可以为你的数据类型使用适当的命令,而无需担心数据在内存中是如何存储的。Valkey 自动管理数据模型与编码之间的映射。 ## 重新掌控 除了使用硬编码逻辑的字符串之外,其他数据类型的编码由一个或多个配置阈值决定:小于或等于某个阈值的值使用一种编码,而超出该阈值的值使用另一种编码。例如,考虑 Valkey 9.1 (https://github.com/valkey-io/valkey/releases/tag/9.1.0) 中哈希数据类型的默认配置: `` hash-max-listpack-entries 512 hash-max-listpack-value 64 `` 这意味着,如果哈希的所有值长度最多为 64 字节(`hash-max-listpack-value`),并且它包含少于 512 个字段-值对(`hash-max-listpack-entries`),则哈希被编码为 `listpack`。如果超过任一阈值,Valkey 会使用 `hashtable` 编码。在最初的示例中,`hash2` 中的值长度为 65 字节,触发了向 `hashtable` 编码的转换,这种编码紧凑性明显较差,导致了 76.67% 的内存使用增加。 以下数据类型存在编码配置: - 哈希(`hash-max-listpack-entries`、`hash-max-listpack-value`) - 列表(`list-max-listpack-size`) - 集合(`set-max-intset-entries`、`set-max-listpack-entries`、`set-max-listpack-value`) - 有序集合(`zset-max-listpack-entries`、`zset-max-listpack-value`) 流也使用编码,但它们没有配置阈值,因为只使用一种编码。模块类型也使用编码,但其行为取决于特定模块的逻辑和配置。 ### 你是否应该更改这些设置? 也许。这取决于你正在优化什么以及你的数据看起来如何。Valkey 的默认编码配置适用于许多用例,但如果你的策略是成本优化或最大化基础设施效率,那么根据你的具体数据模式和使用情况评估这些设置是值得的。 对于最基本的评估,你可以使用 `OBJECT ENCODING` 对键进行采样,以识别数据使用更庞大编码的位置,然后了解触发因素,并对配置做出决定。当然,有些情况是不言而喻的:假设你将数据存储在哈希中,并且这些数据的值长度通常为 65 字节,刚好超过阈值,而且你的吞吐量需求较低。将 `hash-max-listpack-value` 的配置值提高到符合数据实际长度,可以通过降低基础设施需求或让你在同一集群中缓存更多数据 (https://www.youtube.com/watch?v=cd4-UnU8lWY) 来节省大量成本。 举一个极端的例子:一个 100GB 的集群(5 个节点,每个 20GB),其中 95% 的键的值长度恰好超过 `hash-max-listpack-value` 阈值 1 个字节。 | 集群大小 | 超出阈值的数据量(GB) | 每个键的内存使用量 | 主节点大小 | 主节点数量 | |---|---|---|---|---| | 原始 | 100 GB | 95 GB | 212 B | 20 GB | 5 | | 新 | 58.8 GB | 0 | 120 B (占原来的 56.6%) | 20 GB | 3 | 因此,你可以减少两个完整的主节点及其相关的副本。如前所述,这是一个极端的例子,你不太可能有 95% 的数据超出此阈值。假设你有 60% 的键达到了配置阈值以上,*并且*你可以将基础设施缩减到更小的机器或实例,这些机器只有 16GB 的 RAM。 | 集群大小 | 超出阈值的数据量(GB) | 每个键的内存使用量 | 主节点大小 | 主节点数量 | |---|---|---|---|---| | 原始 | 100 GB | 60 GB | 212 B | 20 GB | 5 | | 新 | 74 GB | 0 | 120 B (占原来的 56.6%) | 14.8 GB | 5 | 实际节省取决于新旧基础设施之间的价格差异。无论是在云端还是本地环境,定价通常基于硬件规格有明确的分档。如果你之前刚好超出某个较高定价层级,这种优化可以帮助你降至更低的层级。或者,你可以利用回收的空间来缓存之前未缓存的数据,减少逐出,或允许更长的 TTL。 > 等等!为什么 65 和 64 长度值都会导致键的大小为 120?眼尖的你可能会注意到,最初示例中 `hash1` 的大小为 64 字节,内存占用为 120,然后在这个示例中,我们展示的值长度为 65 的键也有 120 字节的内存占用。这怎么可能?采用 listpack 编码的哈希,不同大小的值的开销并非完全线性,而是阶梯式的。使用相同的键模式和字段名,值长度 49 到 63 字节时占用 104 字节,长度 64 到 79 字节时占用 120 字节。这解释了为什么在最初的示例中,长度为 64 时比长度为 63 增加了 15.38%,但编码仍然相同。 所有这些都回到了一个认识上:这些配置并非万能的,盲目地将编码配置值设得过高或过低都可能导致性能或效率不佳。结果可能因情况而异:了解你的数据类型、大小和使用模式,以及(现在)Valkey 中编码的工作原理,你就有了调整配置的工具。 ## 分享你的故事 你是否通过调整 Valkey 中的配置而节省了 RAM 或最大化吞吐量?通过提交问题来提议一篇博客文章 (https://github.com/valkey-io/valkey-io.github.io/issues/new?template=blog_post_template.md) 或为即将举行的 ValkeyConf (https://events.linuxfoundation.org/valkeyconf/) 提交演讲摘要来分享知识。

相似文章

KV缓存压缩比TurboQuant与逐向量香农极限高出900000倍

Hacker News Top

一篇新论文提出了一种基于概率语言Trie树和预测差分编码的顺序KV缓存压缩方法。该方法通过利用语言模型Token的序列结构而非对向量进行独立处理,实现了超越TurboQuant约91.4万倍的理论压缩比。

PorTAL(1分钟阅读)

TLDR AI

介绍Latent Briefing,一种通过KV缓存压缩实现多智能体系统通信的方法,在保持相同准确率的情况下减少31%的令牌使用,并实现高达20倍的加速。

PolyKV: 异构保留与分配的KV缓存压缩

arXiv cs.LG

PolyKV是一种逐层的KV缓存压缩框架,为每一层分配异构的驱逐策略和非均匀的预算,在LongBench上使用LLaMA-3.1-8B和Qwen3-8B相比统一基线有显著提升。