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:
62
pos/migrations/v3.2_db_performance.sql
Normal file
62
pos/migrations/v3.2_db_performance.sql
Normal file
@@ -0,0 +1,62 @@
|
||||
-- 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();
|
||||
Reference in New Issue
Block a user