Arquitectura — Visión general
Diagrama de componentes
graph TD
subgraph MySQL["MySQL (Rosgan DB)"]
HAC_CERT[HAC_CERTIFICADO]
ATRIB_VAL[ATRIBUTOS_VALORES]
ESTAB[REG_CUENTA_ESTABLECIMIENTO]
OFERTAS[HAC_OFERTAS]
MOV[HAC_CERTIFICADO_MOV]
DETALLE[HAC_CERT_DETALLE]
PRE_ORDEN[HAC_PRE_ORDEN]
ATRIBUTOS[ATRIBUTOS]
QUEUE[(meilisearch_sync_queue)]
end
subgraph Worker["sync_worker.py (Docker)"]
CHECK_LOTES[run_lotes_cycle]
CHECK_ATTR[check_atributos_changed]
SYNC_ATTR[sync_atributos]
TRANSFORM[limpiar_certificado]
end
subgraph Meili["Meilisearch"]
INDEX[(índice: certificados)]
SETTINGS[settings <br/> filterable <br/> searchable <br/> sortable]
end
HAC_CERT -->|trigger| QUEUE
ATRIB_VAL -->|trigger| QUEUE
ESTAB -->|trigger| QUEUE
OFERTAS -->|trigger| QUEUE
MOV -->|trigger| QUEUE
DETALLE -->|trigger| QUEUE
PRE_ORDEN -->|trigger| QUEUE
QUEUE -->|SELECT + DELETE| CHECK_LOTES
CHECK_LOTES -->|query JOIN 12 tablas| TRANSFORM
TRANSFORM -->|POST documents| INDEX
ATRIBUTOS -->|updated_at >= NOW - 1min| CHECK_ATTR
CHECK_ATTR -->|sí hay cambios| SYNC_ATTR
SYNC_ATTR -->|PUT settings| SETTINGS
Decisiones de diseño
Cola con PK = certificado_id
La tabla meilisearch_sync_queue tiene certificado_id como Primary Key. Esto hace que INSERT IGNORE sea suficiente para deduplicar: si un lote se modifica 30 veces antes de que el worker corra, se indexa exactamente una vez con el estado final.
Triggers en MySQL, no polling
Las alternativas consideradas fueron:
| Opción | Pros | Contras |
|---|---|---|
| Triggers + cola ✅ | Captura cualquier cambio, deduplicado, atómico con el UPDATE | Requiere permisos TRIGGER en MySQL |
Polling con updated_at |
Sin triggers | Solo funciona en tablas directas, no captura cambios en tablas relacionadas (establecimiento, atributos, etc.) |
| Binlog (Debezium/Maxwell) | Captura todo sin tocar el schema | Overhead operacional alto, requiere replication habilitado |
is_delete en la cola
Cuando se hace DELETE en HAC_CERTIFICADO el trigger usa ON DUPLICATE KEY UPDATE is_delete = 1 en lugar de INSERT IGNORE. Esto garantiza que si el lote estaba encolado para upsert y se elimina antes de que el worker corra, el worker lo eliminará de Meilisearch en lugar de intentar reindexarlo.
Atributos por ventana de tiempo
Los atributos dinámicos (ATRIBUTOS) se detectan por updated_at >= NOW() - INTERVAL 1 MINUTE sin guardar estado. Es simple, funciona, y el costo de 6 queries por minuto a una tabla pequeña es despreciable.
Python + asyncio
El worker usa aiomysql y httpx de forma asíncrona. En la práctica las operaciones son secuenciales (leer cola → query → upsert → borrar cola), pero asyncio permite que el sleep entre ciclos no bloquee recursos y que en el futuro se puedan paralelizar batches sin reescribir el core.