ix
ix

informatix

EN version Porozmawiajmy o projekcie
Strona główna / Blog / Cache w Laravel — strategie, tagi i unieważnianie [2026]
• 9 min czytania •

Cache w Laravel — strategie, tagi i unieważnianie [2026]

Cache to najpotężniejsze narzędzie wydajnościowe w Laravelu i najczęstsze źródło subtelnych błędów. Strategie, które działają długoterminowo: co cache'ować, jak unieważniać i czego nie robić.

Cache w Laravel — strategie, tagi i unieważnianie [2026]
Web development

Cache to najpotężniejsze narzędzie wydajnościowe w Laravelu — i jednocześnie najczęstsze źródło subtelnych błędów. „Dodałem cache i strona przyspieszyła" bywa prawdą przez tydzień, po którym użytkownicy zgłaszają nieaktualne dane, a zespół wyłącza cache w panice. Ten poradnik pokazuje strategie, które działają długoterminowo: co cache'ować, jak unieważniać i jak nie strzelić sobie w stopę.

Najważniejsze: Najważniejsze — cache'uj dane drogie do policzenia i rzadko zmieniane (agregaty, konfiguracje, drzewa kategorii), nie całe aplikacje „na wszelki wypadek". Unieważniaj przez tagi (Redis) albo wersjonowanie kluczy, nie przez Cache::flush(). Do widoków użyj @cache w Blade, do całych odpowiedzi — response cache z unieważnianiem po zapisie modelu.

Po co cache — trzy warstwy, które warto rozróżniać

Zanim dodasz pierwszy Cache::remember, zrozum, że „cache" w aplikacji webowej to trzy różne warstwy — każda z innym narzędziem.

Warstwa Co cache'uje Narzędzie
Opcode cache skompilowane pliki PHP OPcache (konfiguracja)
Cache aplikacyjny wyniki zapytań, obliczenia, fragmenty widoków Cache::remember, @cache
Cache HTTP całe odpowiedzi dla anonimowych użytkowników response cache, CDN, Cache-Control

Ten artykuł dotyczy dwóch ostatnich. Kluczowa zasada: cache nie tworzy danych, tylko je przechowuje — więc projektujesz go razem z pytaniem „kiedy te dane przestają być prawdziwe?".

remember, rememberForever i flexible — API, które pokrywa 90% przypadków

// Wynik na godzinę
$stats = Cache::remember('dashboard:stats', now()->addHour(), function () {
    return Order::query()
        ->selectRaw('status, count(*) as total, sum(total) as revenue')
        ->groupBy('status')
        ->get();
});

// Dane konfiguracyjne — bez wygasania (unieważniane ręcznie)
$settings = Cache::rememberForever('app:settings', fn () => Setting::pluck('value', 'key'));

// Flexible — świeże dla użytkownika, odświeżane w tle po wygaśnięciu
$categories = Cache::flexible('categories:tree', [300, 3600], fn () =>
    Category::with('children')->whereNull('parent_id')->get()
);

flexible() (Laravel 11+) to wzorzec stale-while-revalidate: pierwsza wartość czasu to „świeżość", druga — „ważność". Po upływie świeżości użytkownik dostaje starą wartość, a aplikacja odświeża cache w tle. Idealne dla danych, które mogą być o kilka minut nieaktualne (rankingi, liczniki).

Wskazówka: Klucze cache projektuj jak schemat: {domena}:{typ}:{id} — np. products:list:page:3, user:42:permissions. Łatwiej je wtedy znajdować, unieważniać i monitorować. Zawsze wersjonuj format klucza (v2:), gdy zmieniasz strukturę cache'owanej wartości.

Cache tagów — selektywne unieważnianie (tylko Redis i Memcached)

Cache::flush() czyści wszystko — w produkcji to akt o desperacji: tracisz cały cache aplikacji, żeby usunąć jedną nieaktualną listę. Tagi pozwalają unieważniać grupy:

// Zapis z tagami
Cache::tags(['products', 'category:5'])->remember(
    "products:category:5:page:1",
    now()->addHour(),
    fn () => Product::where('category_id', 5)->paginate(24)
);

// Unieważnienie tylko produktów kategorii 5
Cache::tags(['category:5'])->flush();

// Wszystkie produkty (zmiana cennika, import)
Cache::tags(['products'])->flush();

