Lance 表格式版本化完全指南:Feature Flags 位图协议、读写兼容性校验与源码实现解析
2026/9/17 23:01:03 网站建设 项目流程

Lance 表格式版本化完全指南:Feature Flags 位图协议、读写兼容性校验与源码实现解析

【免费下载链接】lanceOpen Lakehouse Format for Multimodal AI. Convert from Parquet in 2 lines of code for 100x faster random access, vector index, and data versioning. Compatible with Pandas, DuckDB, Polars, Pyarrow, and PyTorch with more integrations coming..项目地址: https://gitcode.com/GitHub_Trending/la/lance

导读

Lance 表格式(Lance Table Format)以不可变的 Manifest 描述每一个数据集版本,而格式本身仍在持续演进——新版本会引入删除文件、稳定行 ID、多基路径、数据覆盖文件等新能力。如何在旧读/写器与新旧数据集之间建立安全边界?答案就是本文的主题:Format Versioning(格式版本化)。本指南以仓库中的官方规范文档 versioning.md 为主体,完整解读reader_feature_flagswriter_feature_flags两套位图标志的语义、未知标志拒绝机制、配对约束,并结合 table.proto、feature_flags.rs 等源码给出实现级证据。读完本文,你将掌握 Lance 格式兼容性协议的全部 9 个标志位,理解"为什么未知标志必须拒绝、半置位的清单是非法的",并能独立判断某个 Lance 数据集需要何种版本的读/写器。

一、为什么需要 Feature Flags:格式演进与兼容性的安全阀

Lance 表格式将数据集组织为带版本号的片段(Fragment)、数据文件(Data File)、删除文件(Deletion File)与索引(Index)集合,每个版本由一个不可变的 Manifest 描述,Manifest 中引用该快照的物理数据(见 表格式索引)。这种设计天然要求"向后可读":一个 2024 年写的表,2026 年的新版本库应当能打开;反过来,一个使用新格式特性写出的表,旧版本库必须明确拒绝,而不是"猜着读"导致静默的数据错误。

Feature Flags(特性标志)正是这条安全边界的实现载体。格式每引入一种读者/写者需要特殊处理的新特性,就在格式中登记一个标志位。核心规则(原文):

  • 表格式中有两个独立的标志字段,分别面向读取与写入场景:
    • 读取方应检查reader_feature_flags,确认其中没有自己不认识(不支持)的标志;
    • 写入方应检查writer_feature_flags
    • 任一方只要发现未知标志,就必须在任何读或写操作上返回 "unsupported"(不支持)错误,而不是继续执行。

这样做的逻辑很朴素:不认识某个标志 = 无法兑现该标志所要求的语义 = 继续操作会产出错误结果。例如忽略删除文件会把已删除的行重新返回给用户,忽略覆盖文件会返回过期的旧值。

