L4 passthrough: cache l4_routes in memory instead of querying DB on every HTTPS connection #4
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found during the final whole-branch review of the L4 SNI Passthrough (Phase A) feature (#3-adjacent work, Obsidian plan
2026-07-14-l4-sni-passthrough-phase-a.md).Problem
handle_connection(src/l4.rs) callsdb.list_l4_routes()on every SNI-bearing HTTPS connection through the shared accept loop (main.rs's HTTPS accept loop) — including deployments with zero L4 routes registered. Since browsers always send SNI, this means every normal HTTPS connection (not just L4 passthrough traffic) now triggers a SQLiteSELECT ... FROM l4_routesbefore falling through to normal TLS termination.This adds a per-connection DB round-trip and connection-pool contention (shared with request logging, health checks, cert operations) to all existing HTTPS traffic.
Why it matters
At current homelab scale this is not urgent, but it's a real scalability regression on the shared, universal HTTPS path — not scoped to L4 traffic.
Suggested fix
Cache routes in an
Arc<RwLock<Vec<L4Route>>>(or similar), refreshed on registration/deregistration/update and on config-file reload — mirroring the existingSniResolverpattern (src/tls.rs) which already solves the identical "hot-path-read, occasionally-mutated" problem for TLS certs. Alternatively, at minimum, a fast-path that skips the query entirely when the table is known-empty.Context
Not a correctness bug — behavior is currently correct, this is purely a performance/scalability concern. Reviewer's verdict: does not block merge, logged as a follow-up.
Resolved in
b18bbf4.Every terminating HTTPS connection was calling
db.list_l4_routes()during the SNI peek to decide passthrough routing — a SQLite round-trip on the per-connection hot path. That's now backed by an in-memoryL4RouteCache(RwLock<Arc<Vec<L4Route>>>, atomic Arc-swap on refresh, following theSniResolverpattern). The accept loop reads a lock-freesnapshot(); no DB query per connection.The cache is refreshed at every point routes change, so it never goes stale:
register/update/deregisterL4 route API handlersTests: a discriminating test registers a route in the DB but leaves the cache empty — the connection falls through to
Terminateinstead of tunneling, proving the hot path reads the cache and not the DB — plus anL4RouteCacherefresh/snapshot unit test. Full suite green (150 passing), clippy and fmt clean.