Saltar a contenido

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(*) a ATRIBUTOS (tabla pequeña, ~100 filas)
  • Si hay cambios: 1 query SELECT completa + 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.