Skip to content

Drop _tmp_metadata_row_index column from the output of Delta Scan on GPU - #13991

Merged
razajafri merged 7 commits into
NVIDIA:release/25.12from
razajafri:nv-5729433-prunePartitionSchema
Dec 12, 2025
Merged

Drop _tmp_metadata_row_index column from the output of Delta Scan on GPU#13991
razajafri merged 7 commits into
NVIDIA:release/25.12from
razajafri:nv-5729433-prunePartitionSchema

Conversation

@razajafri

@razajafri razajafri commented Dec 11, 2025

Copy link
Copy Markdown
Collaborator

Description

When returning output from GpuFileSourceScanExec, we leave the temporary row_index column that is generated as part of the DV scan. The presence of this column breaks our assumption that partition columns follow immediately after the output column. This PR removes the _tmp_metadata_row_index column from the output.

Checklists

  • This PR has added documentation for new or modified features or behaviors.
  • This PR has added new tests or modified existing tests to cover new code paths.
    (Please explain in the PR description how the new code paths are tested, such as names of the new/existing tests that cover them.)
  • Performance testing has been performed and its results are added in the PR description. Or, an issue has been filed with a link in the PR description.

@sameerz
sameerz requested a review from a team December 11, 2025 15:17
@razajafri
razajafri changed the base branch from main to release/25.12 December 11, 2025 17:15

@jihoonson jihoonson left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding of this bug is that the _tmp_metadata_row_index is still part of the output of GpuFileSourceScanExec even after DeltaProvider.pruneFileMetadata(). This is messing up with the the logic to find the partition column. My suggestion is to modify DeltaProviderBase.pruneFileMetadata() as below, so that we can have the metadata pruning logic in one place. I think this should be safe as we never use the _tmp_metadata_row_index column anyway. I tested this change manually and it seems to work.

diff --git a/delta-lake/common/src/main/delta-33x-40x/scala/com/nvidia/spark/rapids/delta/common/DeltaProviderBase.scala b/delta-lake/common/src/main/delta-33x-40x/scala/com/nvidia/spark/rapids/delta/common/DeltaProviderBase.scala
index 126de3544..96ff4e0e1 100644
--- a/delta-lake/common/src/main/delta-33x-40x/scala/com/nvidia/spark/rapids/delta/common/DeltaProviderBase.scala
+++ b/delta-lake/common/src/main/delta-33x-40x/scala/com/nvidia/spark/rapids/delta/common/DeltaProviderBase.scala
@@ -162,6 +162,7 @@ abstract class DeltaProviderBase extends DeltaIOProvider {
             dvFilterInput.copy(projectList = inputList.filterNot(_.name == "_metadata"))
               .withNewChildren(Seq(
                 fsse.copy(
+                  originalOutput = fsse.originalOutput.filterNot(_.name == "_tmp_metadata_row_index"),
                   requiredSchema = StructType(
                     fsse.requiredSchema.filterNot(_.name == "_tmp_metadata_row_index")
                   ))(fsse.rapidsConf)))))))

Also, please add some test to cover this bug.

@razajafri

Copy link
Copy Markdown
Collaborator Author

My understanding of this bug is that the _tmp_metadata_row_index is still part of the output of GpuFileSourceScanExec even after DeltaProvider.pruneFileMetadata(). This is messing up with the the logic to find the partition column. My suggestion is to modify DeltaProviderBase.pruneFileMetadata() as below, so that we can have the metadata pruning logic in one place. I think this should be safe as we never use the _tmp_metadata_row_index column anyway. I tested this change manually and it seems to work.

