惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

V
V2EX
Cisco Talos Blog
Cisco Talos Blog
MongoDB | Blog
MongoDB | Blog
IT之家
IT之家
N
News and Events Feed by Topic
博客园 - 叶小钗
Help Net Security
Help Net Security
美团技术团队
Attack and Defense Labs
Attack and Defense Labs
雷峰网
雷峰网
S
Security @ Cisco Blogs
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
WordPress大学
WordPress大学
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
AI
AI
Hacker News: Ask HN
Hacker News: Ask HN
T
The Blog of Author Tim Ferriss
Security Latest
Security Latest
Last Week in AI
Last Week in AI
S
Secure Thoughts
Simon Willison's Weblog
Simon Willison's Weblog
TaoSecurity Blog
TaoSecurity Blog
N
News and Events Feed by Topic
A
Arctic Wolf
Y
Y Combinator Blog
MyScale Blog
MyScale Blog
Cyberwarzone
Cyberwarzone
酷 壳 – CoolShell
酷 壳 – CoolShell
S
SegmentFault 最新的问题
罗磊的独立博客
Vercel News
Vercel News
D
DataBreaches.Net
博客园 - 聂微东
P
Palo Alto Networks Blog
N
News | PayPal Newsroom
GbyAI
GbyAI
B
Blog
A
About on SuperTechFans
PCI Perspectives
PCI Perspectives
S
Schneier on Security
Apple Machine Learning Research
Apple Machine Learning Research
I
InfoQ
The GitHub Blog
The GitHub Blog
P
Privacy International News Feed
Blog — PlanetScale
Blog — PlanetScale
博客园_首页
T
The Exploit Database - CXSecurity.com
V
Vulnerabilities – Threatpost
aimingoo的专栏
aimingoo的专栏
Google DeepMind News
Google DeepMind News

博客园 - Aitozi

An Empirical Evaluation of Columnar Storage Formats Lance 写入链路:Merge Into、Compaction 与 Stable Row ID 从本地目录理解 Lance Dataset:Manifest、Fragment 与 Blob 论文解读:Lance 如何通过自适应结构编码提升列式存储随机访问 中国最大广告机器简史 学习Facebook,超越Meta|字节跳动 第3集 Paimon merge into 实现原理 Paimon lookup store 实现 Flink Batch Hash Aggregate 理解 Paimon changelog producer 笔记工具 FlinkSQL类型系统 二叉堆原理与实现 SkipList原理与实现 Lakehouse: A New Generation of Open Platforms that Unify Data Warehousing and Advanced Analytics Delta Lake: High-Performance ACID Table Storage over Cloud Object Stores Paimon Compaction实现 Paimon读取流程 Paimon的写入流程 Calcite sql2rel 过程 用rust 写一个jar包 class冲突检测工具 rust 中 str 与 String; &str &String 好奇心: 保持对未知世界用不停息的热情 Apache hudi 核心功能点分析
Paimon Deletion Vector
Aitozi · 2024-11-18 · via 博客园 - Aitozi

deletion vector 是通过一组向量, 维护一个文件中被删除的行, 可以理解为一种索引. 这种方式可以以 Merge On Write 的方式, 来避免 Merge On Read 的过程, 从而以写入性能换取读取性能. 对于写少读多, 或者对读取性能有更高要求的场景会比较适合.

避免 Merge On Read 会带来以下几个好处:

  • 读取时仅需要直接读取文件, 并根据 bitmap 跳过对应的删除行即可, 跳过 Merge 过程, 直观的提升读取效率
  • 和 Native 引擎结合更好, 可以通过 C++ reader, 直接读取文件并进行向量过滤, 避免了通过 jni 的 merge 过程
  • 由于不再需要 merge, 因此 split 切分可以更细, 从而增多 split, 提升读取的并发度
  • 可以进行 value 字段的 filter push down, 从而实现更好的过滤效果.
    • 因为在 MOR 的情况下, 由于存在多个 Key value 需要合并的情况, 因此在合并之前不能进行 value filter 下推.

写入/compaction 过程

LookupChangelogMergeFunctionWrapper

deletion vector 的创建依赖于 compaction. 在 compaction 过程中通过 lookup 查找高层文件, 并进行文件删除行的标记.

开启 delete vector 后, 会强制进行 Lookup, 从而使用 ForceUpLevel0Compaction 的 Compaction 策略.

org.apache.paimon.operation.KeyValueFileStoreWrite#createRewriter

// 对于needLookup的场景, 采用ForceUpLevel0 compaction的机制
CompactStrategy compactStrategy =  
        options.needLookup()  
                ? new ForceUpLevel0Compaction(universalCompaction)  
                : universalCompaction;
processor =  
        lookupStrategy.deletionVector  
                ? new PositionedKeyValueProcessor(  
                        valueType,  
                        lookupStrategy.produceChangelog  
                                || mergeEngine != DEDUPLICATE
                                || !options.sequenceField().isEmpty())  
                : new KeyValueProcessor(valueType);

这里的 processor 对应于 lookup 过程中如何处理 value 字段, 首先对于 deletion vector 场景, 我们查找到一个 key 时, 需要知道对应的"行号" position, 因此需要 PositionedKeyValueProcessor 即记录对应 KV pair 的行号.

