Repository navigation
Severe performance regression filtering dataset imported from pyarrow in duckdb>=1.3.0 #52
Copy link
Copy link
Closed
Labels
Description
What happens?
Running a filter of type
WHERE x in (long list of integers)
hangs when running on a dataset imported from Arrow. This is new in DuckDB v1.3, and is also an issue in the latest build of v1.4.
To Reproduce
Script below will
- Write some random data in hive partitioning
- Read the data through pyarrow
- Import it to duckdb
- Run a filter of form "id in (0,4,8,12,16,...,9996)".
Step four hangs in v1.3 and beyond. I am not sure if it actually has anything to do with the hive partitioning.
import time
import duckdb
import pyarrow.dataset as ds
import tempfile
IDS = 10000
class measure_execution_time:
def __init__(self, name: str):
self._name = name
self._start_time = None
def __enter__(self):
self._start_time = time.perf_counter()
def __exit__(self, *args):
assert self._start_time is not None
end_time = time.perf_counter()
elapsed_time = end_time - self._start_time
print(f"{self._name} took {elapsed_time:.2f} seconds")
def write_data(rootdir: str):
duckdb.sql(
f"""
with ids as (
select unnest(range({IDS})) as id
),
dates as (
select unnest(range('2020-01-01'::date, '2021-01-01'::date, '1 day'::interval)) as date
),
data as (
select
*, random() as value, random() as sort_order,
date_part('year', date) as year,
date_part('month', date) as month,
date_part('day', date) as day
from ids cross join dates
order by sort_order
)
select * from data
"""
).to_table("tmptable")
duckdb.sql(f"copy tmptable to '{rootdir}' (format parquet, partition_by (year, month, day))")
duckdb.sql("drop table tmptable")
def read_data(rootdir: str):
dataset = ds.dataset(
rootdir,
partitioning="hive",
format="parquet",
)
# select every fourth id
id_filter = tuple(range(0, IDS, 4))
(
duckdb.from_arrow(dataset)
# This specific line is extremely slow on DuckDB v1.3
.filter(f"id in {id_filter}")
.to_table("tmptable")
)
if __name__ == "__main__":
with tempfile.TemporaryDirectory() as rootdir:
with measure_execution_time("Writing data"):
write_data(rootdir)
with measure_execution_time("Reading data"):
read_data(rootdir)This code, when run with
pip install pyarrow==21.0.0 duckdb==1.2.2
prints
Writing data took 2.52 seconds
Reading data took 1.40 seconds
When run with
pip install pyarrow==21.0.0 duckdb==1.3.2
or
pip install pyarrow==21.0.0 duckdb==1.4.0.dev215
prints
Writing data took 1.92 seconds
and then hangs.
Specifically, it hangs on line 67,
.filter(f"id in {id_filter}")
This seems specific to when importing data from pyarrow. If I read the data using DuckDB's parquet reader, this is not an issue.
OS:
Ubuntu
DuckDB Version:
1.3.2
DuckDB Client:
Python 3.10
Hardware:
No response
Full Name:
Jakob Stigenberg
Affiliation:
Qubos Systematic
What is the latest build you tested with? If possible, we recommend testing with the latest nightly build.
I have tested with a nightly build
Did you include all relevant data sets for reproducing the issue?
Not applicable - the reproduction does not require a data set
Did you include all code required to reproduce the issue?
- Yes, I have
Did you include all relevant configuration (e.g., CPU architecture, Python version, Linux distribution) to reproduce the issue?
- Yes, I have
Reactions are currently unavailable
Activity
Tishj commentedon Sep 15, 2025
@szarnyasg can you please move this to https://github.com/duckdb/duckdb-python ?
Tishj commentedon Sep 15, 2025
Problem here is likely a change made to the TableFilters, gravitating towards the use of ExpressionFilter and that change isn't properly supported by the filter pushdown of arrow performed by the python client.
Hmm, this is an OptionalFilter:
So it's not deemed critical by the filter pushdown mechanism, meaning it likely has a fallback later on that handles it regardless, it's "optional" in the sense that it improves performance but doesn't impact correctness to ignore it.
Currently all OptionalFilters in the pyarrow filter pushdown implementation are ignored
Tishj commentedon Sep 15, 2025
Actually, I'm lying, OptionalFilter is not ignored, and
IN_FILTERis explicitly handled: duckdb/duckdb#16224Tishj commentedon Sep 15, 2025
In general I'd urge you to not use IN in this way, the optimizer doesn't have the context to rewrite this.
This sounds like it could have been
id % 4 == 0 AND id <= 9996, if this is actually representative of your use-casejakkes commentedon Sep 15, 2025
In my use case I just have a big list of numbers.
jakkes commentedon Sep 15, 2025
I take it you were able to reproduce the issue? If not, please let me know. This issue is rather critical to us, as it blocks us upgrading to DuckDB v1.3, and thus from exploring DuckLake too.
evertlammerts commentedon Sep 15, 2025
Could you e.g. do:
That seems to work for me:
In general, huge
IN(...)filters are probably best avoided.jakkes commentedon Sep 16, 2025
I'm sure there are workarounds, but I don't really want to go through my entire codebase to fix this. It seems this is a bug introduced in a recent release, rather than a deliberate drop of functionality?
Also, when I have benchmarked this in the past, running a big
IN(...)filter proved much faster than doing an inner join.Tishj commentedon Sep 16, 2025
I didn't run your reproduction, I just looked at the path to figure out what might be happening
I wonder if somehow the filter pushdown being implemented instead of skipped is actually hurting your performance?
That would depend on how efficient arrow can execute this.
The pushdown is implemented as a bunch of
OR'd comparison expressions, it could useis_ininstead (https://arrow.apache.org/docs/python/generated/pyarrow.compute.is_in.html#)Tishj commentedon Sep 16, 2025
You could potentially verify this by replicating this filter yourself on the arrow table, then scanning the result with duckdb.
jakkes commentedon Sep 17, 2025
Am I understanding correctly that since
v1.2.2,IN(...)filters are now being pushed down into pyarrow? And that this pushdown possibly is really inefficient?Tishj commentedon Sep 19, 2025
If you read my message, you can see that I'm asking you to help confirm/deny this, so we can fix it.
I don't think your comment is very constructive to the conversation here.
jakkes commentedon Sep 19, 2025
Not sure why you got annoyed at my comment. I am just asking if I understood your comments correctly. It seems I did.
If you want me to debug this further inside DuckDB, then you will point me to the relevant file where I should insert the code snippet above. I have never contributed to the DuckDB codebase before.
Is it here:
tools/pythonpkg/src/arrow/arrow_array_stream.cpp?jakkes commentedon Sep 19, 2025
I see you have made a draft PR on this. It is unclear to me if you still want me to test the above somehow. I am happy to do what I can to have this landed before the next release. Please let me know.
jakkes commentedon Sep 25, 2025
Seems it missed v1.4 :( Any chance for 1.4.1 planned for 2025-10-06?
Can try to give this a shot myself during the weekend.
evertlammerts commentedon Feb 26, 2026
Fixed in #335