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
63 lines
2.6 KiB
PL/PgSQL
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();
|