Skalowalność PrestaShop, porównanie z Magento

PrestaShop czy Magento? Właściciele sklepów internetowych często zwracają się do nas z tym pytaniem. Spróbujemy na nie odpowiedzieć, biorąc pod lupę skalowalność.

Czym jest skalowalność?

Skalowalność jest możliwością rozbudowy systemu informatycznego w przypadku zwiększonej liczby użytkowników korzystającej z tego systemu lub zwiększonej liczby danych obsługiwanych przez system. Można wyróżnić dwa rodzaje skalowania – pionowe i poziome. Aplikacja skaluje się pionowo, jeżeli zmiana komponentów serwera na mające wyższe parametry pozwoli istotnie zwiększyć ruch możliwy do obsłużenia przez nią. Nie zawsze tak się dzieje, bo jeżeli aplikacja opiera się na algorytmach o wysokiej złożoności obliczeniowej, wpływ zwiększenia dostępnych zasobów sprzętowych na jej szybkość może być niezauważalny. O skalowaniu poziomym można mówić wtedy, gdy aplikacja może funkcjonować w środowisku wieloserwerowym i w pełni korzystać z zasobów wszystkich serwerów jednocześnie. W przypadku skalowania poziomego prawie zawsze konieczne jest, żeby aplikacja była zaprojektowana z myślą o nim, bo często optymalne w środowisku jednoserwerowym sposoby wykonywania różnych operacji są niemożliwe lub bardzo nieoptymalne po zmianie środowiska na wieloserwerowe.

Baza danych

Jednym z miejsc, w których w przypadku wzrostu popularności sklepu następuje wzrost zapotrzebowania na zasoby sprzętowe jest baza danych. PrestaShop używa relacyjnego systemu zarządzania bazą danych MySQL, która udostępnia administratorowi wiele możliwości związanych ze skalowalnością.

Wykorzystanie w pełni możliwości jednego serwera na operacje bazodanowe nie jest większym problemem, chociaż w przypadku domyślnej konfiguracji MySQL istnieją pewne ograniczenia dotyczące pamięci, które warto zwiększyć, w szczególności ustawieniami tymi są rozmiar puli buforów InnoDB (innodb_buffer_pool_size), który gdy jest duży zmniejsza użycie dysku i maksymalny rozmiar tabeli tymczasowej przechowywanej w pamięci (tmp_table_size, max_heap_table_size), którego zwiększenie przyspiesza szczególnie operacje grupowania i usuwania duplikatów, poprzez możliwość nieużywania podczas tego procesu tabel tymczasowych w plikach na dysku.

Inną możliwością zwiększenia możliwej do osiągnięcia liczby operacji dyskowych na jednostkę czasu jest włączenie w pliku konfiguracyjnym serwera opcji ograniczającej zapisywanie zawartości pamięci podręcznej dysku do maksymalnie jednego zapisu na sekundę (innodb_flush_log_at_trx_commit=2), czego wpływ na możliwą do jednoczesnego obsłużenia liczbę użytkowników sklepu w przypadku skryptu takiego jak PrestaShop, w którym liczba transakcji bazodanowych jest bardzo wysoka, a średni rozmiar zapytań wchodzących w skład transakcji niski, jest dość wysoki. Jedynym problemem związanym z włączeniem tego ustawienia jest możliwość utraty transakcji z ostatniej sekundy w przypadku awarii zasilania serwera, jednak wysoka popularność starszego silnika MyISAM gwarantującego w domyślnej konfiguracji jeszcze mniej pokazuje, że problem ten nie jest szczególnie istotny.

Przydatnym dla administratorów serwerów niebędących specjalistami od systemów zarządzania bazami danych narzędziem może być skrypt MySQLTuner dostępny na stronie https://github.com/major/MySQLTuner-perl, który na podstawie statystyk sposobu użycia bazy generuje listę proponowanych zmian do wprowadzenia w konfiguracji serwera.

Gdy czynnikiem ograniczającym przepustowość optymalnie skonfigurowanego systemu bazy danych jest liczba operacji dyskowych, możliwymi bez zwiększania liczby serwerów rozwiązaniami są m.in.: użycie dysków SSD, które mogą obsłużyć wielokrotnie więcej operacji na jednostkę czasu, użycie wielu dysków w konfiguracji RAID, użycie wielu dysków i umieszczenie różnych tabel na różnych dyskach, co w nowych wersjach MySQL jest możliwe przy użyciu opcji DATA DIRECTORY podczas tworzenia tabeli.

