
























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

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;
}
rawConvertible 的, 即表示对应的 reader 可以转化为 raw reader.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, 因此也就可以使用索引机制了.
除此之外, 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 {
__paimon_file_path 和 __paimon_row_index , 这两个是上面 deletion vector 构建的依赖元信息update 输入构建 deletion vector (indexCommitMsg), 根据 update 输出构建addCommitMsg此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。