Skip to content

perf: pool de conexiones + frontend optimista (arregla la lentitud)#58

Merged
gonzaloMorenoc merged 7 commits into
mainfrom
feat/mnemo-perf-connpool
Jul 1, 2026
Merged

perf: pool de conexiones + frontend optimista (arregla la lentitud)#58
gonzaloMorenoc merged 7 commits into
mainfrom
feat/mnemo-perf-connpool

Conversation

@gonzaloMorenoc

Copy link
Copy Markdown
Owner

El problema

Producción lenta: /v2/orgs "se quedaba pensando" y con ella toda la app. Análisis: el backend abría una conexión nueva por request (psycopg.connect → SSL handshake al pooler de Supabase → 1–8 s), y el OrgProvider gateaba toda la app esperando /v2/orgs.

Cambios

Backend — pool de conexiones

  • src/db/pool.py: ConnectionPool global (psycopg_pool), singleton lazy, pre-abre conexiones y las reusa. configure centraliza row_factory=dict_row + register_vector.
  • Los 9 repos en el request path (orgs, certify, jira, graph/service, defects, knowledge, repo_ingest, graph/gaps, actions, xray) → _connect usa get_pool().connection() (mismo with self._connect() as conn:).
  • asgi.py: lifespan pre-calienta el pool al arranque (con log si falla, no tumba el boot).
  • requirements: psycopg[binary]psycopg[binary,pool].
  • Efecto: connect por request 1–8 s → ~0 (reuso) una vez caliente.

Frontend — que /v2/orgs no bloquee

  • OrgProvider usa el activeOrgId de localStorage en el 1er render (sin esperar a /v2/orgs) → las páginas arrancan ya; valida/corrige al cargar la lista. staleTime 5 min.

Seguridad (revisado con cuidado — es un pool multi-tenant)

El review final (opus) verificó contra el source de psycopg_pool que los claims set_config(..., is_local=true) (transaction-local) se limpian por doble mecanismo al devolver la conexión → no fugan entre checkouts. Añadido un test de integración (pool max_size=1) que lo confirma con BD real: el 2º checkout no ve el claim del 1º. Membership app-layer y bypass RLS del pooler intactos.

Plan de pruebas

  • Backend sin .env: 819 passed, cov 79.4%, eval_ai 10/10.
  • Frontend: lint:ci 0 + 155 tests + build.
  • Aislamiento de claims: test de integración PASA con BD real.
  • Sin migración; queries y _set_claims sin cambios.

Complementos (fuera de este PR)

El pinger (cold start de HF) y maxDuration=60 (ya en main) siguen siendo necesarios; este PR ataca la latencia por-request.

https://claude.ai/code/session_0198KfgRWvAM8BhiVz24uTok

Backend: pool global (psycopg_pool) reusa conexiones → connect por request 1-8s → <0,3s;
repos _connect→pool.connection() (mismo API); register_vector/dict_row al configure del
pool; lifespan pre-calienta; añade psycopg[pool]. Frontend: OrgProvider usa activeOrgId de
localStorage optimista (no bloquea la app en /v2/orgs) + staleTime largo. Multitenancy
intacta (claims transaction-local). Aborda las capas 3 y 4 del análisis; 0€.

Claude-Session: https://claude.ai/code/session_0198KfgRWvAM8BhiVz24uTok
T1 src/db/pool.py (ConnectionPool singleton, configure=dict_row+register_vector) +
psycopg[pool]; T2 los 7 repos + graph/gaps _connect→get_pool().connection() (quita
register_vector/row_factory inline, mantiene claims); T3 asgi lifespan pre-calienta; T4
OrgProvider optimista (activeOrgId de localStorage sin bloquear + staleTime). Verificación
local=CI.

Claude-Session: https://claude.ai/code/session_0198KfgRWvAM8BhiVz24uTok
- requirements.txt: psycopg[binary,pool]==3.3.3 (añade pool extra)
- src/db/pool.py: singleton ConnectionPool (min=2/max=10), lazy open,
  _configure centraliza dict_row + register_vector por conexión nueva
- tests/test_db_pool.py: singleton + _configure (TDD RED→GREEN)
- no-.env gate: 819 passed, rc=0

Claude-Session: https://claude.ai/code/session_0198KfgRWvAM8BhiVz24uTok
- src/{orgs,certify,jira/integrations}_repository.py: _connect → get_pool().connection()
- src/{graph/service,defects,knowledge,repo_ingest}/repository.py: mismo cambio;
  elimina register_vector + row_factory inline (ya los maneja configure del pool);
  quita import register_vector
- src/graph/gaps.py: _connect(db_url) → get_pool().connection() (db_url queda por compat)
- tests/test_graph_gaps.py: parcheaba psycopg.connect → ahora parchea get_pool con
  _fake_pool(conn_ctx) helper; sin debilitamiento de aserciones

Gate: DATABASE_URL= pytest -m "not integration" → 819 passed, rc=0

Claude-Session: https://claude.ai/code/session_0198KfgRWvAM8BhiVz24uTok
Lee el activeOrgId de localStorage en el primer render (antes de que
/v2/orgs resuelva) y lo valida/corrige cuando la lista llega. Agrega
staleTime: 5 min para reducir refetch. Test O1 cubre la ruta optimista
(pending → stored) y la corrección cuando el id guardado no pertenece a
ninguna org (→ orgs[0]).

Claude-Session: https://claude.ai/code/session_0198KfgRWvAM8BhiVz24uTok
… + log en lifespan

- ActionRepository._connect y XrayConfig._connect migrados a get_pool().connection()
  (eliminado psycopg.connect directo por petición; row_factory/vector los gestiona el pool)
- test_pooled_connection_does_not_leak_claims: test de integración con pool max_size=1
  que verifica que set_config transaction-local no se filtra entre checkouts
- asgi.py lifespan: except Exception:pass → logging.warning con el exc (no swallow silencioso)

Claude-Session: https://claude.ai/code/session_0198KfgRWvAM8BhiVz24uTok
@vercel

vercel Bot commented Jul 1, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
mnemo Ready Ready Preview, Comment Jul 1, 2026 2:43pm

@gonzaloMorenoc gonzaloMorenoc merged commit a9393b1 into main Jul 1, 2026
4 checks passed
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.

1 participant