Marcin Chyłek Blog

Symfony - Propel Lazy Load

Lazy load (wzorzec projektowy) – w przypadku Propela oznaczenie atrybutu modelu lazyLoad powoduje, że zostanie on zwrócony w momencie jego jawnego wywołania. Wykorzystuje się to w przypadku jeśli nie potrzebujemy danego atrybutu (z powodu jego rozmiaru) lub chcemy wykorzystać w późniejszym etapie (w widoku). Najczęściej stosuje się to do typów text (longvarchar), blob, clob.

Definicja w schema.yml

schema.yml

documents:
  id:         { type: integer, required: true, primaryKey: true, autoincrement: true }
  created_at: { type: timestamp }
  name:       { type: varchar, size: 100 }
  content:    { type: longvarchar, lazyLoad: true }

Pobieramy obiekt documents, atrybut content ma ustawionym lazyLoad, zostaje on pominięty w zapytaniu SQL. Jeśli wywołamy metodę ->getContent(), zostanie wykonane dodatkowe zapytanie i pole zostanie zwrócone. Lazy load można wykorzystać w celu zoptymalizowania obiektów Propela, jednak trzeba pamiętać że w momencie żądania zwrócenia atrybutu zostanie wykonane dodatkowe zapytanie do bazy danych.

Kategoria: Bazy danych, PHP, Propel, Symfony | Marcin Chyłek | Komentarze: 0

Symfony - przyśpieszanie Propela z wykorzystaniem widoków (view) baz danych

Szybkie serwisy, zoptymalizowane pod względem zużycia pamięci i ilości odwołań do bazy – czasami mogą być kluczowym elementem w powodzeniu naszego projektu. Chciałbym przedstawić jedną z takich możliwości, czyli użycie w Symfony widoków baz danych. Przykład można zastosować w bazach: PostgreSQL, MySQL, Oracle, itd. Najważniejszym elementem jest to, czy baza danych obsługuje widoki.

Dla przykładu użyję tabeli użytkownik i grupa.

schema.yml

propel:

  groups:
    id:              { type: integer, required: true, primaryKey: true, autoincrement: true }
    owner_id:        { type: integer, foreignTable: users, foreignReference: id, index: true }
    last_user_id:    { type: integer, foreignTable: users, foreignReference: id, index: true }
    name:            { type: varchar, size: 80, index: true }
    routing_name:    { type: varchar, size: 80, index: true, uniq: true }
    description:     { type: varchar, size: 400 }
    created_at:      { type: timestamp }
    user_count:      { type: integer, index: true }
    position_rank:   { type: integer }

  users:
    id:              { type: integer, required: true, primaryKey: true, autoincrement: true }
    login:           { type: varchar, size: 15, index: true, uniq: true }
    password:        { type: varchar, size: 255 }
    name:            { type: varchar, size: 255 }
    created_at:      { type: timestamp }
    updated_at:      { type: timestamp }
    last_login_at:   { type: timestamp }
    last_request_at: { type: timestamp }
    group_id:        { type: integer, foreignTable: groups, foreignReference: id, index: true }

Załóżmy, że mamy template w którym pokazujemy 20 grup według ilości użytkowników, w której wyświetlamy: nazwę grupy wraz z linkiem do profilu grupy, właściciela grupy, ostatniego dodanego użytkownika do tej grupy i ilość użytkowników w grupie. Stosując klasyczne wykorzystanie modelów Propela, dostaniemy modele grup z zależnymi 2 modelami użytkowników – właściciel i ostatnio dodany użytkownik. Od razu widać że większość danych jest zbędna, a co z tym idzie wykorzystanie pamięci będzie większe i czas zwracania danych z bazy będzie dłuższy.

Definiowanie widoku (views) w schema

Aby rozdzielić pliki w którym są definicje tabel i widoki, warto utworzyć osobny plik w którym zapiszemy naszą definicje widoku.

config/views_schema.yml

propel:
  groups_view:
    _attributes:              { skipSQL: true, readOnly: true }
    id:                       { type: integer }
    name:                     { type: varchar }
    routing_name:             { type: varchar }
    user_count:               { type: integer }
    owner_name:               { type: varchar }
    last_user:                { type: varchar }