W samym silniku PrestaShop nie ma nic, co powodowałoby wysokie obciążenie serwera bazy danych w porównaniu z alternatywami takimi jak Magento. W przypadku tego drugiego nawet obciążenie bazy danych jest większe, ze względu na zastosowanie w wielu miejscach modelu entity-attribute-value, który chociaż umożliwia programistom dodawanie nowych atrybutów poprzez wstawianie w odpowiednich miejscach wierszy bez konieczności edytowania schematu np. dodawania kolumny lub tworzenia nowej tabeli, utrudnia serwerowi MySQL stosowanie różnych metod optymalizacji dostępu do danych.

W większości systemów informatycznych opartych na relacyjnych bazach danych często o wiele większy problem sprawia jednoczesne używanie wielu serwerów z bazą, ponieważ wymaga to obsługi ze strony aplikacji, kod obsługujący taką konfigurację nie jest też szczególnie prosty – losowanie serwera przy każdym wyświetleniu strony i używanie go do wszystkich zapytań SQL potrzebnych do wygenerowania zawartości strony nie jest wystarczającym rozwiązaniem. Na szczęście autorzy PrestaShopa przewidzieli, że ich oprogramowaniem może być zainteresowany sklep z bardzo dużym ruchem, dlatego też PrestaShop może podczas odczytywania danych z bazy korzystać z wielu serwerów MySQL, rozkładając obciążenie pomiędzy te serwery. W tym celu należy skonfigurować na serwerach MySQL replikację na poziomie zapytań lub wierszy, a następnie w pliku db_slave_server.inc.php podać dane dostępowe do tych serwerów – nazwy hostów (DNS) lub adresy IP, nazwy użytkowników wraz z hasłami do tych kont, a także nazwy baz danych.

Replikacja jest asynchroniczna, czyli w przypadku serwerów znajdujących się w tej samej sieci lokalnej dane na serwerach innych niż główny są przeważnie o ułamek sekundy nieaktualne, co PrestaShop uwzględnia i nie wysyła zapytań SQL do serwerów innych niż główny wtedy kiedy nie jest to bezpieczne, np. przy odczytywaniu zawartości wiersza od razu po jego dodaniu lub edycji na serwerze głównym. Zapytania modyfikujące bazę są w całości wykluczone z tego mechanizmu – wszystkie trafiają do serwera głównego, który następnie rozsyła je (lub zawartość zmienionych wierszy, jeżeli administrator wybrał replikację na poziomie wiersza) do pozostałych serwerów, ale w sklepach internetowych, podobnie jak większości innego rodzaju aplikacji webowych, zdecydowana większość zapytań odczytuje dane bez ich edycji. Takie zachowanie jest wymagane, ponieważ w przypadku konfiguracji pozwalającej zapisywać na dowolnym serwerze w momencie wystąpienia awarii któregokolwiek z serwerów obsługujących bazę danych, nie tylko głównego, byłoby konieczne zablokowanie w całej bazie wszystkich zapisów lub dopuszczenie niespójnych danych, co zwiększyłoby awaryjność całości.

PHP

Innym problemem pojawiającym się w przypadku bardzo dużego sklepu internetowego napisanego w języku PHP, co dotyczy każdego popularnego skryptu sklepu open source, w tym skryptu PrestaShop, jest konieczność jednoczesnego obsłużenia wysokiej liczby procesów interpretujących kod PHP. Pojedynczy proces albo obsługuje pojedyncze żądanie HTTP, tzn. otrzymuje dane wejściowe od przeglądarki, wykonuje skrypt i wysyła wynik, albo czeka na nadejście nowego żądania po to, żeby serwer w momencie jego nadejścia nie musiał rozpoczynać nowego procesu, czego koszt jest wyższy niż użycia już istniejącego procesu. Wynika z tego, że serwer lub klaster serwerów musi być w stanie obsłużyć przynajmniej tyle interpreterów PHP, ile wynosi liczba jednocześnie przetwarzanych żądań skierowanych do skryptów.