diff --git a/delta-lake/common/src/main/delta-33x-40x/scala/com/nvidia/spark/rapids/delta/common/DeltaProviderBase.scala b/delta-lake/common/src/main/delta-33x-40x/scala/com/nvidia/spark/rapids/delta/common/DeltaProviderBase.scala
index 126de3544..96ff4e0e1 100644
--- a/delta-lake/common/src/main/delta-33x-40x/scala/com/nvidia/spark/rapids/delta/common/DeltaProviderBase.scala
+++ b/delta-lake/common/src/main/delta-33x-40x/scala/com/nvidia/spark/rapids/delta/common/DeltaProviderBase.scala
@@ -162,6 +162,7 @@ abstract class DeltaProviderBase extends DeltaIOProvider {
             dvFilterInput.copy(projectList = inputList.filterNot(_.name == "_metadata"))
               .withNewChildren(Seq(
                 fsse.copy(
+                  originalOutput = fsse.originalOutput.filterNot(_.name == "_tmp_metadata_row_index"),
                   requiredSchema = StructType(
                     fsse.requiredSchema.filterNot(_.name == "_tmp_metadata_row_index")
                   ))(fsse.rapidsConf)))))))

Also, please add some test to cover this bug.

Thanks, I think that is a much cleaner approach. I will update

@razajafri
razajafri force-pushed the nv-5729433-prunePartitionSchema branch from 425e311 to 0cf8bb5 Compare December 11, 2025 19:56
Signed-off-by: Raza Jafri <raza.jafri@gmail.com>
@razajafri
razajafri marked this pull request as ready for review December 11, 2025 21:10
@razajafri
razajafri requested a review from jihoonson December 11, 2025 21:11
@razajafri

Copy link
Copy Markdown
Collaborator Author

build

@jihoonson

Copy link
Copy Markdown
Collaborator

Also, please add some test to cover this bug.

You seem to have missed my previous comment. Please add some test.

@greptile-apps

greptile-apps Bot commented Dec 11, 2025

Copy link
Copy Markdown
Contributor

Greptile Overview

Greptile Summary

This PR fixes a bug in the GPU-accelerated Delta Lake scan with deletion vectors (DV). When GpuFileSourceScanExec returns output, the temporary _tmp_metadata_row_index column was remaining in originalOutput, breaking the assumption that partition columns immediately follow data columns (since output is split at requiredSchema.length). The fix filters _tmp_metadata_row_index from both requiredSchema (already done) and now originalOutput.

  • Added filtering of _tmp_metadata_row_index from fsse.originalOutput in pruneFileMetadata method
  • Added test test_delta_filter_out_metadata_col that creates a partitioned Delta table with deletion vectors and verifies GPU/CPU equality

Confidence Score: 5/5

  • This PR is safe to merge - it's a targeted bug fix with appropriate test coverage.
  • The change is minimal (2 lines of Scala) and follows an existing pattern. The fix is symmetric - filtering the column from originalOutput the same way it was already being filtered from requiredSchema. The new test validates the fix using the established GPU/CPU equality assertion pattern.
  • No files require special attention.

Important Files Changed

File Analysis

Filename Score Overview
delta-lake/common/src/main/delta-33x-40x/scala/com/nvidia/spark/rapids/delta/common/DeltaProviderBase.scala 5/5 Added filtering of _tmp_metadata_row_index from originalOutput in the pruneFileMetadata method, fixing partition column position assumptions when deletion vectors are used.
integration_tests/src/main/python/delta_lake_test.py 5/5 Added test test_delta_filter_out_metadata_col to verify Delta Lake DV scan correctly handles metadata column removal when partitioned tables are read.

Sequence Diagram

sequenceDiagram
    participant Delta as Delta Table (with DVs)
    participant Plan as Query Plan
    participant Prune as pruneFileMetadata
    participant FSSE as GpuFileSourceScanExec
    participant Output as Output Attributes

    Delta->>Plan: Read partitioned table
    Plan->>Plan: Add _tmp_metadata_row_index<br/>for DV filtering
    Plan->>Prune: Match DV scan pattern
    Prune->>FSSE: Filter _metadata from project list
    Prune->>FSSE: Filter _tmp_metadata_row_index<br/>from requiredSchema
    Prune->>FSSE: Filter _tmp_metadata_row_index<br/>from originalOutput (NEW)
    FSSE->>Output: Split at requiredSchema.length
    Output->>Output: Data attrs ++ Partition attrs<br/>(correctly positioned)
Loading

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 file reviewed, no comments

Edit Code Review Agent Settings | Greptile

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional Comments (1)

  1. integration_tests/src/main/python/delta_lake_test.py, line 170 (link)

    style: Unused import - DeltaTable is imported but never used in this test.

