Skip to content

perf(db_handler): use LRU eviction for backend prepared statements#980

Merged
v0idpwn merged 8 commits into
supabase:mainfrom
MorganaFuture:MorganaFuture/prepared-statements-backend-rotation
Jun 10, 2026
Merged

perf(db_handler): use LRU eviction for backend prepared statements#980
v0idpwn merged 8 commits into
supabase:mainfrom
MorganaFuture:MorganaFuture/prepared-statements-backend-rotation

Conversation

@MorganaFuture

Copy link
Copy Markdown
Contributor

What kind of change does this PR introduce?

Performance improvement.

What is the current behavior?

When the per-backend prepared statement set hits the 200-statement limit, eviction picks 40 entries at random (Enum.take_random) and sends Close for them. Hot statements get evicted with the same probability as cold ones,
so under churn we can drop a frequently used statement and pay for a fresh Parse round-trip on the next Bind. Relates to #69 / #239.

What is the new behavior?

Replaces the MapSet-backed set with a small LRU storage (Supavisor.Protocol.PreparedStatements.BackendStorage). Recency is bumped on Parse and on Bind for statements already known to the backend. Eviction now drops the least recently used statements first, so hot ones stay around.

No default behavior change still gated by the named_prepared_statements feature flag. Telemetry (prepared_statements_evicted) is unchanged. Covered by unit tests (basic API + edge cases) and a StreamData property test that checks the LRU ordering invariant across arbitrary put/touch/delete sequences.

When the backend prepared statement set hits the 200-statement limit, eviction
picks 40 entries at random and closes them. That can drop hot statements as
readily as cold ones, so the next Bind pays for a fresh Parse round-trip.

Replaces the MapSet with a tiny LRU storage that drops the least recently
used statements first. Recency is bumped on Parse and on Bind for statements
already known to the backend. No default behavior change — still gated by
the named_prepared_statements feature flag.
@MorganaFuture MorganaFuture requested a review from a team as a code owner May 12, 2026 14:14

@v0idpwn v0idpwn left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR, @MorganaFuture, it looks great conceptually and the implementation is sound! I wanted to implement something like this but didn't get around to do it.

I'd like to suggest an architectural change, though: I'd like to make the db handler prepared statement storage backend pluggable. Why: we haven't really adopted this yet in production, and it would be nice to have both implementations at the same time so we can compare them in different scenarios. We could select it based on a feature flag, and eventually settle in a default. This also sets us up for a future in case we want to try a third storage.

WDYT?

Splits BackendStorage into a behaviour with LRU and Random impls,
selectable per-tenant via the "backend_prepared_statements_storage"
feature flag. Default stays Random.
@MorganaFuture

Copy link
Copy Markdown
Contributor Author

Thanks for the PR, @MorganaFuture, it looks great conceptually and the implementation is sound! I wanted to implement something like this but didn't get around to do it.

I'd like to suggest an architectural change, though: I'd like to make the db handler prepared statement storage backend pluggable. Why: we haven't really adopted this yet in production, and it would be nice to have both implementations at the same time so we can compare them in different scenarios. We could select it based on a feature flag, and eventually settle in a default. This also sets us up for a future in case we want to try a third storage.

WDYT?

Great suggestion. BackendStorage is now a behaviour with two impls LRU and Random. Random is default.
Also could you please take a look at this pr 978. I am little annoyed by the failing CI/CD. Let's fix it.

@MorganaFuture MorganaFuture requested a review from v0idpwn May 14, 2026 09:02
Comment thread lib/supavisor/protocol/prepared_statements/backend_storage.ex Outdated

@v0idpwn v0idpwn left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great, just one small note!

@v0idpwn

v0idpwn commented May 14, 2026

Copy link
Copy Markdown
Member

Regarding #978, we will do the bump separately as it requires appups to ship these version upgrades with zero downtime in production. These appups can be a bit nasty to write and test (and potentially not possible), so we will take our time to explore the version bumping in the next couple days.

@v0idpwn v0idpwn added this to the v2.10.0 milestone May 14, 2026
MorganaFuture and others added 4 commits May 21, 2026 23:29
Random doesn't track recency, so "oldest" misnamed the callback.
"evict" describes the operation regardless of strategy.
The defensive 2-tuple clause in handle_auth_pkts moved from line 818 to
822 after the backend prepared-statement storage changes, but the
.dialyzer_ignore.exs entry still pinned 818. That left the real
pattern_match warning unignored and the 818 skip unnecessary, failing
the Dialyze CI job (Skipped: 26, Unnecessary Skips: 1).
@MorganaFuture

Copy link
Copy Markdown
Contributor Author

@v0idpwn So let's merge?

@v0idpwn v0idpwn enabled auto-merge (squash) June 9, 2026 20:20
@v0idpwn v0idpwn merged commit 469aa09 into supabase:main Jun 10, 2026
11 checks passed
@v0idpwn

v0idpwn commented Jun 12, 2026

Copy link
Copy Markdown
Member

Just wanted to thank you for this one @MorganaFuture, sorry for how long it took to merge! 💚

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