Saltar al contenido principal

Qué es la caché

Las consultas a la base de datos o las llamadas a APIs externas tienen un coste elevado en CPU y en red, y su procesamiento puede tardar varios segundos. Si necesitas obtener los mismos datos una y otra vez, guardar el resultado en caché permite atender las peticiones posteriores mucho más rápido. Laravel ofrece una API unificada compatible con distintos backends de caché: Memcached, Redis, DynamoDB, base de datos y otros.
Por defecto se utiliza el driver database. Puedes cambiarlo a Redis o Memcached para conseguir una caché aún más rápida.

Configuración de la caché

config/cache.php

La configuración de la caché se centraliza en config/cache.php. La variable de entorno CACHE_STORE determina el driver por defecto.
// config/cache.php
'default' => env('CACHE_STORE', 'database'),

Drivers disponibles

Guarda los datos de caché serializados en una tabla de la base de datos. Los nuevos proyectos con Laravel 11 y posteriores incluyen la migración desde el principio.
CACHE_STORE=database
Si no se incluye la migración, créala con Artisan.
php artisan make:cache-table
php artisan migrate
Guarda los datos de caché en el sistema de ficheros. No requiere configuración adicional y es adecuado para aplicaciones pequeñas.
CACHE_STORE=file
Driver de caché en memoria muy rápido. Es el más habitual en producción. Requiere la extensión PhpRedis o el paquete predis/predis.
CACHE_STORE=redis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
Requiere el paquete PECL de Memcached. Configura los servidores en config/cache.php.
'memcached' => [
    'servers' => [
        [
            'host' => env('MEMCACHED_HOST', '127.0.0.1'),
            'port' => env('MEMCACHED_PORT', 11211),
            'weight' => 100,
        ],
    ],
],
Utiliza AWS DynamoDB como almacén de caché. Debes crear la tabla en DynamoDB con antelación e instalar el SDK de AWS.
composer require aws/aws-sdk-php
CACHE_STORE=dynamodb
DYNAMODB_CACHE_TABLE=cache
AWS_DEFAULT_REGION=us-east-1
AWS_ACCESS_KEY_ID=your-key-id
AWS_SECRET_ACCESS_KEY=your-secret-key
Utiliza cualquier disco del sistema de ficheros como almacén clave/valor de caché. Es útil si quieres reutilizar un disco S3 existente como caché.
'storage' => [
    'driver' => 'storage',
    'disk' => env('CACHE_STORAGE_DISK'),
    'path' => env('CACHE_STORAGE_PATH', 'framework/cache/data'),
],
array es una caché en memoria válida solo durante la petición. null ignora todas las operaciones. Ambos resultan útiles en pruebas automatizadas.
CACHE_STORE=array

Jerarquía de drivers de caché

Elige el driver según el uso y la velocidad que necesites.

Operaciones básicas

Uso del facade Cache

Utiliza el facade Cache para interactuar con la caché.
<?php

namespace App\Http\Controllers;

use Illuminate\Support\Facades\Cache;

class UserController extends Controller
{
    public function index(): array
    {
        $value = Cache::get('key');

        return [
            // ...
        ];
    }
}
Para alternar entre varios almacenes utiliza el método store().
$value = Cache::store('file')->get('foo');

Cache::store('redis')->put('bar', 'baz', 600); // guarda 10 minutos

Obtener datos: Cache::get()

El método get() obtiene datos de la caché. Devuelve null si no existen. También puedes indicar un valor por defecto.
$value = Cache::get('key');

// Valor por defecto
$value = Cache::get('key', 'default');

// Valor por defecto perezoso mediante closure
$value = Cache::get('key', function () {
    return DB::table('settings')->get();
});

Guardar datos: Cache::put()

El método put() guarda datos en la caché. En el tercer argumento se indica el TTL en segundos.
// Guardar durante 10 segundos
Cache::put('key', 'value', 10);

// Indicar el TTL con un objeto Carbon
Cache::put('key', 'value', now()->plus(minutes: 10));

