Przejdź do treści
Architektura 7 min czytania

Cache w sklepie B2B - warstwy, strategie, invalidacja

Bez cache duży sklep B2B nie istnieje. Każda strona produktu, każda lista, każda kalkulacja ceny kontraktowej - bez cache to każdorazowe zapytanie do bazy, do ERP, do silnika cen. Mnoży się to przez liczbę odsłon i kładzie cały stack. Z dobrym, warstwowym cache - sklep z 100k SKU i 500 kontrahentami chodzi na 2 maszynach. Bez - 20 maszyn nie wystarczy.

Jakub Owsianka Autor
Zaktualizowano:
Okladka artykulu: Cache w sklepie B2B - warstwy, strategie, invalidacja (kategoria: Architektura)
Okladka artykulu: Cache w sklepie B2B - warstwy, strategie, invalidacja (kategoria: Architektura)
Spis treści (8)

W skrócie

  • 1. Cztery warstwy cache: APCu (in-process) → Redis (distributed) → Varnish (HTTP) → CDN (edge)
  • 2. Każda ma własny TTL, własną strategię, własną invalidację
  • 3. Cache cen kontraktowych: najtrudniejszy - klucz (kontrahent, sku, ilosc), TTL 5-15 min, invalidacja webhook ERP
  • 4. Cache HTML stron dla zalogowanych B2B: trudny, najczęściej tylko fragmenty (ESI), nie cała strona
  • 5. Brak monitoringu cache hit ratio: krytyczny błąd - bez metryki latasz na ślepo

Cztery warstwy cache w sklepie B2B

Warstwa 1: APCu / OPcache / application memory (in-process)

Pamięć procesu PHP (lub Node). Najszybsza (~mikrosekundy), ale niewspółdzielona między procesami.

Co tu cachować:

  • Konfigurację aplikacji (load raz, używaj wielokrotnie)
  • Stałe encje (kategorie, słowniki)
  • Małe, bardzo często czytane dane

TTL: krótki (sekundy do minut). Dane są też po prostu „w pamięci" procesu - niwelowane przy restarcie.

Warstwa 2: Redis (lub Memcached) - distributed cache

Współdzielona pamięć między procesami i maszynami. Szybka (~ms), persistowana opcjonalnie.

Co tu cachować:

  • Sesje użytkowników
  • Cache cen kontraktowych (kontrahent × SKU × ilość)
  • Cache stanów magazynowych
  • Wyniki częstych queries (popularne produkty, kategorie)
  • Cache HTML fragmentów

TTL: minuty do godzin. Strategia invalidacji per typ danych.

Warstwa 3: Varnish / Fastly - HTTP cache (full-page)

Cache całych stron HTML. Najszybszy dla niezalogowanych (cache hit = 50ms, miss = 1-3s).

Co tu cachować:

  • Strony dla niezalogowanych (homepage, kategorie publiczne, blog, static pages)
  • Strony produktów dla niezalogowanych (jeśli sklep pokazuje ceny przed logowaniem)
  • API responses dla danych publicznych

Czego NIE cachować w Varnish (B2B specyfika):

  • Strony dla zalogowanych B2B (kontent zależny od user → trudny cache)
  • Strony z cenami kontraktowymi (per user)
  • Koszyk, checkout, panel klienta

