-
Notifications
You must be signed in to change notification settings - Fork 4.3k
[C++][Parquet] High memory usage on large Parquet file reading with Project and Filter, with Scanner::Scan() #49976
Hello.
I'm trying to fulfill a proof-of-concept for Arrow C++ usage for large Parquet file reading with partial-Project and Filter. (I've done the same earlier, with Java code of Apache Parquet project.)
I've seen an example of a code that has ScannerBuilder::Project() and ScannerBuilder::Filter(). In my case, it uses over 300% of CPU, gathers above 30GB of virtual memory, starts thrashing, and at some moment OOM killer kills it.
Some background on the Parquet file contents.
- For now, I create a Parquet file by myself, and fully control it.
- Its schema contains of D0..D9 (10 non-nullable strings as BINARY), M0..M4 (5 nullable DOUBLEs).
- Each of the D(i) is randomly selected from a relatively small subset of strings (up to thousands).
- M0 has a random value. Other M(j)s are nulls.
- For D5 and D6 I enable dictionary, statistics and Bloom filter.
- For M(j)s I disable dictionary, statistics and Bloom filter.
- The rows are not sorted.
- The Parquet file is uncompressed.
- The Parquet file contains 1 billion of rows, its file size is 9.2 GiB. (The file creation time is ~50 minutes.)
- The original comma-separated file in a tabular form is of 96.5 GiB. (When compressed with "
gzip -9", it makes it 8.1 GiB).
The reading context:
- I project all D(i), M0 and M1 columns. No artificial columns used.
- I filter on: (D5==s51 || D5==s52) && (D6==s61 || D6==s61). (s51, s52, s61, s62 are all strings matching relevant columns.)
- The expected number of rows is ~77 million (out of 1 billion).
- I use
ScannerBuilder::Project(),ScannerBuilder::Filter()and thenScanner::Scan(). - Inside the
Scanner::Scan(), sometimes batches have 0 rows, not sure whether this is relevant.
I trace high memory allocations (500 MiB and above), all doing PoolBuffer::Resize() and/or PoolBuffer::Reserve().
I see in the stacktraces:
parquet::TypedDecoder<parquet::PhysicalType<(parquet::Type::type)6> >::DecodeArrowNonNull(int, parquet::EncodingTraits<parquet::PhysicalType<(parquet::Type::type)6> >::Accumulator*)TransferColumnData(parquet::internal::RecordReader*, std::unique_ptr<parquet::ColumnChunkMetaData, std::default_delete<parquet::ColumnChunkMetaData> >, std::shared_ptr<arrow::Field> const&, parquet::ColumnDescriptor const*, parquet::arrow::ReaderContext const*, std::shared_ptr<arrow::ChunkedArray>*)
All reactions
Replies: 2 comments 1 reply
This looks less like a leak and more like scan concurrency / buffering
Scanner is designed to stream record batches, but with threads and readahead enabled it can still have multiple batches/fragments in flight at the same time. For wide/string-heavy Parquet columns, the decoded in-memory representation can be much larger than the file size
A few things worth trying:
- Disable threading as a diagnostic check:
ARROW_RETURN_NOT_OK(builder.UseThreads(false));
If RSS drops significantly, then the issue is likely parallel scan buffering rather than the filter itself
- Iterate batches instead of materializing a large result:
ARROW_ASSIGN_OR_RAISE(auto scanner, builder.Finish()); ARROW_ASSIGN_OR_RAISE(auto it, scanner->ScanBatches()); for (;;) { ARROW_ASSIGN_OR_RAISE(auto maybe_batch, it.Next()); if (!maybe_batch) break; const auto& batch = maybe_batch->record_batch; // process batch here; don't accumulate all batches unless needed }
- Set a smaller batch size:
ARROW_RETURN_NOT_OK(builder.BatchSize(65536));
- If memory is still high, reduce readahead:
ARROW_RETURN_NOT_OK(builder.FragmentReadahead(1)); // or 0 for the most conservative diagnostic run
This should not prevent normal Parquet predicate/statistics pushdown. The tradeoff is that lower threading/readahead may reduce peak memory at the cost of throughput
All reactions
None of the (1), (3), or (4) alone helped. All three ((1), (3), and (4)) also did not help.
When I've implemented (2) with ScanBatches, with or without (1), (3), and (4), the same issue of high memory usage occurred.
All reactions
-
👀 1
In my case, without ARROW_DEFAULT_MEMORY_POOL=system the too high memory usage is reached before Scan/ScanBatches. Not sure this is relevant.
All reactions
-
🚀 1