Kolejnym krokiem jest przygotowanie SQL z definicją naszego vidoku. Dobrym rozwiązaniem jest by każdy widok umieszczać w osobnym pliku i dodawać do sqldb.map, w celu zbudowania widoku z automatu (nie musimy już ręcznie wywoływać SQL).

Tworzymy plik sql

data/sql/views/groups_view.sql

CREATE OR REPLACE VIEW groups_view AS
  SELECT groups.id,
         groups.name ,
         groups.routing_name,
         groups.user_count,
         owner.login AS owner_name,
         last.login AS last_user
    FROM groups
    JOIN users owner ON owner.id = groups.owner_id
    JOIN users last ON last.id = groups.ast_user_id;

Dodajemy widok do sqldb.map

Edytujemy plik sqldb.map i dodajemy nową pozycję

data/sql/sdldb.map

views/groups_view.sql=propel

Kolejnym krokiem jest zbudowanie bazy danych, dla przykładu użyje polecenia, które tworzy modele, dodaje SQL do bazy i wczytuje przykładowe dane.

./symfony propel:build-all-load

Po wygenerowanie powstał model o nazwie GroupsView, teraz tylko nam zostaje dodanie metody, która będzie zwracać 20 grup z największą ilością użytkowników.

Edytujemy GroupsViewPeer.class.php

Dodajemy metodę

public static function getTopGroups( $limit = 20 )
{
  $c = new Criteria();
  $c->addDescendingOrderByColumn( GroupsViewPeer::USER_COUNT );
  $c->setLimit( $limit );

  return GroupsViewPeer::doSelect( $c );
}

Metoda ta zwraca 20 obiektów, w których mamy tylko i wyłącznie pola, które będą nam potrzebne do wyświetlenia.

Często na rożnych forach spotyka się opinie, że Symfony jest wolnym i zasobożernym frameworkiem, jeśli nie znamy możliwości, zasad jego działania jak i sposobów optymalizacji to niestety ale taka teoria dla pewnych osób jest prawdziwa. Wykorzystując możliwości baz, takie jak plsql, vidoki, triggery i funkcje można osiągnąć rewelacyjne efekty i obsługa milionowych tabel na “słabym” sprzęcie jest czymś realnym.

Kategoria: Bazy danych, MySQL, Oracle, PHP, PostgreSQL, Propel, Symfony | Marcin Chyłek | Komentarze: 9

Cache danych w PHP - testy wydajności (MEMORY, MyISAM, Plik)

Przeprowadziłem kilka testów szybkości odczytu z pliku i tabel typu: MEMORY, MyISAM.

Jak był przeprowadzany test:

Test przeprowadzałem na Ubuntu Dapper (apache2, php5.1.x, MySQL 5.0.22) na laptopie, więc wiele rzeczy mogło wpłynąć na na nieprawidłowość testów.

Test podzieliłem na kilka etapów:

  • z włączonym cache zapytań
  • z wyłączonym cache zapytań
  • z pomiarem połączenia z bazą danych

Odnośnie ilości wywołań testu, to był wykonywany 1000 razy, liczone były pojedyńcze czasy testu, a następnie sumowane i liczona z tego średnia testu. Jako danych testowych użyłem wygenerowanego ciągu znaków o długości około 65 KB.

Wyniki:

Odczyt z pliku:

Do odczytu z pliku użyłem funkcji: file_get_contents()
Średni czas odczytu jaki uzyskałem to: 0.000357507705688 (szczegółowe informacje)

Odczyt z bazy danych:

Do odczytu z bazy użyłem funkcji mysql_* (połączenie, wybór bazy, wykonanie zapytanie i zwrócenie rekordu). Tabela zawierała 1 rekord z id i polem w którym był zapisany taki sam ciąg znaków jak w przypadku pliku. Dlaczego nie więcej? Chciałem wyeliminować czas jaki będzie potrzebny na przeliczenie jaki rekord ma zostać zwrócony.

Wyłączony zapis cache w MySQL i czas połączenia z bazą pominięty:

Tabela MyISAM - średni czas to: 0.00516110825539 (szczegółowe informacje)
Tabela MEMORY - średni czas to: 0.00368802952766 (szczegółowe informacje)

Widać, że odczyt z pliku ma przewagę nad odczytem z bazy danych, a czasy odczytu z tabel typu MyISAM i MEMORY są bardzo do siebie zbliżone.

