fix(workflow-operator): fail loudly on unparsable scan rows#6323
fix(workflow-operator): fail loudly on unparsable scan rows#6323eugenegujing wants to merge 1 commit into
Conversation
- Throw a RuntimeException (original exception kept as cause) instead of silently dropping a row that does not parse into the inferred type, in CSVScanSourceOpExec, JSONLScanSourceOpExec, ParallelCSVScanSourceOpExec, and CSVOldScanSourceOpExec. - Add ScanRowParseError helper that identifies the offending column and builds an actionable message (row number, value, column, expected type), with a generic fallback when no single column is identifiable. - Remove the now-dead null filter in CSV and csvOld; keep ParallelCSV's legitimate null-skip paths and filter (only parse failures abort). - Update the scan exec specs to assert the thrown error; add a JSONL malformed-line fallback test. Closes apache#6279
Automated Reviewer SuggestionsBased on the
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #6323 +/- ##
============================================
+ Coverage 60.64% 60.79% +0.14%
- Complexity 3368 3378 +10
============================================
Files 1133 1135 +2
Lines 44141 44219 +78
Branches 4825 4841 +16
============================================
+ Hits 26771 26883 +112
+ Misses 15908 15852 -56
- Partials 1462 1484 +22
*This pull request uses carry forward flags. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
| config | throughput | MB/s | latency | max Δ latest / 7d | |
|---|---|---|---|---|---|
| 🔴 | bs=10 sw=10 sl=64 | 376 | 0.229 | 26,891/31,412/31,412 us | 🔴 +17.0% / 🔴 +121.9% |
| 🔴 | bs=100 sw=10 sl=64 | 796 | 0.486 | 127,291/142,499/142,499 us | 🔴 +6.0% / 🔴 +38.1% |
| 🔴 | bs=1000 sw=10 sl=64 | 898 | 0.548 | 1,110,430/1,231,620/1,231,620 us | 🔴 +6.1% / 🔴 +24.2% |
Baseline details
Latest main 3e08ebb from same runner
| config | metric | PR | latest main | 7d avg | Δ latest | Δ 7d |
|---|---|---|---|---|---|---|
| bs=10 sw=10 sl=64 | throughput | 376 tuples/sec | 428 tuples/sec | 814.14 tuples/sec | -12.1% | -53.8% |
| bs=10 sw=10 sl=64 | MB/s | 0.229 MB/s | 0.261 MB/s | 0.497 MB/s | -12.3% | -53.9% |
| bs=10 sw=10 sl=64 | p50 | 26,891 us | 22,992 us | 12,120 us | +17.0% | +121.9% |
| bs=10 sw=10 sl=64 | p95 | 31,412 us | 29,376 us | 14,792 us | +6.9% | +112.4% |
| bs=10 sw=10 sl=64 | p99 | 31,412 us | 29,376 us | 18,232 us | +6.9% | +72.3% |
| bs=100 sw=10 sl=64 | throughput | 796 tuples/sec | 831 tuples/sec | 1,050 tuples/sec | -4.2% | -24.2% |
| bs=100 sw=10 sl=64 | MB/s | 0.486 MB/s | 0.507 MB/s | 0.641 MB/s | -4.1% | -24.2% |
| bs=100 sw=10 sl=64 | p50 | 127,291 us | 120,061 us | 96,227 us | +6.0% | +32.3% |
| bs=100 sw=10 sl=64 | p95 | 142,499 us | 142,140 us | 103,174 us | +0.3% | +38.1% |
| bs=100 sw=10 sl=64 | p99 | 142,499 us | 142,140 us | 111,033 us | +0.3% | +28.3% |
| bs=1000 sw=10 sl=64 | throughput | 898 tuples/sec | 917 tuples/sec | 1,072 tuples/sec | -2.1% | -16.3% |
| bs=1000 sw=10 sl=64 | MB/s | 0.548 MB/s | 0.559 MB/s | 0.655 MB/s | -2.0% | -16.3% |
| bs=1000 sw=10 sl=64 | p50 | 1,110,430 us | 1,089,107 us | 950,645 us | +2.0% | +16.8% |
| bs=1000 sw=10 sl=64 | p95 | 1,231,620 us | 1,161,156 us | 991,936 us | +6.1% | +24.2% |
| bs=1000 sw=10 sl=64 | p99 | 1,231,620 us | 1,161,156 us | 1,022,498 us | +6.1% | +20.5% |
Raw CSV
config_idx,batch_size,schema_width,string_len,num_batches,total_ms,total_tuples,total_bytes,tuples_per_sec,mb_per_sec,lat_p50_us,lat_p95_us,lat_p99_us
0,10,10,64,20,532.55,200,128000,376,0.229,26890.50,31412.29,31412.29
1,100,10,64,20,2511.38,2000,1280000,796,0.486,127291.10,142498.84,142498.84
2,1000,10,64,20,22270.25,20000,12800000,898,0.548,1110429.60,1231619.69,1231619.69|
Since it is a behavior change, can you open a discussion post for this? |
Yes I have raised one. |
What changes were proposed in this PR?
CSV/JSONL scan sources infer each column's type from only the first
INFER_READ_LIMIT(= 100) rows. When a later row held a value that did not parse as the inferred type, the exec caught the error, mapped the row tonull/None, and filtered it out — silently dropping the entire row with no error, warning, skipped-row count, or log. Every downstream count, aggregate, and join then ran on a silently truncated dataset. The bug is the silence, not the rejection; no comparable engine (Spark, Polars, DuckDB, Arrow) silently drops whole rows.Changes:
Throw a
RuntimeException(original exception kept as cause) instead of silently dropping an unparsable row, inCSVScanSourceOpExec,JSONLScanSourceOpExec,ParallelCSVScanSourceOpExec, andCSVOldScanSourceOpExec. The throw surfaces throughDataProcessor.handleExecutorExceptionas a console error and stops the run (fail-fast, matching the default of Spark FAILFAST / Polars / DuckDB / Arrow).Add a shared
ScanRowParseErrorhelper that identifies the offending column by re-parsing each field and builds a message that leads with the essential facts, so it stays useful even when the console title line truncates, e.g.:A generic fallback covers rows with no single identifiable column (e.g. a malformed JSON line).
Remove the now-dead
nullfilter in CSV and csvOld;ParallelCSVkeeps its legitimatenull-skip paths (exhausted block, all-null/blank line) and its filter — only parse failures abort.Inference logic is unchanged (the 100-row sampling is untouched). Scala only — no UI change.
Behavior-change note for reviewers: workflows that previously ran to completion while silently discarding malformed rows will now fail on the first such row. This is the intended fix (loud over silent) and matches the modern-engine default, but it is a behavior change — see the discussion on #6279. Sample photo is attached below.
Any related issues, documentation, discussions?
Closes #6279
How was this PR tested?
Scan-operator unit tests (updated to assert the thrown error and message; added a JSONL malformed-line fallback test):
Full backend gate in one clean session (lint + format + whole suite):
Manual, in the Texera UI: scanned a CSV whose column is integer for the first 100 rows with a non-integer value (

55.5) at data row 150. The CSV File Scan operator turned red and the console showed the message above; the run stopped at row 150 instead of silently emitting a short result.Was this PR authored or co-authored using generative AI tooling?
Co-authored by: Claude Code (Fable 5)