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.
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
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:
- Klucz cache:
(kontrahent_id, sku, ilosc)lub uproszczenie(kontrahent_id, sku)dla cen bez progów ilościowych. - TTL: 5-15 min (zależnie od dynamiki cenników w ERP).
- Storage: Redis (cache distributed, dostępny dla wszystkich procesów PHP).
- Cache miss: zapytanie do ERP, zapis w cache, zwrot do klienta.
- 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ąć.
Cache wyników search
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
- CDN - czwarta warstwa: CDN dla katalogów
- Strategie cache (głębiej): Cache strategie
- Wydajność (pillar): Wydajność e-commerce
- Pillar architektury: Architektura dużych sklepów
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.
Czytaj dalej w temacie wydajności
Wszystkie wpisyMasz 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.