Files
Autoparts-DB/pos/migrations/v3.2_db_performance.sql
consultoria-as e3c85fd647 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
2026-04-27 07:34:31 +00:00

63 lines
2.6 KiB
PL/PgSQL

-- FASE 2: DB Performance Optimizations
-- Applies to: master DB (nexus_autoparts) and all tenant DBs
-- ─── MASTER DB ─────────────────────────────────
-- Index for parts name lookups (used by get_part_types, shop supplies)
CREATE INDEX IF NOT EXISTS idx_parts_name_part ON parts(name_part);
CREATE INDEX IF NOT EXISTS idx_parts_name_part_pattern ON parts(name_part text_pattern_ops);
-- Partial index for warehouse_inventory stock lookups (already created in Fase 1,
-- kept here for completeness on fresh installs)
CREATE INDEX IF NOT EXISTS idx_wi_part_stock_positive
ON warehouse_inventory(part_id) WHERE stock_quantity > 0;
-- ─── TENANT DB (run on each tenant) ────────────
-- O(1) stock summary table with trigger-based updates
CREATE TABLE IF NOT EXISTS inventory_stock_summary (
inventory_id INT PRIMARY KEY REFERENCES inventory(id) ON DELETE CASCADE,
branch_id INT REFERENCES branches(id),
stock INT NOT NULL DEFAULT 0,
last_updated TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_iss_branch ON inventory_stock_summary(branch_id);
-- Trigger function: recalculates stock for an item after every operation insert
CREATE OR REPLACE FUNCTION update_stock_summary()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO inventory_stock_summary (inventory_id, branch_id, stock)
SELECT i.id, i.branch_id, COALESCE(SUM(io.quantity), 0)
FROM inventory i
LEFT JOIN inventory_operations io ON io.inventory_id = i.id
WHERE i.id = NEW.inventory_id
GROUP BY i.id, i.branch_id
ON CONFLICT (inventory_id) DO UPDATE SET
stock = EXCLUDED.stock,
branch_id = EXCLUDED.branch_id,
last_updated = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS trg_update_stock_summary ON inventory_operations;
CREATE TRIGGER trg_update_stock_summary
AFTER INSERT ON inventory_operations
FOR EACH ROW EXECUTE FUNCTION update_stock_summary();
-- Composite index for inventory_operations (used by stock + history queries)
CREATE INDEX IF NOT EXISTS idx_inv_ops_inventory_branch_created
ON inventory_operations(inventory_id, branch_id, created_at DESC);
-- Initial population of summary table (idempotent)
INSERT INTO inventory_stock_summary (inventory_id, branch_id, stock)
SELECT i.id, i.branch_id, COALESCE(SUM(io.quantity), 0)
FROM inventory i
LEFT JOIN inventory_operations io ON io.inventory_id = i.id
GROUP BY i.id, i.branch_id
ON CONFLICT (inventory_id) DO UPDATE SET
stock = EXCLUDED.stock,
branch_id = EXCLUDED.branch_id,
last_updated = NOW();