Varnish + ESI (Edge Side Includes): technika dla mieszanego contentu. Strona w cache, ale fragmenty (np. „twoje konto: X zł limit") wstawiane z osobnego endpointa per request.

Warstwa 4: CDN edge cache (Cloudflare, BunnyCDN, AWS CloudFront)

Cache na edge nodes po świecie. Najszybszy dla użytkownika (latency 10-50ms).

Co tu cachować:

  • Statyki: obrazy, CSS, JS, fonty
  • Możliwie API endpoints z agresywnym caching headers

CDN dla katalogów 10k+ SKU.

Cache cen kontraktowych - najtrudniejszy

To największy ból sklepu B2B. Cena dla klienta X za produkt Y w ilości Z wymaga wywołania ERP (silnik cen). Sklep w peak'u może mieć 50 req/s tylko na zapytania o cenę.

Strategia:

  1. Klucz cache: (kontrahent_id, sku, ilosc) lub uproszczenie (kontrahent_id, sku) dla cen bez progów ilościowych.
  2. TTL: 5-15 min (zależnie od dynamiki cenników w ERP).
  3. Storage: Redis (cache distributed, dostępny dla wszystkich procesów PHP).
  4. Cache miss: zapytanie do ERP, zapis w cache, zwrot do klienta.
  5. Cache hit: zwrot z cache, bez wywołania ERP.

Invalidacja:

  • Time-based (TTL wygasa) - domyślne.
  • Event-based (webhook z ERP przy zmianie cennika) - agresywniejsze, ale trudniejsze.
  • Manual (admin może wyczyścić cache cen dla klienta z panelu) - must-have dla edge case'ów.

Praktyka:

GET cena_kontraktowa(kontrahent=K001, sku=ABC123, qty=10)
  -> sprawdź Redis: klucz "price:K001:ABC123:10"
    -> hit: zwróć z Redis, log hit metric
    -> miss: zapytanie do ERP via middleware
      -> middleware zwraca cenę
      -> zapis w Redis z TTL 600s
      -> log miss metric
  -> zwróć cenę klientowi

Hit ratio target: >85% dla cen kontraktowych w produkcji. Poniżej - TTL za krótkie lub cache leak.

Cache stanów magazynowych

Podobnie do cen, ale z innym TTL:

  • TTL: 30s-2min (stany zmieniają się częściej niż cenniki)
  • Klucz: (sku, magazyn) lub uproszczenie (sku) jeśli sklep nie wybiera magazynu
  • Webhook invalidation: ERP / WMS przy zmianie stanu → flush konkretnego klucza
  • Cache hit ratio: >70% (akceptowalne, częściej cache miss)

Edge case: zatwierdzanie zamówienia (checkout). Tu nie używamy cache - pytamy ERP synchronicznie o aktualny stan i blokujemy.

Cache HTML dla zalogowanych B2B

To pułapka, w którą wpada wiele projektów. Zalogowany B2B widzi:

  • Swoje ceny kontraktowe
  • Swoje limity
  • Swoją historię zakupów
  • Spersonalizowane rekomendacje

Cachować całą stronę = absurd (każdy klient ma swoją). Cache fragmentów:

Strategia 1: ESI (Edge Side Includes)

<!-- Cachowany szablon strony produktu -->
<div class="product-card">
  <h1>Produkt X</h1>
  <img src="..." />
  <esi:include src="/price-fragment/{kontrahent_id}/{sku}" />
  <esi:include src="/stock-fragment/{sku}" />
</div>

Każdy fragment ma własną politykę cache, własny TTL. Strona główna cachowana raz, fragmenty per user.

Wspierane przez Varnish, Fastly, niektóre CDN-y.

Strategia 2: Cache fragmentów po stronie aplikacji (Redis)

Aplikacja składa stronę z kawałków, każdy kawałek cachowany w Redis z odpowiednim kluczem.

Strategia 3: Brak HTML cache, agresywny Redis cache dla danych + szybki rendering

Jeśli pierwsze dwa scenariusze są skomplikowane - alternatywa to brak cache HTML, ale agresywny cache wszystkich danych podstawowych (produkty, ceny, stany w Redis). Rendering w Symfony / Magento jest na tyle szybki, że LCP < 2s da się osiągnąć.

Search engine (Elasticsearch / Algolia) sam jest „cache" w pewnym sensie. Ale popularne queries (top kategorii, top filtrów) jeszcze warto cachować w Redis:

  • Klucz: hash query params (kategoria + filtry + sort + page)
  • TTL: 5-30 min
  • Hit ratio target: >50% (sporo unique queries)

Wartość: redukcja obciążenia Elastic, szybsza odpowiedź klientowi.

Cache invalidation - najtrudniejsza część

Stare powiedzenie w IT: są dwa trudne problemy - naming things, cache invalidation, and off-by-one errors.

W sklepie B2B najczęstsze strategie invalidacji:

1. Time-based (TTL). Najprostsze, domyślne. Cache wygasa po X. Akceptowalne stale dane.

2. Event-based. Webhook z ERP / PIM / WMS → flush konkretnego klucza. Najświeższe dane, ale wymaga niezawodności webhooków.

3. Versioned keys. Wbudowanie wersji w klucz cache: price:K001:ABC123:v42. Wzrost wersji = automatyczna invalidacja wszystkich starych kluczy.

4. Tag-based. Cache wpisany z tagami (product:ABC123, category:tools). Flush per tag invaliduje wszystko z tym tagiem. Symfony Cache i Redis tagging to wspierają.

5. Stale-while-revalidate. Serwujemy stary cache + asynchronicznie odświeżamy w tle. Brak „pauzy" przy invalidate. Pattern z CDN-ów.

Monitoring cache

Bez monitoringu - latasz na ślepo. Krytyczne metryki:

Cache hit ratio per warstwa:

  • APCu: target >95%
  • Redis: target >80% (zależnie od scope)
  • Varnish: target >70% dla stron publicznych, >40% globalnie
  • CDN: target >85% dla statyków

Cache miss latency:

  • Ile trwa wygenerowanie strony przy cache miss
  • Jeśli >3s - problem w aplikacji, nie w cache

Cache size:

  • Czy nie eviction'ujesz cache (LRU usuwa stare wpisy żeby zmieścić nowe)
  • Czy Redis ma wystarczająco RAM

Hot keys:

  • Które klucze są najczęściej odpytywane
  • Pozwala wykryć anomalie

Narzędzia:

  • Redis: INFO memory, MONITOR (uważnie, drogi)
  • Varnish: varnishstat, varnishlog
  • CDN: dashboard providera
  • Custom: Prometheus + Grafana

Najczęstsze błędy

1. Brak warstwy Redis. Tylko APCu (in-process) - nie skaluje się.

2. Za długie TTL. Klient widzi cenę z wczoraj. Reklamacja, rozjazd, dramat.

3. Za krótkie TTL. Cache hit ratio < 30%, ERP zasypywany requestami.

4. Brak invalidacji event-based. Tylko TTL - w godzinach piku stare dane.

5. Cache w jednym Redis dla wszystkiego. Sesje + cache cen + cache HTML = OOM podczas piku. Separate instances (sesje osobno).

6. Brak monitoringu hit ratio. Cache „działa", a w rzeczywistości w peak'u się sypie.

7. Cache HTML dla zalogowanych B2B bez ESI. Po prostu nie cachowanie i nadzieja że storage wytrzyma - wszystko będzie wolne.

FAQ

Czy Magento ma wbudowany cache? Tak, Magento ma full-page cache (FPC). Można używać Built-in lub Varnish jako backend. Varnish 5x szybszy, jest standardem w produkcji.

Czy Shopware używa Varnisha? Wspiera, ale nie wymaga. Dla małych projektów wbudowany cache wystarcza. Dla większych - Varnish lub HTTP Cache za CDN.

Czy mogę użyć Memcached zamiast Redis? Tak, ale Redis ma więcej funkcji (persistence, pub/sub, structures). W praktyce większość nowych projektów PHP/Symfony wybiera Redis.

Czy CDN może cachować dynamiczne strony? Tak, ale wymaga ostrożności. Cloudflare/Fastly pozwalają na cache stron z odpowiednimi cache-control headers, ale dla B2B z zalogowanymi użytkownikami - najczęściej tylko fragmenty / statyki.

Czy KSeF coś zmienia w cache? Nie wprost. KSeF to dokumenty, nie strony. Cache działa jak zwykle.

Co dalej

O autorze

Jakub Owsianka

Architekt rozwiązania w WiseB2B - silniku platform B2B. Zaczynał po stronie biznesu (własne sklepy), potem deweloper, dziś projektuje wdrożenia dla sklepów z katalogami w dziesiątkach tysięcy SKU. W ostatnich latach wdrożył AI-development w zespole i funkcjonalności oparte o AI bezpośrednio w silniku sklepu.

Masz pytanie do tego artykułu?

Dodatkowy kontekst, problem z własnym wdrożeniem, druga opinia - napisz wprost. Odpowiadam osobiście w 1-2 dni robocze.