Bartosz Ratajczyk

Bartosz Ratajczyk MS SQL Developer

Temat: Zend_Paginator - wydajność tablicy

W pewnej aplikacji używam Zend_Paginator z adapterem DbSelect lub DbTableSelect. Po dyskusji na grupie nt. modeli zastanawiam się nad zwracaniem wyników wyszukiwania jako tablicy obiektów (jak w przykładzie z quickstarta ZF) - zamiast dotychczasowego szukania na bazie i zwracania np. Zend_Db_Table_Rowset_Abstract - ale przez to tracę automatykę paginatora przy obliczaniu ilości danych w selekcie i być może ogólnie na wydajności.

Znacie jakieś testy, artykuły, albo wiecie z własnego doświadczenia jak wygląda wydajność paginacji na tablicy np. z wyników wyszukiwania w porównaniu do podobnego wyniku przy zastosowaniu adaptera DbSelect/DbTableSelect? Niby DbSelect też zwraca tablicę, ale już ograniczoną do zestawu wyników ograniczonego o limit/offset.

Oczywiście użycie cache może sporo przyspieszyć i biorę to pod uwagę.

A może zastosowanie własnej klasy rozszerzającej Zend_Db_Table_Row_Abstract może coś usprawnić?

EDIT. Zrobiłem test, być może mało miarodajny. Stworzyłem tablicę zawierającą 10 000 obiektów, każdy obiekt ma 4 właściwości. Zrobienie 10 000 razy array_slice() z offset między 0 a 10000 oraz limit = 20 na tej tablicy trwało sekundę. A losowe array_slice($a, 8032, 100, true) trwało 0.007 sekundy. Testy na "zwykłym" laptopie. Samo utworzenie tablicy 10k elementów zajmowało < 0.1 s.
Być może zatem nie ma co się aż tak stresować, ale nadal jestem ciekawy innych testów.

EDIT2. Tworzenie tablicy odbywało się z palca, nie jako przetwarzanie wyniku odpytania bazyBartosz Ratajczyk edytował(a) ten post dnia 24.08.10 o godzinie 09:33
Krzysztof Suszyński

Krzysztof Suszyński Chief Programmer
(Java & Puppet)

Temat: Zend_Paginator - wydajność tablicy

Baza danych zawsze wykona Ci to sprawniej niż nawet najlepszy twój kod w php. To nie jest dobre podejście - zamiast tego proponuje użyć sensownego rozwiązania i wykorzystać wzorzec projektowy Data Mapper (Fowler). Dzięki jego wykorzystaniu zamapujesz otrzymany rowset do tablicy obiektów dziedziny o które Ci chodzi do tego wykonasz to zgodnie ze sztuką i będzie działać szybko.
Bartosz Ratajczyk

Bartosz Ratajczyk MS SQL Developer

Temat: Zend_Paginator - wydajność tablicy

