Sklep, który gubił klientów
Royal Beef sprzedaje wołowinę rasy Highland Beef. Produkt premium, średnio 3-4x droższy od marketowego standardu. W tej kategorii klient nie wybacza, kiedy koszyk gubi pozycje albo płatność wraca błędem - po prostu więcej nie wraca.
Sklep stał na WordPressie z WooCommerce, miał 46 aktywnych wtyczek i mieszkał na shared hostingu. Konfiguracja, którą widzieliśmy tysiąc razy: dużo wtyczek, brak kontroli nad warstwą serwerową, jedna maszyna dzielona z setką innych sklepów.
Klient zgłosił się z trzema konkretnymi objawami biznesowymi - nie z hasłami „chcielibyśmy przyspieszyć stronę”, tylko z konkretami, które kosztowały pieniądze.
Strony się pętliły
Po wejściu na produkt - timeout. Klient odświeża, ładuje się 12 sekund. Bounce rate dwucyfrowy.
Płatność padała losowo
Redirect z koszyka do Przelewy24 wracał błędem. Klient zostawał ze stanem „oczekuje na płatność”.
Eksport zamówień padał
Każdy raport kończył ręcznym przepisywaniem. Operacja w sekundach zajmowała godziny.
Co ten chaos kosztował naprawdę.
„Strona wolno chodzi” to abstrakcja. Konkretne koszty to godziny pracy zespołu i utracone zamówienia w piku.
Ręczne zwroty po każdym weekendzie
Co kilka dni jeden klient kupował sztukę, której fizycznie nie było. Drugi już zarezerwował ją wcześniej. Telefon, przeprosiny, ręczny zwrot przez panel P24, przegrzebanie maila. Każdy taki przypadek - kilkanaście minut zespołu plus utracona reputacja.
Klienci nie kończyli płatności
Redirect z koszyka do Przelewy24 wracał błędem losowo (nie zawsze, nie tylko z konkretnej przeglądarki - po prostu czasem). Klient widział „spróbuj jeszcze raz”, w panelu wisiał status „oczekuje na płatność”, koszyk zostawał. Nikt nie kupował drugi raz.
Eksport zamówień jako tortura
Raport tygodniowy do księgowości - klik „eksportuj XLS” - timeout PHP po 30 sekundach. Ręczne przepisywanie z panelu admin. W piku świątecznym 200 zamówień tygodniowo = pół dnia pracy jednej osoby zamiast 30 sekund klikania.
Stronka „dla swoich”
Royal Beef sprzedaje wołowinę za 200-400 zł/kg. Klienci wracają po kilkaset złotych miesięcznie. Każdy taki klient ma znajomych, którym mówi „nie kupuj tam, im zawsze coś pada”. Najtańszy klient to powracający - i to ich Royal Beef tracił najszybciej.
Black Friday na horyzoncie
Termin migracji nie był „wolałbyśmy do końca roku”. Był „Black Friday za 8 tygodni”. W 2023 sklep padał w piku - klient pamiętał. W 2024 padać nie mógł.
30 minut audytu
Pierwsza rzecz, którą robimy przy każdym audycie WooCommerce - klikamy „Stan witryny” w panelu WP. Druga - patrzymy w logi serwera z ostatnich 7 dni. Trzecia - sprawdzamy autoload options w bazie. Te trzy ruchy pokazały od razu, że nie chodzi o „kolejną wtyczkę optymalizacyjną”.
Trzy z tych liczb tłumaczyły wszystko, co klient zgłosił:
- 40 MB pamięci PHP - mimo że host w php.ini deklarował 512M. Limit hardcoded'owany w
wp-config.phpprzez poprzedniego dewelopera. Każde cięższe zapytanie (export, raport, kompozytowy produkt) zżerało wolną pamięć i wracało jako 500. - Brak object cache - każda strona produktu robiła 200+ zapytań do MySQL przy każdym wejściu. Przy 46 wtyczkach to oznaczało setki milisekund samej bazy danych na każdy klik.
- Brak natywnego blokowania stocku - WooCommerce out-of-the-box pozwala dwóm klientom dodać tę samą sztukę do koszyka równocześnie. To nie jest bug, to feature. Dla sklepu z unikatowymi produktami to klęska.
Jak sklep żyje teraz. Po 6 tygodniach.
Nie chodziło o to, żeby strona była „szybsza” - chodziło o to, żeby zespół klienta nie musiał oddzwaniać do swoich klientów z przeprosinami.
Przedtem
- W piku - kilkanaście minut tygodniowo na ręczne zwroty
- Failed redirects do P24 - kilka tygodniowo
- Eksport raportów - 40 minut ręcznej pracy
- Strona ładuje się 12s w peak hours
- Stres przed Black Friday - „czy padniemy znowu”
- Brak wglądu, co dzieje się pod maską
Po 6 tygodniach
- Zero podwójnie sprzedanych sztuk od deployu - 6 miesięcy temu
- Zero failed redirects do P24 (sklep dostał własną kolejkę)
- Eksport raportów - 2 sekundy, jedno kliknięcie
- Strona odpowiada w 0.148 s, ładuje się w 2.1 s
- Black Friday 2024: zero downtime, +38% obrotu r/r
- Dashboard z metrykami sklepu i alertami w realtime
Najważniejsza zmiana nie jest mierzalna w sekundach ani w gigabajtach pamięci. To zmiana w tym, ile godzin tygodniowo zespół Royal Beef poświęca na sklep zamiast na klientów, mięso i hodowlę.
Tutaj opisujemy jak. Tam zobaczysz, jak wygląda.
Case study to widok od kuchni — decyzje techniczne, kompromisy, kod, który niesie atomowe blokowanie stocku. Wpis portfolio to ten sam projekt od strony klienta: jak wygląda sklep Royal Beef, kolory, fotografie produktów, koszyk, który wreszcie nie gubi pozycji.
Royal Beef — premium sklep z wołowiną Highland, który chodzi jak należy.
Zobacz, jak wygląda sklep z perspektywy klienta — paleta wołowiny, karty produktów, koszyk, checkout. To samo wdrożenie, tylko od strony tego, dla kogo to wszystko zbudowaliśmy — dla klienta, który zamawia polędwicę na niedzielę i nie chce próbować dwa razy.
Liczby, które widzi klient. I co znaczą dla biznesu.
Wszystkie pomiary z produkcji - PageSpeed Insights z polem CrUX (28-day rolling), wp-cli dla pamięci PHP, logi serwera dla protokołów. Bez „lab” testów na pusto.
Strona odpowiada zanim klient zauważy, że klikał. Mniej osób bounce'uje na pierwszej stronie produktu.
Eksporty, raporty, kompozytowe produkty wreszcie kończą, zamiast wracać błędem.
Google rankuje sklep wyżej. Klient ładuje szybciej. Lepsza pozycja w wyszukiwarce.
| Metryka | Przed | Po | Co to znaczy dla biznesu |
|---|---|---|---|
| Czas odpowiedzi serwera (TTFB) | 1.8 s | 0.148 s -91% | Klient nie zdąży się znudzić zanim strona zacznie się ładować. Niższy bounce rate, więcej widocznych produktów. |
| Pamięć dostępna sklepowi | 40 MB | 512 MB 12.8x | Eksporty, raporty, kompozytowe produkty wreszcie kończą. Zero „błąd 500” przy normalnej operacji. |
| Czas pojawienia się głównego elementu | brak danych | 2.1 s good | Klient widzi produkt i przycisk „dodaj do koszyka” w 2 sekundy. To próg, w którym Google podbija ranking SEO. |
| Skakanie układu strony | brak danych | 0 perfect | Strona nie skacze klientowi pod kursorem. Mniej przypadkowych kliknięć w reklamy zamiast w produkt. |
| Podwójnie sprzedane sztuki | regularne | 0 solved | Zero ręcznych zwrotów, zero przepraszających telefonów, zero zniszczonej reputacji premium-brand. |
| Niedoszłe płatności w P24 | regularne | 0 solved | Klient kończy płatność za pierwszym razem. Mniej koszyków „oczekuje na płatność” do ręcznego sprzątania. |
| Eksport zamówień do XLS | 40 min ręcznie | 2 s solved | Raport do księgowości zajmuje sekundy. Zespół zajmuje się sklepem, nie przepisywaniem zamówień. |
| Stabilność serwera w piku | zmienne | CPU 99.5% idle stable | Black Friday 2024: zero downtime, +38% obrotu rok do roku. Sklep wytrzymał trzy piki bez ingerencji. |
| Wolna pamięć VPS | 1404 MB | 2355 MB +951 MB | Sklep ma zapas mocy na promocje, kolejne wtyczki, integracje. Bez przepłacania za większy serwer. |
| Protokół połączenia | HTTP/1.1 | HTTP/2 + QUIC verified | Strona ładuje się szybciej szczególnie na mobile (gdzie sieć jest niestabilna). Apple, Google, Cloudflare wszyscy używają tego od lat. |
| Higiena bazy danych | rozsypane | 447 opt. / 66 KB czyste | Wpisy bazy są policzone i nie rosną w nieskończoność. Bazujemy na czystym fundamencie. |
Pierwszy raz, kiedy w piku sprzedaży nie musieliśmy ręcznie zwracać pieniędzy klientom za podwójnie sprzedaną sztukę. Nie wiedzieliśmy, że tak po prostu może być.
Rozpoznajesz swój sklep w tej historii?
Większość sklepów WooCommerce w Polsce, które robią obrót 500K-5M zł rocznie, ma podobny układ pod maską. Jeśli któreś z poniższych pytań brzmi znajomo - prawdopodobnie czeka Cię ta sama rozmowa.
To prawie nigdy nie jest wina P24. W 9 na 10 przypadków - race condition na warstwie WooCommerce: dwóch klientów próbuje płacić za tę samą sztukę, jeden z nich dostaje błąd. Rozwiązanie: atomowe blokowanie stocku na poziomie bazy.
Prawie zawsze to PHP, który dochodzi do limitu pamięci (typowo 40-128 MB) i wraca błędem 500. Sprawdź w panelu „Stan witryny” - jeśli widzisz mniej niż 256 MB, host hardcoded'ował limit. To naprawia się jednym wpisem w wp-config.php - ale ktoś musi wiedzieć, gdzie szukać.
Sprawdź w PageSpeed Insights z polem CrUX (nie „lab”). Jeśli LCP > 2.5s, tracisz miejsca w rankingu Google i klientów na pierwszym ekranie. Typowa diagnoza: brak object cache, nieoptymalny serwer, zbyt dużo niepotrzebnych wtyczek.
Shared hosting ma jedną wadę: dzielisz CPU z setką sklepów obok. W ich piku - ty czujesz. Migracja na własny VPS z OpenLiteSpeed daje stabilną odpowiedź pod presją - i kosztuje średnio 600-1200 zł/mc plus jednorazowy setup.
To moment, w którym kontrakt LTS (long-term support) zwraca się w pierwszym kwartale. Patche w 48h, monitoring uptime, kwartalny review, dedykowany kontakt - zamiast „a może by się odezwać do tego programisty od trzech lat temu”.
Dla developerów: stack, decyzje, kod.
12 elementów stacka produkcyjnego, 8 decyzji architektonicznych z uzasadnieniem, 3 bloki kodu pluginu rb-cart-reservation, diagram flow requestu.
Engineering appendix Stack technologiczny, decyzje architektoniczne i kod Rozwiń, aby zobaczyć stack produkcyjny, decyzje architektoniczne, kod pluginu, diagram flow requestu.
Stack produkcyjny
12 elementów, które wjeżdżają w produkcję
Diagram architektury
Cloudflare → OpenLiteSpeed → PHP → Redis / MySQL
Flow requestu od klienta
Decyzje architektoniczne
Trzy decyzje, które niosą cały projekt
Migracja na własny VPS Ubuntu
Shared hosting kosztował 80 zł/mc, ale zżerał 15-20% obrotu na timeoutach w piku. VPS kosztuje 750 zł/mc i daje pełną kontrolę nad pamięcią, opcache, my.cnf. Zwrot z inwestycji - jeden Black Friday bez padów.
Shared hosting nie pozwala tunować php.ini, kompilować rozszerzeń ani konfigurować my.cnf. Bez kontroli nad warstwą PHP/MySQL każda optymalizacja jest grą w „uda się, jeśli host nie zmieni czegoś w tle”.
OpenLiteSpeed zamiast Apache
LiteSpeed Cache to plugin, który rozumie WooCommerce - wie, że koszyka i checkoutu nie cache'uje, ale strony produktów już tak. Efekt: response w 12ms zamiast 800ms, przy zerowych zmianach w kodzie sklepu.
LiteSpeed Cache działa wprost z serwerem przez moduł natywny, bez warstwy .htaccess i bez mod_php. WooCommerce-aware z pudełka, QUIC out-of-the-box, dedykowany ESI dla dynamicznych fragmentów.
Custom plugin rb-cart-reservation
WooCommerce out-of-the-box pozwala dwóm klientom kupić tę samą sztukę. Dla supermarketu z zapasem 200 sztuk to nie problem. Dla Royal Beef z paczkami z konkretnej tuszy - każda taka kolizja to telefon przepraszający. Plugin blokuje stock w bazie w momencie dodania do koszyka, na 15 minut.
Klucz: atomowy SELECT ... FOR UPDATE w transakcji - blokuje wiersz stocku do końca commita, więc drugi proces poczeka. Bez tego WooCommerce gubi sztuki przy współbieżnym ruchu, niezależnie od liczby pluginów cache. Implementacja: dedykowana tabela rb_stock_reservation z TTL i CRON cleanup.
Pozostałe decyzje - krótko
- 04
Redis object cache przez socket, nie TCP
TCP loopback dodaje narzut ~80μs per request, plus context switche. Unix socket leci wprost przez file descriptor. Przy 200+ object cache hitów per page request to różnica ~16ms.
- 05
HPOS + classic postmeta compatibility
Plugin sam wykrywa, czy WooCommerce ma włączone High-Performance Order Storage i wybiera tabelę. Migracja w obie strony bez przepisywania kodu.
- 06
MySQL 8 bez deprecated query_cache_type
MySQL 8 usunął
query_cache_type- jeśli zostawisz wpis z 5.7 wmy.cnf, serwer wystartuje, ale rzuca warningi przy każdym zapytaniu. Klasyczny pełzający bug po migracjach. - 07
Cloudflare jako CDN + WAF
Statyczne assety z worker edge, WAF blokuje boty walące o
/xmlrpc.phpi/wp-login.php. Origin nie widzi 73% ruchu, który nigdy do niego nie dochodzi. - 08
HTTP/2 + QUIC (h3) zweryfikowane przez alt-svc
QUIC trzeba zweryfikować nagłówkiem
alt-svcw odpowiedzi - bez tego klient nie zna alternatywnego endpointu i nie przełączy się na UDP. Różnica między „enabled w configu” a „faktycznie używane”.
Kod
Trzy bloki, które niosą cały plugin
PHP 1. Atomowa rezerwacja stocku - rb_reserve_stock() class-stock.php
function rb_reserve_stock($product_id, $qty, $session_id) {
global $wpdb;
$table = rb_stock_table(); // HPOS vs postmeta
$ttl = 900; // 15 min hold
$wpdb->query('START TRANSACTION');
// row-level lock — drugi proces poczeka
$row = $wpdb->get_row( $wpdb->prepare(
"SELECT stock, reserved FROM {$table}
WHERE product_id = %d FOR UPDATE",
$product_id
));
$available = $row->stock - $row->reserved;
if ($available < $qty) {
$wpdb->query('ROLLBACK');
return new WP_Error('rb_no_stock', __('Brak na stanie', 'rb'));
}
$wpdb->query( $wpdb->prepare(
"INSERT INTO rb_stock_reservation
(product_id, qty, session_id, expires_at)
VALUES (%d, %d, %s, NOW() + INTERVAL %d SECOND)",
$product_id, $qty, $session_id, $ttl
));
$wpdb->query('COMMIT');
return true;
}PHP 2. HPOS detection - kod sam wybiera tabelę helpers.php
function rb_stock_table(): string {
global $wpdb;
// HPOS enabled? Sprawdź feature flag WooCommerce
if (class_exists( '\\Automattic\\WooCommerce\\Utilities\\OrderUtil' )
&& \\Automattic\\WooCommerce\\Utilities\\OrderUtil::custom_orders_table_usage_is_enabled()) {
return "{$wpdb->prefix}wc_orders";
}
// fallback: klasyczne postmeta
return "{$wpdb->prefix}postmeta";
}PHP 3. CRON cleanup z LIMIT 200 (batch) cron.php
add_action('rb_cleanup_reservations', function () {
global $wpdb;
$wpdb->query(
"DELETE FROM rb_stock_reservation
WHERE expires_at < NOW()
LIMIT 200" // batch — nie blokuj request'u
);
});
// schedule co minutę
if (! wp_next_scheduled('rb_cleanup_reservations')) {
wp_schedule_event( time(), 'rb_every_minute', 'rb_cleanup_reservations' );
}Plan rozwoju
Co dalej z projektem
Najczęstsze pytania.
Ile to wszystko kosztowało?
Projekt Royal Beef był rozłożony na 6 tygodni: audyt + plan (1 tydzień), migracja serwerowa (2 tygodnie), custom plugin rb-cart-reservation (2 tygodnie), optymalizacja i testy obciążeniowe (1 tydzień). Plus opieka LTS - kontrakt miesięczny.
Konkretne kwoty omawiamy w rozmowie - zależą od skali, stack'u, ile kodu trzeba napisać od zera, ile można odzyskać. Każdy projekt zaczynamy od bezpłatnej rozmowy, w której mówimy, co byśmy zrobili i jaką cenę za to oferujemy. Bez zobowiązań.
Dlaczego nie zostaliśmy na shared hostingu?
Shared hosting daje stały, deterministyczny koszt (50-150 zł/mc) i zero ops. Ale za to płacisz w trzech walutach: nie kontrolujesz pamięci PHP ani konfiguracji bazy, plus dzielisz CPU z setką sklepów obok - więc nigdy nie wiesz, czy „dziś wolno” to wina kodu, czy sklepu obok.
Dla sklepu robiącego 500K-5M zł obrotu rocznie różnica między shared a VPS z opieką (~600-1200 zł/mc) zwraca się po pierwszym Black Friday, w którym nie tracisz konwersji na timeoutach. Royal Beef policzył to sam - jeden weekend padów kosztował więcej niż roczna różnica w hostingu.
Co dokładnie znaczy „race condition w koszyku”?
WooCommerce rezerwuje stock dopiero przy zakończeniu zamówienia. Między „kliknij dodaj do koszyka” a „zapłać teraz” stock jest formalnie wolny - co wystarcza, dopóki nie masz dwóch klientów w tym samym oknie czasowym, którzy klikają tę samą sztukę.
W premium e-commerce z unikatowymi produktami (Royal Beef sprzedaje konkretne paczki z konkretnej tuszy) każda taka kolizja to ręczny zwrot, telefon, przeprosiny i utracona reputacja. Rozwiązanie: dedykowany plugin, który blokuje stock w bazie natychmiast po dodaniu do koszyka, na 15 minut.
Czy te liczby mam też dla swojego sklepu po migracji?
Krótka odpowiedź: zależy od punktu startowego. Royal Beef miał wyjątkowo zły punkt startowy (40 MB pamięci, brak object cache, HTTP/1.1) i wyjątkowo dobry sufit (premium e-commerce, niewielka liczba produktów). 12x na czasie odpowiedzi to liczba dla takiej luki.
Dla sklepu, który jest już na decent VPS z LiteSpeed lub na zarządzanym WordPress hostingu, realna poprawa to typowo 2-4x na czasie odpowiedzi i wejście do progu „dobry” w Google PageSpeed. Dla porównania z konkretnymi liczbami z Twojego sklepu - zacznijmy od rozmowy o tym, co dzieje się w Twoim panelu „Stan witryny”.
Następny krok
Twój sklep też losowo gubi zamówienia?
Sprawdzimy to za 30 minut. Bez slajdów, bez zobowiązań, bez „a może by tak rewolucję”. Jeśli Twój sklep ma symptomy podobne do Royal Beef - od razu Ci powiemy, co konkretnie, w jakiej kolejności, za ile.
- Raport „Stan witryny” z konkretnymi liczbami z Twojego panelu - nie ogólne rekomendacje
- Analiza Core Web Vitals z CrUX - dane od Google z Twojego ruchu, nie testy lab
- 3 priorytetowe akcje do wdrożenia w 30 dni z konkretnym efektem biznesowym
- Estymacja kosztów z góry, bez „wycenimy później” i bez zobowiązania współpracy
®