Skip to content

Cache residuals with stateless evaluators#3666

Open
dossett wants to merge 3 commits into
apache:mainfrom
dossett:stateless-residual-evaluator
Open

Cache residuals with stateless evaluators#3666
dossett wants to merge 3 commits into
apache:mainfrom
dossett:stateless-residual-evaluator

Conversation

@dossett

@dossett dossett commented Jul 15, 2026

Copy link
Copy Markdown

I used CODEX to analyze this problem and create this PR. I've reviewed the code and tests and stand by them. This summary is written completely by a human (me) other than very light copy editing by an LLM.

This PR does two important things. First, it makes the _ResidualEvaluation object stateless to allow for more aggressive reuse. Second, it caches the residual calculated for a given partition spec and partition values. Currently the residual is calculated on a per file basis, even though the residual isn't a function of the file. Collectively, these changes make residual evaluation safer and faster.

_ResidualEvaluation was mutable because its self.struct member was mutated when evaluating a residual. This change moves the mutability into the associated visitor object, which is created on a per file basis and then discarded. Making _ResidualEvaluation stateless therefore makes it safe to reuse concurrently.

The caching is relatively straight forward. A 128 starting size was somewhat arbitrary, but the benefits of caching are significant so I would not want to start too small.

CODEX-generated summary follows:


Residual planning currently creates a mutable evaluator per data file because evaluating one partition changes evaluator state. That avoids unsafe sharing, but it repeatedly prepares equivalent evaluation state and recalculates identical residuals for files whose relevant partition values are the same.

This change makes residual evaluators safe to share by keeping partition-specific state in a short-lived visitor. Scan planning then reuses one prepared evaluator per partition spec and maintains a bounded residual cache keyed by the spec and only the partition fields that can affect the filter. This reduces repeated planning work without allowing high-cardinality, unrelated partition fields to grow or defeat the cache.

Commit structure

  1. bca46fc3 — Make residual evaluation stateless: moves partition-local state into _ResidualEvaluationVisitor, prepares the partition schema once, and adds state-isolation and concurrency tests.
  2. 5676096d — Cache residuals during scan planning: reuses one evaluator per spec and adds a bounded 128-entry cache keyed by relevant partition values, with planner tests covering spec isolation, irrelevant fields, multiple transforms, and eviction.
  3. e9fc9107 — Benchmark residual planning: adds a benchmark using a realistic 15-leaf predicate and both repeated and unique relevant partition values.

Performance

Mean of three runs planning 2,000 files with a 15-leaf predicate:

Relevant partition values main This PR Improvement
7 repeated values 1.117 s 0.004 s ~99.6%
2,000 unique values 1.170 s 0.456 s ~61.0%

The unique-values case intentionally forces cache misses and exercises evaluator reuse independently of cache hits.

Testing

  • pytest tests/expressions tests/table -q — 896 passed
  • pytest tests/benchmark/test_residual_evaluator_benchmark.py -q -s -m benchmark — 2 passed
  • UV_NO_CONFIG=1 .venv/bin/prek run -a — all hooks passed

@dossett dossett marked this pull request as ready for review July 15, 2026 17:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant