Skip to main content
← Back to list
01Issue
BugShippedSwamp Club
Assigneeskeeb

Relationships

#921 Auth hot path: 7 serial Atlas round trips per API-key request; session cookie cache dies after 5 min; session/apikey lookups unindexed

Opened by keeb · 7/2/2026· Shipped 7/2/2026

From the 2026-07-02 full-stack performance audit. Prod topology: app on DO k8s, Mongo on Atlas — ~50-80ms per round trip, so serial round-trip count dominates. Trivial authenticated endpoints average 700-800ms; this issue accounts for it.

CLI/API-key requests pay 7 serial Atlas round trips before doing any work

A trivial authenticated CLI GET (e.g. /api/whoami) pays, in order:

  1. A guaranteed-miss findSession. BetterAuth's bearer plugin takes any dot-less Authorization: Bearer value — including swamp_... API keys — signs it itself and runs getSessioninternalAdapter.findSession against Mongo. It can never match. Triggered at routes/_middleware.ts:191 before the API-key branch runs.
  2. auth.api.verifyApiKeyfindOne on apikey.key (routes/_middleware.ts:246).
  3. An awaited bookkeeping WRITE on every request: verifyApiKey decrements remaining / bumps updatedAt synchronously because deferUpdates is not set in the apiKey plugin options (lib/auth.ts:575-583). better-auth 1.4.18 supports deferUpdates: true (moves it to background).
  4. operativeRepo.findById (routes/_middleware.ts:250-253). 5-7. Inside whoami: listUserCollectivesWithRoles runs member find → organization find → operative_profile find, strictly serial (lib/infrastructure/better-auth-collective-queries.ts:219-271).

7 × 50-80ms ≈ 420-560ms — matches the observed latency floor.

Fixes:

  • Reorder the middleware: when the header starts with Bearer swamp_, skip getSession entirely (it can never be a session token).
  • Set deferUpdates: true on the apiKey plugin.
  • Add a small in-process TTL cache (30-60s, keyed by key hash) mapping API key → synthetic session. API keys are long-lived; steady state becomes ~0-1 round trips.
  • Collapse listUserCollectivesWithRoles to one aggregation: $match {userId}$lookup organization$lookup organization_profile. Supporting indexes already exist (better-auth-collective-queries.ts:426-439).
  • Same 2-serial-reads pattern in the collective-token branch (routes/_middleware.ts:214-224): findByHash then findById$lookup join or fold into the same cache.

session.cookieCache IS enabled (lib/auth.ts:364-369, maxAge 300s) and a cache hit costs 0 DB. But the middleware calls auth.api.getSession({ headers }) without returnHeaders: true (routes/_middleware.ts:191), so when the cache cookie expires and BetterAuth re-issues it via Set-Cookie, we discard it. Result: every authenticated web request after minute 5 of a session pays a findSession round trip that the cache was configured to eliminate.

Fix: call getSession({ headers, returnHeaders: true }) and append the returned Set-Cookie header(s) to the outgoing response. Restores the designed steady state of 1 session read per user per 5 minutes.

No index on session.token or apikey.key

BetterAuth's Mongo adapter creates no indexes; migrations 001-011 don't cover these. Every session lookup (findOne({token})) and API-key lookup (findOne({key})) is a collscan — currently masked by small collections, becomes a second latency cliff on the growth curve.

Fix: migration: session.createIndex({token: 1}, {unique: true}), apikey.createIndex({key: 1}). Verify against prod Atlas first in case they were created manually.

Expected impact

CLI/API requests: 7 serial RTs → ~1 warm (5-7x on every CLI command). Web: removes 50-80ms from every authenticated page/API request after minute 5.

Verified clean (no action): in-memory rate limiter, fire-and-forget track() with 500ms timeout + circuit breaker, shared Mongo client with maxPoolSize 50, tracedRepo/withSpan overhead.

02Bog Flow
OPENTRIAGEDIN PROGRESSSHIPPED+ 1 MOREASSIGNED+ 3 MOREREVIEW+ 4 MOREPR_MERGED+ 1 MORENOTIFICATION_SKIPPED

Shipped

7/2/2026, 11:47:24 PM

Click a lifecycle step above to view its details.

03Sludge Pulse
keeb assigned keeb7/2/2026, 9:29:37 PM

Sign in to post a ripple.