You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
-
概述
🚀 为了彻底解决“在线分析风险”这一痛点,PikiwiDB 社区推出了全新的离线分析利器 ——BigKey Analyzer。
✨ 零侵入:直接读取磁盘上SST 文件,完全不占用线上服务的 CPU 和网络资源,对生产环境零干扰。
✨ 全景透视:不仅支持 Strings,更完美支持 Hashes, Sets, Zsets, Lists 等所有复杂数据结构。
✨ 真实存储视角:它计算的不是简单的 Value 长度,而是键值对在存储引擎中的真实大小。这对于磁盘容量规划具有极高的参考价值。
🔍 使用步骤详解
1️⃣工具支持的功能
(1) 大键检测和筛选
--min-size=SIZE:只显示大于指定字节数的键
--top=N:只显示最大的N个键
(2) 键类型分析
--type=TYPE:按类型筛选键
(3) 前缀统计分析
--prefix-stat:按键前缀进行统计
--prefix-delimiter=C:自定义前缀分隔符
(4) 输出控制
--output=FILE:将结果输出到文件
./bigkey_analyzer --help
2️⃣ 操作案例
查看大于1MB的最大的10个键.
./bigkey_analyzer --min-size=1048576 --top=10 /data/pikiwidb/db
在海量数据面前,全量输出往往会刷屏。bigkey_analyzer 提供了过滤参数,能够在茫茫数据中精准定位出大key所在。图中列出了大小超过1MB的键,并给出了key的类型、大小、名称以及TTL的相关数据。
前缀聚合统计
大 Key 问题不是单个 Key 造成的,而是某个业务模块(具有相同前缀)生成了海量数据。使用 --prefix-stat 可以按前缀聚合统计。
./bigkey_analyzer --prefix-stat /data/pikiwidb/db
查看所有键
./bigkey_analyzer /data/pikiwidb/db
输出到文件
./bigkey_analyzer --output=result.txt /data/pikiwidb/db
检测原理:底层直读
分析工具之所以能做到“快而准”,关键在于它通过直接调用 RocksDB 的 API 打开数据库,彻底绕过了网络协议栈的开销。
在源码层面,它利用 rocksdb::DB::OpenForReadOnly 以只读模式打开指定路径的数据文件。这意味着它不需要建立 TCP 连接,不需要进行协议解析,直接遍历底层的迭代器(Iterator),效率获得了数量级的提升。
//以只读模式直接打开底层存储
rocksdb::DB* db;
rocksdb::Status status = rocksdb::DB::OpenForReadOnly(options, path, &db);
if (!status.ok()) {
// Error handling...
}
PikiwiDB 利用 RocksDB 的 Column Family 特性来实现复杂数据结构(如 Hash 和 Zset)的分离存储,分析工具必须具备“重组数据”的能力。它需要像拼图一样,将分散在不同 Column Family 中的元数据(Metadata)和数据实体(Data Entity)重新聚合,才能计算出一个大 Key 的真实体量。
源码分析
string 类型
String 类型的分析最为直观,因为它的元数据和数据体都存储在同一个 RocksDB 实例中。 源码中,工具直接遍历 default 列族,计算 Key 与 Value 的字节大小之和。
void AnalyzeStrings(const std::string& path, std::vector& key_infos, const Config& config) {
......
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
const std::string& key = iter->key().ToString();
const std::string& value = iter->value().ToString();
}
delete iter;
delete db;
}
复杂数据结构
对于 Hash、Set、Zset 和 List,情况则变得相对复杂。PikiwiDB 为了支持高并发读写,将这些集合类型的Metadata 与 Data 拆分到了不同的 Column Families (CF) 中。
以 Hash 为例,工具必须同时打开两个视窗:
Default CF:存储 Hash 的 Key 以及字段个数、过期时间等元数据。
Data CF:存储具体的 field 和 value。
这意味着,一个 Hash Key 的真实大小,等于 元数据开销+ 所有 Field 的大小 + 所有 Value 的大小。为了不遗漏任何一个字节,采用了一种 分阶段聚合 的策略。
第一阶段:元数据扫描 首先遍历默认列族,捕获所有的 Hash Key,并在内存中建立一个Map。
// bigkey_analyzer -> AnalyzeHashes
std::unordered_map<std::string, std::pair<int64_t, int64_t>> hash_sizes;
auto meta_iter = db->NewIterator(read_options, handles[0]); // handles[0] 是 default CF
for (meta_iter->SeekToFirst(); meta_iter->Valid(); meta_iter->Next()) {
// 解析元数据,初始化基础大小(Key本身大小 + 内部开销)
int64_t sum = key.size() + 12;
hash_sizes[key] = std::make_pair(sum, ttl);
}
第二阶段:数据归并 切换到 data_cf 列族进行全量扫描。每扫描到一个数据项(Field-Value 对),它会解析出其归属的 Hash Key,然后去 map 中累加大小。
auto data_iter = db->NewIterator(read_options, handles[1]); // handles[1] 是 data_cf
for (data_iter->SeekToFirst(); data_iter->Valid(); data_iter->Next()) {
// 反序列化 DataKey,提取其归属的主 Key
storage::ParsedHashesDataKey parsed_key(encoded_key_slice);
std::string hash_key = parsed_key.key().ToString();
}
随机查询是磁盘 I/O 的杀手,而我们的工具通过两次顺序扫描(Sequential Scan)配合内存聚合,极大地提升了分析效率。这正是离线分析工具相比在线 SCAN 命令在底层原理上的本质区别。
Beta Was this translation helpful? Give feedback.
All reactions