// Sin caducidad (almacenamiento permanente)
Cache::put('key', 'value');
También existe add(), que guarda el valor solo si la clave no existe.
// Solo se añade si no existe (operación atómica)
Cache::add('key', 'value', $seconds);
Para guardar de forma permanente utiliza forever().
Cache::forever('key', 'value');

Obtener o guardar: Cache::remember()

Es la operación más habitual. Si la clave existe en la caché devuelve su valor; si no, ejecuta la closure y almacena el resultado.
$users = Cache::remember('users', 3600, function () {
    return DB::table('users')->get();
});
Cache::remember() reduce a una sola línea el patrón «comprobar la caché → si no está, obtener el valor → almacenarlo». Es ideal para cachear resultados de consultas a base de datos o respuestas de APIs externas.

Flujo de remember()

También existe la versión permanente rememberForever().
$value = Cache::rememberForever('users', function () {
    return DB::table('users')->get();
});
Si necesitas saber si la caché ha «acertado» o si el valor se acaba de generar en la closure, utiliza rememberWithWarmth(). Devuelve un array con el valor y un booleano que indica si es «cálido» (proveniente de la caché).
[$value, $warm] = Cache::rememberWithWarmth('users', 3600, function () {
    return DB::table('users')->get();
});

if ($warm) {
    // Datos de la caché
} else {
    // Datos recién obtenidos por la closure
}
rememberWithWarmth() es útil para monitorizar la eficiencia de la caché. Si $warm es false la mayoría de las veces, puede que te convenga ajustar el TTL.

Stale While Revalidate (actualización flexible)

Cache::flexible() recibe un array con el «periodo de frescura» y el «periodo aceptable aunque caduco». Devuelve datos antiguos al usuario y refresca la caché en segundo plano.
// [tiempo fresco (s), tiempo aceptable aunque caduco (s)]
$value = Cache::flexible('users', [5, 10], function () {
    return DB::table('users')->get();
});

Comprobar existencia: Cache::has()

if (Cache::has('key')) {
    // Procesar si la caché existe
}

Incrementar y decrementar valores

Puedes manipular contadores enteros.
// Inicializa el valor si no existe
Cache::add('key', 0, now()->addHours(4));

// Incrementar / decrementar
Cache::increment('key');
Cache::increment('key', $amount);
Cache::decrement('key');
Cache::decrement('key', $amount);

Obtener y eliminar: Cache::pull()

Obtiene el valor y a continuación lo elimina de la caché. Es útil para datos de un solo uso.
$value = Cache::pull('key');

Eliminar datos: Cache::forget()

// Eliminar una clave concreta
Cache::forget('key');

// Vaciar toda la caché
Cache::flush();
Cache::flush() elimina todas las entradas independientemente del «prefijo» de caché. Ten cuidado si compartes la caché entre varias aplicaciones.
Cache::flushLocks() limpia todos los locks atómicos almacenados en la caché.
Cache::flushLocks();

Ampliar el TTL: Cache::touch()

Amplía la caducidad de un elemento de caché existente.
// Indicándolo en segundos
Cache::touch('key', 3600);

// Con un objeto Carbon
Cache::touch('key', now()->addHours(2));

Memoización de caché

Con el driver memo puedes memorizar en memoria los accesos a la caché durante una misma petición. Reduce los viajes de ida y vuelta al almacén de caché cuando se accede repetidas veces a la misma clave.
use Illuminate\Support\Facades\Cache;

// Memoizar el almacén por defecto
$value = Cache::memo()->get('key');

// Memoizar el almacén Redis
$value = Cache::memo('redis')->get('key');
// El primer acceso consulta al almacén de caché
$value = Cache::memo()->get('key');

// Los accesos posteriores devuelven desde memoria (sin ir al almacén)
$value = Cache::memo()->get('key');

Etiquetas de caché

Puedes agrupar elementos de caché relacionados mediante etiquetas y eliminarlos en bloque.
Las etiquetas de caché no están soportadas por los drivers file, dynamodb, database ni storage. Necesitas redis o memcached.

Estructura de la caché con etiquetas

Un elemento de caché con varias etiquetas puede eliminarse a través de cualquiera de ellas.