其次对于以下三种场景, 还要求 lookup 的过程中, 读取完整的 value

  • lookupStrategy.produceChangelog 由于要产生 Changelog, 所以需要知道前值, 因此需要完整的 value 读取
  • mergeEngine != DEDUPLICATE
  • !options.sequenceField().isEmpty() 和上面的一样, 这几类场景都是基于 L0 key 查找到高层值的时候, 不能简单的将高层标记为 delete, 而是需要执行一次 Merge 过程, 例如 Partial-Update, 或者根据 sequence field 比较后才 deduplicate, 所以这几类也需要读取完整value.

如果不是这几类, 比如不带排序字段的 deduplicate, 那么在 lookup 的过程中, 只需要读取对应的 key 即可, 那么就可以大大降低 lookup 的 IO 开销.

DeletionVectorsMaintainer

Lookup 过程中, 对于查找到高层的 key, 可以对高层数据标记删除. DeletionVectorsMaintainer 中维护了文件到 DeletionVector 的映射, DeletionVector 的实现通常是一个 RoaringBitmap.

if (lookupResult != null) {  
    if (lookupStrategy.deletionVector) {  
        PositionedKeyValue positionedKeyValue = (PositionedKeyValue) lookupResult;  
        highLevel = positionedKeyValue.keyValue();  
        deletionVectorsMaintainer.notifyNewDeletion(  
                positionedKeyValue.fileName(), positionedKeyValue.rowPosition());  
    } else {  
        highLevel = (KeyValue) lookupResult;  
    }  
}

按照 pip-16 中的描述, 每个 bucket 会维护一个 delete vector 文件 , 这个文件中维护了所有有删除 key 的文件和对应的 bitmap.
Pasted image 20241103220849

  • 同步生成: 在 Compaction 完成后, 将内存中维护的 Map<String, DeletionVector> 数据结构写入对应的 index 文件. 这块其实会在内存里面维护每个有删除行为的文件的 deletion vector. 启动阶段也会从元数据中读取恢复. 当文件比较多的时候, 这块的内存开销可能也不容忽视. 而且由于 Map 维护, 只要有一个文件更新, 整个 index 文件也是要被重写的.
  • 异步生成: delete vector 生成也支持异步化, 这样就可以不阻塞主链路的写入流程.

查询/读取

RawSplitRead

KeyValueTableRead

this.readProviders =
		Arrays.asList(
				new RawFileSplitReadProvider(batchRawReadSupplier, this::assignValues),
				new MergeFileSplitReadProvider(mergeReadSupplier, this::assignValues),
				new IncrementalChangelogReadProvider(mergeReadSupplier, this::assignValues),
				new IncrementalDiffReadProvider(mergeReadSupplier, this::assignValues));

对于 KeyValueTableRead, 会创建一堆的 SplitReadProvider, 哪个 match 就走哪个读取.

public boolean match(DataSplit split, boolean forceKeepDelete) {
	boolean matched = !forceKeepDelete && !split.isStreaming() && split.rawConvertible();
	if (matched) {
		// for legacy version, we are not sure if there are delete rows, but in order to be
		// compatible with the query acceleration of the OLAP engine, we have generated raw
		// files.
		// Here, for the sake of correctness, we still need to perform drop delete filtering.
		for (DataFileMeta file : split.dataFiles()) {
			if (!file.deleteRowCount().isPresent()) {
				return false;
			}
		}
	}
	return matched;
}
  • 对于 dv 表, 他的 split 是 rawConvertible 的, 即表示对应的 reader 可以转化为 raw reader.

ApplyDeletionFileRecordIterator

public InternalRow next() throws IOException {
	while (true) {
		InternalRow next = iterator.next();
		if (next == null) {
			return null;
		}
		if (!deletionVector.isDeleted(returnedPosition())) {
			return next;
		}
	}
}

真正的读取过程, 就是根据提前加载的 delete vector 根据行号进行过滤.

还有一些其他关于读取的改动, 主要是 filter 下推相关的. 因为当文件可以 raw read, 不需要合并后, 非主键字段也就可以安全下推了.

例如: 开启 dv 的表, 可以应用其他的 value filter, 因此也就可以使用索引机制了.

Append Table DV support

除此之外, Paimon 还利用 deletion vector 实现了对 Append 表的删除

append 表的删除可以类比 iceberg 的实现, 根据输入数据, 构建删除的 deletion vector, 从而实现 append 表的删除逻辑.

  if (deletionVectorsEnabled) {
	// Step2: collect all the deletion vectors that marks the deleted rows.
	val deletionVectors = collectDeletionVectors(
	  candidateDataSplits,
	  dataFilePathToMeta,
	  condition,
	  relation,
	  sparkSession)

	deletionVectors.cache()
	try {
	  // Step3: write these updated data
	  val touchedDataSplits = deletionVectors.collect().map {
		SparkDeletionVectors.toDataSplit(_, root, pathFactory, dataFilePathToMeta)
	  }
	  val addCommitMessage = writeOnlyUpdatedData(sparkSession, touchedDataSplits)

	  // Step4: write these deletion vectors.
	  val indexCommitMsg = writer.persistDeletionVectors(deletionVectors)

	  addCommitMessage ++ indexCommitMsg
	} finally {
	  deletionVectors.unpersist()
	}
  } else {
  1. 通过 filter 过滤, 先拿到 update 或 delete 语句可能影响的 split
  2. 构建 Reader 读取, 读取的 plan 额外添加 Metadata column, __paimon_file_path__paimon_row_index , 这两个是上面 deletion vector 构建的依赖元信息
  3. 根据 update 输入构建 deletion vector (indexCommitMsg), 根据 update 输出构建addCommitMsg