Uwaga: Tagi działają wyłącznie na Redisie i Memcached. Na sterownikach file i database Cache::tags() rzuci wyjątek BadMethodCallException. Jeśli architektura cache opiera się na tagach, wybór Redisa nie jest opcją — jest wymaganiem. Konfigurację Redisa opisuję w Redis w Laravel — cache, kolejki i sesje.

Wzorce unieważniania — event-driven i wersjonowanie kluczy

Dwa podejścia do inwalidacji bez tagów

1. Unieważnianie po zdarzeniu modelu — observer czyści dokładnie to, co trzeba

class ProductObserver
{
    public function saved(Product $product): void
    {
        Cache::forget("product:{$product->id}");
        Cache::tags(['products'])->flush(); // listy produktów
    }
}

2. Wersjonowanie kluczy — zamiast usuwać klucze, zmieniasz „generację" w kluczu

$version = Cache::get('products:version', 1);
$key = "products:v{$version}:list:page:3";

// „Unieważnienie" = inkrementacja wersji (stare klucze wygasną same)
Cache::increment('products:version');

Wersjonowanie jest odporne na wyścigi (race conditions) między serwerami aplikacji i nie wymaga tagów — kosztem pamięci: stare wpisy żyją do wygaśnięcia TTL.

Cache widoków — @cache w Blade

Ciężkie fragmenty widoków (menu, stopka z danymi z bazy, sidebar z kategoriami) renderują się przy każdym żądaniu. Dyrektywa @cache zapamiętuje gotowy HTML:

@cache('sidebar:categories', now()->addHours(6))
    <aside>
        @foreach ($categories as $category)
            <a href="{{ route('category', $category) }}">{{ $category->name }}</a>
        @endforeach
    </aside>
@endcache
  • Uwaga na kontekst: @cache zapamiętuje gotowy HTML, więc nie używaj go dla fragmentów zależnych od zalogowanego użytkownika (imię, koszyk, uprawnienia). Dla treści per-user cache jest możliwy, ale klucz musi zawierać ID użytkownika — a przy tysiącach użytkowników lepszy bywa inny wzorzec (cache danych, nie HTML).

Response cache — całe odpowiedzi HTTP

Strony publiczne o identycznej treści dla wszystkich (blog, landingi, katalog, strony usług) można cache'ować w całości — z pominięciem PHP, bazy i Blade. Pakiet spatie/laravel-responsecache zapamiętuje pełną odpowiedź i unieważnia ją po zapisie modelu:

// Middleware na wybranych routach
Route::middleware('cacheResponse:3600')->group(function () {
    Route::get('/blog', [BlogController::class, 'index']);
    Route::get('/blog/{slug}', [BlogController::class, 'show']);
});

To największy zysk „na żądanie": odpowiedź z cache to kilka milisekund zamiast dziesiątek lub setek. Warstwy wyżej: Varnish albo Nginx FastCGI Cache (cache przed PHP) i CDN (cache przed serwerem). Każda warstwa niżej = mniej pracy dla warstw poniżej.

