Skip to content

Commit 5efcd17

Browse files
authored
Support IF_NOT_CONTAINED filter type and loading inline deletion vectors for OSS delta [databricks] (#15368)
Fixes #15326. ### Description The CDF read with deletion vectors currently fails. Two things were missing to support this case: - The `IF_NOT_CONTAINED` row index filter type support. The Delta CDC reader can use this type of row index filter. - Inline deletion vector support. The CDC reader creates inline deletion vectors. This PR adds those supports based on rapidsai/cudf#23402 for OSS Delta. The plugin now can load inline deletion vectors and process the `IF_NOT_CONTAINED` filter properly with all 3 Delta readers. Note that the issue exists only with the native readers (`GpuDeltaParquetFileFormatBase2`). The legacy reader (`GpuDeltaParquetFileFormatBase`) does not have this issue. Databricks readers have the same issue, and will be fixed in #15365. ### Checklists Documentation - [ ] Updated for new or modified user-facing features or behaviors - [x] No user-facing change Testing - [x] Added or modified tests to cover new code paths - [ ] Covered by existing tests (Please provide the names of the existing tests in the PR description.) - [ ] Not required Performance - [ ] Tests ran and results are added in the PR description - [ ] Issue filed with a link in the PR description - [x] Not required --------- Signed-off-by: Jihoon Son <ghoonson@gmail.com>
1 parent 1dbb5ce commit 5efcd17

10 files changed

Lines changed: 765 additions & 193 deletions

File tree

delta-lake/common/src/main/delta-33x-41x/scala/com/nvidia/spark/rapids/delta/common/GpuDeltaParquetFileFormatBase2.scala

Lines changed: 63 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import com.nvidia.spark.rapids._
2626
import com.nvidia.spark.rapids.Arm.{closeOnExcept, withResource}
2727
import com.nvidia.spark.rapids.GpuMetric._
2828
import com.nvidia.spark.rapids.RapidsPluginImplicits._
29-
import com.nvidia.spark.rapids.delta.RapidsDeletionVectorRowCountUtils
3029
import com.nvidia.spark.rapids.jni.fileio.RapidsFileIO
3130
import com.nvidia.spark.rapids.parquet._
3231
import org.apache.hadoop.conf.Configuration
@@ -215,7 +214,7 @@ class GpuDeltaParquetFileFormatBase2(
215214
filters: Array[Filter],
216215
@transient rapidsConf: RapidsConf,
217216
metrics: Map[String, GpuMetric],
218-
params: Map[String, String]
217+
@transient params: Map[String, String]
219218
) extends GpuParquetPartitionReaderFactoryBase(sqlConf, broadcastedConf, dataSchema,
220219
readDataSchema, partitionSchema, rapidsConf, metrics = metrics, params = params) {
221220

@@ -280,12 +279,10 @@ class GpuDeltaParquetFileFormatBase2(
280279
} else {
281280
val scalaBitmap = RapidsDeletionVectors.loadScalaBitmap(
282281
conf, dvDescriptorOpt, filterTypeOpt, tablePath.get)
283-
RapidsDeletionVectorRowCountUtils.computeNumRowsAlive(
284-
totalNumRows, scalaBitmap.cardinality, chunkedBlocks) { countDeletedRow =>
285-
scalaBitmap.forEach { deletedIndex: Long =>
286-
countDeletedRow(deletedIndex)
287-
}
288-
}
282+
val (rowGroupOffsets, rowGroupNumRows) =
283+
RapidsDeletionVectors.getRowGroupMetadata(chunkedBlocks)
284+
Math.toIntExact(RapidsDeletionVectors.computeNumRowsAlive(
285+
totalNumRows, scalaBitmap, filterTypeOpt, rowGroupOffsets, rowGroupNumRows))
289286
}
290287
}
291288
}
@@ -313,7 +310,8 @@ class GpuDeltaParquetFileFormatBase2(
313310
RapidsDeletionVectors.getRowGroupMetadata(chunkedBlocks)
314311
val maybeDvInfo = maybeSerializedDV.map(serializedDV =>
315312
new DeletionVector.DeletionVectorInfo(serializedDV,
316-
false, rowGroupOffsets, rowGroupNumRows))
313+
RapidsDeletionVectors.isIfNotContainedRowIndexFilter(filterTypeOpt),
314+
rowGroupOffsets, rowGroupNumRows))
317315

