A FULL or LEFT hash join whose probe side gives no row emits its unmatched build rows as one batch, ignoring batch_size
维护者通常 1 天内回复
@lakshaydevoo-arch 已经在做这个了。
开始于 2026年11月12日。
评估
这个 Issue 还没有评估数据。
描述
Describe the bug
A FULL or LEFT join planned as HashJoinExec: mode=CollectLeft whose right (probe) side yields no row emits every unmatched row of its left (build) side as a single batch, whatever datafusion.execution.batch_size is. With one probe row, matching or not, the same rows come in batches of batch_size.
This matters under a memory pool. A SortExec above the join reserves about twice the batch's size (9.2 MB below, for a batch of about 4.6 MB) before it holds anything it could spill, so the statement fails with Resources exhausted ... for ExternalSorter[0] with 0.0 B already allocated, where the same rows in batches of 8192 sort and spill within the pool.
Settings that did not change it: datafusion.execution.batch_size = 1000 (still one batch of 200000), enforce_batch_size_in_joins = true, coalesce_batches = false, hash_join_buffering_capacity = 0 and 1048576.
To Reproduce
With datafusion-python 55.0.0:
import pyarrow as pa
from datafusion import RuntimeEnvBuilder, SessionConfig, SessionContext
rows = 200_000
table = pa.table({"k": pa.array(range(rows), pa.int64()), "v": pa.array(range(rows), pa.float64())})
def context(pool_bytes=None):
config = SessionConfig().with_target_partitions(1).set("datafusion.execution.sort_spill_reservation_bytes", str(2**20))
runtime = RuntimeEnvBuilder().with_disk_manager_os()
if pool_bytes:
runtime = runtime.with_fair_spill_pool(pool_bytes)
ctx = SessionContext(config, runtime)
ctx.register_record_batches("a", [table.to_batches(max_chunksize=8192)])
ctx.register_record_batches("b", [table.to_batches(max_chunksize=8192)])
return ctx
for where in ("k < 0", "k < 1"): # the probe side gives no row, then one row
sql = f"SELECT a.k, a.v, b.v AS w FROM a FULL JOIN (SELECT * FROM b WHERE {where}) b ON a.k = b.k"
sizes = [batch.to_pyarrow().num_rows for batch in context().sql(sql).execute_stream()]
print(f"WHERE {where}: {len(sizes)} batches, largest {max(sizes)}")
try:
sorted_rows = sum(b.to_pyarrow().num_rows for b in context(8 * 2**20).sql(sql + " ORDER BY a.v DESC").execute_stream())
print(f" ORDER BY under an 8 MB fair spill pool: sorted {sorted_rows} rows")
except Exception as error:
print(f" ORDER BY under an 8 MB fair spill pool: {str(error).splitlines()[-1]}")
Output:
WHERE k < 0: 1 batches, largest 200000
ORDER BY under an 8 MB fair spill pool: Resources exhausted: Failed to allocate additional 9.2 MB for ExternalSorter[0] with 0.0 B already allocated for this reservation - 5.3 MB remain available for the total memory pool: fair(pool_size: 8.0 MB)
WHERE k < 1: 25 batches, largest 8192
ORDER BY under an 8 MB fair spill pool: sorted 200000 rows
The full error starts with Not enough memory to continue external sort. Consider increasing the memory limit config .... The plan:
SortExec: expr=[v@1 DESC], preserve_partitioning=[false]
ProjectionExec: expr=[k@0 as k, v@1 as v, v@2 as w]
HashJoinExec: mode=CollectLeft, join_type=Full, on=[(k@0, k@0)], projection=[k@0, v@1, v@3]
DataSourceExec: partitions=1, partition_sizes=[25]
FilterExec: k@0 < 0
DataSourceExec: partitions=1, partition_sizes=[25]
LEFT JOIN gives the same batches ([200000] against batches of 8192). Reading the two tables from Parquet does too; there, with collect_statistics = true, the planner makes the side without rows the build side and the rows come in batches.
Expected behavior
The unmatched build rows emitted in batches of batch_size, as they are once the probe side has given a row, so the ORDER BY above sorts and spills within the pool in both cases.
Additional context
The probe side giving no row is common in practice: a filter on the joined table that matches nothing, such as a key range one table holds no row of. A query then fails or succeeds depending on whether one table happens to be empty for it, with the same memory pool.
Willingness to contribute
I cannot contribute a fix for this bug at this time
- 主要语言
- Rust
- 星标
- 9.6k
- 派生
- 2.6k
- 平均合并
- 3 天 4 小时
- 30 天内合并 PR
- 345
环境准备
在浏览器里用你自己的 GitHub 账号启动这个项目的开发容器。
- 没有 Dockerfile 或 Docker Compose 文件
- 有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
apache/datafusion 的其他 Issue
-
难度 2/5 1-3 小时 新手友好度 72/100
apache/datafusion#27315 · 2 条评论 ·
维护者通常 1 天内回复
-
bug
难度 2/5 1-3 小时 新手友好度 76/100
apache/datafusion#27094 · 1 条评论 ·
维护者通常 1 天内回复
-
Substrait consumer doesn't recognise `shift_left` / `shift_right` from functions_arithmetic.yaml可能已有人在做 @jonasHanhan 于 9 天前认领。 未关闭bug
难度 2/5 1-3 小时 新手友好度 76/100
apache/datafusion#27059 ·
维护者通常 1 天内回复
-
`ProjectionExprs::project_statistics` computes approximate `Statistics::total_byte_size` ignoring column stats可能已有人在做 @joseph-isaacs 于 10 天前认领。 未关闭bug
难度 2/5 1-3 小时 新手友好度 72/100
apache/datafusion#27050 ·
维护者通常 1 天内回复
-
physical-plan: decoding an Unnest or Explain node with an unset field panics可能已有人在做 @geoHeil 于 8 天前认领。 未关闭
难度 2/5 1-3 小时 新手友好度 75/100
apache/datafusion#26999 · 1 条评论 ·
维护者通常 1 天内回复
查看 apache/datafusion 的全部 Issue
相似的 Issue
-
P3
难度 2/5 1-3 小时 新手友好度 70/100
HerringtonDarkholme/hd-lang#1770 ·
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 82/100
jonathanong/no-mistakes#3332 ·
维护者通常 1 天内回复
-
bug
难度 2/5 1-3 小时 新手友好度 78/100
swc-project/swc-next-issues#310 ·
-
bug
难度 2/5 1-3 小时 新手友好度 72/100
swc-project/swc-ai-issues#653 ·
-
难度 2/5 1-3 小时 新手友好度 72/100
lbjlaq/Antigravity-Manager#3937 ·
维护者通常 1 天内回复