Flujo de atributos
Describe cómo los cambios en la tabla ATRIBUTOS se sincronizan con los settings del índice de Meilisearch.
El problema
Meilisearch requiere que los campos por los que se filtra o busca estén declarados explícitamente en filterable-attributes y searchable-attributes. Los atributos de los lotes son dinámicos — se agregan y modifican desde el backoffice de Rosgan. Hay que mantener esos settings actualizados automáticamente.
Diagrama
sequenceDiagram
actor Admin
participant MySQL
participant Worker as sync_worker.py
participant Meili as Meilisearch
Admin->>MySQL: INSERT INTO ATRIBUTOS (nombre='Dentición', tag='[dent]', tipo='select')
Note over MySQL: updated_at = NOW() automáticamente
loop cada 10 segundos
Worker->>MySQL: SELECT COUNT(*) FROM ATRIBUTOS\nWHERE updated_at >= NOW() - INTERVAL 1 MINUTE
MySQL-->>Worker: count = 1
Worker->>MySQL: SELECT id_atributo, tag, tipo_atributo\nFROM ATRIBUTOS WHERE tag != '' AND deshabilitado = 0
MySQL-->>Worker: [...todos los atributos activos...]
Worker->>Worker: normalizar_clave('[dent]') → 'attr_dent'
Worker->>Worker: Construir listas searchable + filterable + sortable
Worker->>Meili: PUT /indexes/certificados/settings/filterable-attributes
Worker->>Meili: PUT /indexes/certificados/settings/searchable-attributes
Worker->>Meili: PUT /indexes/certificados/settings/sortable-attributes
end
Ventana de tiempo sin estado
El chequeo usa NOW() - INTERVAL 1 MINUTE directamente en MySQL. No hay ningún estado guardado en memoria ni en disco:
async def check_atributos_changed(pool) -> bool:
async with pool.acquire() as conn:
await conn.commit() # ver datos frescos
async with conn.cursor() as cur:
await cur.execute(
"SELECT COUNT(*) FROM ATRIBUTOS "
"WHERE updated_at >= NOW() - INTERVAL %s MINUTE",
(settings.atributos_ventana_min,)
)
(count,) = await cur.fetchone()
return count > 0
Si el worker se reinicia, el próximo ciclo detecta los cambios de hasta 1 minuto atrás de todas formas.
Clasificación de atributos
No todos los atributos se incluyen en searchable-attributes — solo los de tipo texto tienen sentido para búsqueda full-text:
tipo_atributo |
filterable | searchable |
|---|---|---|
select |
✅ | ✅ |
text |
✅ | ✅ |
textarea |
✅ | ✅ |
autocomplete |
✅ | ✅ |
check |
✅ | ❌ |
number |
✅ | ❌ |
date |
✅ | ❌ |
Los atributos con deshabilitado_atributo = 1 no se incluyen en ninguna lista.
Normalización de tags
El tag de cada atributo se normaliza para ser un nombre de campo válido:
| Tag original | Campo en Meilisearch |
|---|---|
[raza] |
attr_raza |
[desc.lote] |
attr_desc_lote |
[est repr] |
attr_est_repr |
[Categoría] |
attr_categoria |
Reglas aplicadas en orden:
1. Sacar corchetes [ y ]
2. Reemplazar . y espacios por _
3. Lowercase
4. Normalizar acentos (á→a, é→e, etc.)
5. Colapsar __ en _
6. Agregar prefijo attr_
Costo operacional
Con WORKER_INTERVAL=10 y ATRIBUTOS_VENTANA_MIN=1:
- 6 ciclos por minuto
- Cada ciclo: 1 query
COUNT(*)aATRIBUTOS(tabla pequeña, ~100 filas) - Si hay cambios: 1 query
SELECTcompleta + 3 PUT a Meilisearch - Tiempo que dura el "ruido" después de un cambio: exactamente 1 minuto (6 ciclos)
El costo es despreciable. No vale la complejidad de agregar un campo sincronizado o guardar estado.