318316
val hostBuf = dataBuffer.getDataHostBuffer()
319317
// Duplicate request is ok, and start to use the GPU just after the host
@@ -364,8 +362,9 @@ class GpuDeltaParquetFileFormatBase2(
364362
*/
365363
case class SpillableDeletionVectorInfo(
366364
serializedBitmap: SpillableHostBuffer,
367-
// Pre-computed number of deleted rows across all row groups.
368-
numRowsDeleted: Long,
365+
filterTypeOpt: Option[RowIndexFilterType],
366+
// Pre-computed number of rows remaining after applying the deletion vector.
367+
numRowsAlive: Long,
369368
// The offsets and numRows below are the original row group offsets and row counts
370369
// in the file. The combining process in multi-threaded reader involves re-organizing
371370
// row groups across files, but the offsets and numRows here are not changed even
@@ -384,16 +383,19 @@ class GpuDeltaParquetFileFormatBase2(
384383
def apply(
385384
serializedBitmap: HostMemoryBuffer,
386385
scalaBitmap: RoaringBitmapArray,
386+
filterTypeOpt: Option[RowIndexFilterType],
387387
rowGroupOffsets: Array[Long],
388388
rowGroupNumRows: Array[Int]): SpillableDeletionVectorInfo = {
389-
val numRowsDeleted =
390-
RapidsDeletionVectors.countDeletedRows(scalaBitmap, rowGroupOffsets, rowGroupNumRows)
389+
val totalNumRows = rowGroupNumRows.map(_.toLong).sum
390+
val numRowsAlive = RapidsDeletionVectors.computeNumRowsAlive(
391+
totalNumRows, scalaBitmap, filterTypeOpt, rowGroupOffsets, rowGroupNumRows)
391392
new SpillableDeletionVectorInfo(
392393
SpillableHostBuffer(
393394
serializedBitmap,
394395
serializedBitmap.getLength(),
395396
SpillPriorities.ACTIVE_BATCHING_PRIORITY),
396-
numRowsDeleted,
397+
filterTypeOpt,
398+
numRowsAlive,
397399
rowGroupOffsets,
398400
rowGroupNumRows)
399401
}
@@ -428,8 +430,9 @@ class GpuDeltaParquetFileFormatBase2(
428430
timestampRebaseMode: DateTimeRebaseMode,
429431
hasInt96Timestamps: Boolean,
430432
// Base64-encoded DV descriptor string for this block's source file. None if no DV.
431-
// The filter type is always RowIndexFilterType.IF_CONTAINED.
432433
val dvDescriptor: Option[String],
434+
// Controls whether bitmap-marked rows are removed or retained. None if no DV.
435+
val filterTypeOpt: Option[RowIndexFilterType],
433436
// Within-file row-index ordinal of this row group's first row.
434437
// Captured from BlockMetaData before any merging; invariant to computeBlockMetaData().
435438
val rowGroupOffset: Long,
@@ -440,12 +443,14 @@ class GpuDeltaParquetFileFormatBase2(
440443
* Per-file DV entry assembled during [[augmentChunkMeta]].
441444
*
442445
* @param dvDescriptor base64-encoded DV descriptor for this file; None if no DV
446+
* @param filterTypeOpt controls whether bitmap-marked rows are removed or retained; None if no DV
443447
* @param rowGroupOffsets within-file row-index ordinals of each row group's first row
444448
* @param rowGroupNumRows number of rows in each row group
445449
* @param partitionIndex index into rowsPerPartition / allPartValues this file contributes to
446450
*/
447451
case class PerFileDVEntry(
448452
dvDescriptor: Option[String],
453+
filterTypeOpt: Option[RowIndexFilterType],
449454
rowGroupOffsets: Array[Long],
450455
rowGroupNumRows: Array[Int],
451456
partitionIndex: Int)
@@ -454,7 +459,7 @@ class GpuDeltaParquetFileFormatBase2(
454459
* Per-file DV load result produced during [[prepareForDecode]].
455460
*
456461
* @param gpuBitmap serialized roaring bitmap buffer for the file's deletion vector
457-
* @param aliveCount number of alive (non-deleted) rows in the file
462+
* @param aliveCount number of rows remaining after applying the deletion vector
458463
*/
459464
case class SerializedRoaringBitmap(gpuBitmap: SpillableHostBuffer, aliveCount: Long)
460465

@@ -577,7 +582,9 @@ class GpuDeltaParquetFileFormatBase2(
577582
val filterTypeOpt = metaAndFile.file.otherConstantMetadataColumnValues
578583
.get(FILE_ROW_INDEX_FILTER_TYPE).asInstanceOf[Option[RowIndexFilterType]]
579584
filterTypeOpt.foreach { ft =>
580-
require(ft == RowIndexFilterType.IF_CONTAINED,
585+
require(
586+
ft == RowIndexFilterType.IF_CONTAINED ||
587+
ft == RowIndexFilterType.IF_NOT_CONTAINED,
581588
s"Unexpected DV filter type for coalescing reader: $ft")
582589
}
583590
val singleFileInfo = metaAndFile.meta
@@ -596,6 +603,7 @@ class GpuDeltaParquetFileFormatBase2(
596603
singleFileInfo.timestampRebaseMode,
597604
singleFileInfo.hasInt96Timestamps,
598605
dvDescriptorOpt,
606+
filterTypeOpt,
599607
rowGroupOffsets(i),
600608
rowGroupNumRows(i)))
601609
}
@@ -687,7 +695,8 @@ class GpuDeltaParquetFileFormatBase2(
687695
.map(spillableDvInfo =>
688696
new DeletionVector.DeletionVectorInfo(
689697
spillableDvInfo.serializedBitmap.getDataHostBuffer(),
690-
false,
698+
RapidsDeletionVectors.isIfNotContainedRowIndexFilter(
699+
spillableDvInfo.filterTypeOpt),
691700
spillableDvInfo.rowGroupOffsets,
692701
spillableDvInfo.rowGroupNumRows
693702
))
@@ -725,7 +734,7 @@ class GpuDeltaParquetFileFormatBase2(
725734

726735
if (allPartValues.isDefined) {
727736
val allPartInternalRows = allPartValues.get.map(_._2)
728-
// rowsPerPartition has been adjusted already to account only the alive rows.
737+
// rowsPerPartition has been adjusted to account for the deletion vectors.
729738
val rowsPerPartition = allPartValues.get.map(_._1)
730739
new GpuColumnarBatchWithPartitionValuesIterator(batchIter, allPartInternalRows,
731740
rowsPerPartition, partitionSchema, maxGpuColumnSizeBytes)
@@ -748,6 +757,7 @@ class GpuDeltaParquetFileFormatBase2(
748757
* Deletion vector metadata for a single host memory buffer containing a part of data.
749758
*/
750759
private case class SingleBufferDVMetadata(
760+
// maybeDvInfo is None only when the tablePath is not defined.
751761
maybeDvInfo: Option[SpillableDeletionVectorInfo]
752762
)
753763

@@ -824,7 +834,7 @@ class GpuDeltaParquetFileFormatBase2(
824834
numRows: Long,
825835
blocks: collection.Seq[BlockMetaData]
826836
): HostMemoryEmptyMetaData = {
827-
val (maybeSerializedDV, maybeScalaBitmap) = if (numRows > 0) {
837+
val (maybeSerializedDV, maybeScalaBitmap, filterTypeOpt) = if (numRows > 0) {
828838
// numRows == 0 means the data is empty because of an empty file,
829839
// file not found, or a corrupted file. In all these cases, we don't
830840
// need to load deletion vectors.
@@ -839,9 +849,9 @@ class GpuDeltaParquetFileFormatBase2(
839849
// clause.
840850
val maybeSerializedDV = tablePath.map(tp =>
841851
RapidsDeletionVectors.loadDeletionVector(fileIO, dvDescriptorOpt, filterTypeOpt, tp))
842-
(maybeSerializedDV, maybeScalaBitmap)
852+
(maybeSerializedDV, maybeScalaBitmap, filterTypeOpt)
843853
} else {
844-
(None, None)
854+
(None, None, None)
845855
}
846856

847857
closeOnExcept(maybeSerializedDV) { _ =>
@@ -852,6 +862,7 @@ class GpuDeltaParquetFileFormatBase2(
852862
SpillableDeletionVectorInfo(
853863
serializedDV,
854864
maybeScalaBitmap.get,
865+
filterTypeOpt,
855866
rowGroupOffsets,
856867
rowGroupNumRows)}
857868
)
@@ -920,6 +931,7 @@ class GpuDeltaParquetFileFormatBase2(
920931
SpillableDeletionVectorInfo(
921932
serializedDV,
922933
maybeScalaBitmap.get,
934+
filterTypeOpt,
923935
rowGroupOffsets,
924936
rowGroupNumRows)
925937
})
@@ -974,20 +986,23 @@ class GpuDeltaParquetFileFormatBase2(
974986
return 0
975987
}
976988

977-
val numDeletedRows = metadata match {
989+
val dvInfos = metadata match {
978990
case emptyMeta: DeltaParquetHostMemoryEmptyMetaData =>
979991
emptyMeta.dvMetadata.flatMap(_.metadatas).flatMap(_.maybeDvInfo)
980-
.map(_.numRowsDeleted).sum
981992
case buffersMeta: DeltaParquetHostMemoryBuffersWithMetaData =>
982993
buffersMeta.dvMetadata.flatMap(_.metadatas).flatMap(_.maybeDvInfo)
983-
.map(_.numRowsDeleted).sum
984994
case _ =>
985995
throw new IllegalArgumentException(s"Unexpected metadata type ${metadata.getClass()}")
986996
}
987997

988-
require(numDeletedRows <= totalNumRows,
989-
s"Deletion vector cardinality ($numDeletedRows) exceeds file row count ($totalNumRows)")
990-
Math.toIntExact(totalNumRows - numDeletedRows)
998+
val numRowsAlive = if (dvInfos.isEmpty) {
999+
totalNumRows
1000+
} else {
1001+
dvInfos.map(_.numRowsAlive).sum
1002+
}
1003+
require(numRowsAlive <= totalNumRows,
1004+
s"Alive row count ($numRowsAlive) exceeds file row count ($totalNumRows)")
1005+
Math.toIntExact(numRowsAlive)
9911006
}
9921007
}
9931008

@@ -1060,7 +1075,8 @@ class GpuDeltaParquetFileFormatBase2(
10601075
* - collect per-block DV descriptors during batch assembly ([[augmentChunkMeta]])
10611076
* - load DV bitmaps concurrently after the copy phase ([[prepareForDecode]])
10621077
* - pass DV info to the cuDF Parquet reader ([[readBufferToTablesAndClose]])
1063-
* - substitute DV-filtered alive row counts for partition routing ([[getRowsPerPartition]])
1078+
* - substitute DV-filtered alive row counts for partition routing
1079+
* ([[getRowsPerPartition]])
10641080
*/
10651081
class MultiFileDeltaCoalescingParquetPartitionReader(
10661082
fileIO: RapidsFileIO,
@@ -1103,6 +1119,7 @@ class GpuDeltaParquetFileFormatBase2(
11031119
require(group.blocks.nonEmpty, s"File group must contain blocks: ${group.filePath}")
11041120
val firstExtra = group.blocks.head.extraInfo.asInstanceOf[DeltaParquetExtraInfo]
11051121
val fileDesc = firstExtra.dvDescriptor
1122+
val filterTypeOpt = firstExtra.filterTypeOpt
11061123
val fileOffsets = ArrayBuffer[Long]()
11071124
val fileNumRows = ArrayBuffer[Int]()
11081125

@@ -1115,7 +1132,12 @@ class GpuDeltaParquetFileFormatBase2(
11151132
fileNumRows += extra.rowGroupNumRows
11161133
}
11171134

1118-
PerFileDVEntry(fileDesc, fileOffsets.toArray, fileNumRows.toArray, partitionIndex)
1135+
PerFileDVEntry(
1136+
fileDesc,
1137+
filterTypeOpt,
1138+
fileOffsets.toArray,
1139+
fileNumRows.toArray,
1140+
partitionIndex)
11191141
}.toSeq
11201142

11211143
val batchExtra = new DeltaBatchExtraInfo(
@@ -1126,7 +1148,8 @@ class GpuDeltaParquetFileFormatBase2(
11261148

11271149
/**
11281150
* Loads DV bitmaps for all files in the batch concurrently after the copy phase.
1129-
* Also computes per-file alive row counts (used later by [[getRowsPerPartition]]).
1151+
* Also computes the rows remaining in each file after applying its deletion vector
1152+
* (used later by [[getRowsPerPartition]]).
11301153
* Fast path: if no file in the batch has a DV, returns meta unchanged.
11311154
*/
11321155
override protected def prepareForDecode(meta: CurrentChunkMeta): CurrentChunkMeta = {
@@ -1143,26 +1166,19 @@ class GpuDeltaParquetFileFormatBase2(
11431166
threadPool.submit(new Callable[SerializedRoaringBitmap] {
11441167
override def call(): SerializedRoaringBitmap = {
11451168
val rawBitmap = RapidsDeletionVectors.loadDeletionVector(
1146-
fileIO, entry.dvDescriptor, tp)
1169+
fileIO, entry.dvDescriptor, entry.filterTypeOpt, tp)
11471170
// DeltaBatchExtraInfo.close() releases the SpillableHostBuffer when the decode
11481171
// phase completes (via withRetryNoSplit in readBatchData).
11491172
val gpuBitmap = SpillableHostBuffer(rawBitmap, rawBitmap.getLength,
11501173
SpillPriorities.ACTIVE_BATCHING_PRIORITY)
11511174
closeOnExcept(gpuBitmap) { _ =>
1152-
val filterTypeOpt = entry.dvDescriptor.map(_ => RowIndexFilterType.IF_CONTAINED)
11531175
val totalRows = entry.rowGroupNumRows.map(_.toLong).sum
1154-
val numDeleted = if (entry.dvDescriptor.isEmpty) {
1155-
0L
1156-
} else {
1157-
val scalaBitmap = RapidsDeletionVectors.loadScalaBitmap(
1158-
conf, entry.dvDescriptor, filterTypeOpt, tp)
1159-
RapidsDeletionVectors.countDeletedRows(
1160-
scalaBitmap, entry.rowGroupOffsets, entry.rowGroupNumRows)
1161-
}
1162-
require(numDeleted <= totalRows,
1163-
s"Deletion vector cardinality ($numDeleted) exceeds " +
1164-
s"file row count ($totalRows)")
1165-
SerializedRoaringBitmap(gpuBitmap, totalRows - numDeleted)
1176+
val scalaBitmap = RapidsDeletionVectors.loadScalaBitmap(
1177+
conf, entry.dvDescriptor, entry.filterTypeOpt, tp)
1178+
val aliveCount = RapidsDeletionVectors.computeNumRowsAlive(
1179+
totalRows, scalaBitmap, entry.filterTypeOpt,
1180+
entry.rowGroupOffsets, entry.rowGroupNumRows)
1181+
SerializedRoaringBitmap(gpuBitmap, aliveCount)
11661182
}
11671183
}
11681184
})
@@ -1196,7 +1212,8 @@ class GpuDeltaParquetFileFormatBase2(
11961212
.map { case (loaded, entry) =>
11971213
new DeletionVector.DeletionVectorInfo(
11981214
loaded.gpuBitmap.getDataHostBuffer(),
1199-
false, entry.rowGroupOffsets, entry.rowGroupNumRows)
1215+
RapidsDeletionVectors.isIfNotContainedRowIndexFilter(entry.filterTypeOpt),
1216+
entry.rowGroupOffsets, entry.rowGroupNumRows)
12001217
}.toArray
12011218
// MakeParquetTableWithDVProducer closes the dataBuffer and the bitmaps in dvInfos.
12021219
MakeParquetTableWithDVProducer(useChunkedReader, maxChunkedReaderMemoryUsageSizeBytes,

0 commit comments

Comments
 (0)