Wolny sklep to utracone przychody. Każde 100ms opóźnienia to mierzalny spadek konwersji. Optymalizacja Magento 2 to nie magia – to systematyczna diagnostyka i eliminacja wąskich gardeł. Pokazuję jak zidentyfikować co spowalnia sklep, jak mierzyć poprawę i które zmiany dają największy efekt w stosunku do nakładu pracy.
Narzędzia diagnostyczne – zacznij od pomiaru
Nie optymalizuj na ślepo. Najpierw zmierz gdzie jest problem:
# Blackfire - profesjonalny profiler PHP (polecany dla Magento) # Instalacja agenta i probe, potem profilowanie przez CLI lub przeglądarkę blackfire run php bin/magento catalog:reindex # Tideways XHProf - darmowa alternatywa composer require tideways/xhprof # New Relic APM - monitoring produkcyjny # Pokazuje slow queries, zewnętrzne wywołania, memory usage per request # Wbudowane narzędzia Magento bin/magento dev:query-log:enable # logowanie slow queries bin/magento dev:template-hints:enable # pokazuje czas renderowania bloków
<?php
// Prosta diagnostyka czasu ładowania w kodzie
namespace Vendor\Module\Plugin;
class PerformanceLogger
{
private array $timers = [];
public function start(string $label): void
{
$this->timers[$label] = microtime(true);
}
public function stop(string $label): float
{
$elapsed = (microtime(true) - ($this->timers[$label] ?? microtime(true))) * 1000;
unset($this->timers[$label]);
if ($elapsed > 100) { // loguj tylko jeśli > 100ms
\Magento\Framework\App\ObjectManager::getInstance()
->get(\Psr\Log\LoggerInterface::class)
->warning("Slow operation: {$label} took {$elapsed}ms");
}
return $elapsed;
}
}
Indeksery – pierwsza linia optymalizacji
# Sprawdź stan indekserów bin/magento indexer:status # Stare indeksy = wolne zapytania bin/magento indexer:reindex # Ustaw tryb Update on Schedule zamiast Update on Save # dla dużych katalogów - unikasz reindeksacji przy każdym zapisie produktu bin/magento indexer:set-mode schedule catalog_product_price bin/magento indexer:set-mode schedule cataloginventory_stock bin/magento indexer:set-mode schedule catalog_category_product # Flat catalog - dla sklepów z > 50k produktów bin/magento config:set catalog/frontend/flat_catalog_category 1 bin/magento config:set catalog/frontend/flat_catalog_product 1 bin/magento indexer:reindex catalog_category_flat catalog_product_flat
Cache – konfiguracja dla maksymalnej wydajności
# Sprawdź co zajmuje miejsce w cache bin/magento cache:status # Redis dla cache i sesji (omówione we wpisie z 2022-01) # Kluczowe ustawienia które często są pominięte: # OPcache - największy wpływ na PHP # php.ini: # opcache.enable=1 # opcache.memory_consumption=512 <- nie 128MB jak domyślnie # opcache.max_accelerated_files=130000 <- Magento ma dużo plików # opcache.validate_timestamps=0 <- na produkcji wyłącz walidację # opcache.revalidate_freq=0 # opcache.interned_strings_buffer=16
<?php
// app/etc/env.php - optymalna konfiguracja cache
return [
'cache' => [
'frontend' => [
'default' => [
'id_prefix' => 'prod_',
'backend' => 'Magento\\Framework\\Cache\\Backend\\Redis',
'backend_options' => [
'server' => 'redis-cache',
'port' => 6379,
'database' => 0,
'compress_data' => '1',
'compression_lib' => 'l4z', // lz4 szybsze niż gzip
'compress_threshold' => 2048,
'connect_retries' => 1,
],
],
'page_cache' => [
'backend' => 'Magento\\Framework\\Cache\\Backend\\Redis',
'backend_options' => [
'server' => 'redis-fpc',
'port' => 6379,
'database' => 0,
'compress_data' => '0', // FPC nie kompresuj
],
],
],
],
];
Baza danych – najczęstsze wąskie gardła
# Włącz slow query log w MySQL # my.cnf: # slow_query_log = 1 # slow_query_log_file = /var/log/mysql/slow.log # long_query_time = 1 # Analiza slow query log mysqldumpslow -s t -t 20 /var/log/mysql/slow.log # EXPLAIN dla podejrzanych zapytań # EXPLAIN SELECT * FROM catalog_product_entity WHERE sku IN (...)
<?php
// Częste problemy z N+1 query w Magento
// Problem - N+1 query w pętli
$collection = $this->productCollectionFactory->create();
$collection->addAttributeToSelect('*');
foreach ($collection as $product) {
// KAŻDA iteracja generuje osobne zapytanie do bazy!
$category = $this->categoryRepository->get($product->getCategoryId());
echo $product->getName() . ' - ' . $category->getName();
}
// Rozwiązanie - załaduj dane z joinami w jednym zapytaniu
$collection = $this->productCollectionFactory->create();
$collection->addAttributeToSelect(['name', 'price', 'sku']);
$collection->joinField(
'category_name',
'catalog_category_entity_varchar',
'value',
'entity_id=entity_id',
['store_id' => 0]
);
$collection->setPageSize(100); // zawsze paginuj!
Statyczne zasoby – deployment i CDN
# Deployment statycznych zasobów - produkcja
bin/magento setup:static-content:deploy pl_PL en_US \
--jobs=$(nproc) \ # równoległe procesy
-f \ # force - nawet bez nowych wersji
--no-html-minify # pomiń minifikację HTML jeśli masz CDN z kompresją
# Kompresja JS/CSS
bin/magento config:set dev/js/merge_files 1
bin/magento config:set dev/js/enable_js_bundling 1
bin/magento config:set dev/css/merge_css_files 1
# Weryfikacja rozmiaru bundli
ls -lh pub/static/frontend/Vendor/theme/pl_PL/
# CDN - ustaw base URL dla statycznych zasobów
bin/magento config:set web/secure/base_static_url https://cdn.example.com/
bin/magento config:set web/unsecure/base_static_url https://cdn.example.com/
PHP-FPM – tuning procesu
# /etc/php/8.2/fpm/pool.d/magento.conf [magento] user = www-data group = www-data # pm = dynamic dla zmiennego ruchu pm = dynamic pm.max_children = 50 # RAM / ~50MB per proces pm.start_servers = 10 pm.min_spare_servers = 5 pm.max_spare_servers = 20 pm.max_requests = 500 # recykluj procesy - zapobiega memory leak # Timeouty request_terminate_timeout = 300 request_slowlog_timeout = 10 slowlog = /var/log/php/magento-slow.log # Zmienne środowiskowe env[MAGE_MODE] = production env[MAGE_RUN_CODE] = base
Pomiar efektów – przed i po
# Apache Bench - prosty benchmark HTTP
ab -n 100 -c 10 https://example.com/catalog/category/view/id/42
# Siege - bardziej realistyczny test
siege -c 20 -t 60S https://example.com/
# curl z czasami
curl -o /dev/null -s -w \
"DNS: %{time_namelookup}s\nConnect: %{time_connect}s\nTTFB: %{time_starttransfer}s\nTotal: %{time_total}s\n" \
https://example.com/
# Lighthouse CI - metryki Core Web Vitals
npx lighthouse https://example.com --output=json --quiet \
| jq '.categories.performance.score'
Priorytetyzacja – co daje największy efekt
| Optymalizacja | Efekt | Nakład pracy |
|---|---|---|
| OPcache (prawidłowa konfiguracja) | Bardzo wysoki | Niski |
| Redis dla cache i sesji | Bardzo wysoki | Średni |
| Varnish FPC | Wysoki (strony niezalogowanych) | Średni |
| Reindeksacja + Update on Schedule | Wysoki (przy dużym katalogu) | Niski |
| Eliminacja N+1 queries | Wysoki (zależy od kodu) | Wysoki |
| CDN dla statycznych zasobów | Średni (zależy od ruchu) | Średni |
| PHP-FPM tuning | Średni | Niski |
| Flat catalog | Średni (> 50k produktów) | Niski |
Podsumowanie
Optymalizacja Magento 2 to proces, nie jednorazowe działanie. Zacznij od pomiaru – Blackfire lub New Relic pokażą gdzie naprawdę jest problem. OPcache i Redis to fundament który musi być poprawnie skonfigurowany zanim zaczniesz szukać bardziej egzotycznych optymalizacji. N+1 queries w customowym kodzie to często największe rezerwy – i jedyne miejsce gdzie nie wystarczy zmiana konfiguracji, trzeba zrozumieć kod.
