Tworzenie sklepów na Magento to ciągła walka o wydajność. Można ją poprawiać dzięki narzędziom cache-ującym (np. Varnish, Redis – na warstwie cache), równoważeniu obciążenia na serwerach poprzez load-balancing, czy sprytne zarządzanie bazą danych. To jednak największe efekty wydajnościowe osiągane są przy dobrze napisanym kodzie.

W jednym z wcześniejszych artykułów opisałem jakimi narzędziami należy się posługiwać, by pisać wydajny kod Magento oraz opisałem najczęściej popełniane błędy wydajnościowe.

W tym artykule skupię się na bazie danych. W Magento przy komunikacji z bazą danych korzystamy z ORM w dwóch konfiguracjach, proste (płaska struktura tabeli) oraz EAV (Entity Attribute Value). Często programiści nie widzą różnicy między tymi dwoma typami, a prawda jest taka, że bez dobrego zrozumienia różnicy kod jest mocno narażony na spadek wydajności.

Artykuł opisywać będzie obie konfiguracje wraz z wyszczególnieniem plusów i minusów dla każdej z nich. Zwrócę uwagę również jakie instrukcję SQL są wykonywane podczas standardowych odwołań do bazy danych, co powinno Was utwierdzić jaka konfiguracja jest bardziej przydatna w określonym przypadku.

ORM – Object Relational Mapping

ORM jest to technika programowania służąca do przekształcenia różnych typów danych w obiekty i odwrotnie.
W Magento ORM jest zrealizowany za pomocą modeli w dwóch konfiguracjach:

  • Proste – płaska struktura tabeli (Flat)
  • EAV (Entity Attribute Value)

Pobranie czy zapisanie danych w obu typach ORM jest albo identyczne albo bardzo zbliżone  w Magento, co może skłaniać do myślenia, że nie ma różnicy w sposobie dostępu do danych czy ich zapisie. Różnicę są jednak duże, o czym łatwo się przekonacie w dalszej części artykułu.

Czym jest płaska struktura tabeli o tym wie bardzo dobrze każdy nawet początkujący programista, co to jest jednak EAV ?

EAV – Entity Attribute Value

EAV jest to wzorzec programistyczny zapisujący dane w zależności od tego jakiego typu jest dana wartość. To, w jaki sposób rozmieszczone są dane przedstawia poniższy diagram:
1
Źródło: http://www.magentocommerce.com/knowledge-base/entry/magento-for-dev-part-7-advanced-orm-entity-attribute-value

W powyższym diagramie jest przedstawiony układ tabel w konfiguracji EAV opisujący produkty w sklepie. Pierwsza tabela zawiera zbiór encji produktów (entity_id) czyli bazowe indeksy produktów wraz z typem (entity_type_id) mówiącym jaki jest to typ encji w strukturze EAV, oraz ewentualnie bazowymi kluczami (np. w przypadku produktów może to być SKU produktu).

Środkowa tabela zawiera zbiór wszystkich atrybutów występujących w Magento. Filtrując tabele po typie encji (entity_type_id) możemy się dowiedzieć jakie są wszystkie możliwe atrybuty produktu (np. cena, wymiary, kolor, itd.).

Tabele po prawej przedstawiają już końcowe wartości danego atrybutu. To w której kolumnie zostanie zapisana wartość zależy od tego, jakiego dana wartość jest typu. Czyli np. chcąc zapisać atrybut produktu „kolor” zapiszemy go w tabeli catalog_product_entity_varachar ponieważ wiemy, że wartości dla atrybutu „kolor” są typu string.

Chcąc opisać produkt w płaskiej strukturze tabeli powyższy diagram mogli byśmy przekształcić w jedną tabelę z tyloma kolumnami ile potrzebujemy atrybutów. Po co więc EAV ?

Flat vs EAV

Otóż EAV ma coś czego nie ma tabela płaska – elastyczność. W dowolnym momencie realizacji sklepu możemy dodawać dowolną ilość nowych atrybutów, które będą nowymi rekordami w tabeli eav_attribute. W płaskiej strukturze trzeba by było dodawać nową kolumnę do tabeli.

