-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added support to skip unparseable records in the csv record reader #11487
Conversation
bb65b6d
to
97bd3f7
Compare
Codecov Report
@@ Coverage Diff @@
## master #11487 +/- ##
============================================
+ Coverage 62.92% 63.06% +0.13%
- Complexity 1108 1109 +1
============================================
Files 2318 2320 +2
Lines 124328 124551 +223
Branches 18980 19016 +36
============================================
+ Hits 78234 78546 +312
+ Misses 40539 40418 -121
- Partials 5555 5587 +32
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 82 files with indirect coverage changes 📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more |
|
||
|
||
/** | ||
* Record reader for CSV file. | ||
*/ | ||
@NotThreadSafe |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does other record reader has this annotation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, they do not. I have added this as a good practice. This is purely for documentation.
//validate header for the delimiter before splitting | ||
validateHeaderForDelimiter(delimiter, csvHeader, format); | ||
format = format.withHeader(StringUtils.split(csvHeader, delimiter)); | ||
// do not validate header if using the line iterator |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that there's no harm in running validateHeaderForDelimiter
? It's reading the first line and check the delimiter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Following problems exist with the current validation.
- It calls
iterator.hasNext()
which is what we are trying to avoid in the first place - It checks if the record has multiple values. User can pass in header and the record can have a single line which is valid. Hence, this check is not valid.
- It checks if delimiter is present in header. This is also not valid if the file is like this.
id
100
As all these checks are harmful, I have not made use of it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in that case, we should change the validation code. My concern is more on the feature parity. Running validation on the existing approach while not running it on line iterator will give us inconsistent behavior.
// do not validate header if using the line iterator | ||
if (_useLineIterator) { | ||
String[] header = StringUtils.split(csvHeader, delimiter); | ||
setHeaderMap(header); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This assumes that the header will be in good format. Why we are not doing the validation in this case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yet to determine what would be the correct validation for a header line. Existing validation does not look correct.
...-format/pinot-csv/src/main/java/org/apache/pinot/plugin/inputformat/csv/CSVRecordReader.java
Outdated
Show resolved
Hide resolved
...-format/pinot-csv/src/main/java/org/apache/pinot/plugin/inputformat/csv/CSVRecordReader.java
Show resolved
Hide resolved
...-format/pinot-csv/src/main/java/org/apache/pinot/plugin/inputformat/csv/CSVRecordReader.java
Show resolved
Hide resolved
...mat/pinot-csv/src/test/java/org/apache/pinot/plugin/inputformat/csv/CSVRecordReaderTest.java
Show resolved
Hide resolved
...-format/pinot-csv/src/main/java/org/apache/pinot/plugin/inputformat/csv/CSVRecordReader.java
Show resolved
Hide resolved
} else { | ||
// Read the first line and set the header | ||
String headerLine = _bufferedReader.readLine(); | ||
String[] header = StringUtils.split(headerLine, _format.getDelimiter()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why don't we use the same approach as the regular csv for reading the header? I don't think that we need the custom handling here? (header is anyway the first line of the file. So, we can first consume header using the shared code and then diverge?)
Otherwise, I would see different behavior for parsing the header in some edge cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I doubt if the regular csv parser respects this config option at all. I see it marked as TODO
in the library version that we are using.
The line iterator respects this property and following is the behavior:
- If header is supplied by the client and skip header is not set – it is considered that the input file has just data records.
- if header is supplied by the client and skip header is set – it is considered that the input file contains a header record but the user wishes to override it.
- if header is not provided – first line is considered as the header.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's investigate the following and address in the follow-up PR.
- check if newer library version has the proper wiring for these options
- we should keep the same behavior across 2 approaches.
- do the same investigation on
validateHeaderForDelimiter
- add the unit tests for these configs (skip header)
97bd3f7
to
c445cbd
Compare
c445cbd
to
330faec
Compare
Problem
The CSVRecordReader in place uses the apache common-csv library under the hood to iterate over the records. The default and the only iterator from the commons-csv library throws an exception on the
hasNext()
method. This makes it impossible to iterate over the records whenever an unparseable record is encountered. There is no way to override this iterator either as theCSVParser
class is declared final and the iterator is internal to theCSVParser
class.Open issue with commons-csv library.
Solution
To work around the above problem, the change here is to use an alternate iterator by getting hold of the underlying buffered reader and modifying the methods
next()
andhasNext()
in theCSVRecordReader
. With this change, thehasNext()
method would not throw an exception thereby allowing to make progress even when unparseable records are encountered. The drawbacks to this approach are: 1) data loss and 2) Reduced ingestion throughputHowever, there are situations when this option is desirable and making progress is more important. Under such scenarios, the flag
skipUnParseableLines
can be set to make use of the line based iterator.Alternate Solutions
Following were the alternate options considered:
OpenCSV
which allows to plugin a custom iterator. [Not considered due to: 1) maintenance overhead 2) regression and 3) open vulnerability with the library]Testing
The change is supplemented with unit tests which ensure the regression is not caused and the new changes are covered. Additionally, tested the performance on a 200MB file and the current parser took on average 5seconds(ran 20 times) and the new parser took on average 7seconds (ran 20 times).