ADR-0002 · Acceso por producto a product_item_view mediante query scopeada (forProduct())¶
- Estado: aceptado
- Decisores: hquintero
- Fecha de la decisión: 2026-06-16 (commit
9a6959a)
Contexto y problema¶
product_item_view es una vista SQL con GROUP BY que consolida la
representación por canal de cada producto. Consultarla con
->where('id', ...) aplica el filtro después de materializar la vista
completa: MariaDB no puede empujar el filtro adentro del GROUP BY, así que
cada lookup construía y ordenaba la vista entera (decenas de segundos). Como
todos los jobs de salida hacen ese lookup, en junio 2026 las colas se atascaron
con miles de jobs muriendo por timeout (post-mortem:
incidente 2026-06).
Opciones consideradas¶
- Método estático
ProductItemView::forProduct($id, $channel)que replica el SQL de la vista con el filtro inyectado adentro delWHERE, antes delGROUP BY. - Seguir consultando la vista y compensar con más workers/timeout.
- Materializar la vista como tabla indexada.
Decisión¶
Opción 1: el SQL de la vista se replica en scopedSelectSql() con vp.id = ?
adentro del cuerpo, de modo que el optimizador arranca por la PK de
product_items y resuelve en ~1ms con resultado idéntico a la vista. Migrados
todos los call sites de los canales de salida y ProductPriceService.
La materialización (opción 3) quedó como optimización futura si más lugares
necesitan listados completos de la vista.
Consecuencias¶
Positivas¶
- Lookup por producto pasa de decenas de segundos a ~1ms; las colas de salida drenan con normalidad.
Negativas / deuda asumida¶
- El SQL inlined debe mantenerse sincronizado a mano con la definición de
la vista (
SHOW CREATE VIEW product_item_view). Si la vista cambia yscopedSelectSql()no, los canales publican datos incorrectos. Hay scripts de auditoría de equivalencia:php audit/audit_scoped_equiv.php 50. - La vista completa sigue siendo cara para listados; solo se resolvió el acceso de a un producto.