Uwaga: Response cache i @cache nie mogą obejmować treści zależnej od sesji (koszyk, „zalogowany jako", CSRF tokeny w formularzach). Cache'owanie strony z tokenem CSRF dla użytkownika A pokaże go użytkownikowi B. Filtruj routy świadomie — tylko publiczne GET-y bez stanu.

Cache warmup — żeby pierwszy użytkownik nie płacił

Po deployu (i po Cache::flush()) cache jest zimny: pierwsze żądania każdej strony płacą pełny koszt. Warmup to skrypt, który „przechodzi" najważniejsze widoki po wdrożeniu:

# Prosty warmup kluczowych URL-i po deployu
for url in / /blog /cennik /uslugi/laravel-development; do
    curl -s -o /dev/null "https://example.com${url}"
done

W aplikacji możesz też precyzyjnie odgrzać konkretne klucze komendą artisan (własna php artisan cache:warmup, która wywołuje te same Cache::remember, co kod produkcyjny). Warmup wpina się na koniec pipeline'u CI/CD.

Pułapki, które kosztują najwięcej

  • Cache stampede — gdy popularny klucz wygaśnie, sto równoległych żądań liczy tę samą wartość naraz. Rozwiązania: Cache::flexible(), blokady atomowe (Cache::lock()), losowy jitter w TTL.
  • Cache'owanie null — Cache::remember z null wynikiem zapamięta null i będzie go zwracać. Świadomie decyduj, czy „brak wyniku" też cache'ujesz.
  • TTL bez planu — „ustawię godzinę i zobaczymy" to nie strategia. Dla każdego klucza wiedz, jak długo dane mogą być nieaktualne — to determinuje TTL i mechanizm unieważniania.
  • Cache jako źródło prawdy — jeśli po flush() aplikacja nie działa poprawnie, cache maskuje błąd architektury. Aplikacja musi działać bez cache (wolniej), a nie tylko z nim.

Cache a cała strategia wydajności

Cache jest trzecią warstwą optymalizacji — po eliminacji N+1 i dobraniu zapytań Eloquenta. Kolejność ma znaczenie: cache'owanie N+1 to zamrażanie problemu, nie jego rozwiązanie. Pełną mapę optymalizacji — od zapytań po Octane — znajdziesz w optymalizacji wydajności aplikacji Laravel.

Najczęściej zadawane pytania

Co cache'ować w aplikacji Laravel w pierwszej kolejności?

Dane drogie do policzenia i rzadko zmieniane: agregaty (statystyki, liczniki), drzewa kategorii, konfiguracje, listy słownikowe, ciężkie fragmenty widoków. Druga fala: całe odpowiedzi publicznych stron (response cache). Nie zaczynaj od cache'owania wszystkiego — zacznij od najdroższych zapytań z profilu.

Czym różni się Cache::remember od Cache::flexible?

remember zwraca wartość i odświeża ją po wygaśnięciu TTL — pierwsze żądanie po wygaśnięciu czeka na przeliczenie. flexible (stale-while-revalidate) zwraca nieświeżą wartość natychmiast i odświeża ją w tle — użytkownik nigdy nie czeka, kosztem kilkuminutowej nieaktualności danych.

Jak unieważnić cache po zmianie danych?

  • Trzy poziomy: Cache::forget($key) dla pojedynczego klucza, Cache::tags([...])->flush() dla grup (tylko Redis/Memcached) i wersjonowanie kluczy (inkrementacja „generacji" w kluczu) dla systemów rozproszonych. Nigdy Cache::flush() w produkcji — to czyści cały cache aplikacji.

Czy cache tagów działa na sterowniku file?

Nie — Cache::tags() wymaga Redisa lub Memcached. Na file i database rzuci wyjątek. To jeden z powodów, dla których Redis jest standardem produkcyjnym: tagi, blokady atomowe i wspólny cache dla wielu serwerów.

Jak długi TTL ustawić?

Tak długi, jak długo dane mogą być nieaktualne bez szkody dla biznesu: konfiguracje — godziny/dni, agregaty — minuty/godziny, listy produktów — godziny. TTL to kompromis między świeżością a obciążeniem bazy — ustal go świadomie per klucz, nie globalnie.

Jak zabezpieczyć się przed cache stampede?

  • Trzy techniki: Cache::flexible() (odświeżanie w tle), Cache::lock() (tylko jeden proces liczy wartość, reszta czeka) i losowy jitter w TTL (klucze nie wygasają równocześnie). Przy popularnych kluczach i dużym ruchu stosuj co najmniej jedną.

👉 Baza danych nie wyrabia, a cache budzi wątpliwości? Zaprojektujemy strategię cache z unieważnianiem — opisz projekt przez formularz. Zobacz też utrzymanie techniczne i SLA.

📖 Zobacz również: Redis w Laravel — cache, kolejki i sesje, optymalizacja wydajności aplikacji Laravel, wydajność Eloquent oraz load testing i profilowanie.

KS

Krzysztof Słonina

Full stack developer i właściciel Inoventis Solutions (informatix). Od 2009 roku buduje aplikacje webowe w Laravel/PHP, integracje e-commerce i automatyzacje procesów — m.in. OneCRMApp i FamilyTimelineApp, działające na Laravel, React i Inertia.js.

O autorze
Darmowy PDF

Checklista wymagań do wyceny aplikacji

12 pytań kontrolnych, które warto przygotować przed rozmową z wykonawcą — szybsza wycena, mniej ukrytych kosztów.

Powiązane wpisy