Skip to Content

Caché en vivo con Redis

Tener todas las filas de una consulta en memoria y al día con ReadAllLiveAsync: los eventos de Redis las actualizan y varias apps comparten la misma copia.

Una caché en vivo guarda en la memoria de tu app todas las filas de una consulta y las mantiene al día con los eventos que Dinaup publica en tu Redis. Tu web lee de memoria, sin preguntar a Dinaup en cada visita.

Sirve para lo que se lee todo el tiempo: el catálogo de una tienda, un buscador público, las tablas de países o impuestos.

var recambios = await RecambiosD.List(client)
    .ArchivadoIsFalse()
    .ReadAllLiveAsync(redis, maxRows: 1_000_000);

var uno = recambios.GetById(recambioId);   // de memoria
var total = recambios.Count;

Antes de empezar

  • Los eventos de Dinaup en tu Redis: Eventos Redis.
  • Un usuario de Redis que pueda leer, suscribirse y escribir las claves dinaup.cache.*.
  • La serie de tu licencia y la cadena de conexión de ese Redis.
  • Tu consulta escrita con List: Consultas con List.

Montarla en una web

Registra un RedisClient

Uno por empresa, para toda la vida de la app.

builder.Services.AddSingleton(new RedisClient(cadenaDeConexionRedis, serieDeLaLicencia));

Guarda las cachés en un servicio singleton

La caché vive mientras vive la app. El servicio la guarda y las pantallas le piden las filas.

public sealed class CatalogoService(DinaupClientC dinaupClient, RedisClient redisClient)
{
    private DinaupLiveCache<RecambiosD.RecambiosC> recambios;
    private DinaupLiveCache<ImpuestosD.ImpuestosC> impuestos;

    public async Task ArrancarCachesAsync()
    {
        impuestos = await ImpuestosD.List(dinaupClient).ReadAllLiveAsync(redisClient);
        recambios = await RecambiosD.List(dinaupClient).ArchivadoIsFalse().ReadAllLiveAsync(redisClient, maxRows: 1_000_000);
    }

    public List<RecambiosD.RecambiosC> BuscarRecambios(string texto, int cuantos)
    {
        return recambios.Rows.Where(r => r.Base__Label.Contains(texto, StringComparison.OrdinalIgnoreCase)).Take(cuantos).ToList();
    }
}

Arráncalas en segundo plano

Un BackgroundService espera a que Dinaup conteste y arranca las cachés una tras otra, primero las pequeñas.

public sealed class CatalogoScheduler(CatalogoService catalogoService, DinaupClientC dinaupClient) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (dinaupClient.IsConnected == false)
        {
            await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
        }
        await catalogoService.ArrancarCachesAsync();
    }
}

Una sección grande tarda minutos en leerse de Dinaup la primera vez. Hasta que ReadAllLiveAsync vuelve, la caché no existe: enseña un estado de carga.

Qué te da la caché

MiembroQué es
RowsLas filas, tal como están ahora.
CountCuántas hay.
GetById(id)La fila con ese id, o null.
RowsChangedEvento con los ids añadidos, cambiados o quitados tras cada lote.
InvalidateAsync()Vuelve a leer todas las filas de Dinaup y reemplaza la memoria.
IsWriterSi esta app es la que escribe la copia en Redis.
CacheKey, DefinitionEl nombre de la copia en Redis y el texto de la consulta que la define.
Dispose()Deja de seguir los eventos y, si era la escritora, suelta el turno.

Para seguir la carga, pasa un IProgress<ProgressReport>: recibe las filas leídas (Done), el total estimado (Total) y cuándo llegó la última página (LastStep_UTC).

var progreso = new Progress<ProgressReport>(p => Logs.Information("catalogo.cargando {Leidas} {Total}", p.Done, p.Total));
var recambios = await RecambiosD.List(client).ArchivadoIsFalse().ReadAllLiveAsync(redis, 1_000_000, progreso);

Cómo se mantiene al día

  1. La primera app que pide la consulta lee las filas de Dinaup y las guarda en Redis.
  2. La siguiente app que pide la misma consulta las carga de Redis, sin leer de Dinaup. «La misma» es misma sección, mismos filtros y mismos campos, en cualquier orden.
  3. Cada evento trae la fila entera. La caché la compara con los filtros: la guarda si cumple y la quita si ya no.
  4. Una sola app escribe la copia de Redis. Si se para, otra toma el relevo en 30 segundos. Todas aplican los eventos a su propia memoria.
  5. Al reconectar con Redis y cada hora, la caché lee de Dinaup las filas cuya fecha de actividad cambió, por si se perdió un evento.
  6. Si nadie usa una copia en 7 días, Redis la borra.

Si Redis se cae, las cachés siguen en memoria con lo último que vieron y se ponen al día al volver.

Lo que hay que saber

  • Los archivados entran. Un evento no los distingue. Para dejarlos fuera, filtra por el campo de archivado, como ArchivadoIsFalse().
  • Sin Search ni Skip. Lanzan: una búsqueda no se puede comprobar con un evento. Filtra por campos.
  • El orden no aplica. La caché es un conjunto de filas. Ordena en memoria.
  • El nombre de una relación puede llegar como «¿?». Los eventos traen el id del registro relacionado, no su nombre. El id es correcto; el nombre vuelve en la siguiente carga completa.
  • Lo relacionado, en su propia caché. Si necesitas el nombre de la marca de cada recambio, abre una caché de marcas y cruza en memoria por el id.
  • maxRows protege la memoria. Sin número son 10.000 y el máximo es 1.000.000. Si la consulta tiene más, lanza ReadCeilingException.
  • Fields(…) aligera cada fila. La caché añade sola los campos que necesita para seguir los eventos.

Un ejemplo completo

El panel de pruebas de Dinaup arranca una caché por cada sección que lee una tienda en línea, unas treinta, y enseña en directo su estado, los eventos que llegan y cuánto tardan. Sus piezas son las de esta página:

PiezaQué hace
Program.csConecta el cliente y registra el RedisClient, el servicio y el scheduler.
Un servicio singletonGuarda las cachés, una línea por consulta List.
Un BackgroundServiceArranca las cachés cuando Dinaup contesta y reintenta cada minuto las que fallaron.

Para comprobar una caché contra Dinaup, lee la misma consulta con ReadAllAsync y compara los ids y la fecha de actividad de cada fila.

Ver también

En esta página