Głównym czynnikiem ograniczającym maksymalną liczbę procesów jest ilość dostępnej pamięci operacyjnej na serwerze, rzadziej (w przypadku skryptów wykonujących skomplikowane obliczenia) moc obliczeniowa procesora. Ponieważ procesy PHP nie utrzymują żadnego stanu aplikacji pomiędzy poszczególnymi żądaniami, możliwa do osiągnięcia skalowalnosć jest w ich przypadku bliska doskonałości, w przeciwieństwie do wielu innych środowisk programistycznych w których procesy przechowują w pamięci operacyjnej pewien stan aplikacji, który trzeba wtedy synchronizować między serwerami.

Jednym z rozwiązań pozwalających na rozłożenie obciążenia spowodowanego wykonywaniem skryptów PHP pomiędzy wiele serwerów jest uruchomienie lekkiego serwera HTTP np. nginx lub lighttpd umożliwiającego w przeciwieństwie do najpopularniejszego Apache w domyślnej konfiguracji obsłużenie nawet dziesiątek tysięcy jednoczesnych połączeń, następnie uruchomienie na różnych komputerach procesów PHP w trybie FastCGI nasłuchujących na gniazdach TCP/IP i skonfigurowanie serwera HTTP, żeby przekazywał przychodzące żądania dotyczące skryptów PHP tym procesom, opcjonalnie z nierównymi wagami w sytuacji gdy te komputery mają nierówne parametry sprzętowe. W przypadku PrestaShopa wszystkie komputery z procesami PHP powinny mieć dostęp do tego samego folderu ze sklepem, np. udostępnionego w sieci za pomocą NFS (Network File System), ponieważ PrestaShop zapisuje dane nie tylko w bazie danych, ale także w plikach i oczekuje, żeby te dane były w przyszłości dostępne dla każdego procesu, a nie tylko dla jednego, który zapisał dane. Nie oznacza to jednak, że każde wyświetlenie sklepu będzie wymagało przesyłania ogromnej ilości danych między serwerem obsługującym PHP a serwerem NFS, ponieważ każdy system operacyjny przeznacza nieużywaną do niczego ważniejszego pamięć na przechowywanie zawartości wcześniej odczytanych z systemu plików danych, co w przypadku folderu ze stroną zamontowanego przy użyciu NFS oznacza że większość lokalnego ruchu sieciowego stanowi sprawdzanie aktualności cache, a nie przesyłanie zawartości plików.

Poza modyfikowaniem infrastruktury w celu zwiększenia maksymalnej możliwej do obsłużenia liczby procesów PHP warto też podjąć działania mające na celu zmniejszenie liczby koniecznych procesów PHP do funkcjonowania strony. Rozwiązania zapamiętujące kod bajtowy generowany z kodu źródłowego w postaci tekstowej takie jak Zend OPcache pozwalają skrócić czas interpretowania skryptów PHP kosztem niewielkiego wzrostu użycia pamięci poprzez wyeliminowanie konieczności generowania kodu bajtowego przy każdym wyświetleniu, tak jak dzieje się w standardowym PHP. Warto zadbać o to, żeby serwer HTTP buforował wszystkie poza za długimi do trzymania w pamięci np. dużymi plikami do pobrania odpowiedzi przesyłane ze skryptów PHP do przeglądarki zamiast czekać aż przeglądarka odbierze dane, po to żeby użytkownicy połączeń o niskiej przepustowości nie blokowali niepotrzebnie ograniczonych zasobów na serwerze.

Duże znaczenie ma tutaj wybrany skrypt sklepu i jakość jego kodu – w przypadku PrestaShopa czas generowania strony nie jest na szczególnie wysokim poziomie, dzięki czemu procesy PHP szybciej stają się dostępne do obsługi żądań innych użytkowników sklepu. Chociaż czas ten nie jest aż tak dobry jak w przypadku większości rozwiązań dedykowanych, mających tylko i wyłącznie potrzebne w konkretnym sklepie funkcje czy jak w przypadku mało rozszerzalnych, monolitycznych aplikacji z ograniczonymi możliwościami dostosowania do potrzeb jak osCommerce, jest on lepszy niż w przypadku głównego konkurenta open source jakim jest Magento, co widać w wynikach testu na http://www.comentum.com/ecommerce-platform-comparison-benchmarks.html, co można też potwierdzić instalując obydwa skrypty na tym samym, nieobciążonym niczym innym serwerze i używając ApacheBench lub podobnego narzędzia mierzącego czas generowania strony. W większości konfiguracji użycie RAM-u podczas generowania strony także powinno być mniejsze w PrestaShopie niż w Magento, ponieważ cały kod PrestaShopa jest krótszy i prostszy.