Guardar y obtener con etiquetas

use Illuminate\Support\Facades\Cache;

// Guardar con etiquetas
Cache::tags(['people', 'artists'])->put('John', $john, $seconds);
Cache::tags(['people', 'authors'])->put('Anne', $anne, $seconds);

// Obtener indicando las etiquetas
$john = Cache::tags(['people', 'artists'])->get('John');
$anne = Cache::tags(['people', 'authors'])->get('Anne');

Borrar caché por etiquetas

// Elimina todos los elementos con las etiquetas 'people' y 'authors' (borra John y Anne)
Cache::tags(['people', 'authors'])->flush();

// Elimina solo los de la etiqueta 'authors' (borra Anne; John se mantiene)
Cache::tags('authors')->flush();
Las etiquetas son útiles cuando quieres invalidar caché por grupos (por usuario, por artículo, etc.). Ejemplo: Cache::tags(['user', "user:{$userId}"])->flush() borra todas las cachés de ese usuario.

Operaciones atómicas (locks)

Cache::lock() permite implementar locks distribuidos para evitar condiciones de carrera entre procesos o peticiones paralelas.
Esta función está disponible con los drivers memcached, redis, dynamodb, database, file y array. Todos los servidores deben comunicarse con el mismo servidor central de caché.

Lock básico

use Illuminate\Support\Facades\Cache;

$lock = Cache::lock('foo', 10);

if ($lock->get()) {
    // Lock obtenido (válido durante 10 segundos)

    // Realiza el procesamiento exclusivo

    $lock->release();
}
Si le pasas una closure, el lock se libera automáticamente cuando termina.
Cache::lock('foo', 10)->get(function () {
    // El lock se libera solo
});

Esperar al lock

Espera hasta el número indicado de segundos a obtener el lock. Si se agota el tiempo se lanza LockTimeoutException.
use Illuminate\Contracts\Cache\LockTimeoutException;

$lock = Cache::lock('foo', 10);

try {
    $lock->block(5); // Espera hasta 5 segundos

    // Procesamiento tras obtener el lock

} catch (LockTimeoutException $e) {
    // No se ha podido obtener el lock
} finally {
    $lock->release();
}
Con una closure el código queda más limpio.
Cache::lock('foo', 10)->block(5, function () {
    // Espera hasta 5 segundos por el lock y lo libera al finalizar
});

Evitar ejecuciones simultáneas: withoutOverlapping()

Una forma sencilla de impedir que un mismo proceso se ejecute varias veces a la vez.
Cache::withoutOverlapping('foo', function () {
    // Solo se ejecuta uno a la vez
});

// Personaliza el tiempo de espera y la duración del lock
Cache::withoutOverlapping('foo', function () {
    // ...
}, lockFor: 120, waitFor: 5);

Limitar la concurrencia: funnel()

Limita el número de ejecuciones en paralelo.
Cache::funnel('foo')
    ->limit(3)          // Permite hasta 3 ejecuciones paralelas
    ->releaseAfter(60)  // Se libera automáticamente a los 60 s
    ->block(10)         // Espera hasta 10 s
    ->then(function () {
        // Bloque de código si se ha obtenido una ranura
    }, function () {
        // Bloque de código si no se ha obtenido ranura
    });

Transferir un lock entre procesos

Caso típico: obtener el lock en una petición web y liberarlo desde un job de la cola.
// En la petición se obtiene el lock y se despacha el job
$lock = Cache::lock('processing', 120);

if ($lock->get()) {
    ProcessPodcast::dispatch($podcast, $lock->owner());
}

// Dentro del job se libera el lock
Cache::restoreLock('processing', $this->owner)->release();
Para liberar el lock de forma forzada, sin importar el propietario actual, utiliza el método forceRelease.
Cache::lock('processing')->forceRelease();

Refrescar el lock

Con el método refresh puedes ampliar la validez del lock que ya tienes. Si no indicas los segundos se usa la duración con la que se creó. Es útil para procesos largos en los que quieres ir extendiendo un lock corto en lugar de configurar de entrada un TTL muy grande.
$lock = Cache::lock('generate-reports', 60);

if ($lock->get()) {
    foreach ($reports as $report) {
        $report->generate();

        // Amplía el lock otros 60 segundos
        $lock->refresh();
    }

    $lock->release();
}

Helper de caché

La función auxiliar cache() permite hacer lo mismo que el facade Cache con una sintaxis más breve.
// Obtener un valor
$value = cache('key');

// Guardar un valor (con TTL)
cache(['key' => 'value'], $seconds);
cache(['key' => 'value'], now()->addMinutes(10));

// Sin argumentos obtienes la misma instancia que con el facade
cache()->remember('users', $seconds, function () {
    return DB::table('users')->get();
});

Ejemplos prácticos

Cachear resultados de consultas a la base de datos

1

Cachear el resultado de la consulta en el controlador

use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;

public function index(): array
{
    $users = Cache::remember('all-users', 3600, function () {
        return DB::table('users')->orderBy('name')->get();
    });

    return compact('users');
}
2

Cuando los datos cambien, invalida la caché

public function store(Request $request): RedirectResponse
{
    User::create($request->validated());

    // Elimina la caché para que se regenere en la siguiente petición
    Cache::forget('all-users');

    return redirect()->route('users.index');
}

Cachear modelos Eloquent

use App\Models\Product;
use Illuminate\Support\Facades\Cache;

// Cachear una hora el listado de productos por categoría
public function byCategory(int $categoryId): array
{
    $products = Cache::remember(
        "products:category:{$categoryId}",
        3600,
        fn () => Product::where('category_id', $categoryId)
            ->where('is_active', true)
            ->orderBy('name')
            ->get()
    );

    return compact('products');
}

Cachear respuestas de una API

use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;

public function getWeather(string $city): array
{
    return Cache::remember(
        "weather:{$city}",
        1800, // 30 minutos de caché
        function () use ($city) {
            $response = Http::get('https://api.weather.example.com/current', [
                'city' => $city,
            ]);

            return $response->json();
        }
    );
}

Gestión por grupos con etiquetas de caché

use Illuminate\Support\Facades\Cache;

class ArticleController extends Controller
{
    public function show(Article $article): array
    {
        $data = Cache::tags(['articles', "article:{$article->id}"])
            ->remember("article:{$article->id}:detail", 3600, function () use ($article) {
                return $article->load(['author', 'tags', 'comments']);
            });

        return compact('data');
    }

    public function update(Request $request, Article $article): RedirectResponse
    {
        $article->update($request->validated());

        // Limpia todas las cachés relacionadas con este artículo
        Cache::tags(["article:{$article->id}"])->flush();

        return redirect()->route('articles.show', $article);
    }
}

Resumen

MétodoDescripción
Cache::get('key')Obtiene un valor de la caché
Cache::put('key', $value, $ttl)Guarda un valor en la caché
Cache::remember('key', $ttl, fn)Obtiene o guarda (el más importante)
Cache::forget('key')Elimina un valor
Cache::has('key')Comprueba si existe
Cache::flush()Vacía toda la caché
Cache::forever('key', $value)Guarda de forma permanente
Cache::pull('key')Obtiene y elimina
Cache::increment('key')Incrementa un contador
Cache::tags([...])->flush()Vacía la caché por etiquetas
  • Desarrollo o baja escala: file o database.
  • Producción o alto tráfico: redis (combinado con Laravel Horizon, además, facilita el monitoreo).
  • Entorno AWS: dynamodb o storage (si reutilizas un disco S3).
  • Tests: array o null.
  • Da a las claves un nombre único en toda la aplicación (por ejemplo, users:1:profile).
  • Cuando los datos cambien invalídalos con Cache::forget() o Cache::tags()->flush().
  • El TTL no debe ser ni demasiado corto ni demasiado largo: ajústalo a la frecuencia con la que cambian los datos.
  • Cache::remember() te ayuda a escribir de forma limpia el flujo de miss.
  • En producción utiliza Redis y valora configurar failover.
Última modificación el 13 de julio de 2026