-
Notifications
You must be signed in to change notification settings - Fork 3.6k
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
WIP: [C++][Parquet] Add RowRanges API #45234
base: main
Are you sure you want to change the base?
Conversation
Thanks for opening a pull request! If this is not a minor PR. Could you open an issue for this pull request on GitHub? https://github.com/apache/arrow/issues/new/choose Opening GitHub issues ahead of time contributes to the Openness of the Apache Arrow project. Then could you also rename the pull request title in the following format?
or
See also: |
@emkornfield @mapleFU @pitrou Could you help check the API (which has been discussed in the Google doc from the PR description)? I will add the |
namespace parquet { | ||
|
||
/// RowRanges is a collection of non-overlapping and ascendingly ordered row ranges. | ||
class PARQUET_EXPORT RowRanges { |
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.
should we mark internal or experimental first? I think this might change fastly
class Iterator { | ||
public: | ||
virtual ~Iterator() = default; | ||
virtual std::variant<IntervalRange, BitmapRange, End> NextRange() = 0; |
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.
Would this virtual function being slow?
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 agree that doesn't look terrific. Perhaps something like:
/// Return a batch of ranges. The returned batch has size 0 iff the iterator is exhausted.
virtual util::span<std::variant<IntervalRange, BitmapRange>> NextRanges() = 0;
} | ||
|
||
private: | ||
decltype(RowRanges::ranges_.cbegin()) iter_; |
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.
should we using IterT = decltype(RowRanges::ranges_.cbegin());
here?
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 haven't looked at the implementation, just commenting on the API.
struct IntervalRange { | ||
/// Start row of the range (inclusive). | ||
int64_t start; | ||
/// End row of the range (inclusive). |
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 would suggest either a length or a exclusive end. Inclusive is more confusing and error-prone IMHO.
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.
We commonly use the (start, length) convention in other places.
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.
(start, length) is good for API but not good for computation because we have to transform it back and forth to (start, start + length).
struct End {}; | ||
|
||
/// \brief An iterator for accessing row ranges in order. | ||
class 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.
We have iteration facilities in arrow/util/iterator.h
, should they be reused instead of defining our own here?
|
||
#pragma once | ||
|
||
#include <variant> |
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.
Also need to include <cstdint>
namespace parquet { | ||
|
||
/// RowRanges is a collection of non-overlapping and ascendingly ordered row ranges. | ||
class PARQUET_EXPORT RowRanges { |
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.
Plural class names are always a bit weird, should we perhaps name it RowSelection
?
static RowRanges MakeSingle(int64_t start, int64_t end); | ||
|
||
/// \brief Make a row range from a list of intervals. | ||
static RowRanges MakeIntervals(const std::vector<IntervalRange>& intervals); |
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.
Could take a span<const IntervalRange>
instead.
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.
Also, perhaps name this FromIntervals
.
/// \brief Make a single row range of [0, row_count - 1]. | ||
static RowRanges MakeSingle(int64_t row_count); | ||
|
||
/// \brief Make a single row range of [start, end]. | ||
static RowRanges MakeSingle(int64_t start, int64_t end); |
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 is basically a simpler version of the IntervalRange-taking constructor?
}; | ||
|
||
/// \brief A range of contiguous rows represented by a bitmap. | ||
struct BitmapRange { |
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.
Is this actually convenient to use? I see it's not supported yet in the implementation...
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.
There are other possible representations, for example:
/// \brief A range of 8 rows represented by a start offset and small indices
struct IndexedRange {
/// Start row of the range
int64_t offset;
/// Indices from `offset` that point to selected rows, in ascending order.
std::array<uint8_t, 8> indices;
};
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 is proposed by @emkornfield from the discussion https://docs.google.com/document/d/1SeVcYudu6uD9rb9zRAnlLGgdauutaNZlAaS0gVzjkgM
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.
Okay, two comments:
- this shows that a Google Doc is really not the right medium to discuss API changes
- I would ask that any API comes with an actual implementation; if it's too complicated to make use of the proposed
BitmapRange
efficiently, then it shows thatBitmapRange
is not the right abstraction
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'm also a little bit skeptical about the effectiveness of mixing BitmapRange
and IntervalRange
. According to my experience, I need either a list of IntervalRange
(like the RowRanges API in parquet-java) or simply a roaring bitmap, but not mixed. The former is used for intermediate result of filter pushdown (e.g. compute selected row ranges of a row group when evaluating different filters) and the latter is for row-level selection/deselection (e.g. apply iceberg position delete file to a base file).
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.
The former is used for intermediate result of filter pushdown (e.g. compute selected row ranges of a row group when evaluating different filters) and the latter is for row-level selection/deselection (e.g. apply iceberg position delete file to a base file).
Ok, so several questions follow:
- at which use case is this API targeted?
- what is the typical bitmap length this is meant to represent?
- do we want a structure to select across an entire row group, or is it better to have one selection per data page, or perhaps a two-level selection (with a page bitmap and then a per-page bitmap for those pages that haven't been pruned)?
For example, given the typical size of a page, an uncompressed bitmap might be fine if selecting inside a page.
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.
- at which use case is this API targeted?
a. Specify the required row ranges when creating a Parquet reader (be it a ParquetFileReader
or RowGroupReader
). This applies to both interval and bitmap forms depending on the use case. Perhaps this is the main reason to use a mixed form.
b. Used internally when computing matched row ranges from filter pushdown evaluation result with the help of page index. This applies to the interval range because operations are usually based on the page level.
c. Advanced row-level filtering discussed in #37559. This should be bitmap range used by ColumnReader
and Decoder
.
- what is the typical bitmap length this is meant to represent?
It depends on the use case above and can be any of num_rows_in_file
, num_rows_in_a_row_group
and batch_size
.
- do we want a structure to select across an entire row group, or is it better to have one selection per data page, or perhaps a two-level selection (with a page bitmap and then a per-page bitmap for those pages that haven't been pruned)?
I don't think we need an intra-page selection since page boundaries are usually not aligned across different columns. I think file-level selection
is easy to use for users and row-group-level selection
is required by the RowGroupReader
implementation.
class Iterator { | ||
public: | ||
virtual ~Iterator() = default; | ||
virtual std::variant<IntervalRange, BitmapRange, End> NextRange() = 0; |
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 agree that doesn't look terrific. Perhaps something like:
/// Return a batch of ranges. The returned batch has size 0 iff the iterator is exhausted.
virtual util::span<std::variant<IntervalRange, BitmapRange>> NextRanges() = 0;
|
||
~IteratorImpl() override = default; | ||
|
||
std::variant<RowRanges::IntervalRange, RowRanges::BitmapRange, RowRanges::End> |
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'm thinking that would variant being slow in storage? What would an internal:
enum class Type {
IntervalRange,
BitmapRange
};
struct Range {
int64_t begin;
/// for Interval this means range
/// for bitmap this is a 64-bits map
int64_t payload;
};
std::vector<Type> types_;
std::vector<Range> ranges_;
I think this is more space efficient than current storage api...
Rationale for this change
This is part of the effort to support efficient filtering to the Parquet reader. The overall design has been discussed at https://docs.google.com/document/d/1SeVcYudu6uD9rb9zRAnlLGgdauutaNZlAaS0gVzjkgM
What changes are included in this PR?
Add a new
RowRanges
API and support basic operations on it.Are these changes tested?
TODO
Are there any user-facing changes?
No.