ScyllaDB SSTable 3.x 格式全解析:从 sstable_format 参数到数据文件、索引与统计的磁盘布局
【免费下载链接】scylladbNoSQL data store using the Seastar framework, compatible with Apache Cassandra and Amazon DynamoDB项目地址: https://gitcode.com/GitHub_Trending/sc/scylladb
ScyllaDB 在 3.x 系列中采用了与 Apache Cassandra 3.0 完全一致的 SSTable 格式,同时在此基线上发展出md、me、ms、mt等多个变体。本文以 docs/architecture/sstable/sstable3/sstable-format.rst 为主线,结合该目录下的数据文件、索引、统计、摘要等配套格式文档与仓库源码,系统讲解 3.x SSTable 的格式变体选择、组件文件构成、磁盘二进制布局以及升级迁移路径,帮助读者掌握从scylla.yaml配置到单个字节如何落盘的完整知识链。
与 Cassandra 3.0 的兼容性:SSTable 可直接迁移
ScyllaDB 对 3.x SSTable 格式的支持原则很明确:与 Apache Cassandra 3.0 的 SSTable 格式保持一致。这意味着一个非常实用的操作路径是:
- 将 Cassandra 数据目录中的 SSTable 文件直接放入 ScyllaDB 的 uploads 目录;
- 执行
nodetool refresh命令,即可把数据摄入到对应表中,无需任何格式转换。
这种兼容性让从 Cassandra 到 ScyllaDB 的数据迁移、以及两个系统之间共享数据文件成为可能。不过,仔细观察会发现一个关键差异:ScyllaDB 维护的 SSTable 数量更多、单个文件更小。原因在于 ScyllaDB 采用 Seastar 框架的 shared-nothing 架构,每个 CPU 核(shard)独立管理自己的一份 SSTable 子集。这种内部 sharding 让每个核可以高效地独立工作,避免了多核竞争同一份数据所带来的复杂性和延迟——这正是 sstable-format.rst 所强调的 ScyllaDB 与 Cassandra 在文件组织层面的本质区别。
SSTable 格式变体:ms / me / md
ScyllaDB 3.x SSTable 通过scylla.yaml中的sstable_format参数选择格式变体,共有三种:
| 值 | 说明 |
|---|---|
ms | 引入基于 Trie 的 SSTable 索引(见 sstable-ms-index.rst),是 2026.2 起的新默认格式 |
me | 3.x 基线格式,从 ScyllaDB 2022.2 到 2026.1 的默认格式 |
md | 更早的 3.x 变体,仅用于从既有md集群升级的场景;将sstable_format设置为md时该参数会被忽略 |
从源码角度,这些版本被完整定义在 sstables/version.hh 中:
enum class sstable_version_types { ka, la, mc, md, me, ms, mt }; constexpr std::array<sstable_version_types, 7> all_sstable_versions = { sstable_version_types::ka, sstable_version_types::la, sstable_version_types::mc, sstable_version_types::md, sstable_version_types::me, sstable_version_types::ms, sstable_version_types::mt, }; constexpr std::array<sstable_version_types, 5> writable_sstable_versions = { sstable_version_types::mc, sstable_version_types::md, sstable_version_types::me, sstable_version_types::ms, sstable_version_types::mt, }; constexpr sstable_version_types oldest_writable_sstable_format = sstable_version_types::mc;可以看到,虽然 ScyllaDB 能够读取ka、la等更古老的历史版本(all_sstable_versions共 7 项),但可写入版本仅限mc起的 5 个。字符串与版本的映射关系位于 sstables/sstables.cc,version_from_string()与format_from_string()两个解析函数也在同文件实现(sstables.cc)。
值得注意的是 sstables.cc 中的这一行:
bool has_summary_and_index(sstable_version_types v) { return v != sstable_version_types::ms && v != sstable_version_types::mt; }它揭示了ms/mt与me/md最根本的结构差异:ms和mt不再使用传统的 Index.db 与 Summary.db 组件,而是以基于 Trie 的索引组件(Partitions.db 与 Rows.db)取而代之。
当前仓库中的默认配置
在本仓库的默认 conf/scylla.yaml 中,可以看到如下配置与注释:
# sstable format version for newly written sstables. # Currently allowed values are `me` and `mt`. # If not specified in the config, this defaults to `me`. # # The difference between `me` and `mt` are the data structures used # in the primary index. # In short, `mt` needs more CPU during sstable writes, # but should behave better during reads, # although it might behave worse for very long clustering keys. # # `mt` sstable format works even better with `column_index_size_in_kb` set to 1, # so keep those two settings in sync (either both set, or both unset). sstable_format: mt column_index_size_in_kb: 1这里有两个值得注意的实操点:
sstable_format影响的是新建SSTable 的格式,不会重写已有文件;mt与column_index_size_in_kb: 1建议成对设置——更细的索引粒度(1 KB)配合mt的 Trie 索引在读路径上表现更好。
升级到 2026.2 的行为
根据 sstable-format.rst 的说明:
- 升级不会自动重写已有的 SSTable。从旧版本升级到 2026.2 时,现有 SSTable 保持原格式;
- 这些旧格式 SSTable 会在下一次 compaction 时被升级为
ms格式; - 如需立即转换,可在更新节点配置后执行
nodetool upgradesstables -a主动触发(见 sstable-ms-index.rst)。
3.x SSTable 的组件文件全景
3.x 的 SSTable 从来不是一个孤立的文件,而是由一组组件文件共同构成。下表总结了每个组件的作用(来源:sstables-3-data-file-format.rst):
| 文件类型 | 典型文件名 | 描述 |
|---|---|---|
| 压缩信息 | mc-1-big-CompressionInfo.db | 若启用了压缩,则保存压缩算法相关信息 |
| 数据文件 | mc-1-big-Data.db | 存储实际数据 |
| 校验和 | mc-1-big-Digest.crc32 | 数据文件的 CRC32 校验和 |
| 布隆过滤器 | mc-1-big-Filter.db | 用于判断特定数据是否可能存在于数据文件中 |
| 索引 | mc-1-big-Index.db | 数据的主索引,便于检索 |
| 统计 | mc-1-big-Statistics.db | 关于数据的聚合统计 |
| 摘要 | mc-1-big-Summary.db | 索引文件的抽样(可视为"粗粒度"索引) |
| 目录 | mc-1-big-TOC.txt | 列出当前 SSTable 的所有组件文件 |
文件名中的mc是格式版本前缀(mc/md/me等),1是 generation 号,big表示格式类型(format_string映射中big是唯一的格式类型,见 sstables/sstables.cc)。
变体之间的细微差异
数据文件本身的磁盘格式对mc、md、me全部适用,但三个版本之间存在两个语义层面的修正(见 sstables-3-data-file-format.rst):
md格式:修正了 Statistics 文件中(min|max)_clustering_key字段的语义,使其能准确描述 SSTable 中实际存在的聚类前缀范围;me格式:在 Statistics 文件中新增写入 SSTable 的host_id(写节点的 UUID),用于限定同样存储在 Statistics 文件中的 commit log replay 位置。
从 ScyllaDB 2025.4 起,还存在ms-mt格式:它是me与 Cassandra 5.0 引入的da格式的混合体——绝大多数组件与me完全相同,唯独索引组件(Index.db、Summary.db)被替换为da使用的基于 Trie 的索引格式(Partitions.db、Rows.db)。
数据文件格式:磁盘上的二进制布局
设计动机:从"分区由细胞构成"到"分区由行构成"
Cassandra 2.x 的数据文件是一系列分区的序列,而分区本质上是一系列细胞(cell)的序列——每个细胞的名字都由聚类前缀(所有聚类列的值)加上非主键列名构成。这种设计有两个明显缺陷:
- 大量磁盘浪费:同一分区内,每一行都要在自己的所有细胞名字里重复存储聚类列的值,长聚类列下尤为严重;
- 解析困难:存储引擎必须在不预先知道数量与总大小的情况下,自行识别并归组属于同一行的细胞。
3.0 格式重新组织了数据:每个分区由行(row)构成,行由聚类列值定义,行内包含共享该聚类前缀的一批细胞。即从"分区 → 细胞"变为"分区 → 行 → 细胞"的三级结构。同时,3.0 格式深度依赖表 schema——主键/非主键列、数据类型(定宽 vs 变宽)、聚类列排序等信息都直接参与编码。
三大构建块(Building Blocks)
3.0 格式的磁盘编码大量复用以下三种机制(详见 sstables-3-data-file-format.rst):
- 可变长整数(Varint):受 Google Protocol Buffers 内部整数序列化启发,占用 1 到 9 个字节,数值越小占用越少,非常适合大量相对较小的数值;
- 增量编码(Delta Encoding):时间戳、TTL 等值通常很大(微秒级 UNIX 时间戳),直接以 varint 存储收益有限。因此只完整存储一组对象中的最小时间戳/TTL,其余对象存储其与最小值的差值——差值通常小得多,能以更少的字节序列化。这些最小值基准来自 Memtable 维护的聚合统计(记录最小时间戳、TTL、本地删除时间),刷盘时作为增量编码基准,并保存在
-Statistics.db中; - 可选条目(Optional Items):部分条目可根据标志位或上下文省略。注意这与 C++ 的
std::optional无关,只是格式文档中用来标记"可能不出现"的约定。
分区(Partition)结构
数据文件本质上是连续序列化的分区:
struct data_file { struct partition[]; };每个分区由头部(partition_header)、可选的静态行(static_row)以及一组 unfiltered 对象构成:
struct partition { struct partition_header header; optional<struct row> static_row; // Has IS_STATIC flag set struct unfiltered unfiltereds[]; };一个关键概念:SSTable 中的分区保存的是数据的更新记录(mutation),而非数据的最终状态。每个分区是一系列按顺序应用的修改(插入、更新、删除)的集合。
unfiltered是按聚类前缀可用clustering_comparator排序的对象,它要么是一个行(row),要么是一个范围墓碑标记(range tombstone marker)。
分区头部(Partition Header)
分区头部格式自 2.x 以来未变:
struct partition_header { be16 key_length; byte key[key_length]; struct deletion_time deletion_time; };其中deletion_time决定该分区是否整体被删除(分区墓碑):
struct deletion_time { be32 local_deletion_time; be64 marked_for_delete_at; };- 存活分区的特殊值
LIVE = (MAX_BE32, MIN_BE64),即字节序列7F FF FF FF 80 00 00 00 00 00 00 00; marked_for_delete_at是数据应视为已删除的时间戳(通常为 UNIX 纪元以来的微秒数);若为MIN_BE64则表示从未标记删除;local_deletion_time是墓碑创建时的本地服务器时间戳(秒),仅在gc_grace_seconds过后用于清理墓碑。
行(Row)结构
struct row { byte flags; optional<byte> extended_flags; // only present for non-static rows optional<struct clustering_block[]> clustering_blocks; varint row_body_size; varint prev_unfiltered_size; // for backward traversing optional<struct liveness_info> liveness_info; optional<struct delta_deletion_time> deletion_time; optional<varint[]> missing_columns; cell[] cells; };第一字节flags是以下标志的按位或:
| 标志 | 值 | 含义 |
|---|---|---|
END_OF_PARTITION | 0x01 | 分区结束,其后不再有内容 |
IS_MARKER | 0x02 | 编码的 unfiltered 是标记而非行 |
HAS_TIMESTAMP | 0x04 | 行有时间戳(liveness_info 非空) |
HAS_TTL | 0x08 | 行有 TTL / 过期信息 |
HAS_DELETION | 0x10 | 行有删除信息 |
HAS_ALL_COLUMNS | 0x20 | 行包含头部中的全部列 |
HAS_COMPLEX_DELETION | 0x40 | 至少一个复杂列有整体删除 |
EXTENSION_FLAG | 0x80 | 后续还有一个字节的扩展标志 |
若设置EXTENSION_FLAG,紧跟的extended_flags字节是以下标志的按位或:
| 标志 | 值 | 含义 |
|---|---|---|
IS_STATIC | 0x01 | 行是静态行 |
HAS_SHADOWABLE_DELETION_CASSANDRA | 0x02 | Cassandra 的可影墓碑标志(已废弃,ScyllaDB 不支持,遇到会拒绝加载) |
HAS_SHADOWABLE_DELETION_SCYLLA | 0x80 | ScyllaDB 专用标志,表示存在可影墓碑 |
每个分区最多有一个静态行,若存在则位于所有其他行与范围墓碑标记之前,并设置EXTENSION_FLAG与IS_STATIC;静态行按定义不含任何聚类信息。
聚类块(Clustering Blocks)
非静态行带有一组聚类块,表示各聚类列的值:
struct clustering_block { varint clustering_block_header; simple_cell[] clustering_cells; };编码方式为:将所有聚类列按每批 32 个(最后一批可以少于 32)分组;对批内每个列,用 64 位整数中的 2 个 bit 编码其细胞是否为 null 或 empty(高位置位表示 null,低位置位表示 empty)。null表示该列在当前行没有值,empty表示值存在但为空(如text/blob类型的零长度)。行的聚类细胞永远不会是null,但这种编码同样用于范围墓碑标记(其中聚类细胞可能只有前缀)。聚类块的数量不存储在数据文件中——可以从 schema 直接推导。
尺寸与前向/后向遍历
聚类块之后,以varint存储序列化行的大小:即最后一个聚类块之后的字节到下一个unfiltered的flags字节(不含)之间的字节数。下一个varint是前一个 unfiltered的大小(可能是行或范围墓碑标记),用于支持向后遍历。
Liveness 信息与删除信息
liveness_info在HAS_TIMESTAMP标志存在时出现:
struct liveness_info { varint delta_timestamp; optional<varint> delta_ttl; optional<varint> delta_local_deletion_time; };它用于区分"死行"(无活细胞且主键 liveness 为空)与"活行但所有非主键列为 null"(无活细胞但主键 liveness 非空)。可以把它理解为 2.x 数据格式中 CQL row marker 的改进版——只作用于主键列,不影响行内容。时间戳、TTL 与本地删除时间都以上文所述的 delta 编码存储,基准来自 Memtable 统计的最小值。对于死行标记或已过期的 liveness 信息,TTL 使用特殊值 -1。
若行被删除(HAS_DELETION),则存储 delta 编码的删除时间,注意字段顺序与分区头部相反:
struct delta_deletion_time { varint delta_marked_for_delete_at; varint delta_local_deletion_time; };可影墓碑(Shadowable Tombstones)
Cassandra 每行只维护一个墓碑;若其可影,则设置HAS_SHADOWABLE_DELETION_CASSANDRA。由于 Cassandra 的可影删除支持存在已知问题,ScyllaDB 在常规墓碑之外额外维护一个独立的可影墓碑——即 ScyllaDB 写入的 SSTable 中一行最多可以有两个墓碑。若第二个墓碑存在,则设置 ScyllaDB 专用扩展标志HAS_SHADOWABLE_DELETION_SCYLLA(0x80)。
注意:Cassandra 不认识这个标志,会将这些文件视为无效。之所以安全,是因为可影墓碑只可能出现在物化视图(Materialized Views)表中,而物化视图表本就不应在 ScyllaDB 与 Cassandra 之间导出导入。
缺失列编码(Missing Columns Encoding)
若未设置HAS_ALL_COLUMNS,则missing_columns字段编码缺失列的索引。需要理解的是:HAS_ALL_COLUMNS并不要求行包含表 schema 中的全部列,而是指行包含当前 Memtable 中被填充过的列的超集。例如表有 5 个非聚类列 a–e,Memtable 中所有记录只填充了 a、b、c,那么一行只要包含 a、b、c 就会设置HAS_ALL_COLUMNS。这个"已填充列超集"信息同样保存在-Statistics.db中。
编码策略针对列集合大小做了优化:
- 超集列数 < 64:用 64 位整数作位图,为缺失列置位,整体存为一个
varint; - 超集列数 ≥ 64:先写超集列数与当前行列数之差(
varint),再根据当前行列数是否小于超集一半决定编码存在列还是缺失列的索引——总是编码数量更少的一方。因此字段名虽叫missing_columns,实际存的可能是存在列索引,但无论如何都可以还原出缺失列列表。
简单细胞与复杂细胞
任何非聚类列要么是"简单"列(每行最多关联一个细胞),要么是"复杂"列(可关联任意数量细胞);目前复杂列即非冻结集合(non-frozen collection)。所有聚类列都是简单列。由于已经编码了填充列信息,每个细胞按定义非 null(但仍可能 empty)。
简单细胞布局:
struct simple_cell : cell { byte flags; optional<varint> delta_timestamp; optional<varint> delta_local_deletion_time; optional<varint> delta_ttl; optional<cell_path> path; // only in cells nested into complex_cells optional<struct cell_value> value; };flags字节的标志:
| 标志 | 值 | 含义 |
|---|---|---|
IS_DELETED_MASK | 0x01 | 细胞是墓碑 |
IS_EXPIRING_MASK | 0x02 | 细胞会过期 |
HAS_EMPTY_VALUE_MASK | 0x04 | 细胞值为空(墓碑尤其如此) |
USE_ROW_TIMESTAMP_MASK | 0x08 | 细胞时间戳与所属行相同 |
USE_ROW_TTL_MASK | 0x10 | 细胞 TTL 与所属行相同 |
IS_DELETED_MASK与IS_EXPIRING_MASK互斥。细胞时间戳、删除时间、TTL 在不同于行值(对应掩码未设置)时才单独存储为 delta varint。值本身分定宽与变宽两类编码:定宽类型(int、boolean等)无需存储长度,长度可从 schema 推导;变宽类型(text、blob等)则需前缀长度:
struct cell_value { optional<varint> length; byte value[]; };复杂细胞是多个简单细胞的容器,用cell_path区分:
struct complex_cell : cell { optional<struct delta_deletion_time> complex_deletion_time; varint items_count; struct simple_cell[items_count]; };complex_deletion_time表示对整个复杂细胞(如整个集合)的删除,其存在与否由行的HAS_COMPLEX_DELETION标志决定——注意该标志只要任一复杂列有整体删除就会被置位,因此实际会为所有复杂列写出该字段;- 目前
cell_path唯一实现是集合:list 用自动生成的timeuuid,map 用当前 map key,set 用实际值(此时complex_cell_item.value为空)。
范围墓碑标记(Range Tombstone Marker)
范围墓碑覆盖一片/一段行。自 3.0 起,它们以成对的标记存储——一个开始标记加一个结束标记,因此每个墓碑对应两个有序的unfiltered。这种设计简化了合并:按聚类前缀排序后,开始标记一定位于被覆盖行之前,结束标记位于其后。读取器遍历时只需维护至多一个范围墓碑删除标记,若已填充,则其后的行都被视为已删除,直到遇到结束标记。
标记分为两种:
- range_tombstone_bound_marker:表示单个边界;
- range_tombstone_boundary_marker:表示两个相邻范围墓碑之间的分界,编码为"开结束、闭开始"类型,既省磁盘(1 个标记代替 2 个)又简化合并逻辑。
布局如下:
struct range_tombstone_marker { byte flags = IS_MARKER; byte kind_ordinal; be16 bound_values_count; struct clustering_block[] clustering_blocks; varint marker_body_size; varint prev_unfiltered_size; };kind_ordinal取bound_kind枚举的序号:
enum class bound_kind : uint8_t { EXCL_END_BOUND = 0, INCL_START_BOUND = 1, EXCL_END_INCL_START_BOUNDARY = 2, STATIC_CLUSTERING = 3, CLUSTERING = 4, INCL_END_EXCL_START_BOUNDARY = 5, INCL_END_BOUND = 6, EXCL_START_BOUND = 7 };bound marker 取 {0, 1, 6, 7} 之一,boundary marker 取 2 或 5。与行不同,范围墓碑标记必须存储聚类前缀中的非 null 列数(bound_values_count),因为其尾部列可以为 null——而行总是完整前缀,长度从 schema 推导。bound marker 带一个 delta 编码的deletion_time;boundary marker 则带两个(end 与 start 各一个)。
索引文件格式:从 Index.db 到 promoted index
三层检索路径
SSTable 的索引文件与摘要文件共同构成高效定位机制(详见 sstables-3-index.rst):
- Summary.db(常驻内存):保存索引键的抽样,指向索引文件中的位置区间;
- Index.db:按序列出数据文件中的键及其位置;
- Data.db:实际数据。
搜索一个键时,先用 Summary 定位索引文件中可能含该键的(相对较短的)区间,再读取该区间并查找具体键。
索引条目
索引文件是条目的长序列:
struct index_file { struct index_entry entries[]; }; struct index_entry { be16 key_length; char key[key_length]; varint position; // decoded into a 64-bit integer varint promoted_index_length; // decoded into a 32-bit integer byte promoted_index[promoted_index_length]; };key是分区键,position是分区在数据文件中的位置。与 2.x 相比,3.0 索引文件的主要变化是新增了offsets数组与end_open_marker结构。
Promoted Index(提升索引)
对于大分区,仅靠起始位置无法高效定位列区间,因此会附带所谓的promoted index:按column_index_size_in_kb(默认 64 KB)为粒度对分区采样,为每个块给出聚类前缀范围。"promoted" 之名源于历史:它最初是独立存储的列索引,在 Cassandra 1.2 时被"提升"进索引文件内部,使定位一列所需的 seek 从 3 次降为 2 次。promoted_index_length为该字段之后到当前index_entry末尾的字节数;为 0 表示无 promoted index。
struct promoted_index { varint partition_header_length; // decoded into a 64-bit integer struct deletion_time deletion_time; varint promoted_index_blocks_count; // decoded into a 32-bit integer struct promoted_index_block blocks[promoted_index_blocks_count]; be32 offsets[promoted_index_blocks_count]; };partition_header_length是数据文件中分区键、分区墓碑及静态行(若有)的序列化长度,可让读取器直接跳过分区前缀跳到第一行;deletion_time是分区deletion_time的副本(无论存活还是墓碑)。之所以在索引中保留副本,是因为借助 promoted index 我们可能直接跳到超大分区的中间,此时不希望再读分区开头来获取分区墓碑;promoted_index_blocks_count可为 0(小分区)或 ≥ 2(存储单个块没有意义)。
每个 promoted index 块:
struct promoted_index_block { struct clustering_prefix first_name; struct clustering_prefix last_name; varint offset; varint delta_width; byte end_open_marker_present; optional<struct deletion_time> end_open_marker; };first_name/last_name是块边界聚类列前缀。其clustering_prefix结构首字节kind是bound_kind枚举序号:4(CLUSTERING)表示对应行,0/1/2 表示范围墓碑标记;size仅对范围墓碑标记出现(kind != 4),因为行的聚类前缀总是完整的、数量可由 schema 推导;offset是块相对当前分区在数据文件中的起始位置的偏移;width是当前 promoted index 块在索引文件中长度的 delta 编码值。基值为column_index_size_in_kb配置(默认 64 KB = 65536),该基值不随文件存储、恒等于 65536。由于实际块大小可能小于 64 KB,delta 可能为负,因此该值应按有符号处理;end_open_marker_present是布尔字节,指示是否序列化后面的end_open_marker。当当前块在数据文件中对应的 unfiltered 区间包含一个范围墓碑开始标记、但其配对结束标记落在块外时,该结构存在(此时块的结束边界落在两个范围墓碑标记之间),取INCL_START_BOUND或EXCL_END_INCL_START_BOUNDARY类型的范围墓碑标记,并携带该开放边界的删除时间。读取器读取切片时若只遇到结束标记而没有开始标记,就会用end_open_marker中的删除时间合成对应切片起始边界的开始标记——这保证了迭代器式读取永远看到成对的标记;- 最后的
offsets数组与promoted_index_blocks_count等长,第一个偏移恒为 0,其余为各块相对promoted_index.blocks起点的偏移。它可以脱离整个 promoted index 单独读取,从而支持对 promoted index 做二分查找,把搜索复杂度从 O(N) 降到 O(log N)。column_index_size_in_kb的默认值及注释可在 conf/scylla.yaml 中看到。
统计文件格式:Statistics.db 的四种元数据
Statistics 文件保存 SSTable 的元数据,共四类(详见 sstables-3-statistics.rst):
- Validation metadata(校验元数据,类型 0)——用于校验 SSTable 正确性,包含创建该 SSTable 的分区器名称(modified UTF-8 编码字符串)与布隆过滤器误判概率
bloom_filter_fp_chance(be64 double); - Compaction metadata(压缩元数据,类型 1)——序列化的 HyperLogLogPlus,用于估算 SSTable 分区键数量;缺失时可用 Summary 文件推算;
- Statistics(统计,类型 2)——加载到内存、用于加速读取与压缩的信息;
- Serialization header(序列化头,类型 3)——保存 SSTable 的 schema 信息。
文件由两部分构成:先是目录表(TOC),按type字段排序,每项含type(be32 整数)与offset(be32,该元数据在文件中的起始偏移);随后是按顺序排列的元数据条目。
Statistics 条目包含的关键字段:
partition_sizes与column_counts:EstimatedHistogram,前者统计分区未压缩大小(字节),后者统计每分区细胞数;commit_log_upper_bound/commit_log_lower_bound:CommitLogPosition(segment_id + 段内位置),用于限定数据的 commit log 回放位置;min_timestamp/max_timestamp:数据最小/最大时间戳(通常为 UNIX 纪元以来的微秒数);min_local_deletion_time/max_local_deletion_time(秒);min_ttl/max_ttl;compression_rate:压缩率 = 压缩后大小 / 未压缩大小;tombstones:细胞墓碑直方图(StreamingHistogram,键为墓碑的本地删除时间);level:SSTable 的 LCS 层级;repaired_at:最近修复时间相对 1970-01-01 的毫秒差;min_clustering_key/max_clustering_key:SSTable 中存在的聚类键前缀最小/最大值(自md格式起语义有效)。注意聚类行总是完整聚类键,范围墓碑可能有部分前缀,分区墓碑隐式覆盖整个无界聚类范围,因此空前缀表示无界范围;has_legacy_counters、number_of_columns、number_of_rows;commit_log_intervals:提交日志区间数组(版本 MB 起);host_id:写入节点的 UUID(版本 MC/MD 起),用于限定文件中所有 commit log 位置——这正是me格式引入的字段。
序列化头(serialization header)保存 schema 相关信息:min_timestamp、min_local_deletion_time、min_ttl(vint)、分区键类型(单列时为该列类型,否则为 CompositeType)、聚类键类型数组、静态列与常规列的列表(每列含列名与类型)。类型编码为带 vint 长度前缀的 UTF-8 字节串:跳过前导空白,第一段非空白字符为类型名,若不含.则自动前缀org.apache.cassandra.db.marshal.,随后从该类取 "instance" 静态字段;若类型名后紧跟(,则调用getInstance静态方法并传入剩余字符串作为参数。文档列出了 Ascii、Boolean、Bytes、Composite、CounterColumn、Date、Decimal、Double、Duration、Float、Frozen、InetAddress、Int32、Integer、List、Long、Map、Reversed、Set、Short、SimpleDate、Timestamp、Time、TimeUUID、Tuple、User、UTF8、UUID、Vector 等类型及其是否参数化。
摘要文件格式:Summary.db 的粗粒度索引
Summary 文件保存键的抽样,用于检索的第一阶段(详见 sstables-3-summary.rst)。每个 summary 条目指向索引文件中一页(index page)条目,从而只需读取并搜索索引的一小部分。摘要文件设计为整体常驻内存,因此必须足够小,这带来摘要大小与索引页基数(每页覆盖的索引条目数)之间的权衡——抽样级别(sampling level)负责调节该平衡。
3.0 的 Summary 格式与 2.0 相比变化极小,唯一明显变化是不再存储段边界信息。与数据、索引文件不同,Summary 文件不使用可变长整数:
struct summary { struct summary_header header; struct summary_entries_block summary_entries; struct serialized_key first; struct serialized_key last; };头部字段:
min_index_interval(be32):索引摘要条目之间平均分区数的下界,值越小、满采样时进入摘要的分区越多;entries_count(be32):offsets与entries的个数;summary_entries_size(be64):summary_entries结构的完整大小;sampling_level(be32):1 到BASE_SAMPLING_LEVEL(=128)之间的值,表示保留了多少原始摘要条目,即(samplingLevel / BASE_SAMPLING_LEVEL) * ((1 / indexInterval) * numKeys);size_at_full_sampling(be32):若抽样级别等于min_index_interval时摘要应有的条目数。
条目块由offsets数组与entries数组组成:
struct summary_entries_block { uint32 offsets[header.entries_count]; struct summary_entry entries[header.entries_count]; }; struct summary_entry { byte key[]; // variable-length. be64 position; };offsets从summary_entries_block起点计算,offsets[0] == sizeof(uint32) * header.entries_count。注意 offsets 使用本机字节序(ScyllaDB 中总是小端),这与 SSTable 其他文件统一大端的惯例不同——其目的是与常见小端机器上 Cassandra 写出的 Summary 文件、以及罕见大端机器上 ScyllaDB 写出的文件互操作。summary_entry不存储键长度,可从 offsets 推导(最后一个条目的长度由它的 offset 与summary_entries_size计算)。结构末尾的first/last是serialized_key(be32 长度 + 键字节),保存索引/数据文件中的第一个与最后一个键。
ms 格式:基于 Trie 的索引
ms格式(详见 sstable-ms-index.rst)用基于 Trie 的分区索引取代了me/md沿用的 Cassandra 3.0 索引格式,带来的收益包括:
- 更低的内存占用——每个 SSTable 的常驻索引数据更少;
- 更快的分区查找——索引遍历更高效;
- 更小的磁盘索引文件——共享键前缀减少了冗余存储。
配置方式分两种场景:
- 新集群(2026.2 及以后):
ms是默认sstable_format,无需额外配置; - 升级集群:沿用
scylla.yaml中既有格式,若要切换为ms,在 conf/scylla.yaml 中加入:
sstable_format: ms更新节点配置后,新建的 SSTable 即使用ms格式;已有 SSTable 不会自动转换,而是在下一次 compaction 时重写为ms,也可用nodetool upgradesstables -a主动触发转换。
小结:格式选择与运维要点
回到本文的核心问题——如何理解并选用 3.x SSTable 格式:
- 兼容性:3.x 格式与 Cassandra 3.0 一致,SSTable 可直接从 Cassandra 目录迁移到 uploads 目录并
nodetool refresh摄入; - 变体选择:
sstable_format决定新建文件的格式——md是历史升级专用(设置会被忽略),me是 2022.2–2026.1 的基线,ms自 2026.2 起成为默认,mt是仓库默认配置中使用的 Trie 索引变体(conf/scylla.yaml); - 升级路径:旧格式文件不会自动重写,将在下次 compaction 时升级为
ms,或用nodetool upgradesstables -a主动转换; - 组件体系:一个 SSTable 由 Data、Index、Summary、Filter、Statistics、Digest、CompressionInfo、TOC 等组件构成,
ms/mt用 Trie 索引组件(Partitions.db、Rows.db)取代 Index.db 与 Summary.db(源码见 sstables/sstables.cc); - 磁盘布局:数据文件的核心是 varint + delta encoding + optional items 三件套,以及"分区 → 行 → 细胞"的三级模型;索引文件通过 promoted index 与 offsets 数组把大分区内定位复杂度降到 O(log N)。
进一步的格式细节可继续阅读本目录下的配套文档:数据文件格式、索引文件格式、统计文件格式、摘要文件格式 与 ms Trie 索引,并在 sstables/ 目录下的源码中对照实现。
【免费下载链接】scylladbNoSQL data store using the Seastar framework, compatible with Apache Cassandra and Amazon DynamoDB项目地址: https://gitcode.com/GitHub_Trending/sc/scylladb
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考