2 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

@razajafri

Copy link
Copy Markdown
Collaborator Author

build

USING DELTA
LOCATION '{data_path}'
PARTITIONED BY (region)
TBLPROPERTIES ('delta.enableDeletionVectors' = 'true')

@gerashegalov gerashegalov Dec 11, 2025

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an internal example that is prone to a single record per file where DVs are not necessary upon delete since the whole file can be dropped.

Please use the more robust example for regression test. You can rewrite in SQL if you like

from delta import DeltaTable
spark.range(1000).withColumnRenamed('id', 'l_id').join(spark.range(10)).write.partitionBy('l_id').format('delta').option('delta.enableDeletionVectors', True).save('/tmp/range_dvp4')
dt = DeltaTable.forPath(spark, '/tmp/range_dvp4')
dt.delete('id = 1')
dt.toDF().explain()

@jihoonson

Copy link
Copy Markdown
Collaborator

Can you also update the PR title and description to match with the updated change?

@razajafri razajafri changed the title Prune partition schema should drop _metadata columns Drop _tmp_metadata_row_index column from the output of Delta Scan on GPU Dec 12, 2025
@razajafri

Copy link
Copy Markdown
Collaborator Author

@jihoonson @gerashegalov thank you for reviewing the PR. I have addressed all your concerns PTAL

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional Comments (1)

  1. integration_tests/src/main/python/delta_lake_test.py, line 178-179 (link)

    style: Trailing whitespace on line 178, and line 179 has 7 spaces instead of 8 for indentation.

2 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

jihoonson
jihoonson previously approved these changes Dec 12, 2025

@jihoonson jihoonson left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@razajafri

Copy link
Copy Markdown
Collaborator Author

build

data_path = spark_tmp_path + "/DELTA_DATA"

def create_delta(spark):
two_col_df(spark, int_gen, int_gen).coalesce(1).write.format("delta") \

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is int_gen guaranteed to have a 0 that you are later deleting?

Is coalesce(1) the same as passing num_slices=1 to two_col_df ?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 is guaranteed to be present as it's a special_case in the IntegerGen

.option("delta.enableDeletionVectors", "true") \
.partitionBy("a").save(data_path)

spark.sql(f"DELETE FROM delta.`{data_path}` WHERE b = 0")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we assert that we deleted at least one row by looking at the result df returned by this statement?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good idea. I think we should

spark.sql(f"DELETE FROM delta.`{data_path}` WHERE b = 0")

def read_table(spark):
return spark.sql(f"SELECT * FROM delta.`{data_path}` ")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively to the above suggestions, can we assert the explain output includes DV-enabled scan with __delta_internal_is_row_deleted column?

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional Comments (1)

  1. integration_tests/src/main/python/delta_lake_test.py, line 179-180 (link)

    style: Trailing whitespace on line 179 and inconsistent indentation (7 spaces instead of standard 8).

2 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

@razajafri

Copy link
Copy Markdown
Collaborator Author

build

@gerashegalov gerashegalov left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@sameerz sameerz added the bug Something isn't working label Dec 12, 2025
@razajafri
razajafri merged commit 01090d8 into NVIDIA:release/25.12 Dec 12, 2025
62 checks passed
@razajafri
razajafri deleted the nv-5729433-prunePartitionSchema branch December 12, 2025 06:23
razajafri added a commit that referenced this pull request Dec 12, 2025
### Description

A recently introduced test in #13991 is irrelevant for DBR as we
currently don't support Deletion Vectors on DBR. This PR skips the test
on DBR and for versions of Spark that don't support Deletion Vectors

### Checklists


- [ ] This PR has added documentation for new or modified features or
behaviors.
- [x] This PR has added new tests or modified existing tests to cover
new code paths.
(Please explain in the PR description how the new code paths are tested,
such as names of the new/existing tests that cover them.)
- [ ] Performance testing has been performed and its results are added
in the PR description. Or, an issue has been filed with a link in the PR
description.

---------

Signed-off-by: Raza Jafri <raza.jafri@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants