Saltar a contenido

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

  1. Método estático ProductItemView::forProduct($id, $channel) que replica el SQL de la vista con el filtro inyectado adentro del WHERE, antes del GROUP BY.
  2. Seguir consultando la vista y compensar con más workers/timeout.
  3. 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 y scopedSelectSql() 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.