Skip to content

Run joins on the local engine - #91

Merged
clojure-finance merged 1 commit into
2.x.xfrom
local-engine-joins
Oct 20, 2026
Merged

clojure-finance merged 1 commit into
2.x.xfrom
local-engine-joins

Conversation

@buehlmaier

Copy link
Copy Markdown
Collaborator

Stage 3 of replacing Onyx: with clojask.engine=local (or CLOJASK_ENGINE=local), every join runs without starting Onyx, including its group-by phase. The default engine is still Onyx.

Changes

  • engine/start-join (inner, left, right, rolling forward/backward): a's rows are evaluated on the pool; the lookup in b's groups runs on the calling thread, so output is in a's order.
  • engine/start-outer: one group per pool task, first a's groups with their matches in b, then b's unmatched groups.
  • Both take the same arguments as start-onyx-join / start-onyx-outer. JoinedDataFrame.compute picks the engine once; local runs read the dataframe's 300-row batches instead of Onyx's 10 segments per batch.
  • join/join-func returns the lookup function instead of defing it. The global source and the rolling join's memo/res are locals now; defn-join remains as a one-line wrapper for the Onyx sink.

Tests

engine-test gains join, join-select and join-failure-is-staged: inner, swapped inner, left, right, outer, swapped outer, rolling forward, rolling backward and rolling backward with :limit, each on disk and in memory, compared against Onyx with Onyx made unreachable during the local run. The tests were seen failing before the implementation. The failure test checks for a staged ExecutionException and that no .clojask/join files remain.

Suite: 60 tests / 304 assertions, green locally on both engines (JDK 25). CI runs the default engine.

Not changed: an operation that throws inside a join is swallowed on both engines unless :exception true.

Benchmark

Synthetic 1.8M-row CSV joined to a 2.3k-row table (rolling: 22k rows), 4 workers, one run after warm-up, seconds. Sorted outputs are byte-identical between engines.

Join Onyx Local
inner, disk 78.6 70.3
inner, memory 31.5 17.0
left, memory 33.4 17.2
outer, disk 81.6 67.9
outer, memory 31.0 12.2
rolling forward, disk 121.4 85.0
rolling forward, memory 35.3 15.9

With clojask.engine=local, inner, left, right, outer and rolling joins
no longer start Onyx, including their group-by phase.
engine/start-join evaluates a's rows on the pool and does the lookup
in b's groups on the calling thread, in a's order; engine/start-outer
works one group per pool task, first a's groups with their matches,
then b's unmatched groups. Both take the same arguments as the Onyx
drivers they replace. Local joins read the dataframe's batches of rows
instead of Onyx's segments per batch.

join/join-func returns the lookup as a function instead of def'ing it,
so the global source and the rolling join's memo and res are now
locals; defn-join stays as the Onyx sink's wrapper.

engine-test runs every join kind on disk and in memory on both
engines, with and without :select, and checks that a failing join
surfaces as an ExecutionException and leaves no join files.
@clojure-finance
clojure-finance merged commit c535478 into 2.x.x Oct 20, 2026
3 checks passed
@buehlmaier
buehlmaier deleted the local-engine-joins branch October 20, 2026 10:22
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.

2 participants