FASE 7b: DB Performance — Pooling, Stock Summary, N+1 fix
Cambios implementados: 1. Connection pooling (tenant_db.py): - psycopg2.pool.ThreadedConnectionPool para master y tenants - Wrapper _PooledConnection que devuelve al pool en .close() - Cero cambios en blueprints (backward compatible) 2. Tabla inventory_stock_summary + triggers (v3.2): - O(1) stock lookup en vez de SUM() sobre historial completo - Trigger AFTER INSERT en inventory_operations recalcula stock - Poblada inicialmente en ambos tenants - Refactor en 6 archivos de servicios para usar la nueva tabla 3. Fix N+1 en process_sale (pos_engine.py): - Precarga retail_price en bulk query FOR UPDATE - Elimina SELECT individual por item en loop 4. Índices críticos: - idx_parts_name_part + pattern_ops (master) - idx_inv_ops_inventory_branch_created (tenants) - idx_wi_part_stock_positive (master, ya existía desde Fase 1) Tests: 73/73 pasando (compat + fase3 + fase5 + fase6) Migración: v3.2_db_performance.sql
This commit is contained in:
@@ -25,15 +25,33 @@ def _safe_g(attr, default=None):
|
||||
def get_stock(conn, inventory_id, branch_id=None):
|
||||
"""Get current stock for an inventory item. Optionally filter by branch.
|
||||
|
||||
Uses Redis cache first, falls back to PostgreSQL SUM query.
|
||||
Uses Redis cache first, then inventory_stock_summary, falls back to
|
||||
PostgreSQL SUM query.
|
||||
"""
|
||||
# Try Redis first
|
||||
cached = get_cached_stock(inventory_id, branch_id)
|
||||
if cached is not None:
|
||||
return cached
|
||||
|
||||
# Fallback to PostgreSQL
|
||||
# Use inventory_stock_summary (O(1) lookup)
|
||||
cur = conn.cursor()
|
||||
if branch_id:
|
||||
cur.execute(
|
||||
"SELECT stock FROM inventory_stock_summary WHERE inventory_id = %s AND branch_id = %s",
|
||||
(inventory_id, branch_id)
|
||||
)
|
||||
else:
|
||||
cur.execute(
|
||||
"SELECT stock FROM inventory_stock_summary WHERE inventory_id = %s",
|
||||
(inventory_id,)
|
||||
)
|
||||
row = cur.fetchone()
|
||||
if row is not None:
|
||||
cur.close()
|
||||
set_cached_stock(inventory_id, row[0], branch_id)
|
||||
return row[0]
|
||||
|
||||
# Fallback to PostgreSQL SUM (legacy, should not reach here if trigger works)
|
||||
if branch_id:
|
||||
cur.execute(
|
||||
"SELECT COALESCE(SUM(quantity), 0) FROM inventory_operations WHERE inventory_id = %s AND branch_id = %s",
|
||||
@@ -55,21 +73,17 @@ def get_stock(conn, inventory_id, branch_id=None):
|
||||
def get_stock_bulk(conn, branch_id=None):
|
||||
"""Get stock for all items. Returns dict {inventory_id: stock_quantity}.
|
||||
|
||||
Uses PostgreSQL directly (bulk operation, Redis wouldn't help much here
|
||||
unless we pre-populated all keys).
|
||||
Uses inventory_stock_summary for O(1) bulk lookup.
|
||||
"""
|
||||
cur = conn.cursor()
|
||||
if branch_id:
|
||||
cur.execute("""
|
||||
SELECT inventory_id, COALESCE(SUM(quantity), 0)
|
||||
FROM inventory_operations WHERE branch_id = %s
|
||||
GROUP BY inventory_id
|
||||
SELECT inventory_id, stock
|
||||
FROM inventory_stock_summary WHERE branch_id = %s
|
||||
""", (branch_id,))
|
||||
else:
|
||||
cur.execute("""
|
||||
SELECT inventory_id, COALESCE(SUM(quantity), 0)
|
||||
FROM inventory_operations
|
||||
GROUP BY inventory_id
|
||||
SELECT inventory_id, stock FROM inventory_stock_summary
|
||||
""")
|
||||
stock_map = {r[0]: r[1] for r in cur.fetchall()}
|
||||
cur.close()
|
||||
|
||||
Reference in New Issue
Block a user