[Ops] Backend hot-path performance batch: Redis pooling, SSE connection release, N+1s, composite index #96
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?
Context
Four verified, independent hot-path inefficiencies, batched into one issue.
(a) Per-call Redis clients.
webapp/backend/app/auth/session.py:24builds a new client viaaioredis.from_url(...)on every call of its helper — invoked bycreate_session(:29),get_session(:38), anddelete_session(:53), i.e. a fresh connect+teardown on EVERY authenticated request. Likewisewebapp/backend/app/services/bot_pubsub.py:31opens a new sync client per publish, andbot_pubsub.py:40,46,59create new async clients per call.(b) SSE holds a pooled DB session.
session_record_stream(webapp/backend/app/routers/sessions.py:673) keeps its dependency-injectedAsyncSessionfor the whole long-lived SSE stream although it is only used for auth/campaign resolution at the top. The engine (webapp/backend/app/database.py:14-18) uses default pool sizing (5 + 10 overflow), so a handful of open streams can exhaust the pool.(c) N+1 queries. Per-slot vote queries in
bot_session_timeslots(webapp/backend/app/routers/bot.py:255-258) and in_auto_close_voting_async(webapp/backend/app/tasks/reminder_tasks.py:931-933); per-NPC appearance queries inlist_milestones(webapp/backend/app/routers/campaigns.py:1280-1285).(d) Missing composite index.
poll_session_remindersruns every minute and the bot's upcoming-session endpoint queries filter onstatus+confirmed_time, butsessionsonly indexescampaign_id(webapp/backend/app/models/session.py:73-77;statusat:84andconfirmed_timeat:89are unindexed).Fix / Spec
publish_bot_event), created once and reused; helpers return clients off the pool. Nofrom_urlper call anywhere in request paths.AsyncSessionLocal()context or by finishing with the injected session before entering the loop) and ensure the DB connection is released before the stream loop starts. Also set explicitpool_size/max_overflowoncreate_async_engine(database.py:14), sized and commented.select(Vote.time_slot_id, Vote.availability, func.count()).where(Vote.time_slot_id.in_(slot_ids)).group_by(...)for both vote sites, and oneIN-based query (orfunc.countgroup-by) for NPC appearances inlist_milestones.sessions (status, confirmed_time); both upgrade and downgrade implemented.Acceptance criteria
grepshows no per-request/per-publishfrom_urlclient creation inauth/session.pyorbot_pubsub.py.References
webapp/backend/app/auth/session.py:24-53webapp/backend/app/services/bot_pubsub.py:31,40,46,59webapp/backend/app/routers/sessions.py:673webapp/backend/app/database.py:14-18webapp/backend/app/routers/bot.py:255-258,webapp/backend/app/tasks/reminder_tasks.py:931-933,webapp/backend/app/routers/campaigns.py:1280-1285webapp/backend/app/models/session.py:73-89Filed from the July 2026 full-project review.