Włączony zapis cache w MySQL i czas połączenia z bazą pominięty:

Tabela MyISAM - średni czas to: 0.00179107260704 (szczegółowe informacje)

Tabela MEMORY - średni czas to: 0.000719551563263 (szczegółowe informacje)

Z włączonym cache zapytań widać, że czasy są zbliżone do odczytu z pliku.

Włączony zapis cache w MySQL + czas połączenia z bazą danych:

Tabela MyISAM - średni czas to: 0.00202487516403 (szczegółowe informacje)
Tabela MEMORY - średni czas to: 0.00205611944199 (szczegół?owe informacje)

Kluczowym znaczeniem jest czas połączenia z bazą danych a porównanie tych 2 testów zapytań do bazy danych można powiedzieć, że są prawie identyczne.

Różnice w wydajności pomiędzy MyISAM i MEMORY?

Typ tabeli MEMORY będzie lepszy zawsze, kiedy operujemy na pewnym zestawie rekordþw i nie musimy jak w przypadku MyISAM odczytywac z dysku, który jak wiadomo ma fizyczne ograniczenia, które są nie do przeskoczenia.

Polecam zapoznanie się z art. http://dev.mysql.com/tech-resources/articles/mysql_5.0_psea2.html, w którym autor porównał różne typy tabel.

Kategoria: MySQL, PHP | Marcin Chyłek | Komentarze: 0

PDO prepared statements a wydajność zapytań SQL

Na forum php.pl pojawił się post z pytaniem czy zastosowanie prepared statements wpływa na wydajność zapytań.

Troszeczkę teorii:

Jak to wygląda od strony ORACLE:

Zanim dane zostaną zwrócone z bazy danych, po odebraniu odpowiedniego kodu baza danych Oracle musi wykonać określone czynności:

  • tworzenie kursora
  • proces parsowania zapytania jeśli nie istnieje w obszarze wspólnym. Na proces ten również wchodzi kilka etapów:
    • analiza zapytania - sprawdzenie czy zapytanie składniowo jest poprawne
    • sprawdzenie odwołań do obiektów
    • sprawdzenie uprawnień
    • modyfikacja zapytania, w celu uzyskania zapytania równoważnemu danemu, ale wykonywanemu efektywniej
    • przygotowanie planów wykonania na podstawie statystyk, wskazówek
    • wybranie najlepszego planu wykonania (najmniejszy koszt wykonania)
    • zapisanie do obszaru wspólnego

    Wykorzystanie wcześniejszego zapytania jest możliwe tylko wtedy, kiedy:

    • tekst zapytania jest taki sam (wchodzą w to również komentarze i białe znaki),
    • polecenie musi odwoływać sie do tych samych obiektów w bazie danych,
    • zmienne wiązane muszą się tak samo nazywać i być tego samego typu.
  • przygotowanie zapytania
  • podwiązanie zmiennych
  • wykonanie zapytania
  • zwrócenie rekordów do użytkownika

Jeśli do bazy danych jest wysyłane takie samo zapytanie proces parowania nie jest wykonywany, przez co wydajność wzrasta bo pewna czść operacji jest pomijana.

Czy zawsze stosować takie rozwiązanie?

Są przypadki zapytań dla których najlepszy plan pierwszego wykonania dla pewnych parametrów jest nieoptymalny i zaleca się nie stosowanie zmiennych wiązanych.

MySQL:

Jeśli chodzi o bazę danych MySQL mechanizmy nie są aż tak bardzo zaawansowane ale pewne rzeczy są zaimplementowane.

Więcej na stronie: http://dev.mysql.com/tech-resources/articles/4.1/prepared-statements.html

Jak PDO wpływa na wydajność

PDO udostępnia funkcje do tzw bind’a (w literaturze polskiej spotkałem się z określeniem zmienna wiązana), w którym możemy przekazać wartość jaka ma być podstawiona pod zmienną, która w późniejszym etapie zostanie wysłana do bazy. Możemy wysłać zapytanie tylko jeden raz, następnie podstawiać wielokrotnie a następnie wykonywać. Dodatkowo PDO chroni przed atakami SQL Injection.

Post z forum.php.pl: http://forum.php.pl/mysqli-prepared-statements-t54025.html

Kategoria: Bazy danych, PHP | Marcin Chyłek | Komentarze: 0
« Nowsze