Każde zapytanie się liczy: Dlaczego wydajność bazy danych to ukryte wąskie gardło Twojego sklepu

Przeprowadziłem audyt ponad 200 sklepów PrestaShop w ciągu ostatnich pięciu lat. Za każdym razem — bez wyjątku — największy zysk wydajnościowy pochodzi z warstwy bazy danych. Nie z CDN. Nie z kompresji obrazów. Nie z kolejnej wtyczki cachującej. Z bazy danych.

Monitorowanie wydajności serwera do optymalizacji zapytań bazy danych PrestaShop

Typowe ładowanie strony PrestaShop wykonuje od 80 do 300 zapytań SQL. W sklepie z ponad 10 000 produktami ta liczba może przekroczyć 500 na stronach kategorii z nawigacją warstwową. Kiedy nawet kilka z tych zapytań zajmuje 200ms zamiast 2ms, Twój Time to First Byte (TTFB) rośnie, CPU serwera skacze, a wskaźnik konwersji spada. Własne badania Google pokazują, że wzrost czasu ładowania o 100ms redukuje konwersje nawet o 7%.

Ten artykuł nie jest ogólnikowym przeglądem “jak przyspieszyć PrestaShop”. Zagłębiam się w wydajność bazy danych — identyfikację wolnych zapytań, zrozumienie planów wykonania, strojenie InnoDB, wybór właściwych indeksów i oczyszczanie zaśmieconych danych, które po cichu zabijają Twój sklep. Jeśli przeczytałeś już nasz ogólny przewodnik wydajności, traktuj to jako kolejny rozdział.

Krok 1: Włącz Slow Query Log — Twoje najlepsze narzędzie diagnostyczne

Zanim cokolwiek zoptymalizujesz, musisz zobaczyć, co jest naprawdę wolne. Slow query log MySQL rejestruje każde zapytanie, które przekracza zdefiniowany przez Ciebie próg czasu. Straciłem rachubę, ilu właścicieli sklepów pomija ten krok i przeskakuje od razu do “dodaj Redis” — to jak branie środków przeciwbólowych bez zdiagnozowania urazu.

Włączanie Slow Query Log

Dodaj te linie do pliku konfiguracyjnego MySQL lub MariaDB (zazwyczaj /etc/mysql/mysql.conf.d/mysqld.cnf lub /etc/mysql/mariadb.conf.d/50-server.cnf):

# Enable slow query logging
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow-query.log
long_query_time = 0.5
log_queries_not_using_indexes = 1
min_examined_row_count = 100

Kluczowe ustawienia:

  • long_query_time = 0.5 — Loguj każde zapytanie trwające dłużej niż 500ms. Domyślna wartość 10 sekund jest absurdalnie wysoka dla e-commerce; zanim zapytanie zajmie 10 sekund, Twój klient odszedł 8 sekund temu.
  • log_queries_not_using_indexes = 1 — Wyłapuje zapytania wykonujące pełne skany tabel, nawet jeśli są szybkie na małych zbiorach danych. To tykające bomby, które eksplodują, gdy Twój katalog rośnie.
  • min_examined_row_count = 100 — Filtruje trywialne zapytania, żeby Twój log skupiał się na prawdziwych problemach.

Po włączeniu zrestartuj MySQL i pozwól mu zbierać dane przez co najmniej 24 godziny obejmujące pełen cykl ruchu. Następnie przeanalizuj:

# Summarize the worst offenders
mysqldumpslow -s t -t 20 /var/log/mysql/slow-query.log

# Or use pt-query-digest from Percona Toolkit for deeper analysis
pt-query-digest /var/log/mysql/slow-query.log > /tmp/query-report.txt

pt-query-digest z Percona Toolkit to złoty standard. Grupuje podobne zapytania, rankuje je po łącznym czasie wykonania i pokazuje dokładnie, na co Twoja baza danych poświęca swoje zasoby. Zainstaluj go — jest bezpłatny i dostępny w większości menedżerów pakietów.

Krok 2: Czytanie planów EXPLAIN — Umiejętność, która oddziela zgadywanie od wiedzy

