ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Apache Cassandra SSTable 格式 API 全解析:从组件模型到自定义格式实现

Apache Cassandra SSTable 格式 API 全解析:从组件模型到自定义格式实现 数据库分布式数据库后端【免费下载链接】cassandraMirror of Apache Cassandra项目地址https://gitcode.com/gh_mirrors/cassandr/cassandra点击查看免费下载本文以仓库内 SSTable_API.md 为骨架结合SSTableFormat、BigFormat、BtiFormat、DatabaseDescriptor等源码与 cassandra.yaml 配置系统讲解 Cassandra 的 SSTable 格式抽象、基于 ServiceLoader 的格式发现与配置机制、组件Component模型以及如何从零实现一个自定义 SSTable 格式Reader / Writer / Scrubber / Verifier。读完你将掌握sstable.selected_format配置的含义、格式工厂的注册方式以及扩展新格式所需的全部接口与约定。1. SSTable 格式是什么SSTableSorted String Table是 Cassandra 落盘数据的存储单位。在较新版本的 Cassandra 中SSTable 不再是一套写死的文件布局而是一个可插拔的抽象SSTable 格式SSTable format是SSTableFormat接口的一个实现它负责为该格式创建 reader、writer、scrubber、verifier 以及其他处理 sstable 的组件见 SSTable_API.md。该设计源自 CEP-17: SSTable format API对应 CASSANDRA-17056目标是让不同的存储格式例如传统的 big table 格式与新的 Trie 索引 BTI 格式以统一的方式被读写、清理与校验。一个格式实现必须附带一个实现SSTableFormat.Factory接口的工厂类。工厂负责两件事提供该格式实现唯一的名称提供一个创建格式实例的方法。从源码看SSTableFormat接口SSTableFormat.java的完整职责包括返回格式名称name()、版本对象getLatestVersion()/getVersion()、writer/reader 工厂、若干预定义的组件集合allComponents()、primaryComponents()、batchComponents()、uploadComponents()、mutableComponents()、generatedOnLoadComponents()、key cache 值序列化器、scrubber 工厂、格式专属 metrics 提供者以及删除/清理 sstable 组件的方法。可以说一个格式实现定义了该格式 sstable 的全部生命周期行为。2. 格式的发现与配置2.1 通过 Java ServiceLoader 发现格式SSTable 格式工厂使用Java Service Loader机制被发现。在 DatabaseDescriptor.java 的applySSTableFormats()中可以看到实际加载逻辑ServiceLoaderSSTableFormat.Factory loader ServiceLoader.load(SSTableFormat.Factory.class, DatabaseDescriptor.class.getClassLoader()); ListSSTableFormat.Factory factories Iterables.toList(loader); if (factories.isEmpty()) factories ImmutableList.of(new BigFormat.BigFormatFactory()); applySSTableFormats(factories, conf.sstable);这里有两个关键点加载到的所有格式都能用于读取现有 sstable启动时 Cassandra 会把 ServiceLoader 发现的全部工厂实例化并注册到全局格式表sstableFormats中供读取任意版本/任意格式的 sstable 使用若 ServiceLoader 一个工厂都没找到则回退到BigFormat代码中显式兜底为new BigFormat.BigFormatFactory()这与文档中未指定时假定为BigFormat实现的描述一致。随后applySSTableFormats(factories, sstableFormatsConfig)会对每个工厂调用getInstance(options)并验证配置失败时抛出ConfigurationException最后通过getAndValidateWriteFormat(...)确定写入格式selectedSSTableFormat。2.2cassandra.yaml中的格式配置格式相关配置位于cassandra.yaml的sstable键下完整注释示例见 conf/cassandra.yaml 中的#sstable:/# selected_format: big。配置结构如下sstable: selected_format: 〈name of the default SSTableFormat implementation〉 format: 〈format1 name〉: param1: 〈format specific parameter 1〉 param2: 〈format specific parameter 2〉 # ... 〈format2 name〉: param1: 〈format specific parameter 1〉 param2: 〈format specific parameter 2〉 # ...selected_format指定默认写入格式实现的名称。如果省略默认是big即BigFormat。format以格式名为键的嵌套映射每个键下的参数会被原样传给对应格式的工厂方法Factory.getInstance(MapString, String options)。所有参数都是可选的且含义完全由具体实现决定——即不同格式可以定义各自的参数语义。对应的 Java 配置类在 Config.javapublic static class SSTableConfig { public String selected_format BigFormat.NAME; public MapString, MapString, String format new HashMap(); } public final SSTableConfig sstable new SSTableConfig();可以看到默认值就是BigFormat.NAME即字符串bigformat默认是空 map。2.3 两种典型配置示例与空配置等价的默认配置sstable: selected_format: big以bti作为默认写入格式的示例配置sstable: selected_format: bti format: big: param1: value1 param2: value2 bti: param1: value1 param2: value22.4 格式名称的约束每个实现必须有一个唯一名称用于无歧义地标识格式。该名称必须由SSTableFormat和SSTableFormat.Factory实现中的name()方法一致地返回只包含小写 ASCII 字母。工厂接口中对此有明确注释Format name must not be empty, must be unique and must consist only of lowercase letters见 SSTableFormat.java。当前仓库中的两个内置实现分别使用bigBigFormat.java 的NAME常量和btiBtiFormat.java 的NAME常量均满足这一约束。3. SSTable 组件模型3.1 组件与组件类型每个 sstable 由一组组件component构成——既有必需组件也有可选组件。一个组件构成一个标识符用来获得与 sstable descriptor 对应的确切文件。组件按**类型Type**分组单例类型singleton例如stats组件一个 sstable 最多有一个非单例类型non-singleton例如secondary index组件一个 sstable 可以有多个。通用类型集合定义在SSTableFormat.Components中见 SSTableFormat.java被认为对所有 sstable 实现通用。它们包括单例类型单例组件文件名含义据源码注释DATAData.dbsstable 的基础数据其余组件可基于它重新生成COMPRESSION_INFOCompressionInfo.db未压缩数据长度、块偏移等压缩元信息STATSStatistics.dbsstable 内容的统计元数据FILTERFilter.db行键的序列化布隆过滤器DIGESTDigest.crc32数据文件的 CRC32 校验和CRCCRC.db未压缩文件各块的 CRC32TOCTOC.txt目录表列出该 sstable 的全部组件非单例类型包括SECONDARY_INDEX文件名模式SI_.*.db每个 sstable 可有多个和CUSTOM自定义组件例如供自定义压缩策略使用。3.2 格式专属组件类型除通用组件外每种 sstable 格式还可以描述自己的专属组件类型。例如big table 格式额外定义了PRIMARY_INDEXIndex.db行键索引及在数据文件中的位置指针和SUMMARYSummary.dbIndex 组件的抽样用于内存中的快速近似定位两个单例类型及其单例组件见 BigFormat.javapublic static class Types extends SSTableFormat.Components.Types { // index of the row keys with pointers to their positions in the data file public static final Component.Type PRIMARY_INDEX Component.Type.createSingleton(PRIMARY_INDEX, Index.db, true, BigFormat.class); // holds SSTable Index Summary (sampling of Index component) public static final Component.Type SUMMARY Component.Type.createSingleton(SUMMARY, Summary.db, true, BigFormat.class); }BTI 格式则定义PARTITION_INDEXPartitions.db与ROW_INDEXRows.db两个单例类型见 BtiFormat.java这是它与 big 格式在索引结构上的核心差异。3.3 创建自定义类型与类型注册表自定义类型可以通过以下方法创建Component.Type.create(name, repr, streamable, formatClass)Component.Type.createSingleton(name, repr, streamable, formatClass)每个创建出来的类型都会注册进全局类型注册表。类型注册表是分层的hierarchical某个 sstable 格式实现可以使用为它自己的格式类定义的类型也可以使用所有父格式类定义的类型。例如为BigFormat类定义的类型集合扩展了为SSTableFormat接口定义的通用类型集合。3.4 单例组件与非单例组件单例组件与单例类型一一对应通过type.getSingleton()方法立即获取public static class Components extends AbstractSSTableFormat.Components { public final static Component PRIMARY_INDEX Types.PRIMARY_INDEX.getSingleton(); public final static Component SUMMARY Types.SUMMARY.getSingleton(); }非单例组件则需要显式创建例如Component idx1 Types.SECONDARY_INDEX.createComponent(SI_idx1.db);每个格式还要在allComponents()、primaryComponents()、batchComponents()、uploadComponents()、mutableComponents()、generatedOnLoadComponents()中返回预定义的组件集合见 SSTableFormat.java。这些集合各有用途例如allComponents()writer 能产出、reader 能读取的全部组件primaryComponents()定位/读取所需的最小主组件集big 为DATAPRIMARY_INDEXBTI 为DATAPARTITION_INDEXbatchComponents()离线压缩如拆分 sstable所需组件uploadComponents()sstableloader 上传时应选取的组件mutableComponents()sstable 写入后仍可被修改的组件big 为STATS、SUMMARYBTI 仅STATSgeneratedOnLoadComponents()加载时可自动生成、因此非强制存在的组件big 为FILTER、SUMMARYBTI 为FILTER。组件集合的实现细节都集中在对应格式类的Components内部类中ImmutableSet构造这正好对应文档中应将这些集合声明为常量且不可变的集合的约定。4. 实现一个新格式初始化与基类选择文档强烈建议主格式类继承AbstractSSTableFormatAbstractSSTableFormat.java因为它包含了一些不应被重新实现的方法——例如name()返回构造时传入的格式名equals()/hashCode()基于名称判定Objects.equals(name, that.name)保证同名格式全局唯一可比toString()输出name:options。4.1 初始化流程Cassandra 初始化 sstable 格式类有两种方式通过构造函数将格式类作为单例实例化通过访问类中的静态字段instance获取实例。作为初始化的一部分Cassandra 会调用setup方法并提供配置参数。紧接着Cassandra 调用allComponents()方法以确认该格式定义的所有组件都已初始化且可用。这一点在 DatabaseDescriptor.java 中有直接印证sstableFormats.values().forEach(SSTableFormat::allComponents); // make sure to reach all supported components for a type so that we know all of them are registered4.2 预定义的组件集合如前所述格式要定义若干组件集合并应将这些集合声明为常量、不可变的集合使用ImmutableSet.of(...)以保证组件注册的确定性与线程安全。5. 实现 Reader5.1 构造方式simple builder 与 loading builderSSTable readerSSTableReader.java负责从 sstable 读取数据。它由两种 builder 创建simple builderSSTableReader.Builder只做基本校验、存储 reader 构造函数需要访问的值不执行任何逻辑loading builderSSTableReaderLoadingBuilder执行更复杂的操作——复杂校验、打开资源、加载缓存、索引、过滤器等内部会创建一个 simple builder 并最终实例化 reader。两种 builder 均由reader factorySSTableFormat.SSTableReaderFactory提供接口定义了builder(Descriptor)、loadingBuilder(Descriptor, TableMetadataRef, SetComponent)、readKeyRange(Descriptor, IPartitioner)与getReaderClass()四个方法。具体SSTableReader实现的构造函数应接受两个参数格式专属的simple buildersstable owner通常是ColumnFamilyStore实例也可以是null。构造函数应当简单——只把 builder 中的值赋给内部字段不做其他事情。从 SSTableReader.java 的基类构造看它接收Builder?, ?并依次取出statsMetadata、serializationHeader、dataFile、maxDataAge、openReason、first、last等字段赋值——这正是构造即赋值约定的体现。新 reader 实现应包含一个public static 的 simple builder 内部类继承SSTableReader.Builder泛型 reader builder或SSTableReaderWithFilter.Builder见下文Filter。加载入口方面SSTableReader.open(...)SSTableReader.java展示了二者的协作public static SSTableReader open(Owner owner, Descriptor descriptor, SetComponent components, TableMetadataRef metadata, boolean validate, boolean isOffline) { SSTableReaderLoadingBuilder?, ? builder descriptor.getFormat().getReaderFactory().loadingBuilder(descriptor, metadata, components); return builder.build(owner, validate, !isOffline); }即从 descriptor 拿到对应格式经 reader factory 创建 loading builder再由 loading builder 完成build()。5.2 通用注意事项如果 builder 携带了一些可关闭资源给 reader这些资源应通过setupInstance方法返回需要实现一些cloneXXX方法时务必在传给runWithLock()方法的 lambda 中创建 reader 克隆——runWithLock的注释明确指出这是为了避免与 index summary 重分配竞争CASSANDRA-15861见 SSTableReader.java。5.3 UnbuildingunbuildTo方法实现unbuildTo方法很方便它接收一个simple builder并初始化它使该 builder 能产出同一个 reader。方法还接收sharedCopy布尔参数表示引用可关闭资源的字段是直接拷贝给 builder还是以共享拷贝形式传递。约定的细节还包括仅在 builder 中对应字段未设置为null时才拷贝资源方法第一步应调用super.unbuildTo使父类管理的字段先被拷贝实际实现里只需赋值本格式专属的字段。big table 格式 reader 的实现示例文档原文protected final Builder unbuildTo(Builder builder, boolean sharedCopy) { Builder b super.unbuildTo(builder, sharedCopy); if (builder.getIndexFile() null) b.setIndexFile(sharedCopy ? sharedCopyOrNull(ifile) : ifile); if (builder.getIndexSummary() null) b.setIndexSummary(sharedCopy ? sharedCopyOrNull(indexSummary) : indexSummary); b.setKeyCache(keyCache); return b; }基类 SSTableReader.java 的unbuildTo同样遵循资源仅当 builder 中为 null 才覆盖的规则if (builder.getDataFile() null) b.setDataFile(...)并顺带拷贝statsMetadata、serializationHeader、maxDataAge、openReason、first、last等字段。5.4 Filter继承SSTableReaderWithFilter如果 sstable 包含filterreader 类应继承抽象类SSTableReaderWithFilter其 simple builder 则应继承SSTableReaderWithFilter.SSTableReaderWithFilterBuilder。SSTableReaderWithFilter为扩展实现提供了isPresentInFilter方法还实现了系统依赖的其它 filter 专属方法。注意若 reader 继承SSTableReaderWithFilter必须把FILTER组件包含进相应的组件集合reader with filter 实现自带额外的 metrics。5.5 Index summary实现IndexSummarySupport部分格式如 big table 格式会使用index summaries。如果 reader 使用 index summaries应实现IndexSummarySupport接口。index summaries 的支持同样带来额外 metrics。在 big 格式的读取路径中index summary 承担先粗定位再精确查找的角色查主键时先经SUMMARY组件IndexSummary抽样获得在索引文件中的大致位置再进PRIMARY_INDEXRowIndexEntry精确定位数据文件偏移见 BigFormat.java 的组件生命周期注释。5.6 Key cache实现KeyCacheSupport如果格式实现使用行键缓存应实现KeyCacheSupport接口。具体来说存储一个KeyCache实例并通过getKeyCache()返回该接口为系统依赖的若干方法提供了默认实现接口自带额外 metrics。有趣的是key cache 并非所有格式的必需品BtiFormat的getKeyCacheValueSerializer()直接抛出AssertionError(BTI sstables do not use key cache)见 BtiFormat.java说明 Trie 索引格式的设计意图是让索引足够紧凑高效从而不需要额外的行键缓存层。5.7 格式专属 metrics自定义格式可以在表、keyspace 和全局三个层级提供额外指标这些指标可通过 JMX 访问。SSTableFormat实现通过getFormatSpecificMetricsProviders方法暴露这些指标该方法应返回一个实现MetricsProviders接口的单例对象。目前仅支持自定义 gauge但接口可随时扩展。每个自定义指标gauge都是GaugeProvider抽象类的实现。虽然该类要求实现为每个聚合层级都提供 gauge但有一个辅助类SimpleGaugeProvider可以用一个提供的归约reductionlambda 自动完成。此外还有AbstractMetricsProviders它是MetricsProviders接口的部分实现在提供的方法中借助SimpleGaugeProvider。示例——为支持 index summaries 的 sstable 添加指标完整示例见 IndexSummaryMetrics.javaprivate final GaugeProviderLong indexSummaryOffHeapMemoryUsed newGaugeProvider(IndexSummaryOffHeapMemoryUsed, 0L, r - r.getIndexSummary().getOffHeapSize(), Long::sum);big 格式的BigTableSpecificMetricsProvidersBigFormat.java就是把BloomFilterMetrics、IndexSummaryMetrics、KeyCacheMetrics三者的 gauge 提供者拼接起来BTI 格式则只汇聚BloomFilterMetricsBtiFormat.java。6. 实现 Writer6.1 构造方式SSTable writerSSTableWriter.java负责把数据写入 sstable 文件。它由builderSSTableWriter.Builder创建builder 由writer factorySSTableFormat.SSTableWriterFactory提供。writer factory 接口要求builder(Descriptor)返回可创建SSTableWriter实例的 builder与 loading builder 类似应在build(...)调用时打开所需资源不允许调用方通过 setter 直接传入可关闭资源若构建失败所有已打开资源都应被释放estimateSize(SSTableWriter.SSTableSizeParameters)根据参数估算所有 sstable 文件的总大小。两种内置格式的估算策略不同可从源码对比看出big 格式按两倍分区键大小索引项 数据文件中的键 数据大小再乘 1.2 估算BigFormat.javaBTI 格式则用分区数 × 8 字节索引项 分区键大小 数据大小再乘 1.2BtiFormat.java反映出两种索引结构的空间开销差异。6.2 SortedTableWriter通用默认实现writer 需要实现的方法不多最值得注意的是append——它负责把给定的分区写入磁盘。不过有一个通用的默认实现SortedTableWriter已经处理了大量公共工作使用默认序列化器写入数据文件通用支持分区索引通知notifications元数据收集构建 filter。writer 在添加数据时会触发细粒度事件子类可以覆写这些方法以施加特定行为例如onPartitionStart、onRow、onStaticRow等。最终它会调用一个抽象方法createRowIndexEntry由子类实现不同格式在此处生成各自的索引项例如 big 的RowIndexEntry与 BTI 的TrieIndexEntry。7. 实现 Scrubber 与 Verifier自定义 sstable 格式还应自带自己的 verifier 与 scrubber分别实现IVerifier与IScrubber接口。一个通用的部分实现由以下类提供SortedTableVerifier基于有序表语义的通用校验器骨架SortedTableScrubber基于有序表语义的通用清洗器骨架同时提供deleteOrphanedComponents静态工具用于清理孤儿组件。格式类通过getScrubber(ColumnFamilyStore cfs, LifecycleTransaction transaction, OutputHandler outputHandler, IScrubber.Options options)提供 scrubber 实例。big 与 BTI 格式的实现分别返回BigTableScrubber与BtiTableScrubber且都会先断言事务中 sstable 的元数据与当前 CFS 元数据一致Preconditions.checkArgument(cfs.metadata().equals(transaction.onlyOne().metadata()), ...)以保证清洗过程不会破坏 schema 不匹配的数据。8. 仓库中的两种内置格式8.1 BigFormatbig传统 big table 格式对应文件 BigFormat.java。其组件与生命周期在类注释中有完整描述查主键时先经SUMMARY粗定位再经PRIMARY_INDEX精确定位DATA中的位置STATS记录最小时间戳等统计以支持 vint 编码 TTL 与 markForDeleteAtCOMPRESSION_INFO保存压缩元数据DIGEST/CRC提供校验FILTER是布隆过滤器TOC列出全部组件。版本演进BigVersion源码注释从 3.0 系列一路到 5.0ma3.0.0交换布隆过滤器哈希顺序、原生存储行mb/mc引入 commit log lower bound / intervalsmd/me修正 min/max clustering、加入源节点 hostIdna4.0-rc1未压缩块、pending repair session、isTransient、带校验和的元数据文件、新布隆过滤器格式nb4.0.0originating host idoa5.0改进的 min/max、分区级删除存在标记、key rangeCASSANDRA-18134、无符号 deletionTime 防 TTL 溢出、token space coverage。当前版本由存储兼容模式决定DatabaseDescriptor.getStorageCompatibilityMode().isBefore(5) ? nb : oa。8.2 BtiFormatbtiBig Trie-Indexed 格式BtiFormat.java随 CEP-25: Trie-indexed SSTable format。BTI 当前版本为da5.0 初始版本且该格式不依赖 key cache。9. 总结SSTable 格式 API 是 Cassandra 存储层可扩展性的关键抽象。要落地一个新格式核心步骤可以归纳为工厂实现SSTableFormat.Factory提供唯一小写名称与getInstance(options)并通过 ServiceLoader 注册格式类继承AbstractSSTableFormat实现组件集合、reader/writer 工厂、metrics 提供者、scrubber 工厂等接口方法组件在Components.Types中声明格式专属的单例/非单例类型注册进全局分层类型注册表Reader实现 simple builder继承SSTableReader.Builder或SSTableReaderWithFilterBuilder与 loading builder按需接入 filter、index summary、key cache 支持接口Writer复用SortedTableWriter的通用逻辑实现append与createRowIndexEntryScrubber / Verifier继承SortedTableScrubber/SortedTableVerifier骨架配置在cassandra.yaml的sstable.selected_format中选择默认写入格式并在sstable.format.name下提供格式专属参数。无论是评估现有格式big/bti的行为还是为特定工作负载定制存储布局SSTable_API.md 与本文梳理的源码路径SSTableFormat.java、SSTableReader.java、SortedTableWriter.java、DatabaseDescriptor.java都值得作为第一手参考资料。赞分享数据库分布式数据库后端【免费下载链接】cassandraMirror of Apache Cassandra项目地址https://gitcode.com/gh_mirrors/cassandr/cassandra点击查看免费下载相关推荐Cassandra SSTable API 深度指南可插拔 SSTable 格式体系的实现与配置CEP-17 / CASSANDRA-17056Cassandra SSTable API 深度指南可插拔 SSTable 格式体系的实现与配置CEP 17 / CASSANDRA 17056 本指南围数据库分布式数据库大数据后端NumPy 数组打印格式完全指南从 set_printoptions 到自定义格式化NumPy 数组打印格式完全指南从 set_printoptions 到自定义格式化 导读 NumPy 数组在 REPL、Jupyter Notebook 或科学计算数据分析从HuggingFace到自定义格式LLM模型转换完全指南从HuggingFace到自定义格式LLM模型转换完全指南 模型格式转换是大语言模型 LLM 开发与部署中的关键环节尤其对于需要在不同框架间迁移模型的场景。人工智能大模型强化学习RLHF分布式训练微调上一篇Ethereum 模拟测试环境 Ganache CLI 使用教程下一篇【免费下载】 WhoDB 安装与配置指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进