这两个字段在协议层定义于 table.proto 的Manifest消息中(table.proto#L36-L149):

// Feature flags for readers. uint64 reader_feature_flags = 9; // Feature flags for writers. uint64 writer_feature_flags = 10;

它们都是uint64位图:第 N 位(1 << N)对应一个命名标志。从源码看,Lance Rust 实现把所有标志常量集中定义在 feature_flags.rs,从FLAG_DELETION_FILES = 1 << 0一直到FLAG_MIXED_DATA_FILE_VERSIONS = 1 << 8,并声明FLAG_UNKNOWN: u64 = 1 << 9作为"第一个未知位"的边界。

二、当前全部 Feature Flags 速查表

下表完整收录规范文档中的全部标志(bit 值为位图数值,即1 << n):

Flag BitFlag NameReader RequiredWriter RequiredDescription
1FLAG_DELETION_FILESYesYes片段(Fragment)可能包含删除文件,记录软删除行的墓碑(tombstone)。
2FLAG_STABLE_ROW_IDSYesYes行 ID 对移动(move)与更新(update)均保持稳定,片段内含行 ID 到行地址的索引。
4FLAG_USE_V2_FORMAT_DEPRECATEDNoNo数据文件以新版 v2 格式写入。该标志已废弃、不再使用。
8FLAG_TABLE_CONFIGNoYesManifest 中存在表级配置(table config)。
16FLAG_BASE_PATHSYesYes数据集使用多个基路径(用于浅克隆或多基数据集)。
32FLAG_DISABLE_TRANSACTION_FILENoYes事务直接记录在 Manifest 中,而非单独的 transaction 文件。
64FLAG_UNSTABLE_DATA_OVERLAY_FILESYesYes片段可能携带数据覆盖(overlay)文件。不稳定特性:发布构建默认拒绝,除非显式开启。
128FLAG_COVERED_INDEX_METADATAYesYes存在声明了覆盖列(IndexMetadata.covering_fields)的索引,此时fields表示"被键控列 + 被携带列"。不识别该标志的实现按fields成员关系选索引,会把"仅被携带列"的查询错误地交给以另一列为键的索引。
256FLAG_MIXED_DATA_FILE_VERSIONSYesYes快照可能引用不同精确版本的、可识别的 V2 数据文件。读写双方必须都置位,且后续版本必须保持置位。

未知标志与配对约束(原文要点):位值在512 及以上(即1 << 9起)的标志均为未知标志,任何实现遇到都会以 "unsupported" 错误拒绝该数据集;而配对的混合数据文件版本位(256),其读写两位必须同时置位或同时清除,半置位的 Manifest 是非法的。

三、逐位详解:每个标志的含义、触发条件与实现

位 1:FLAG_DELETION_FILES——软删除墓碑文件

当任一片段带有删除文件(Deletion File)时,该位被置位。删除文件以墓碑形式记录被软删除的行偏移,无需重写底层数据文件即可实现删除。在 feature_flags.rs 中,apply_feature_flags会扫描所有片段:

let has_deletion_files = manifest .fragments .iter() .any(|frag| frag.deletion_file.is_some()); if has_deletion_files { manifest.reader_feature_flags |= FLAG_DELETION_FILES; manifest.writer_feature_flags |= FLAG_DELETION_FILES; }

删除文件的结构定义见 table.proto#L597-L632:支持两种存储格式——Arrow IPC 数组(.arrow扩展名,适合稀疏删除)与 Roaring Bitmap(.bin扩展名,适合稠密删除)。删除文件的路径形如{root}/_deletions/{fragment_id}-{read_version}-{id}.{extension}。因为读取时必须过滤被删除行,所以读写双方都必须认识该标志。

位 2:FLAG_STABLE_ROW_IDS——稳定行 ID

该位表示行 ID 对移动与更新都保持稳定,片段内包含行 ID 到行地址的映射索引。它让诸如merge_insertupdate等重写片段的操作可以不改变行的逻辑标识。

实现上的强制约束很有意思:如果任一片段带行 ID,则所有片段都必须带行 ID,否则apply_feature_flags直接返回非法输入错误(feature_flags.rs#L102-L116):

let has_row_ids = manifest.fragments.iter().any(|frag| frag.row_id_meta.is_some()); if has_row_ids || enable_stable_row_id { if !manifest.fragments.iter().all(|frag| frag.row_id_meta.is_some()) { return Err(Error::invalid_input("All fragments must have row ids")); } manifest.reader_feature_flags |= FLAG_STABLE_ROW_IDS; manifest.writer_feature_flags |= FLAG_STABLE_ROW_IDS; }

Manifest 还通过 table.proto#L182 的next_row_id字段记录下一个未使用的行 ID,注释明确指出"仅在设置了 stable_row_ids 特性标志时使用"。行 ID 的完整语义见 行 ID 与血统规范。

位 4:FLAG_USE_V2_FORMAT_DEPRECATED——已废弃的 v2 格式标志

历史上该位标记"数据文件以 v2 格式写入"。如今 v2 格式已是常态,此标志不再使用,读写双方都不要求。源码中保留常量仅为兼容读取(feature_flags.rs#L16),并提供has_deprecated_v2_feature_flag(feature_flags.rs#L244-L246)用于探测旧清单中的该位。

位 8:FLAG_TABLE_CONFIG——表级配置

当 Manifest 中存在表配置(configmap)时置位,且仅写入方需要识别。原因在于:表配置告诉库如何读写与管理表,写入方必须遵守其中的配置;而读取方不需要执行配置策略。

实现位于 feature_flags.rs#L119-L121:if !manifest.config.is_empty() { manifest.writer_feature_flags |= FLAG_TABLE_CONFIG; }。配置键的命名约定见 table.proto#L206-L212:以lance.为前缀的键保留给 Lance 库本身,其他库也应使用自己的前缀避免冲突。

位 16:FLAG_BASE_PATHS——多基路径

当数据集使用多个基路径时置位,典型场景是浅克隆(shallow clone)多基数据集——数据文件可能位于当前数据集根目录之外(如其他桶、其他目录)。Manifest 中的base_paths列表(table.proto#L241-L261)配合数据文件与删除文件上的base_id字段解析真实路径:

base_paths[id = 0] + /data/ + file.path

apply_feature_flagsbase_paths非空时对读写双方同时置位(feature_flags.rs#L124-L127)。对应测试 test_base_paths_feature_flags 验证了:普通数据集不置位,带 base_paths 的数据集双置位。多位置存储的整体规则见 存储布局规范。

位 32:FLAG_DISABLE_TRANSACTION_FILE——内联事务

该位仅在写入方要求,表示"事务直接记录在 Manifest 中,而非单独的 transaction 文件"。Manifest 协议里对应两个字段:table.proto#L170 的transaction_file(路径格式{read_version}-{uuid}.txn,可为空)与 table.proto#L176 的transaction_section(Manifest 文件内联的事务内容位置)。当写入方选择内联事务(disable_transaction_file参数为真)时置位(feature_flags.rs#L141-L143)。事务提交协议与冲突解决的完整说明见 事务规范。

位 64:FLAG_UNSTABLE_DATA_OVERLAY_FILES——数据覆盖文件(不稳定特性)

数据覆盖文件(Data Overlay File)为片段中一小部分单元格提供新值,而无需重写底层数据文件,使小范围更新变得廉价。该位要求读写双方都必须理解——因为读取时若不处理覆盖层,会静默返回过期的基值

这是目前唯一带"不稳定"前缀的标志,其门控策略在源码中体现得淋漓尽致:

  • FLAG_UNSTABLE_DATA_OVERLAY_FILES是未知边界内的最后一个已知位,且发布构建(release)默认把它当作未知标志拒绝,除非设置环境变量LANCE_ENABLE_UNSTABLE_DATA_OVERLAY_FILES显式开启;
  • 调试构建(debug)始终理解该标志,以便测试覆盖此路径。

对应实现(feature_flags.rs#L170-L200):

pub const ENABLE_UNSTABLE_DATA_OVERLAY_FILES_ENV: &str = "LANCE_ENABLE_UNSTABLE_DATA_OVERLAY_FILES"; fn data_overlay_files_enabled() -> bool { cfg!(debug_assertions) || std::env::var_os(ENABLE_UNSTABLE_DATA_OVERLAY_FILES_ENV).is_some() }

apply_feature_flags在存在 overlay 时对读写双方置位(feature_flags.rs#L132-L139),supported_flags_when则按环境决定是否把该位从"受支持集合"中移除。DataOverlayFile的完整协议(覆盖位图、稠密/稀疏布局、committed_version排序、索引集成与压实)见 数据覆盖文件规范。

位 128:FLAG_COVERED_INDEX_METADATA——覆盖列索引元数据

这是语义最微妙的一个标志。Lance 索引可以在IndexMetadata.covering_fields中声明"覆盖列":索引除了自身键控的列,还顺带携带若干列的值,使只投影这些列的查询可以完全由索引回答,免去对基表的回表。

问题在于:covering_fields声明之后,IndexMetadata.fields的含义从"该索引被搜索的列"变为"键控列 + 被携带列"。一个不认识该标志的旧实现:

  • 作为读取方:仍按fields成员关系选择索引,会把"仅被携带列"的查询交给以另一列为键的索引,返回错误的近邻且不报任何错
  • 作为写入方:会把fields中每一项都当作键控列来维护索引,维护在错误的依赖集上。

因此读写双方都必须拒绝这类数据集。该位是从已退役的 MemWAL 索引追赶标志回收而来(边界恰好落在当时已发布构建的未知边界上,从而旧构建无需改动即可自然拒绝含此位的数据集;但 v11.0.0-beta.4 至 beta.17 窗口内的构建仍会将其视为受支持而放行覆盖数据集)。源码注释与栅栏断言详见 feature_flags.rs#L33-L52,相关索引元数据校验(如拒绝covering_fields长于fields)见 index.rs。

位 256:FLAG_MIXED_DATA_FILE_VERSIONS——混合数据文件版本

正常情况下,一个快照内的所有数据文件应使用同一个存储版本(data_format.version,见 table.proto#L184-L204)。而该位声明:快照可以引用不同精确版本的、可识别的 V2 数据文件,此时每个DataFilefile_major_version/file_minor_version各自权威(见 table.proto#L508-L515)。

它的两个特殊约束(源码级实现):

  1. 配对约束(paired):读写两个位必须同时置位或同时清除。validate_paired_feature_flags(feature_flags.rs#L254-L265)会检查半置位状态并返回CorruptFile错误:"Manifest has only one of the mixed>fn supported_flags_when(overlay_enabled: bool) -> u64 { let mut supported = FLAG_UNKNOWN - 1; // 所有已知位 mark_supported(&mut supported, FLAG_UNSTABLE_DATA_OVERLAY_FILES, overlay_enabled); supported } pub fn can_read_dataset(reader_flags: u64) -> bool { reader_flags & !supported_flags() == 0 } pub fn can_write_dataset(writer_flags: u64) -> bool { writer_flags & !supported_flags() == 0 }

    FLAG_UNKNOWN - 1即"所有已知位的集合";只要清单中的标志集合存在本构建不认识的位(与!supported_flags()按位与非零),判定即失败。具体的拒绝动作由ensure_can_read_manifest/ensure_can_write_manifest完成(feature_flags.rs#L212-L242),错误信息会引导用户升级:

    This dataset cannot be read by this version of Lance. Please upgrade Lance to read this dataset. Flags: {flags}

    配对约束的校验逻辑(半置位清单非法)位于 feature_flags.rs#L254-L265,在每次读取、写入、提交前都会执行——注意它只校验、不自动修复:因为"一个位置位"意味着某个旧读/写器仍被允许,这不是任何一种可被归一化的模式。

    五、源码实现:标志的生成与校验全链路

    5.1 写盘前自动计算:apply_feature_flags

    每次构造 Manifest 并落盘前,feature_flags.rs#L73-L151 的apply_feature_flags会根据 Manifest 实际内容自动重算两个标志字段:扫描片段判断是否存在删除文件/行 ID/overlay,检查configbase_paths是否非空,再按参数决定是否置事务内联位。计算完成后把重算的covered_index_metadata与粘性配对位重新合并回去,防止二次调用时被重置丢失。

    5.2 读取准入:打开数据集即校验

    打开一个数据集时,第一道闸门就是读取准入检查:

    • builder.rs#L872-L874:在load_by_uri加载 Manifest 后立即调用ensure_can_read_manifest
    • dataset.rs#L799、dataset.rs#L880(缓存命中时)、dataset.rs#L1241 等处同样执行;
    • 读取后还会调用check_manifest_storage_version(versions/mod.rs#L319)做存储版本契约校验。

    5.3 写入准入:提交与增量写都需放行

    写入路径上的闸门包括:

    • commit.rs#L425、commit.rs#L1160、commit.rs#L1498-L1514:提交(commit)前对源清单与待写清单执行ensure_can_write_manifest
    • insert.rs#L353:增量写入时检查can_write_dataset(dataset.manifest.writer_feature_flags)
    • dataset.rs#L3325:例如在基于源数据集创建新数据集(如 clone)时,先确保源清单可写。

    5.4 存储版本契约:混合版本位的联动

    check_manifest_storage_contract(versions/mod.rs#L338-L462)把混合版本位与数据文件版本强关联:若混合位启用但默认存储版本是 V1,直接报错;若存在与默认版本不一致的数据文件而混合位未启用,读取时拒绝、最终提交(Finalize)时自动为读写双方补上FLAG_MIXED_DATA_FILE_VERSIONS位(versions/mod.rs#L455-L456)。相关集成测试(update 路径见 update.rs#L805、merge_insert 路径见 merge_insert.rs#L4469)验证了混合版本场景下标志的置位与保留行为。

    六、测试验证:单元测试如何守护位图协议

    feature_flags.rs 内置了一整套针对位图协议的单元测试,可作为理解协议的"可执行文档":

    • test_read_check/test_write_check:验证所有已知位均可读可写,而FLAG_UNKNOWN必须被拒绝;
    • test_data_overlay_flag_release_gating:验证发布构建默认把 overlay 位视为未知、开启环境变量后放行;
    • test_base_paths_feature_flags:验证普通数据集与多基数据集在FLAG_BASE_PATHS上的差异;
    • test_apply_feature_flags_sets_overlay_flag:验证带 overlay 的片段会触发双置位;
    • 一组 paired/sticky 测试(inheriting_carries_sticky_paired_bits_from_the_sourceinheriting_refuses_a_half_set_sourceapply_feature_flags_rejects_half_set_sticky_bitspaired_validation_rejects_half_set_mixed_version_capability):覆盖半置位清单被拒绝、粘性位被继承的全部分支;
    • test_covered_index_metadata_fences_older_builds_only:用断言锁定覆盖索引位必须恰好位于旧发布构建的未知边界(128),确保"老构建天然拒绝、新构建放行"的栅栏语义不漂移。

    七、版本化相关的其他规范速览

    Format Versioning 只是 Lance 表格式规范矩阵中的一环,与之紧密相关、可在当前仓库继续深入阅读的规范包括:

    • 表格式索引:Manifest、片段、数据文件、删除文件的总览;
    • 存储布局规范:文件组织、基路径系统与多位置存储;
    • 事务规范:MVCC、提交协议、事务类型与冲突解决;
    • 行 ID 与血统规范:行地址、稳定行 ID、行版本追踪;
    • 数据覆盖文件规范:位 64 对应的完整格式说明;
    • 分支与标签:基于版本的分支管理;
    • 索引格式:向量、标量与全文索引格式。

    掌握这套 Feature Flags 协议,你就掌握了 Lance 格式向前兼容的底层机制:位图声明能力、未知即拒绝、配对与粘性约束三者共同保证了无论格式如何演进,旧工具永远不会在它无法正确理解的数据上"猜着工作"。

    【免费下载链接】lanceOpen Lakehouse Format for Multimodal AI. Convert from Parquet in 2 lines of code for 100x faster random access, vector index, and data versioning. Compatible with Pandas, DuckDB, Polars, Pyarrow, and PyTorch with more integrations coming..项目地址: https://gitcode.com/GitHub_Trending/la/lance

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询