Fix join bug on csv datasources - #13903
Conversation
Signed-off-by: Chong Gao <res_life@163.com>
|
build |
Greptile OverviewGreptile SummaryFixes a corner case where reading an empty CSV file (header only) and joining the result caused an assertion failure in GPU join operations. The fix adds a check in Key Changes:
Technical Context:
The fix prevents step 3 by returning Confidence Score: 5/5
Important Files ChangedFile Analysis
Sequence DiagramsequenceDiagram
participant CSVFile as CSV File (header only)
participant Reader as GpuTextBasedPartitionReader
participant GPU as GPU CSV Decoder
participant Handler as handleResult()
participant Batch as readBatch()
participant Iterator as Iterator (next())
participant Join as Join Logic
Note over CSVFile: c0<br/>(0 data rows)
Reader->>GPU: readToTable(isFirstChunk)
GPU-->>Reader: Table (0 rows, N columns)
alt Before Fix (Bug)
Reader->>Handler: handleResult(table)
Handler-->>Reader: Some(table)
Reader->>Batch: Create ColumnarBatch
Note over Batch: readDataSchema.isEmpty<br/>→ ColumnarBatch(Array.empty, 0)
Batch-->>Iterator: Some(batch with 0 cols, 0 rows)
Iterator->>Join: Pass batch to join
Join->>Join: JoinGathererImpl assertion
Note over Join: FAIL: "data with no columns<br/>should have been filtered"
end
alt After Fix (Correct)
Reader->>Handler: handleResult(table)
Note over Handler: Check: table.getRowCount == 0
Handler->>Handler: table.close()
Handler-->>Reader: None
Reader->>Batch: table.map(...) with None
Batch-->>Iterator: None
Iterator-->>Join: hasNext = false
Note over Join: No batch processed,<br/>join succeeds with empty result
end
|
|
Personally it is not a good idea to fix the join error by changing the CSV reader. This error may happen again if the source is other type, e.g. parquet, orc... I made a fix (#13817) before for similar issues, you can figure out the join type for this case and add it to the whitelist to allow it go into the degenerate path. |
Already tested, other types do not have this error.
IMO, this bug is not related to join types. When reading CSV file with no data(only one row header), the |
| if (table.getRowCount == 0) { | ||
| // CSV reader can return empty table, close it and return None | ||
| // E.g.: CSV file with only header and no data rows, empty table will be returned | ||
| table.close() |
There was a problem hiding this comment.
any concerns that this code can throw instead of returning None even if close fails?
There was a problem hiding this comment.
The row count is zero, it means there is no GPU memory allocated although table has columns.
It is not likely to throw exceptions in practice.
Yeah, this is a fix. |
|
@firestarman I can not construct a right out condition join case as you tried, refer to link. |
|
For conditional right outer join, e.g.: |
|
|
||
|
|
||
| @allow_non_gpu('CollectLimitExec') | ||
| def test_csv_stream_table_is_empty_when_join(std_input_path): |
There was a problem hiding this comment.
I think we want to coordinate with #13938 as they both are near duplicates and fix the problem is slightly different ways. I don't might having both fixes, but I don't want duplicate files checked in.
| with_cpu_session(lambda spark: _create_view(spark)) | ||
|
|
||
| # then do the join on GPU | ||
| with_gpu_session(lambda spark: |
There was a problem hiding this comment.
I would prefer a test that verifies we got the right result. Not just one that shows we didn't crash.
There was a problem hiding this comment.
If using .collect() instead of .show(), the error does not occur.
There was a problem hiding this comment.
You can do a post project for the collect to simulate the "show" action. Just like the additional cast(c as string) at https://github.com/NVIDIA/spark-rapids/blob/main/integration_tests/src/main/python/join_test.py#L442
|
build |
firestarman
left a comment
There was a problem hiding this comment.
LGTM, only some NITs.
| # create views first on CPU | ||
| with_cpu_session(lambda spark: create_views(spark)) | ||
|
|
||
| # limit to 10 rows to produce `LocalLimitExec` node |
There was a problem hiding this comment.
NIT: Better add comment on why this limit node is needed.
|
|
||
| @allow_non_gpu('CollectLimitExec') | ||
| def test_empty_right_outer_side_with_limit(std_input_path): | ||
| built_csv_path = std_input_path + '/t1.csv' |
There was a problem hiding this comment.
NIT: Better add comment on why this file will be read as the built batch ?
fixes #13873
bug analysis
This is a corner case described in the issue #13873.
If reading other type datasources instead of CSV files, the error does not occur.
If using
.collect()instead of.show(), the error does not occur.If the stream data is not empty, the error does not occur.
CSV file with only header and no data rows, empty table(num_rows = 0) will be returned, then after project, the column number of empty table becomes 0, finally empty table(num_rows=0, num_cols=0) occurs in the iterator.
bug fix
If num_rows of table is 0 when reading csv file, return None instead of empty table, the None indicates the iterator of CSV data is empty.
Signed-off-by: Chong Gao res_life@163.com