Inną możliwą czynnością do wykonania mającą na celu zmniejszenie średniego czasu blokowania procesu PHP przez żądanie HTTP jest skorzystanie ze wszystkich mechanizmów cache PrestaShopa. Jedną z takich funkcji jest cache Smarty, z którego może korzystać wiele miejsc w głównym silniku PrestaShop oraz modułów do przechowywania niezmieniających się często fragmentów strony, np. całego bloku z najnowszymi produktami. Dzięki temu PrestaShop nie musi wyliczać na podstawie danych w bazie przy każdym wyświetleniu strony tego samego. Cache Smarty może przechowywać wygenerowane fragmenty strony w systemie plików lub bazie danych. Jedną z korzyści wyboru systemu plików jest to, że można zamontować w folderze z cache Smarty system plików tmpfs przechowujący wszystko w RAM-ie, co w przypadku bazy jest niemożliwe, inną korzyścią jest brak narzutu związanego z przetwarzaniem zapytań SQL. Inną dostępną do wyboru opcją jest przechowywanie cache Smarty w bazie danych, co może być lepsze w przypadku wieloserwerowego klastra i szybkiej bazy danych. Wiąże się to z trochę wyższym narzutem, jednak w przypadku klastra narzut związany z sieciowym systemem plików (NFS) zastosowanym do cache byłby jeszcze wyższy, a pobieranie już wygenerowanego fragmentu strony z jednego miejsca w bazie danych jest w oczywisty sposób wielokrotnie szybsze niż tworzenie tego samego fragmentu strony przy każdym wejściu na podstawie danych porozrzucanych po całej bazie.

W PrestaShopie jest też dostępny cache przechowujący stan często używanych obiektów np. produktów i wyniki najczęstszych zapytań do bazy danych. Jest on domyślnie wyłączony, ponieważ każda opcja inna niż zapisywanie każdego obiektu w pliku w folderze cachefs, co jest mało efektywne, często wolniejsze niż całkowicie wyłączony cache, wymaga dostosowania serwera do jego obsługi poprzez zainstalowanie i skonfigurowanie dodatkowego oprogramowania. W przypadku sklepu działającego na pojedynczym serwerze najlepszym wyborem będzie APC lub XCache, ponieważ przechowują one dane w pamięci procesu PHP lub w pamięci wspólnej dla wszystkich procesów PHP uruchomionych na tym samym serwerze, dzięki czemu nie ma kosztu komunikacji międzyprocesowej lub sieciowej. Chociaż dla najnowszych wersji PHP nie jest dostępny XCache ani oryginalny APC, można użyć nowszego rozszerzenia APCu i warstwy kompatybilności wstecznej apcu-bc, aby móc włączyć w PrestaShopie cache oparty na APC. Gdy sklep działa na wielu serwerach, nie można użyć tych sposobów, ponieważ wtedy cache będzie różny na różnych serwerach, np. uaktualnienie danych będzie powodowało usunięcie starych wartości tylko z jednego cache, a nie wszystkich. Rozwiązaniem w takiej sytuacji jest zainstalowanie i skonfigurowanie w sieci jednego centralnego serwera memcached i ustawienie w konfiguracji PrestaShopa zapisywania pamięci podręcznej w nim.

Podsumowanie

Skalowalność PrestaShopa jest bardzo dobra. Jest on systemem, który może obsługiwać sklep każdej wielkości – przy małym sklepie zapewnia względnie niskie obciążenie serwera, dzięki czemu można go używać na pojedynczym koncie na serwerze obsługującym setki innych stron, przy większym jest skalowalny na porównywalnym poziomie jak każda inna aplikacja PHP/MySQL – przy optymalnej konfiguracji całości pozwala użyć wszystkich zasobów tego serwera, nie jest w niczym gorszy od porównywalnych systemów, używa mniej zasobów niż m.in. Magento pozwalając na serwerze o podobnych parametrach obsłużyć większy ruch, przy jeszcze większym sklepie zaimplementowane w PrestaShopie funkcje w połączeniu ze standardowymi możliwościami PHP/MySQL w tym zakresie umożliwiają użycie w pełni możliwości sprzętowych wielu serwerów.