Skip to content

NEAREST is quadratic (correlated LATERAL); use ASOF joins + a fast overlap candidate instead #220

Description

@conradbzura

Description

NEAREST scales quadratically. It transpiles to a correlated CROSS JOIN LATERAL (SELECT ... FROM b WHERE a.chrom = b.chrom ORDER BY ABS(distance) LIMIT k) — for every left row it scans and sorts all of b on that chromosome. Timings (1 thread), found by the benchmark suite:

N GIQL NEAREST k=1 bioframe closest k=1
1e3 0.086 s
1e4 0.87 s
1e5 hangs (> 2 min) 0.083 s
1e6 hangs ~1 s

Expected Behavior

NEAREST should scale roughly log-linearly like a sorted sweep (as bioframe/bedtools do), not quadratically.

Root Cause

The per-left-row correlated ORDER BY distance LIMIT k subquery is an O(n · m · log m) nested loop.

Proposed fix (validated for k = 1)

DuckDB's ASOF JOIN finds the single nearest match on one side in log-linear time. A correct k = 1 nearest-interval combines three candidate sources, then ranks:

  1. left: a ASOF JOIN b ON a.chrom = b.chrom AND a.start >= b.end (nearest interval ending at/before the left start),
  2. right: a ASOF JOIN b ON a.chrom = b.chrom AND a.end <= b.start (nearest starting at/after the left end),
  3. overlap (distance 0): a column-to-column INTERSECTS join — which already reaches DuckDB's IE_JOIN fast path via DuckDB INTERSECTS SEMI/ANTI joins fall back to BLOCKWISE_NL_JOIN instead of IE_JOIN (~80x slowdown) #208 (per-chromosome partitioning),

UNION ALL-ed and reduced with ROW_NUMBER() OVER (PARTITION BY <left key> ORDER BY ABS(distance), b.start, b.end) keeping rn = 1 (the same distance formula and tie-break the current LATERAL uses).

Validated end-to-end: exact row-for-row match with the current NEAREST at 1e4, and 1.51 s at 1e6 (runtime per-chromosome getvariable overlap form) vs the current quadratic hang. The ASOF core alone is 0.98 s at 1e6; the overlap candidate is the only piece that needs the #208 IEJoin treatment.

Scope

k = 1, unstranded, unsigned, no max_distance is the flagship case (bedtools closest / bioframe closest defaults) and is what this fix targets first. k > 1 needs the k-nearest-per-side (a windowed scan, not a single ASOF); strand/signed/max_distance add partitioning/filtering. Those shapes should decline to the current LATERAL plan until extended, mirroring how the INTERSECTS override declines shapes it does not yet handle.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions