Causa raíz, evidencia de logs y fix propuesto — TsArticulos / Dino, sucursal 1
2500176000005, sucursal 1 (QUESO CREMOSO SAINT PAULIN TROZ X KG, código interno 3500176).dm_artic en orden distinto.expireAfterWrite) es lo que alterna el tráfico entre el camino correcto y el incorrecto.
Ticket a cargo de sprados / jsalvini. Última modificación: 21/09/2026 10:19:11 por lbevolo.
Solo para que puedan ver el lunes este problema que surgió este finde. Hubo un cambio de precio de un producto en el cual no impacto en las cajas de autocobros. Luego de consultar con tipre, nos sugirió que reiniciemos el servicio TsArticulos, en el cual luego de unos minutos se probro en las pantallas y ya pasaba sin problemas. [...] Antes de reiniciar el servicio salía a 1.05 , al reiniciar empezó a salir 8490 pesos.
Adjunto del ticket: Autocompra_ReclamoDino20260919.png.
Se informó que la actualización de cache para Autocompra a partir de novedades y completos ya estaba habilitada; el reinicio se sugirió por falta de acceso remoto en el momento del reclamo.
Log del proceso de novedades (sender) del 18/09 11:03:54:
[REFRESH][ARTIC] POST 172.17.11.210:48082/articulos/v1/cache/refresh -> HTTP 202 {"success":true,...,"status":"STARTED","refreshId":"e8de4533-a7bc-4e6a-ac0f-3888e71682f8"}
[REFRESH][PROMO] POST 172.17.11.210:48085/promos/v1/promociones/update -> HTTP 202
(Un primer log pegado en el ticket, fechado 28/08, fue corregido por el autor del ticket por este del 18/09.)
| Ticket de reclamo + captura | 19–21/09/2026 — síntoma exacto (1.05 vs 8490.00, EAN, efecto del reinicio). |
| Log del proceso de novedades (sender) | 18/09 11:03 — solo prueba que el POST fue aceptado (202), no que el refresh terminó. |
| logs/tsarticulos.log.2026-09-17.0 (3.5 MB) | 17/09 — eventos de refresh, reinicios, y cada respuesta para el EAN. |
| logs/tsarticulos.log.2026-09-18.0 (4.7 MB) | 18/09 — ídem. |
| logs/tsarticulos.log.2026-09-19.0 (5.9 MB) | 19/09 — ídem. |
| logs/tsarticulos.log.2026-09-20.0 (6.8 MB) | 20/09 — ídem. |
| tsarticulos.log (1.1 MB) | 21/09 — ídem. |
| Código fuente TsArticulos | branch main, commit e64bf7f "v20260813- Add iVersion" — ArticuloCacheService, ArticuloCacheAdminController, ArticuloCacheRefreshScheduler, ArticulosCacheLoadRepositoryImpl, ArticulosRepository, ArticuloCacheManager, CacheConfig, application.yml. |
POST /cache/refresh → startRefresh → executor asíncrono
→ buildAndSwapCache). Hallazgo: el HTTP 202 se devuelve también para SKIPPED_ALREADY_RUNNING;
el log del sender no prueba finalización.
refreshRunning=true y todos los siguientes se omiten.cean|inrosuc en dm_artic.>>> Inicio refresh,
Refresh de cache finalizado|fallido, Cache activa actualizada, ERROR.
Resultado: todos los refresh del 19–20/09 finalizaron OK (2–3 s, 84058 artículos), ningún fallido,
ningún skip → H1, H2 y H3 descartadas. Único ERROR relevante: 18/09 16:53:51, arranque fallido
por SQL Server no disponible (localhost:1433 connection refused), recuperado a las 16:54:39.
precioTotal=1.05 → 19 respuestas el 19/09 y 6 el 20/09, todas para el
EAN 2500176000005 sucursal 1.
id distintos para el mismo EAN
(826360887 → 1.05; 826361083 → 8490.00) y correlación exacta de cada respuesta 1.05
con un Producto agregado a cache previo (camino on-miss) → H4 confirmada.
expireAfterWrite(4h) en CacheConfig; el primer 1.05 aparece
a las 11:09, 4h09 después del preload de 07:00; tras el reinicio de 15:54 vuelve el 1.05 a las 20:59.
POST /cache/refresh entre el 18/09 17:13:49 y el
20/09 21:40:22.
iid se infieren de las respuestas
logueadas por el servicio, no de una lectura directa de dm_artic; ejecutar el SQL de duplicados
(sección 6, F4) para confirmarlo. Tampoco se dispone del log del sender (novedades) del 19/09.
La tabla dm_artic tiene dos filas para el mismo cean + inrosuc:
el id 826360887 con precio 1.05 (iVersion 28771) y el id 826361083 con precio 8490.00.
Dos caminos distintos del código resuelven esta ambigüedad de forma diferente, y solo uno de ellos es determinista.
ArticulosCacheLoadRepositoryImpl) ordena ORDER BY dm.inrosuc, dm.iid.ArticuloCacheService.loadSucursalInCache hace targetCache.put(key, row) fila por fila; la última escritura pisa a las anteriores.iid, el id mayor (826361083, 8490.00) siempre queda al final y gana.ArticuloCacheService.getArticuloByEanAndSucursal (línea ~74) llama a articulosRepository.findByEanSuc(ean, sucursal).stream().findFirst().ArticulosRepository.findByEanSuc no tiene ORDER BY; SQL Server es libre de devolver cualquiera de las dos filas primero.826360887 (1.05).CacheConfig (línea 20): Caffeine.newBuilder().expireAfterWrite(4, TimeUnit.HOURS).put() —precarga u on-miss— la entrada expira.
La línea de tiempo reconstruida a partir de logs/tsarticulos.log.2026-09-19.0 y
logs/tsarticulos.log.2026-09-20.0 muestra cómo el reinicio manual del 19/09 solo
desplazó el problema 4 horas, en lugar de resolverlo.
POST /cache/refresh, trigger MANUAL) llegaban cada ~5 minutos.POST /cache/refresh entre el 18/09 17:13:49 y el 20/09 21:40:22.HTTP 202 STARTED, pero eso no prueba que el refresh haya terminado:
el controller también devuelve 202 cuando el estado es SKIPPED_ALREADY_RUNNING (con success:false en el body).
Un 202 en el log del emisor no es evidencia de que la cache se haya actualizado.
La regla de desempate "gana el iid mayor" está pendiente de confirmación por el equipo.
Es la que ya usa el camino de precarga; este plan solo la hace consistente en on-miss.
Agregar ORDER BY dm.iid DESC a la consulta nativa de ArticulosRepository.findByEanSuc
para que on-miss resuelva duplicados con el mismo criterio que la precarga.
" WHERE dm.inrosuc = :nrosuc AND dm.cean = :ean "
" WHERE dm.inrosuc = :nrosuc AND dm.cean = :ean ORDER BY dm.iid DESC "
iVersion mayor": en este caso la fila incorrecta (1.05) tiene el iVersion
más alto (28771, contra 28480 del resto del catálogo). iid e iVersion
apuntan en direcciones opuestas para estas dos filas.
ArticuloCacheServiceTest, con un repositorio que devuelve dos filas para el mismo ean|suc, verificar que el camino on-miss y el camino de precarga resuelven a la misma fila (el iid más alto).
Registrar un WARN en loadSucursalInCache cuando una clave de cache es sobreescrita por
un iid distinto, para que los duplicados dejen de ser invisibles.
El proceso de novedades está generando filas duplicadas en dm_artic; requiere revisión del equipo
responsable de ese proceso. SQL de diagnóstico:
SELECT cean, inrosuc, COUNT(*) c FROM dm_artic WITH(NOLOCK) GROUP BY cean, inrosuc HAVING COUNT(*) > 1
POST /cache/refresh entre el 18/09 17:13 y el 20/09 21:40.status del body de la respuesta, o consultar GET /cache/refresh/status, en lugar de confiar en el HTTP 202.expireAfterWrite ocultaría el síntoma (el precio incorrecto tardaría más en aparecer) pero no corrige la divergencia entre los dos caminos.dm_artic.2500176000005 más de 4 horas después de la última precarga y confirmar que responde con id=826361083 / 8490.00.| ArticulosRepository.java | src/main/java/com/tipre/tsarticulos/repository/ArticulosRepository.java — contiene findByEanSuc, la consulta que necesita ORDER BY (F1). |
| ArticuloCacheService.java | src/main/java/com/tipre/tsarticulos/service/cache/ArticuloCacheService.java — resolución on-miss, precarga y armado de la cache. |
| ArticulosCacheLoadRepositoryImpl.java | src/main/java/com/tipre/tsarticulos/repository/ArticulosCacheLoadRepositoryImpl.java — consulta de precarga con ORDER BY dm.inrosuc, dm.iid. |
| CacheConfig.java | src/main/java/com/tipre/tsarticulos/config/CacheConfig.java — configuración de Caffeine, expireAfterWrite(4, TimeUnit.HOURS). |
| ArticuloCacheAdminController.java | src/main/java/com/tipre/tsarticulos/controller/ArticuloCacheAdminController.java — endpoints POST /cache/refresh y GET /cache/refresh/status. |