W quickstarcie ZF jest właśnie przykład z data mapperem (stosuję coś na jego wzór). Wynik wyszukiwania w bazie zwraca mi tablicę obiektów danego typu (vide fetchAll() na http://framework.zend.com/manual/en/learning.quickstar...

Moje pytanie brzmi: czy przesiadka na adapter 'array' w Zend_Paginatorze - bo taki będzie zwracany wynik użycia metody - będzie drastycznie spowalniać pracę niż wykorzystanie adapterów DbTableSelect lub DbSelect? DbSelect też zwraca tablicę (tyle że już po okrojeniu wyników), ale innego typu wyników. Jeśli dobrze kojarzę to zależy od ustawień typu zwracanych danych adaptera bazy. DbTableSelect zwraca Zend_Db_Table_Rowset_Abstract.

Stosując różne typy zwracanych danych przy różnych zapytaniach w mapperze będę miał dublowanie funkcjonalności przy wyciąganiu danych - bo np. fetchAll() zwróci tablicę obiektów, a jak będę chciał dać dla paginatora to żeby wykorzystać adapter np. DbTableSelect będę musiał np. dopisać metodę, która zwróci te same dane w formacie Zend_Db_Table_Select.

Przykład.

pierwszy: (przekazanie adaptera array)


class UserMapper
{
public function fetchAll()
{
// kod jak w quickstarcie ZF do tworzenia tablicy obiektów
// zwróci tablicę obiektów klasy User()
}
}

[...]

$userRepo = new UserMapper();
$users = $userRepo->fetchAll();

$paginator = new Zend_Paginator::factory($users);


drugi: (przekazanie adaptera DbTableSelect)


$userRepo = new UserMapper();
$select = $userRepo->getSelect();

$paginator = new Zend_Paginator::factory($select);
[...]

class UserMapper
{
public function getSelect()
{
// kod zwróci Zend_Db_Table_Select
// dopiero Paginator zwróci Zend_Db_Table_Rowset_Abstract
return $this->getDbTable()->select();
}
}


W zależności od tego jaką metodę zwracania przyjmę mam różny sposób dostępu do danych w widoku. Mała bieda jeśli kolumny w bazie i nazwy właściwości w obiektach nazywają się tak samo (get i set to obsłużą). Ale jeśli nie, to nie mogę np. zastosować tego samego kodu widoku do renderowania pojedynczego użytkownika lub grupy użytkowników i trzeba utrzymywać zmiany w dodatkowym kawałku kodu. Do tego musze pamiętać, że jeśli jestem w paginatorze, to muszę się odwoływać do danych inaczej niż bez paginatora.

Dodatkowy problem, to np. zwracając tablicę obiektów mogę w obiekcie poustawiać dodatkowe elementy - np. $user->getFullName() zwracające imię i nazwisko. Dla adaptera Db(Table)Select muszę już dorzucić kolumnę do zapytania z określoną nazwą, żeby się móc odwołać w widoku.

Podsumowując dłużyznę: zastanawiam się, czy przesiadka na inny tryb zwracania danych do paginatora (tablica obiektów zamiast np. Zend_Db_Select) nie spowoduje spadku wydajności dla dużej ilości zwracanych rekordów (przykład wyszukania 2000 postów na forum; czy lepiej wyciągnąć dla paginatora tablicę obiektów Post i wiedzieć, że model zawsze zwracając kolekcję wyników zwraca tablicę obiektów typu Post, czy lepiej wyciągać dane stosując adapter Db(Table)Select i mieć na uwadze że nie zawsze dostaję kolekcję danych w takim samym formacie.
Bartosz Ratajczyk

Bartosz Ratajczyk MS SQL Developer

Temat: Zend_Paginator - wydajność tablicy

Ponieważ najbardziej mi przeszkadzało to, że jeśli przekażę do paginatora adapter DbSelect/DbTableSelect to zwracane obiekty będą miały różne sposoby wywołania wyświetlania wartości (np. w tabeli mam pole autor_id, a w obiekcie jest autorId) poszedłem w stronę własnej klasy wiersza zwracanej przez wynik selecta. Z grubsza tak:


class Model_DbTable_User extends Zend_Db_Table_Abstract
{
protected $_name = 'users';
protected $_rowClass = 'Projekt_Db_Table_Row';
}


Plik z klasą wiersza (Projekt\Db\Table\Row.php) jest w katalogu znajdującym się w ścieżce. W klasie wiersza nadpisuję metodę _transformColumn(), która widząc wywołanie np. $oUser->autorId zwraca dane z kolumny wyniku autor_id.


class Projekt_Db_Table_Row extends Zend_Db_Table_Row_Abstract
{

/**
* Przetwarza camelCase na slowo_z_podkresleniami
* http://www.paulferrett.com/2009/php-camel-case-functions/
* @param string $str string w formacie camelCase
* @return string
*/
function fromCamelCase($str) {
$str[0] = strtolower($str[0]);
$func = create_function('$c', 'return "_" . strtolower($c[1]);');
return preg_replace_callback('/([A-Z])/', $func, $str);
}

/**
* Przetwarzanie kolumny w formacie mixedCase na nazwę bazodanową z podkreśleniami
* Np. zdjecieUrl == zdjecie_url
* @param string $sColumn nazwa kolumny
*/
protected function _transformColumn($sColumn)
{
if (!is_string($sColumn)) {
require_once 'Zend/Db/Table/Row/Exception.php';
throw new Zend_Db_Table_Row_Exception("Kolumna $sColumn nie jest typu string");
}

return $this->fromCamelCase($sColumn);

}


Na razie jeszcze nie wiem jak wydajnościowo i czy gdzieś nie wyjdą błędy, ale na razie wydaje się lepsze niż przekazywanie tablicy wszystkich wyników.

W widoku:

foreach($this->paginator as $item) {
?>
[...]
<p><?php echo $item->autorId; ?></p>
[...]
<?php
}
Bartosz Ratajczyk edytował(a) ten post dnia 27.08.10 o godzinie 23:26

Następna dyskusja:

Zend_Form dane w tablicy




Wyślij zaproszenie do