尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

ScyllaDB SSTables 3.0 Statistics 文件格式深度解析:从元数据结构到源码实现

ScyllaDB SSTables 3.0 Statistics 文件格式深度解析:从元数据结构到源码实现 ScyllaDB SSTables 3.0 Statistics 文件格式深度解析从元数据结构到源码实现【免费下载链接】scylladbNoSQL data store using the Seastar framework, compatible with Apache Cassandra and Amazon DynamoDB项目地址: https://gitcode.com/GitHub_Trending/sc/scylladb导读SSTables 3.0 的 Statistics 文件即Statistics.db是 SSTable 家族组件中承载元数据的核心文件负责在读取与压缩compaction时为 ScyllaDB 提供快速决策所需的内存态统计信息。本文将以此文件格式为绝对主体逐字节讲解其表结构Table of Content与四类元数据条目的二进制布局并结合sstables/目录下的真实源码types.hh、metadata_collector.hh、sstables.cc等印证磁盘结构与内存实现之间的映射关系。读完本文你将能够看懂Statistics.db的每个字段及其物理含义、理解不同 SSTable 版本ma/mb/mc/md 以及 Scylla 的 me/ms/mt之间统计条目的演化差异、掌握类型编码Type Encoding规则并具备阅读与核对 ScyllaDB 底层磁盘格式的能力。一、Statistics 文件在 SSTable 家族中的定位SSTables 3.0 是 ScyllaDB 沿用的主要 SSTable 磁盘格式Scylla 在其之上演进出了 me、ms、mt 等内部版本参见 sstables/version.hh 中enum class sstable_version_types { ka, la, mc, md, me, ms, mt }。一个 3.x SSTable 由多个组件文件构成其中Statistics.db承担“元数据中枢”的角色。根据 docs/architecture/sstable/sstable3/sstables-3-statistics.rst 的定义该文件存储四类元数据Validation metadata校验元数据——用于校验 SSTable 的正确性Compaction metadata压缩元数据——用于压缩过程Statistics统计信息——SSTable 的若干统计信息会被加载进内存用于加速读取与压缩Serialization header序列化头——保存 SSTable 的 schema 信息。在源码中这四类元数据分别对应 sstables/types.hh 里metadata_type枚举的四个值enum class metadata_type : uint32_t { Validation 0, Compaction 1, Stats 2, Serialization 3, };源码注释特别说明这些数值是真正写盘的值因此顺序不能随意更改“Numbers are found on disk, so they do matter”。二、文件总体结构目录表TOC 顺序存储的元数据Statistics 文件由两部分组成第一部分是目录表Table of ContentTOC允许快速定位到任意一条元数据第二部分是按顺序连续存放的元数据条目序列。文档先定义一个贯穿全文的数组模板所有变长容器都以此为基础struct arrayLengthType, ElementType { LengthType number_of_elements; ElementType elements[number_of_elements]; }这一抽象在源码中的直接体现是 sstables/disk_types.hh 中的disk_arraySizeType, Members元素个数用定宽整数表示与disk_array_vint_sizeMembers元素个数用变长整数表示两者都以utils::chunked_vectorMembers elements持有数据。目录表TOC格式using toc arraybe32int32_t, toc_entry; struct toc_entry { // 元数据类型 // | Type | 整数表示 | // |----------------------|---------| // | Validation metadata | 0 | // | Compaction metadata | 1 | // | Statistics | 2 | // | Serialization header | 3 | be32int32_t type; // 该元数据条目在文件中的起始偏移量 be32int32_t offset; }TOC 数组按其成员的 type 字段排序。这里be32int32_t表示 32 位大端big-endian有符号整数——这是 Cassandra/SSTable 磁盘格式一贯的网络字节序约定。一个值得注意的工程细节在 sstables/sstables.cc 读取 Statistics 文件时代码会对s.offsets.elements显式执行一次排序注释说明“旧版本 Scylla 不遵守顺序”关联 ScyllaDB issue #3937。也就是说虽然规范要求 TOC 按 type 升序排列读取方仍然做了防御性排序以保证对历史数据的兼容。随后解析器按每个条目的 offset 定位并依据 type 分发到对应的元数据解析逻辑sstables/sstables.cccase metadata_type::Validation: co_await parsevalidation_metadata(...); break; case metadata_type::Compaction: co_await parsecompaction_metadata(...); break; case metadata_type::Stats: co_await parsestats_metadata(...); break; case metadata_type::Serialization: if (v sstable_version_types::mc) { throw_malformed_sstable_exception( Statistics is malformed: SSTable is in 2.x format but contains serialization header.); } else { co_await parseserialization_header(...); } break;这段代码同时揭示了一个版本约束Serialization header 只在 mc 及以后的格式中存在若在 ka/la2.x 格式的 Statistics 文件里发现该条目则直接判定为畸形malformed。写入侧的偏移量计算写入时偏移量由populate_statistics_offsets()计算sstables/sstables.cc先把元数据类型收集进一个 vector 并排序以保证一致的写盘顺序然后先以占位的 -1 填充 offsets 计算整个 TOC 的序列化大小再依序累加每个条目的serialized_size()得到真实偏移。这种“两遍计算”的思路保证了 TOC 与数据区严格衔接。三、Validation Metadata校验元数据struct validation_metadata { // 创建该 SSTable 时使用的 partitioner 名称。 // 使用 modified UTF-8 编码的 UTF8 字符串表示。 Modified_UTF-8_String partitioner_name; // 该 SSTable 布隆过滤器bloom filter的假阳性概率 be64double bloom_filter_fp_chance; }partitioner_name使用 JavaDataInput定义的 modified UTF-8 编码文档中给出了 Oracle 官方规范链接它保证字符串在跨实现Java 的 Cassandra 与 C 的 ScyllaDB之间可互操作。bloom_filter_fp_chance是be64double——即 64 位大端 IEEE 754 双精度浮点数记录创建 SSTable 时配置的布隆过滤器假阳性率Scylla 默认值为 0.01即 1%。源码对应结构体位于 sstables/types.hhstruct validation_metadata : public metadata_basevalidation_metadata { disk_stringuint16_t partitioner; double filter_chance; template typename Describer auto describe_type(sstable_version_types v, Describer f) { return f(partitioner, filter_chance); } };注意源码中的disk_stringuint16_t字符串长度用 16 位无符号整数即文档中Modified_UTF-8_String的长度前缀约定编码与 sstables/disk_types.hh 中disk_stringSize的定义一致——所有磁盘字符串结构都内嵌“长度 字节载荷”长度字段的宽度即模板参数Size。四、Compaction Metadata压缩元数据// 序列化后的 HyperLogLogPlus可用于估算该 SSTable 中的分区键数量。 // 若该条目缺失可用 Summary 文件完成同样的估算。 using compaction_metadata arraybe32int32_t, be8;Compaction metadata 的核心是一个序列化的 HyperLogLogPlus用于估算 SSTable 内的分区键partition key基数。它之所以重要是因为压缩尤其是 leveled compaction 的候选选择与许多统计决策需要知道“这个 SSTable 大概有多少个分区”而精确计数代价高昂HyperLogLog 用固定小空间给出近似估计。文档同时注明如果该条目缺失可以用 Summary 文件Summary.db做等价估算。源码对应结构体位于 sstables/types.hhstruct compaction_metadata : public metadata_basecompaction_metadata { disk_arrayuint32_t, uint32_t ancestors; // DEPRECATED, not available in sstable format mc. disk_arrayuint32_t, uint8_t cardinality; ... };这里有一个值得展开的版本细节源码注释标明ancestors字段已废弃在 mc 及以后格式中不再写盘。describe_type()的switch (v)分支明确展示了这种版本差异ka / la2.x 格式写出ancestorscardinality两个数组mc / md / me / ms / mt3.x 及后续格式只写出cardinality数组。在写入侧metadata_collector::construct_compaction()sstables/metadata_collector.hh把内部维护的 HyperLogLog 实例序列化为字节序列存入m.cardinality.elements而 HyperLogLog 在写入前由add_key()喂入分区键哈希sstables/metadata_collector.hhvoid add_key(bytes_view key) { long hashed utils::murmur_hash::hash2_64(key, 0); _cardinality.offer_hashed(hashed); }也就是说ScyllaDB 将每个分区键先用 MurmurHash2-64 哈希再喂给 HyperLogLog 估计器默认参数 p13、sp25见 sstables/metadata_collector.hh 中对 CASSANDRA-5906 的引用最终得到写入Statistics.db的压缩元数据。五、Statistics Entry统计信息的核心Statistics 条目由EstimatedHistogram、StreamingHistogram与CommitLogPosition三类基础类型组合而成。文档先逐个定义这些基础类型再给出整个条目的布局——这正是阅读 ScyllaDB 磁盘格式文档的标准路径。5.1 基础类型一EstimatedHistogram// 每个 bucket 代表 (前一个 bucket 的 offset, 当前 offset] 区间内的值。 // 最后一个 bucket 的 offset 为 inf。 using estimated_histogram arraybe32int32_t, bucket; struct bucket { // 前一个 bucket 的 offset // 在第一个 bucket 中它等于第一个 bucket 自身的 offset因为没有前一个 bucket。 // 第一个 bucket 的 offset 会在第二个 bucket 中被重复一遍。 be64int64_t prev_bucket_offset; // 本 bucket 的值 be64int64_t value; }EstimatedHistogram是 Cassandra 生态经典的对数分桶直方图桶边界呈指数增长因此少量桶即可覆盖极大动态范围。它的语义要点是第 i 个桶覆盖(offset[i-1], offset[i]]的值区间最后一个桶右端为inf第一个桶的prev_bucket_offset重复其自身 offset每个桶由“上界 offset 落在该桶的计数 value”组成均为 64 位大端整数。源码侧ScyllaDB 在 utils/estimated_histogram.hh 实现了utils::estimated_histogram并通过 sstables/sstables.cc 的parse()解析磁盘形式读取长度后先检查长度是否为 0为零则抛malformed_sstable_exception再重建bucket_offsets与buckets。metadata_collector中两个典型实例sstables/metadata_collector.hh及其容量注释非常说明问题// EH of 150 can track a max value of 1697806495183, i.e., 1.5PB utils::estimated_histogram _estimated_partition_size{150}; // EH of 114 can track a max value of 2395318855, i.e., 2B cells utils::estimated_histogram _estimated_cells_count{114};150 个桶的分区大小直方图最大可跟踪约 1697 万亿字节1.5 PB的单分区大小114 个桶的单元格cell计数直方图最大可跟踪约 23.9 亿个 cell。这两个直方图分别通过add_partition_size()与add_cells_count()在写入数据时累加sstables/metadata_collector.hh。5.2 基础类型二StreamingHistogramstruct streaming_histogram { // 该直方图允许的最大桶数 be32int32_t bucket_number_limit; arraybe32int32_t, bucket buckets; } struct bucket { // 本桶的 offset be64double offset; // 桶的值 be64int64_t value; }与 EstimatedHistogram 不同StreamingHistogram的桶以浮点数 offset组织并带一个bucket_number_limit上限。它的典型用途是“在线合并”式统计当桶数超过上限时通过合并相邻桶来压缩。在 Statistics 条目中它用于存放 tombstone墓碑的分布。源码侧对应utils::streaming_histogramutils/streaming_histogram.hhScyllaDB 为其设定了常量TOMBSTONE_HISTOGRAM_BIN_SIZE 100sstables/metadata_collector.hh即 tombstone 直方图最多 100 个桶。5.3 基础类型三CommitLogPositionstruct commit_log_position { be64int64_t segment_id; be32int32_t position_in_segment; }CommitLogPosition标识某条数据在 commit log 中的写入位置由 64 位 segment id 与 32 位段内偏移组成。在源码中以db::replay_positiondb/commitlog/replay_position.hh表示并直接以db::replay_position position字段出现在stats_metadata中sstables/types.hh。字段用途提示commit log 上界upper bound用于判断某条数据是否仍可能存在于 commit log 中从而决定读取时是否还需要回放 commit loghas_legacy_counters等标志则用于兼容老式 counter 数据的处理。这些统计在读取路径中被加载进内存用于快速决策。5.4 整个 Statistics 条目含版本演化文档给出的完整布局如下逐字段语义已在注释中struct statistics { // 分区未压缩大小的直方图字节 estimated_histogram partition_sizes; // 每个分区的 cell 数量 estimated_histogram column_counts; commit_log_position commit_log_upper_bound; // 通常为自 Unix 纪元以来的微秒数不强制 be64int64_t min_timestamp; // 通常为自 Unix 纪元以来的微秒数不强制 be64int64_t max_timestamp; // 自 Unix 纪元以来的秒数 be32int32_t min_local_deletion_time; // 自 Unix 纪元以来的秒数 be32int32_t max_local_deletion_time; be32int32_t min_ttl; be32int32_t max_ttl; // compressed_size / uncompressed_size be64double compression_rate; // cell tombstone 直方图键为 tombstone 的本地删除时间 streaming_histogram tombstones; be32int32_t level; // 修复时间与 1970-01-01 午夜 UTC 的差值毫秒 be64int64_t repaired_at; // SSTable 中出现的最小/最大 clustering key 前缀自 md 格式起有效 clustering_bound min_clustering_key; clustering_bound max_clustering_key; be8bool has_legacy_counters; be64int64_t number_of_columns; be64int64_t number_of_rows; // 3.x 格式的 MA 版本到此结束 // 只包含一个 commit log 位置区间 [NONE new CommitLogPosition(-1, 0), commit log 上界] commit_log_position commit_log_lower_bound; // 3.x 格式的 MB 版本到此结束 // 只包含一个 commit log 位置区间 [commit log 下界, commit log 上界] arraybe32int32_t, commit_log_interval commit_log_intervals; // 3.x 格式的 MC 与 MD 版本到此结束 // 写入该 SSTable 的主机 UUID限定 Statistics 文件中所有 commitlog 位置 UUID host_id; } using clustering_bound arraybe32int32_t, clustering_column; using clustering_column arraybe16uint16_t, be8; struct commit_log_interval { commit_log_position start; commit_log_position end; }这段定义本身就构成了 3.x 格式的版本演化时间线可归纳如下3.x 子版本新增内容语义MAcommit_log_upper_boundcommit log 区间下界固定为NONE CommitLogPosition(-1, 0)MBcommit_log_lower_bound显式记录下界区间变为[lower, upper]MC / MDcommit_log_intervals、host_id支持多个 commit log 区间host_id限定所有位置归属的主机md 起有效min/max_clustering_key记录 clustering key 前缀范围关于min/max_clustering_key文档给出三条重要的语义约束clustering 行clustering rows总是携带完整的 clustering keyrange tombstone 可能只带部分 clustering key 前缀partition tombstone 隐式作用于整个无界 clustering 区间。因此空的(min|max)_clustering_key表示对应的无界区间它要么源自开放式open-endedrange tombstone要么源自 partition tombstone。这一定义直接关系到读取时对数据范围的裁剪判断。源码侧stats_metadatasstables/types.hh以describe_type()的switch (v)精确控制每个版本的磁盘字段序列。例如ka / la仅写出estimated_partition_size、estimated_cells_count、position、min/max_timestamp、max_local_deletion_time、compression_ratio、estimated_tombstone_drop_time、sstable_level、repaired_at、min/max_column_names、has_legacy_counter_shards——没有 commit log 下界、没有区间数组、没有 host_id、没有列/行计数mc / md追加commitlog_lower_bound、commitlog_intervals、columns_count、rows_count等me / ms / mt再追加originating_host_id对应文档中的host_id源码类型为std::optionallocator::host_id。源码注释还专门解释了repaired_at由于 ScyllaDB 没有增量修复incremental repair该字段没有有意义的取值统一写 0sstables/types.hh。而compression_ratio的写入在 sstables/metadata_collector.hh 中定义语义为compressed/uncompressed未启用压缩时以NO_COMPRESSION_RATIO -1.0兜底sstables/metadata_collector.hh。5.5 统计信息如何被收集与写出统计不是凭空产生的。写入一个 SSTable 时metadata_collectorsstables/metadata_collector.hh逐分区汇总column_statssstables/metadata_collector.hh中的信息cell 数、行数、range tombstone 数、分区大小、时间戳 min/max、本地删除时间 min/max、TTL min/max、tombstone 直方图、legacy counter 标志等。update(column_stats)sstables/metadata_collector.hh负责把这些逐分区统计合并进全局的 tracker 与直方图。最终在construct_stats()sstables/metadata_collector.hh中一次性填好stats_metadata的各个字段随后由seal_statistics()写入 Statistics 条目并重新计算 TOC 偏移sstables/sstables.cc。读取侧的read_statistics()与write_statistics()分别位于 sstables/sstables.cc 与 sstables/sstables.cc。六、Serialization Headerschema 的磁盘载体struct serialization_header { vintuint64_t min_timestamp; vintuint32_t min_local_deletion_time; vintuint32_t min_ttl; // 若分区键只有一列则为该列类型 // 否则为包含所有分区键列类型的 CompositeType。 type partition_key_type; arrayvintuint32_t, type clustering_key_types; columns static_columns; columns regular_columns; } using columns arrayvintuint32_t, column; struct column { arrayvintuint32_t, be8 name; type column_type; } // UTF-8 字符串 using type arrayvintuint_32_t, be8;Serialization header 回答的是“这个 SSTable 的表结构是什么”。它记录了三个基线值min_timestamp、min_local_deletion_time、min_ttl、分区键类型、各 clustering key 列类型以及静态列static columns与常规列regular columns的名字与类型。注意这里所有长度都使用vint变长整数即 varint以节省磁盘空间——小数值只需 1 字节。源码对应结构体位于 sstables/types.hh字段为vintuint64_t min_timestamp_base; vintuint64_t min_local_deletion_time_base; vintuint64_t min_ttl_base; bytes_array_vint_size pk_type_name; disk_array_vint_sizebytes_array_vint_size clustering_key_types_names; disk_array_vint_sizecolumn_desc static_columns; disk_array_vint_sizecolumn_desc regular_columns;一个关键的实现细节是基线值的增量编码delta encoding磁盘上存储的三个*_base并非原始值而是相对各自 epoch 的偏移读取时通过get_min_timestamp()、get_min_ttl()、get_min_local_deletion_time()还原sstables/types.hhapi::timestamp_type get_min_timestamp() const { return static_castapi::timestamp_type(min_timestamp_base.value encoding_stats::timestamp_epoch); } int64_t get_min_ttl() const { return static_castint64_t(min_ttl_base.value encoding_stats::ttl_epoch); } int64_t get_min_local_deletion_time() const { return static_castint64_t(min_local_deletion_time_base.value encoding_stats::deletion_time_epoch); }源码注释提醒这些转换依赖min_*_base.value为无符号类型以避免有符号整数溢出。另外describe_type()明确规定ka/la 格式如果出现 serialization header直接抛异常“Statistics is malformed: SSTable is in 2.x format but contains serialization header.”与 TOC 解析时的版本检查sstables/sstables.cc互相呼应。七、Type Encoding类型名的字符串编码规则Serialization header 中的type本质上是带变长长度前缀的 UTF-8 字符串。文档给出了完整的解析规则这是理解磁盘上类型表示的关键type是一个字节缓冲区前面带一个32 位无符号变长整数作为长度内容是 UTF-8 字符串跳过所有前导空格、制表符和换行符空字符串或 null 表示 bytes 类型首个非空白段只能包含字母数字字符以及-、、.、_、等特殊字符这就是类型名若类型名不含.则自动在前面拼接org.apache.cassandra.db.marshal.前缀即默认的 Cassandra 类型包名再从该类取instance静态字段若类型名后的首个非空白字符是(则改为调用getInstance静态方法并把剩余字符串作为参数传入。文档随后以表格形式列出了全部受支持类型及其是否带参数类型名是否带参数ParametrizedAscii Type否Boolean Type否Bytes Type否Byte Type否ColumnToCollection Type是Composite Type是CounterColumn Type否Date Type否Decimal Type否Double Type否Duration Type否DynamicComposite Type是Empty Type否Float Type否Frozen Type是InetAddress Type否Int32 Type否Integer Type否LexicalUUID Type否List Type是Long Type否Map Type是PartitionerDefinedOrder是Reversed Type是Set Type是Short Type否SimpleDate Type否Timestamp Type否Time Type否TimeUUID Type否Tuple Type是User Type是UTF8 Type否UUID Type否Vector Type是可以看到集合类List、Set、Map、组合类Composite、Frozen、Tuple、User、以及 Reversed、Vector、ColumnToCollection、DynamicComposite、PartitionerDefinedOrder 等都属于参数化类型在getInstance(...)调用中需要传入参数如元素类型、字段类型等而标量类型Ascii、Boolean、Int32、Long、UUID 等无需参数。这套规则源自 Cassandra 的TypeParser约定ScyllaDB 在与 Cassandra 兼容的列类型cql3/types模块中同样实现了等价语义保证了跨实现读取Statistics.db时类型解析的一致性。在 sstables/types.hh 中bytes_array_vint_sizedisk_string_vint_size别名与disk_array_vint_sizecolumn_desc精确对应文档里type与columns的“变长长度前缀”布局其中column_desc由name与type_name两个变长字符串组成sstables/types.hh。八、跨版本兼容与健壮性设计要点综合源码与文档ScyllaDB 在 Statistics 文件上体现的兼容性设计值得总结这对理解“为什么字段布局如此”至关重要TOC 驱动解析解析器从不假设元数据条目的排列顺序而是完全依赖 TOC 中的(type, offset)对逐个 seek 读取且读取端还会做防御性排序sstables/sstables.cc以兼容旧版本不按序写 TOC 的历史问题issue #3937。版本分支严格化stats_metadata、compaction_metadata、serialization_header的describe_type()全部基于sstable_version_types的switch分支控制字段集合每个版本只序列化自己该有的字段sstables/types.hh。畸形数据防护未知的 metadata type 会抛出malformed_sstable_exception“Invalid metadata type at Statistics file: ...”sstables/sstables.cc零长度的 estimated histogram 同样被拒绝sstables/sstables.cc2.x 文件出现 serialization header 属于非法组合。无增量修复的现实约束repaired_at目前恒为 0属于“预留但未启用”的字段sstables/types.hh。九、写在最后如何进一步深入Statistics 文件是 SSTable 磁盘格式的缩影它既包含为读取加速的内存态统计直方图、min/max 时间戳、commit log 位置也承载压缩决策所需的基数估计HyperLogLog与 schema 描述serialization header并通过“TOC 顺序条目”的布局实现随机访问。你可以按以下路径在仓库中继续追读格式定义与类型体系sstables/types.hh、sstables/disk_types.hh统计收集器写入侧sstables/metadata_collector.hh读写与解析读取侧sstables/sstables.ccread_statistics、sstables/sstables.ccwrite_statistics、sstables/sstables.ccpopulate_statistics_offsets与 sstables/sstables.ccseal_statistics原始格式规范本文所依据的 docs/architecture/sstable/sstable3/sstables-3-statistics.rst组件文件整体清单sstables/目录下的component_type.hh、sstable_version*.hh可用于对照 SSTable 各组件Data、Index、Summary、Filter、Statistics 等的命名与职责。附注本文所有字节布局均来自仓库内的格式文档与源码若需读取真实 SSTable 的 Statistics 文件进行验证可结合 ScyllaDB 的scylla sstable系列工具tools/ 目录对生成的组件文件做离线检查。【免费下载链接】scylladbNoSQL data store using the Seastar framework, compatible with Apache Cassandra and Amazon DynamoDB项目地址: https://gitcode.com/GitHub_Trending/sc/scylladb创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表