Kiedy już zidentyfikujesz wolne zapytania, następnym krokiem jest zrozumienie dlaczego są wolne. Polecenie EXPLAIN MySQL pokazuje plan wykonania zapytania — jak optymalizator decyduje o pobraniu Twoich danych.

EXPLAIN SELECT p.id_product, pl.name, p.price
FROM ps_product p
LEFT JOIN ps_product_lang pl ON p.id_product = pl.id_product AND pl.id_lang = 1
LEFT JOIN ps_category_product cp ON p.id_product = cp.id_product
WHERE cp.id_category = 42 AND p.active = 1
ORDER BY p.date_add DESC;

Na co zwrócić uwagę w wynikach

KolumnaCzerwona flagaCo to oznacza
typeALLPełny skan tabeli — MySQL czyta każdy wiersz. Akceptowalne tylko na malutkich tabelach.
typeindexPełny skan indeksu — lepiej niż ALL, ale nadal czyta każdy wpis indeksu.
possible_keysNULLBrak dostępnych indeksów. Optymalizator nie ma z czym pracować.
keyNULLNie wybrano indeksu, nawet jeśli opcje istnieją. Zazwyczaj oznacza, że statystyki indeksów są nieaktualne.
rowsWysoka liczbaSzacowane przejrzane wiersze. Porównaj z faktyczną liczbą wyników — proporcja 50 000:12 sygnalizuje brakujący indeks.
ExtraUsing temporaryMySQL tworzy tymczasową tabelę, często wyrzucając dane na dysk.
ExtraUsing filesortWyniki sortowane w pamięci lub na dysku zamiast przez kolejność indeksu.

Idealny wynik EXPLAIN pokazuje type: ref lub type: eq_ref, konkretny użyty key i niskie oszacowanie rows. Kiedy widzisz type: ALL w połączeniu z Using temporary; Using filesort, znalazłeś zapytanie, które wykonuje maksimum pracy przy minimum wyników.

Przykład z prawdziwego świata PrestaShop

Jedno z najczęstszych wolnych zapytań, na które natrafiam w sklepach PrestaShop, dotyczy tabeli ps_specific_price. W sklepach z tysiącami cen specjalnych (rabaty ilościowe, ceny grupowe, promocje z ograniczeniem czasowym) ten wzorzec zapytania pojawia się w slow logu stale:

EXPLAIN SELECT * FROM ps_specific_price
WHERE id_product = 1542
AND id_shop IN (0, 1)
AND id_currency IN (0, 1)
AND id_country IN (0, 8)
AND id_group IN (0, 1, 3)
AND id_customer = 0
AND from_quantity >= 1
AND (from = '0000-00-00 00:00:00' OR from <= NOW())
AND (to = '0000-00-00 00:00:00' OR to >= NOW());

W sklepie z ponad 200 000 wierszami specific_price to zapytanie przeglądało 180 000 wierszy, żeby zwrócić 3. Naprawą był indeks złożony:

ALTER TABLE ps_specific_price
ADD INDEX idx_product_shop_currency
(id_product, id_shop, id_currency, id_country);

Rezultat: czas zapytania spadł z 340ms do 0,8ms. Na stronie kategorii ładującej 36 produktów ten pojedynczy indeks oszczędził 12 sekund skumulowanego czasu zapytań na jedno załadowanie strony.

Krok 3: Najbardziej problematyczne tabele PrestaShop — i jak je naprawić

Po analizie slow query logów z dziesiątek sklepów, te tabele odpowiadają za większość problemów z wydajnością bazy danych:

ps_connections i ps_connections_page

Te tabele logują każde połączenie odwiedzającego i każdą przeglądaną stronę. W sklepie z 5000 dziennych odwiedzających przeglądających średnio 4 strony to 20 000 wierszy dziennie — 7,3 miliona wierszy rocznie. Widziałem sklepy z 50 milionami wierszy w ps_connections_page, które nigdy nie były czyszczone.

-- Check the damage
SELECT table_name, table_rows,
  ROUND(data_length/1024/1024, 2) AS data_mb,
  ROUND(index_length/1024/1024, 2) AS index_mb
FROM information_schema.tables
WHERE table_schema = 'prestashop'
AND table_name IN ('ps_connections', 'ps_connections_page',
  'ps_log', 'ps_mail', 'ps_guest', 'ps_pagenotfound');

-- Clean old connection data (keep 90 days)
DELETE FROM ps_connections_page
WHERE id_connections IN (
  SELECT id_connections FROM ps_connections
  WHERE date_add < DATE_SUB(NOW(), INTERVAL 90 DAY)
);
DELETE FROM ps_connections WHERE date_add < DATE_SUB(NOW(), INTERVAL 90 DAY);

-- Clean old logs (keep 30 days)
DELETE FROM ps_log WHERE date_add < DATE_SUB(NOW(), INTERVAL 30 DAY);

-- Clean sent emails log (keep 60 days)
DELETE FROM ps_mail WHERE date_add < DATE_SUB(NOW(), INTERVAL 60 DAY);

-- Clean 404 tracking (keep 30 days)
DELETE FROM ps_pagenotfound WHERE date_add < DATE_SUB(NOW(), INTERVAL 30 DAY);

Ważne: Po dużych usunięciach odzyskaj przestrzeń dyskową:

OPTIMIZE TABLE ps_connections, ps_connections_page, ps_log, ps_mail, ps_guest;

ps_cart i ps_cart_product

Porzucone koszyki gromadzą się w nieskończoność. Pięcioletni sklep może mieć 2 miliony rekordów koszyków z 8 milionami wierszy cart_product, z których 95% nigdy nie skonwertuje. Wyczyść koszyki starsze niż 6 miesięcy, które nie mają powiązanego zamówienia:

-- Identify orphan carts (no order placed)
DELETE cp FROM ps_cart_product cp
INNER JOIN ps_cart c ON cp.id_cart = c.id_cart
LEFT JOIN ps_orders o ON c.id_cart = o.id_cart
WHERE o.id_cart IS NULL
AND c.date_add < DATE_SUB(NOW(), INTERVAL 180 DAY);

DELETE c FROM ps_cart c
LEFT JOIN ps_orders o ON c.id_cart = o.id_cart
WHERE o.id_cart IS NULL
AND c.date_add < DATE_SUB(NOW(), INTERVAL 180 DAY);

ps_search_index i ps_search_word

Indeks wyszukiwania produktów może się ogromnie rozrosnąć w dużych katalogach. Po czyszczeniu produktów lub zbiorczej aktualizacji, przebuduj go:

-- Nuclear option: truncate and rebuild
TRUNCATE TABLE ps_search_index;
TRUNCATE TABLE ps_search_word;

-- Then trigger a full reindex via CLI:
php bin/console prestashop:search:reindex

Krok 4: Strategia indeksów dla tabel modułów PrestaShop

Jeśli tworzysz moduły — lub używasz modułów zewnętrznych tworzących niestandardowe tabele — indeksowanie jest Twoją odpowiedzialnością. Tabele rdzenia PrestaShop mają przyzwoite indeksy, ale tabele modułów często nie mają żadnych.

Zasady efektywnego indeksowania

  1. Indeksuj kolumny używane w klauzulach WHERE — Jeśli Twój moduł odpytuje WHERE id_product = X AND id_shop = Y, utwórz indeks złożony na (id_product, id_shop).
  2. Kolejność kolumn ma znaczenie w indeksach złożonych — Umieść najbardziej selektywną kolumnę na początku. Kolumna z 10 000 unikalnymi wartościami powinna poprzedzać tę z 3 unikalnymi wartościami.
  3. Pokryj swoje ORDER BY — Jeśli często sortujesz po date_add DESC, uwzględnij date_add w indeksie, aby uniknąć operacji filesort.
  4. Nie przeindeksowuj — Każdy indeks spowalnia operacje INSERT i UPDATE. Dla tabeli z dużą liczbą zapisów (jak tabela logów) przemyśl dokładnie, zanim dodasz indeksy.
