You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Major performance regression in reading partitioned Parquet data on master #1363
Reading (partitioned) Parquet data got slower, when executing parquet data.
It seems to have started with #1010 - the commit before doesn't have the performance regression.
This is visible when running TPCH benchmarks, for example, executing query 6 became much slower.
I am not sure what the cause is - it would help to find the commit where the "slowness" was introduced.
Looking back at the earlier results I posted - it looks the main difference is that the original arrow / parquet got slower. I am not sure what the cause is.
Hi @Dandandan, sorry if its #1010 that created this regression. I think I only ran the TPCH on CSV for performance testing. Did you do your tests on the latest of master? after #1347 was merged?
For TPCH I remember collecting stats doesn't have a big effect now, as data is very evenly distributed, and I think also doesn't take a long time to collect those.
To reproduce:
Create partitioned Parquet dataset (slowness seems to increase with nr of partitions? - not 100% sure yet)
Run some queries cargo run --release --bin tpch -- benchmark datafusion --iterations 5 --path [data] --format parquet --query 6 --batch-size 8192 -p 16
changed the title [-]Major performance regression in reading Parquet on master[/-][+]Major performance regression in reading partitioned Parquet data on master[/+]on Nov 26, 2021
The abstraction requires the use of dynamic dispatch on the reader (fn sync_chunk_reader(&self,start: u64, length: usize) -> Result<Box<dyn Read + Send + Sync>>), which can indeed reduce performances if read() is called a lot. Actually, now that I'm thinking, some old memories are coming back: if I remember correctly, 2 years ago when I was first playing with the parquet reader I noticed that something like this was happening. read() was called in a way that it was often getting only 1 byte at a time.
EDIT: I tried reverting just that part back to the original std::fs::File wrapped with a SerializedFileReader and the performances remain as bad! Hypothesis invalidated! The issue is not that dynamic dispatch 😕.
Hm - maybe too early. it seems that for query 6 this brings it down to the roughly the earlier performance, but for other queries it's doing even worse 🤔
Interesting fact:
Before #1010, the read_partition method was where 99% of the time was spent:
Query read_partition took 191.6 ms
Query 6 iteration 0 took 193.1 ms
Query read_partition took 178.3 ms
Query 6 iteration 1 took 180.2 ms
Query read_partition took 179.7 ms
Query 6 iteration 2 took 181.2 ms
After #1010, this is not the case anymore, but read_partition itself is also slower:
Query read_partition took 253.3 ms
Query 6 iteration 0 took 354.6 ms
Query read_partition took 252.4 ms
Query 6 iteration 1 took 354.6 ms
Query read_partition took 258.9 ms
Query 6 iteration 2 took 357.8 ms
So everything is slowed down, both on the thread where the reading occurs and in the Tokio threads... WeEEeeIiiiIrd 😅
I'm not sure whether it is relevant or not. In the current implementation, the sync_chunk_reader() method was
invoked for every parquet column chunk which will cause lots of unnecessary file open and seek calls.
fn sync_chunk_reader(
&self,
start: u64,
length: usize,
) -> Result<Box<dyn Read + Send + Sync>> {
// A new file descriptor is opened for each chunk reader.
// This okay because chunks are usually fairly large.
let mut file = File::open(&self.file.path)?;
file.seek(SeekFrom::Start(start))?;
let file = BufReader::new(file.take(length as u64));
Ok(Box::new(file))
}
TPCH Q1:
Read parquet file lineitem.parquet time spent: 590639777 ns, row group count 60, skipped row group 0
total open/seek count 421, bytes read from FS: 97028517
memory alloc size: 1649375985 memory alloc count: 499533 during parquet read.
I'm not sure whether it is relevant or not. In the current implementation, the sync_chunk_reader() method was
invoked for every parquet page which will cause lots of unnecessary file open and seek calls.
fn sync_chunk_reader(
&self,
start: u64,
length: usize,
) -> Result<Box<dyn Read + Send + Sync>> {
// A new file descriptor is opened for each chunk reader.
// This okay because chunks are usually fairly large.
let mut file = File::open(&self.file.path)?;
file.seek(SeekFrom::Start(start))?;
let file = BufReader::new(file.take(length as u64));
Ok(Box::new(file))
}
TPCH Q1:
Read parquet file lineitem.parquet time spent: 590639777 ns, row group count 60, skipped row group 0
total open/seek count 421, bytes read from FS: 97028517
memory alloc size: 1649375985 memory alloc count: 499533 during parquet read.
Query 1 iteration 0 took 679.9 ms
I think that might be relevant. How about opening a new issue to track improving on that (reusing the file descriptor)?
Activity
[-]Parquet reading got slower in newer version[/-][+]Parquet reading got slower on master[/+][-]Parquet reading got slower on master[/-][+]Major performance regression in reading Parquet on master[/+]Dandandan commentedon Nov 26, 2021
@rdettai It seems the PR #1010 introduced this regression.
Do you have any idea maybe what the cause could be?
rdettai commentedon Nov 26, 2021
Hi @Dandandan, sorry if its #1010 that created this regression. I think I only ran the TPCH on CSV for performance testing. Did you do your tests on the latest of master? after #1347 was merged?
Dandandan commentedon Nov 26, 2021
@rdettai
No problem - just want to figure out what could be the reason :)!
So far I tested:
For TPCH I remember collecting stats doesn't have a big effect now, as data is very evenly distributed, and I think also doesn't take a long time to collect those.
To reproduce:
cargo run --release --bin tpch -- benchmark datafusion --iterations 5 --path [data] --format parquet --query 6 --batch-size 8192 -p 16[-]Major performance regression in reading Parquet on master[/-][+]Major performance regression in reading partitioned Parquet data on master[/+]xudong963 commentedon Nov 26, 2021
Off-topic: how about introducing bench results in every PR?
rdettai commentedon Nov 26, 2021
One possible reason might be that #1010 introduces the use of the: https://github.com/apache/arrow-datafusion/blob/414c826bf06fd22e0bb52edbb497791b5fe558e0/datafusion/src/physical_plan/file_format/parquet.rs#L408-L411ObjectStoreThe abstraction requires the use of dynamic dispatch on the reader (fn sync_chunk_reader(&self,start: u64, length: usize) -> Result<Box<dyn Read + Send + Sync>>), which can indeed reduce performances ifread()is called a lot. Actually, now that I'm thinking, some old memories are coming back: if I remember correctly, 2 years ago when I was first playing with the parquet reader I noticed that something like this was happening.read()was called in a way that it was often getting only 1 byte at a time.EDIT: I tried reverting just that part back to the original
std::fs::Filewrapped with aSerializedFileReaderand the performances remain as bad! Hypothesis invalidated! The issue is not that dynamic dispatch 😕.Dandandan commentedon Nov 26, 2021
@rdettai It seems changing
ReadintoBufReadeverywhere seems mostly to recover the performance here. Does that make sense to you to do at this place?Dandandan commentedon Nov 26, 2021
Hm - maybe too early. it seems that for query 6 this brings it down to the roughly the earlier performance, but for other queries it's doing even worse 🤔
Dandandan commentedon Nov 26, 2021
FTR - this is the entire diff:
rdettai commentedon Nov 26, 2021
Interesting fact:
Before #1010, the
read_partitionmethod was where 99% of the time was spent:After #1010, this is not the case anymore, but
read_partitionitself is also slower:So everything is slowed down, both on the thread where the reading occurs and in the Tokio threads... WeEEeeIiiiIrd 😅
Dandandan commentedon Nov 26, 2021
I opened a PR here https://github.com/apache/arrow-datafusion/pull/1366/files so you can have a look too.
It seems the slowdown of query 10 might be unrelated to the reading performance.
Dandandan commentedon Nov 27, 2021
Yes - some continuous benchmarking would definitely be something valuable and could avoid regressions.
mingmwang commentedon Dec 23, 2021
Why the issue was closed? Was the issue resolved and performance come back ?
Dandandan commentedon Dec 23, 2021
Yes, according to my benchmarks performance was around the same as before introducing the regression.
mingmwang commentedon Dec 23, 2021
I'm not sure whether it is relevant or not. In the current implementation, the sync_chunk_reader() method was
invoked for every parquet column chunk which will cause lots of unnecessary file open and seek calls.
FilePageIterator.next() -> FileReader.get_row_group().get_column_page_reader() -> SerializedRowGroupReader.get_column_page_reader() -> ChunkObjectReader.get_read() -> LocalFileReader.sync_chunk_reader()
TPCH Q1:
Read parquet file lineitem.parquet time spent: 590639777 ns, row group count 60, skipped row group 0
total open/seek count 421, bytes read from FS: 97028517
memory alloc size: 1649375985 memory alloc count: 499533 during parquet read.
Query 1 iteration 0 took 679.9 ms
Dandandan commentedon Dec 23, 2021
I think that might be relevant. How about opening a new issue to track improving on that (reusing the file descriptor)?
mingmwang commentedon Dec 23, 2021
Sure, I can open a new issue to track it. But please help to take a look and confirm my understanding is correct.