Patrząc na to wszystko w kontekscie Magento, EAV jest bardzo dobrym wyborem ponieważ różne sklepy lub aplikacje mogą mieć różną specyfikę, a dzięki EAV jest możliwe dowolne szarżowanie atrybutami bez konieczności przeprojektowywania bazy danych.

Dodatkowo płaskie tabele mają określoną, maksymalną ilość kolumn (np. inoDB może mieć max 1000 kolumn), co ogranicza maksymalną ilość atrybutów. W EAV nie ma tego problemu.

EAV ma jednak inną dużą wadę – wydajność. Chcąc się dostać do jednego atrybutu trzeba pobrać dane z kilku tabel co niesie za sobą konieczność wykonania kilku zapytań SQL w Magento. Gdy dodatkowo będziemy sięgać do kolejnych atrybutów mających inny typ danych pojawią się dodatkowe JOIN-y w zapytaniach. Jeżeli do tego dołożymy jeszcze mało doświadczonego programistę który będzie pobierał dużo danych partiami poprzez load w pętli, to liniowy spadek wydajności mamy jak banku.

Przypatrzmy się więc obu typom w praktycznych użyciach.

ORM – płaska struktura tabeli

Otwórzmy płaską tabelę zawierającą dane o mieszkańcach hotelu:2

Tabela zawiera podstawowe informacje o mieszkańcach i ograniczymy się tylko do dwóch typów danych INT oraz VARCHAR.

Następnie wypełnijmy naszą tabelę danymi:
3

i pobierzmy dane drugiego mieszkańca:
4

Powyższy kod odwołuje się do modelu divante_training/residents i poprzez metodę load pobiera dane drugiego mieszkańca. Wynikiem wykonania powyższego kodu Magento jest wyświetlenie imienia i nazwiska mieszkańca:
5

ORM wszystko wykonał za nas jednak nie ufajmy mu i sprawdźmy jakie dokładnie zapytania zostały wykonane:6

Jak widzimy ORM skonstruował proste zapytanie do bazy, nie ma w tym nic nieoczekiwanego.

ORM – EAV

Stwórzmy teraz analogiczną strukturę tabel przedstawiającą mieszkańców hotelu ale tym razem w kontekście EAV.7

8

9

Jak widzimy powyżej tym razem musiałem utworzyć trzy tabele. Pierwsza z nich zawierać będzie encje mieszkańca (indeksy), a druga i trzecia wartości w zależności od typu danych (INT → residents_entity_int, oraz VARCHAR → residents_entity_varchar).

Czujny czytelnik powinien zauważyć że brakuje nam tu jeszcze tabeli zawierającej atrybuty (eav_attribute – opisywaną podczas omawiania teorii EAV), jednak ta była wcześniej utworzona razem z całą bazą Magento, ponieważ z niej korzystają wszystkie typy tabel EAV Magento (produkty, zamówienia, itd.).

Analogicznie wypełnijmy tabele takimi samymi danymi, jak to było przy płaskiej tabeli:10

11

12

oraz pobierzmy tego samego mieszkańca

 13

Tym razem odwołaliśmy się do modelu divante_barcamp/residents i pobraliśmy dane 14-tego mieszkańca:

14

Jak widzimy zarówno kontekst odwołania jak wynik jest ten sam, jednak to co wykonał ORM jest bardzo różne:

15

Podczas pobierania danych ORM wykonał kilka zapytań oraz w ostatnim widzimy operację połączenia wyników dwóch zapytań poprzez komendę SQL – UNION ALL.

Nie mogło tu być inaczej, bo przecież ORM musiał wpierw pobrać typ encji EAV z tabeli eav_entity_type, następnie pobrać dane z naszej głównej tabeli encji residents_entity, później pobrać informacje o atrybutach, które są nam potrzebne (eav_attribute), a na końcu dopiero pobrał wartości tych atrybutów (residents_entity_int oraz residents_entity_varchar).

Już na tym etapie widać spadek wydajności przy EAV przy samej ilości zapytań do bazy.

W następnej części pokażę co się stanie, jeżeli skomplikujemy jeszcze sytuację pobierając wiele danych i popełniając najcięższy grzech programisty Magento…Czujecie ten dreszczyk emocji?