-- Example: module review table with common query patterns
CREATE TABLE ps_mymodule_reviews (
  id_review INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  id_product INT UNSIGNED NOT NULL,
  id_customer INT UNSIGNED NOT NULL,
  id_shop INT UNSIGNED NOT NULL DEFAULT 1,
  rating TINYINT UNSIGNED NOT NULL,
  status TINYINT NOT NULL DEFAULT 0,
  date_add DATETIME NOT NULL,

  -- Composite index for "show approved reviews for product X"
  INDEX idx_product_status (id_product, status, date_add),

  -- Index for "show all reviews by customer"
  INDEX idx_customer (id_customer),

  -- Index for admin list with shop filter
  INDEX idx_shop_status (id_shop, status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

Znajdowanie brakujących indeksów na istniejących tabelach

-- List all module tables without non-primary indexes
SELECT t.table_name, t.table_rows
FROM information_schema.tables t
LEFT JOIN information_schema.statistics s
  ON t.table_name = s.table_name
  AND t.table_schema = s.table_schema
  AND s.index_name != 'PRIMARY'
WHERE t.table_schema = 'prestashop'
AND t.table_name LIKE 'ps_%'
AND t.table_rows > 1000
AND s.index_name IS NULL
ORDER BY t.table_rows DESC;

To zapytanie ujawnia tabele z ponad 1000 wierszami i bez indeksów wtórnych — główni kandydaci do optymalizacji.

Krok 5: Konfiguracja InnoDB — Ustawienia, które naprawdę mają znaczenie

InnoDB to silnik przechowywania danych PrestaShop. Jego domyślna konfiguracja jest zaprojektowana dla ogólnego obciążenia na skromnym sprzęcie. Dla dedykowanego serwera bazy danych e-commerce musisz go dostroić.

Optymalizacja bazy danych i konfiguracja pełnostronicowej pamięci podręcznej dla szybszego PrestaShop

Krytyczne ustawienia

[mysqld]
# === Buffer Pool: The Single Most Important Setting ===
# Set to 70-80% of available RAM on a dedicated DB server.
# For shared hosting: 50% of RAM, minimum 1GB.
innodb_buffer_pool_size = 4G

# Split the buffer pool into instances (1 per GB)
innodb_buffer_pool_instances = 4

# === Log Files: Larger = Fewer Disk Writes ===
# Default 48M is too small. Set to 25% of buffer pool, max 2G each.
innodb_log_file_size = 1G
innodb_log_buffer_size = 64M

# === Flush Behavior ===
# 1 = Full ACID (safest, slower)
# 2 = Flush to OS buffer each commit, disk write once/sec (good compromise)
# 0 = Flush once/sec (fastest, risks 1 sec of data on crash)
innodb_flush_log_at_trx_commit = 2

# Use O_DIRECT to avoid double-buffering with OS page cache
innodb_flush_method = O_DIRECT

# === I/O Capacity ===
# SSD: 2000-4000. HDD: 200-400. Cloud SSD: 1000-2000.
innodb_io_capacity = 2000
innodb_io_capacity_max = 4000

# === Thread Concurrency ===
innodb_read_io_threads = 4
innodb_write_io_threads = 4
innodb_purge_threads = 4

# === Per-Table Tablespace (default in MySQL 5.7+, verify it's on) ===
innodb_file_per_table = 1

# === Temp Tables ===
tmp_table_size = 64M
max_heap_table_size = 64M

# === Sort and Join Buffers (per-connection, don't over-allocate) ===
sort_buffer_size = 2M
join_buffer_size = 4M
read_buffer_size = 2M
read_rnd_buffer_size = 1M

# === Connection Pool ===
max_connections = 200
thread_cache_size = 100

# === Table Cache ===
table_open_cache = 4000
table_definition_cache = 2000

# === Disable Performance Schema in Production (saves ~400MB RAM) ===
performance_schema = OFF

Zrozumienie innodb_buffer_pool_size

To pojedyncze najbardziej wpływowe ustawienie MySQL dla PrestaShop. Buffer pool to miejsce, gdzie InnoDB cachuje dane tabel i indeksy w pamięci. Kiedy zapytanie potrzebuje danych, które już są w buffer pool, są serwowane z RAM zamiast z dysku — o rząd wielkości szybciej.

Jak dobrać rozmiar:

-- Check your total database size
SELECT ROUND(SUM(data_length + index_length) / 1024 / 1024 / 1024, 2) AS total_gb
FROM information_schema.tables
WHERE table_schema = 'prestashop';

-- Check buffer pool hit ratio (should be > 99%)
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read_requests';
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_reads';

-- Calculate: hit_ratio = 1 - (reads / read_requests) * 100
-- If below 99%, increase buffer_pool_size

Benchmark z prawdziwego świata od testów Releem na MariaDB 10.5 ze sklepem PrestaShop pokazał, że właściwe strojenie InnoDB (buffer pool na 3,2GB dla bazy 1GB) zredukowało czas odpowiedzi o 39% — z 610ms do 370ms — i zwiększyło liczbę zapytań na sekundę o 53%.

Query Cache: MariaDB vs MySQL 8

To często źle rozumiany temat. MySQL 8.0 całkowicie usunął query cache. Oracle uznał, że rywalizacja o mutex w cache powodowała więcej problemów niż rozwiązywała przy dużej skali. MariaDB jednak nadal go wspiera i może pomóc sklepom PrestaShop z obciążeniem zdominowanym przez odczyty.

Dla MariaDB (10.5+):

query_cache_type = 1
query_cache_size = 64M
query_cache_limit = 2M

Dla MySQL 8.0+: Nie próbuj go włączać — nie istnieje. Zamiast tego polegaj na prawidłowo dobranym buffer pool i rozważ cachowanie na poziomie aplikacji z Redis.

Jeśli jesteś na MySQL 8 i rozważasz zmianę, MariaDB 10.11 jest w pełni kompatybilna z PrestaShop i wielu dostawców hostingu oferuje ją jako bezpośredni zamiennik.

Krok 6: Specyficzne dla PrestaShop wzorce zapytań do obserwacji

Poza ogólnym strojeniem MySQL, PrestaShop ma specyficzne wzorce zapytań, które powodują problemy wydajnościowe przy dużej skali. Oto te, z którymi spotykam się najczęściej:

Problem zliczania produktów

Nawigacja warstwowa PrestaShop (wyszukiwanie facetowe) wykonuje zapytania COUNT dla każdej kombinacji filtrów. W kategorii z 15 grupami atrybutów i 200 atrybutami może to wygenerować ponad 200 indywidualnych zapytań COUNT na załadowanie strony. Każde zapytanie wygląda niewinnie:

SELECT COUNT(DISTINCT p.id_product)
FROM ps_product p
INNER JOIN ps_product_attribute_combination pac ...
WHERE ... AND pac.id_attribute = 47;

Ale 200 takich zapytań po 10ms każde to 2 sekundy czystego czasu zapytań. Naprawą jest albo użycie pre-obliczonej tabeli indeksów (co mogą zapewnić moduły takie jak nasze moduły SEO i wydajnościowe), albo ograniczenie widocznych filtrów przez konfigurację kategorii.

Problem N+1 zapytań w hookach modułów

Moduł zarejestrowany na displayProductListReviews, który wykonuje zapytanie na produkt, tworzy problem N+1. Jeśli Twoja strona kategorii pokazuje 36 produktów, to 36 dodatkowych zapytań — na moduł:

// Bad: query per product in a list hook
public function hookDisplayProductListReviews($params) {
    $id_product = (int)$params['product']['id_product'];
    $result = Db::getInstance()->getRow(
        'SELECT AVG(rating) as avg_rating
         FROM ps_mymodule_reviews
         WHERE id_product = ' . $id_product
    );
    // ...
}

// Good: batch query all products at once, cache the result
public function hookActionProductSearchAfter($params) {
    $products = $params['result']->getProducts();
    $ids = array_column($products, 'id_product');

    $ratings = Db::getInstance()->executeS(
        'SELECT id_product, AVG(rating) as avg_rating
         FROM ps_mymodule_reviews
         WHERE id_product IN (' . implode(',', array_map('intval', $ids)) . ')
         GROUP BY id_product'
    );
    // Store in static cache for use in display hook
}

Ewaluacja reguł koszyka

Sklepy z setkami aktywnych reguł koszyka cierpią przy każdym załadowaniu strony koszyka. Warunki każdej reguły koszyka (ograniczenia produktowe, kategoriowe, grupy klientów) wyzwalają osobne zapytania. Jeśli masz więcej niż 50 aktywnych reguł koszyka, rozważ:

  • Archiwizację wygasłych reguł (ustaw active na 0 i date_to na datę przeszłą)
  • Konsolidację nakładających się reguł, gdzie to możliwe
  • Dodanie indeksu na ps_cart_rule.active, ps_cart_rule.date_from, ps_cart_rule.date_to

Krok 7: Monitorowanie na produkcji — Nie ustawiaj i zapominaj

Strojenie bazy danych nie jest jednorazowym zadaniem. Dane Twojego sklepu rosną, wzorce ruchu się zmieniają, a aktualizacje modułów mogą wprowadzić nowe wzorce zapytań. Skonfiguruj bieżące monitorowanie:

Podstawowe metryki do śledzenia

-- Buffer pool efficiency (check weekly)
SELECT
  FORMAT(VARIABLE_VALUE, 0) AS buffer_pool_read_requests
FROM performance_schema.global_status
WHERE VARIABLE_NAME = 'Innodb_buffer_pool_read_requests';

-- Slow queries per hour (should trend downward)
SHOW GLOBAL STATUS LIKE 'Slow_queries';

-- Table lock waits (should be near zero for InnoDB)
SHOW GLOBAL STATUS LIKE 'Table_locks_waited';

-- Temporary tables created on disk (high = increase tmp_table_size)
SHOW GLOBAL STATUS LIKE 'Created_tmp_disk_tables';
SHOW GLOBAL STATUS LIKE 'Created_tmp_tables';

-- Thread connection usage
SHOW GLOBAL STATUS LIKE 'Threads_connected';
SHOW GLOBAL STATUS LIKE 'Max_used_connections';

Automatyczne monitorowanie

Dla sklepów, gdzie przestój oznacza realne straty przychodu, rozważ te rozwiązania monitorujące:

  • Percona Monitoring and Management (PMM) — Bezpłatne, open source, kompleksowe dashboardy MySQL/MariaDB z analizą zapytań. Tego używam dla wszystkich sklepów klientów.
  • Releem — Automatyczne strojenie MySQL, które dostosowuje parametry na podstawie Twojego rzeczywistego obciążenia. Przydatne, jeśli nie chcesz ręcznie stroić my.cnf.
  • MySQLTuner — Skrypt Perl dający szybkie rekomendacje: perl mysqltuner.pl --host 127.0.0.1

Krok 8: Kiedy dodać Redis — i co on naprawdę rozwiązuje

Redis nie jest substytutem optymalizacji bazy danych. To warstwa na wierzchu. Jeśli Twoje bazowe zapytania są wolne, Redis maskuje problem dla zcachowanych żądań i pogarsza go, gdy cache wygaśnie (thundering herd).

Po zoptymalizowaniu indeksów, wyczyszczeniu tabel i dostrojeniu InnoDB — wtedy dodaj Redis dla:

  • Przechowywania sesji — Eliminuje blokowanie plików przy równoczesnych żądaniach. Konfiguracja w config/defines.inc.php lub przez handler sesji Symfony.
  • Cache Smarty — Zapobiega zapisywaniu przez Smarty tysięcy skompilowanych plików szablonów na dysk.
  • Cache Symfony — Metadane Doctrine, routing i cache kontenera usług serwowane z pamięci.
  • Cachowanie na poziomie modułów — Wszelkie dane modułów, które są kosztowne do obliczenia, ale zmieniają się rzadko.

Rezultat: na prawidłowo dostrojonym sklepie dodanie Redis typowo redukuje TTFB o kolejne 30-50%. Nasz przewodnik wydajności szczegółowo opisuje konfigurację Redis.

Krok 9: Full Page Cache — Ostatnia warstwa

Kiedy Twoja baza danych jest szczupła, a cache aplikacji jest w Redis, ostateczną optymalizacją jest całkowite wyeliminowanie wykonywania PHP dla anonimowych odwiedzających. Varnish lub nginx FastCGI cache mogą serwować w pełni wyrenderowaną stronę w mniej niż 10ms — w porównaniu z 200-500ms dla strony renderowanej przez PHP.

Wyzwaniem w PrestaShop jest invalidacja cache. Zmiany cen, aktualizacje stanów magazynowych, rozpoczęcia/zakończenia promocji i modyfikacje koszyka wymagają albo:

  • Invalidacji opartej na tagach — Moduł xkey Varnisha pozwala tagować zcachowane strony według produktu, kategorii lub strony CMS i selektywnie je czyścić.
  • Podejścia opartego na TTL — Cachuj strony na 5-15 minut i akceptuj krótkotrwałą nieaktualność. Odpowiednie dla większości sklepów, gdzie dokładność stanów magazynowych na poziomie sekund nie jest krytyczna.
  • Podejścia hybrydowego — Cachuj powłokę strony, użyj ESI (Edge Side Includes) lub JavaScript dla dynamicznych fragmentów jak widget koszyka i status logowania.

Dla sklepów o dużym ruchu połączenie zoptymalizowanej bazy danych + cache aplikacji Redis + Varnish FPC rutynowo osiąga TTFB poniżej 50ms. Jeden z naszych klientów z katalogiem 15 000 produktów przeszedł z 800ms TTFB do 120ms używając dokładnie tego stosu.

Kolejność priorytetów: Maksymalny wpływ, minimalne ryzyko

Jeśli wyniesiesz z tego artykułu jedną rzecz, niech to będzie ta lista priorytetów:

  1. Włącz slow query log i napraw 10 najgorszych zapytań — Natychmiastowy wpływ, zerowy koszt, brak ryzyka.
  2. Wyczyść rozdęte tabeleps_connections, ps_log, ps_mail, porzucone koszyki. Natychmiastowe odzyskanie przestrzeni i wydajności.
  3. Dostrój innodb_buffer_pool — Jedna zmiana ustawienia w my.cnf, ogromny wpływ.
  4. Dodaj brakujące indeksy w tabelach modułów — Wysoki wpływ, jeśli używasz modułów z dużą ilością danych.
  5. Dodaj Redis dla sesji i cache — Niezawodna podstawa wydajności, eliminuje wąskie gardło I/O plików.
  6. Wdróż full page cache — Ostateczny mnożnik dla sklepów o dużym ruchu.

Każdy krok buduje na poprzednim. Przeskoczenie do kroku 6 bez wykonania kroków 1-4 to budowanie na chwiejnych fundamentach.

Jeśli nie wiesz, gdzie stoi Twój sklep, zacznij od slow query log. Dwadzieścia cztery godziny danych powiedzą Ci więcej niż jakakolwiek wtyczka wydajnościowa kiedykolwiek mogła. A jeśli potrzebujesz pomocy w interpretacji tego, co znajdziesz, skontaktuj się z nami — analiza wydajności baz danych to jedna z rzeczy, które robimy najlepiej.

David Miller buduje narzędzia wydajnościowe dla PrestaShop i optymalizuje bazy danych e-commerce od 2017 roku. Jego moduły są dostępne na mypresta.rocks.

Udostępnij ten wpis:
David Miller

David Miller

Ponad dekada praktycznego doświadczenia z PrestaShop. David tworzy wydajne moduły e-commerce skupione na SEO, optymalizacji zamówień i zarządzaniu sklepem. Pasjonat czystego kodu i mierzalnych rezultatów.

Spodobał Ci się ten artykuł?

Otrzymuj nasze najnowsze porady, przewodniki i aktualizacje modułów prosto na swoją skrzynkę.

Komentarze (3)

P
Piotr Nowak 2026-02-14
Good article but I think you should mention the opcache preloading feature for PHP 8.1+. It made a big difference for us combined with the query optimizations you described.
Odpowiedz
L
Laura Bianchi 2026-02-14
Implemented the Redis full page cache approach you described here. Our TTFB went from 800ms to 120ms. Incredible difference for our catalog of 15k products.
Odpowiedz
D
David Miller 2026-02-14
Amazing results Laura! Redis FPC is a game changer especially for large catalogs. If you want to squeeze even more out of it, try combining it with Varnish as a reverse proxy.
Ładowanie...
Do góry