Saltar al contenido
Volver al blog
LaravelHomelabBuenas prácticas

Laravel 13.33 une cache memoizada y etiquetas

Evita lecturas repetidas de Redis durante una petición sin renunciar a invalidar caché por etiquetas.

Ismael Catala4 min de lectura

El coste de repetir lecturas que ya conoces

En una aplicación Laravel es fácil consultar la misma clave de caché varias veces durante una petición. Puede ocurrir entre servicios, policies, componentes Livewire o capas que no deberían conocerse entre sí. Redis responde rápido, pero repetir una lectura ya resuelta sigue siendo trabajo innecesario. En un homelab, además, esa caché suele estar en otro contenedor o máquina y cada acceso implica salir del proceso PHP.

Laravel ya tenía Cache::memo() para guardar en memoria los valores de caché resueltos durante una única petición o ejecución de un job. La primera lectura va al store configurado y las siguientes lecturas de la misma clave se sirven desde memoria. Hasta ahora, ese patrón no encajaba directamente con las etiquetas de caché. Laravel 13.33 incorpora esa combinación.

Una caché local delante de Redis

La idea no es sustituir Redis ni crear una segunda caché persistente. Cache::memo() decora el store que ya usas y conserva los resultados sólo mientras vive la petición o el job. Si el store subyacente es Redis, la primera lectura consulta Redis y las posteriores pueden reutilizar el resultado almacenado en memoria por Laravel. Al terminar la ejecución, esa memoria desaparece.

Las etiquetas siguen resolviendo otro problema: agrupar claves relacionadas para invalidarlas juntas. Si las permisos de un usuario cambian, no necesito conocer todas las claves derivadas para borrarlas una a una. Puedo asociarlas a la etiqueta permissions y vaciar ese grupo cuando corresponda. Con Laravel 13.33, ambas cosas pueden vivir en la misma llamada.

Un ejemplo para permisos

Este patrón encaja bien cuando un dato se consulta desde varios puntos de la misma ejecución. El ejemplo carga los permisos de un usuario, los conserva en Redis durante un tiempo y evita lecturas repetidas durante la petición actual. La etiqueta permite invalidar el grupo cuando cambie la asignación de permisos. La clave debe seguir siendo específica del usuario, porque la etiqueta no reemplaza una estrategia de claves correcta.

use Illuminate\Support\Facades\Cache;
 
public function permissionsFor(User $user)
{
    $key = "permissions:{$user->id}";
 
    return Cache::memo()
        ->tags(['permissions'])
        ->remember(
            $key,
            now()->addMinutes(10),
            fn () => $user->permissions()->get(),
        );
}

Cuando cambien permisos, puedo invalidar las entradas asociadas a esa etiqueta. Si la invalidación ocurre dentro de la misma petición o job, usar el mismo acceso memoizado también descarta la copia en memoria de esa ejecución. Así no mantengo una lectura local obsoleta después de modificar la caché etiquetada.

Cache::memo()
    ->tags(['permissions'])
    ->flush();

La letra pequeña

Esto no comparte valores en memoria entre peticiones, workers ni servidores. Cada petición HTTP y cada job empieza con su propia caché memoizada, así que Redis sigue siendo el punto común entre procesos. Tampoco coordina ejecuciones concurrentes ante una clave fría: dos peticiones simultáneas pueden resolver el mismo valor si ninguna lo encuentra todavía. Para ese caso hay que valorar un bloqueo de caché o revisar si realmente compensa proteger ese cálculo.

Las etiquetas no funcionan con todos los drivers de caché. La documentación de Laravel indica que no están disponibles con los drivers file, dynamodb, database ni storage; antes de adoptar este patrón conviene comprobar el store real de producción. En una instalación autoalojada con Redis, la configuración debe apuntar al store Redis que vaya a servir como backend de caché. No basta con tener Redis levantado si la aplicación sigue usando otro driver como store por defecto.

También hay que ser estricto con el orden de las etiquetas. Laravel requiere usar la misma lista ordenada para recuperar una entrada etiquetada que se utilizó al guardarla. Por eso conviene centralizar las etiquetas en un método o una clase si el conjunto crece. Cache::memo()->tags([...]) mejora una ruta de acceso concreta, pero no arregla claves inconsistentes ni una política de invalidación mal definida.

No aplicaría memo() por inercia a todo el proyecto. Tiene sentido donde la misma clave se consulta varias veces dentro de una ejecución y esas lecturas llegan al mismo store. En rutas con una única lectura por clave apenas aporta nada, y en procesos largos hay que tener claro que su ámbito es la ejecución del job. Es una mejora pequeña, pero elimina código manual que antes mezclaba una caché array con una caché etiquetada.


Fuente: Laravel News Documentación oficial de caché de Laravel