Skip to content

improvement: index runtime_sort rekey by primary key instead of scanning - #2818

Merged
zachdaniel merged 2 commits into
ash-project:mainfrom
matt-beanland:perf/sort-rekey-index
Aug 2, 2026
Merged

improvement: index runtime_sort rekey by primary key instead of scanning#2818
zachdaniel merged 2 commits into
ash-project:mainfrom
matt-beanland:perf/sort-rekey-index

Conversation

@matt-beanland

Copy link
Copy Markdown
Contributor

Contributor checklist

Leave anything that you believe does not apply unchecked.

  • I accept the AI Policy, or AI was not used in the creation of this PR.
  • Bug fixes include regression tests
  • Chores
  • Documentation changes
  • Features include unit/acceptance tests
  • Refactoring
  • Update dependencies

Summary

maybe_rekey/4 mapped each of the n sorted records back onto the input with Enum.find. As the sorted list is a permutation of the input, Enum.find lands on each position exactly once, so the cost is exactly triangular, n(n+1)/2, regardless of sort order.

Performance has been made linear for the common simple-equality primary key case, where matching is plain term equality over Map.take/2, by first building a key -> record index. Primary keys using semantic equality (Ash.Type.equal?/3) keep the existing scan.

The first part of the PR is 'test only'.

The second part refactors maybe_rekey/4 to the indexed form, preserving both of those behaviours.

Evidence

Measured on Ash main, Elixir 1.20.0 / OTP 27. Each run rekeys a shuffled permutation of N records back onto the input — the shape maybe_rekey/4 sees after every runtime sort — with the before/after outputs asserted equal.

The cost

records before after
500 6.4 ms 0.1 ms
1,000 25.5 ms 0.3 ms
2,000 102.9 ms 0.6 ms
4,000 426.6 ms 1.2 ms
8,000 1,679.5 ms 2.6 ms

Before: cost quadruples as N doubles. After: linear.

Pin the two subtle behaviours before optimising: duplicate primary keys
rekey to the first matching input record, and nil-keyed records match
nothing (including each other). Passes against the current scan-based
implementation.
maybe_rekey/4 mapped each of the n sorted records back onto the input
with Enum.find, an n x n scan. Since the sorted list is a permutation of
the input, Enum.find lands on each position exactly once, making the cost
exactly triangular (n(n+1)/2) regardless of sort order.

For the common simple-equality primary key, matching is plain term
equality over Map.take/2, so build a key -> record index once and look up
in O(1). Resources whose primary key uses semantic equality
(Ash.Type.equal?/3, e.g. ci_string) keep the existing scan.
@zachdaniel
zachdaniel merged commit 7e6af63 into ash-project:main Aug 2, 2026
43 of 49 checks passed
@zachdaniel

Copy link
Copy Markdown
Contributor

🚀 Thank you for your contribution! 🚀

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