Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: Propel, relacja "n do m", niska wydajność
Forum PHP.pl > Forum > PHP > Object-oriented programming
joebezucha
Witam,

Pracuje wlaśnie nad projektem z wykorzystaniem frameworka Symfony i pojawił sie pierwszy problem. Problemem jest wydajność Propel-a.

Fragment opisu schematu bazy danych:

  1. CREATE TABLE `answer`
  2. (
  3. `id` INTEGER NOT NULL AUTO_INCREMENT,
  4. `question_id` INTEGER NOT NULL,
  5. `no` INTEGER NOT NULL,
  6. `body` TEXT NOT NULL,
  7. `scale` INTEGER,
  8. `grade` INTEGER,
  9. PRIMARY KEY (`id`),
  10. INDEX `FI_answer_1` (`question_id`),
  11. CONSTRAINT `FK_answer_1`
  12. FOREIGN KEY (`question_id`)
  13. REFERENCES `question` (`id`)
  14. ON DELETE CASCADE
  15. )Type=InnoDB;
  16.  
  17. CREATE TABLE `test`
  18. (
  19. `id` INTEGER NOT NULL AUTO_INCREMENT,
  20. `status` INTEGER NOT NULL,
  21. `test_type` INTEGER NOT NULL,
  22. `lname` VARCHAR(40) NOT NULL,
  23. `fname` VARCHAR(24) NOT NULL,
  24. `birthdate` DATE NOT NULL,
  25. `sex` CHAR NOT NULL,
  26. `school_type` INTEGER NOT NULL,
  27. `user_id` INTEGER NOT NULL,
  28. `password` VARCHAR(20),
  29. `created_at` DATETIME,
  30. `timestart` DATETIME,
  31. `timefinish` DATETIME,
  32. `ip` VARCHAR(15),
  33. PRIMARY KEY (`id`),
  34. INDEX `FI_test_1` (`user_id`),
  35. CONSTRAINT `FK_test_1`
  36. FOREIGN KEY (`user_id`)
  37. REFERENCES `user` (`id`)
  38. ON DELETE CASCADE
  39. )Type=InnoDB;
  40.  
  41. CREATE TABLE `test_answer`
  42. (
  43. `test_id` INTEGER NOT NULL,
  44. `answer_id` INTEGER NOT NULL,
  45. PRIMARY KEY (`test_id`,`answer_id`),
  46. INDEX `FI_ta_1` (`answer_id`),
  47. CONSTRAINT `FK_ta_1`
  48. FOREIGN KEY (`answer_id`)
  49. REFERENCES `answer` (`id`)
  50. ON DELETE CASCADE,
  51. CONSTRAINT `FK_ta_2`
  52. FOREIGN KEY (`test_id`)
  53. REFERENCES `test` (`id`)
  54. ON DELETE CASCADE
  55. )Type=InnoDB;


Jak widać są tu 3 tabele test, asnwer, i tabela test_answer która odpowida za relacje "n do m"

W skrypcie PHP mam:

  1. <?php
  2. $this->test = TestPeer::retrieveByPk($this->getRequestParameter('id')); // 1*
  3. $c = new Criteria();
  4. $c->setLimit(10);
  5. $test_answers = $this->test->getTestAnswersJoinAnswer($c);  // 2*
  6. foreach ($test_answers as $test_answer){
  7. $answer = $test_answer->getAnswer();
  8. }
  9. ?>

No i w tym momencie pojawił się problem z wydajością PROPELa. Czas tworzenia kolekcji $test_answers rośnie po exponencie;) przy limicie ustawionym na 200 skrypt zostaje zakończony w wyniku przekroczenia limitu czasu(60sekund). No a wszystkich odpowiedzi mam do pobrania 214.

W narzędziu WebDebug (część frameworka Symfony) widze, że wykonywane są tylko dwa zapytania do bazy:
1* pobranie testu
2* pobranie danych z tabel test_answer złączonej z answer

Czyli nie ma problemu z zapętlaniem zapytań do bazy danych.
Zresztą robie wszytsko zgodnie z instrukcją propel.phpdb.org/trac/wiki/Users/Documentation/1.2/ManyToManyRelationships

Czy Propel jest aż tak mało wydajny?questionmark.gif

Potrzebuję pełnych danych odpowiedzi na pytania testowe do przeliczenia wyników końcowych. Mogę oczywiście obejść Propela, skorzystac bezposrednio z Creola i operować na tablicach zamiast na obiektach. No ale zastanawia mnie ta bardzo niska wydajność.

Jakieś pomysły?questionmark.gif smile.gif
pawel_k
żeby mi działało zrobiłem takiego sql'a (bo masz odwołania do tabel których nie ma)
  1. CREATE TABLE `answer`
  2. (
  3. `id` INTEGER NOT NULL AUTO_INCREMENT,
  4. `no` INTEGER NOT NULL,
  5. `body` TEXT NOT NULL,
  6. `scale` INTEGER,
  7. `grade` INTEGER,
  8. PRIMARY KEY (`id`)
  9. )Type=InnoDB;
  10.  
  11.  
  12. CREATE TABLE `test`
  13. (
  14. `id` INTEGER NOT NULL AUTO_INCREMENT,
  15. `status` INTEGER NOT NULL,
  16. PRIMARY KEY (`id`)
  17. )Type=InnoDB;
  18.  
  19.  
  20. CREATE TABLE `test_answer`
  21. (
  22. `test_id` INTEGER NOT NULL,
  23. `answer_id` INTEGER NOT NULL,
  24. PRIMARY KEY (`test_id`,`answer_id`),
  25. INDEX `FI_ta_1` (`answer_id`),
  26. CONSTRAINT `FK_ta_1`
  27. FOREIGN KEY (`answer_id`)
  28. REFERENCES `answer` (`id`)
  29. ON DELETE CASCADE,
  30. CONSTRAINT `FK_ta_2`
  31. FOREIGN KEY (`test_id`)
  32. REFERENCES `test` (`id`)
  33. ON DELETE CASCADE
  34. )Type=InnoDB;


do tego wstawiałem:
- 1 wiersz do tabeli "test" (bo jak robisz $this->test = TestPeer::retrieveByPk(1); to więcej mi nie trzeba)
  1. INSERT INTO test (STATUS) VALUES (1);

- 200 wierszy do tabeli "answer"
  1. INSERT INTO answer (body,no,scale,grade) VALUES ("hahaha1", 1, 1, 1);
  2. ...
  3. INSERT INTO answer (body,no,scale,grade) VALUES ("hahaha1", 200, 200, 200);

- 200 wierszy do tabeli "test_answer"
  1. INSERT INTO test_answer (test_id,answer_id) VALUES (1,1);
  2. INSERT INTO test_answer (test_id,answer_id) VALUES (1,2);
  3. ...
  4. INSERT INTO test_answer (test_id,answer_id) VALUES (1,200);


widok dla akcji:
  1. <?php
  2. var_dump(sizeof($test_answers));
  3. var_dump(sizeof($arr));?>

i teraz dla wywołania akcji:
  1. <?php
  2. public function executeIndex()
  3. {
  4. $this->test = TestPeer::retrieveByPk(1); // 1*
  5. $c = new Criteria();
  6. $c->setLimit(10);
  7. $this->test_answers = $this->test->getTestAnswersJoinAnswer($c);  // 2*
  8.  
  9. $arr = array();
  10. foreach ($this->test_answers as $test_answer){
  11. $arr[] = $test_answer->getAnswer();
  12. }
  13. $this->arr = $arr;
  14. }
  15. ?>

robi 2 zapytania
  1. 1. [0.90 ms] SELECT test.ID, test.STATUS FROM test WHERE test.ID=1
  2. 2. [0.91 ms] SELECT test_answer.TEST_ID, test_answer.ANSWER_ID, answer.ID, answer.NO, answer.BODY, answer.SCALE, answer.GRADE FROM test_answer, answer WHERE test_answer.TEST_ID=1 AND test_answer.ANSWER_ID=answer.ID LIMIT 10

wyświetla
Cytat
int 10
int 10

czas wykonania według webdebug: # 250 ms

jak usuniesz linie
  1. <?php
  2. $c->setLimit(10);
  3. ?>

to odpowiednio
robi 2 zapytania
  1. 1. [0.36 ms] SELECT test.ID, test.STATUS FROM test WHERE test.ID=1
  2. 2. [0.45 ms] SELECT test_answer.TEST_ID, test_answer.ANSWER_ID, answer.ID, answer.NO, answer.BODY, answer.SCALE, answer.GRADE FROM test_answer, answer WHERE test_answer.TEST_ID=1 AND test_answer.ANSWER_ID=answer.ID

wyświetla
Cytat
int 200
int 200

czas wykonania według webdebug: # 3030 ms

jak widać nic złego się nie dzieje...
sprzęt testowy:
- system ubiuntu 7.04
- sempron 2600+ (64bit)
- 1024 ramu
- mysql Ver 14.12 Distrib 5.0.38, for pc-linux-gnu (i486) using readline 5.2
- PHP Version 5.2.1

nie wiem czemu u Ciebie coś się złego dzieje, chyba że ja coś za bardzo uprościłem ale nie wydaje mi się
joebezucha
Dzięki za wysiłek pawel_k

Jeszcze dziś sprawdzę czy z uproszczeniem schematu bazy to będzie hulać jak trzeba...

Bez uproszczeń sprawdzałem wydajność w zależności od setLimit(limit)

czasy są w [ms]

limit; query time; module/action time; full time;
1; 1.99; 459; 1328;
5; 1.72; 356; 1009;
10; 3.30; 529; 1196;
20; 2.20; 1000; 1756;
40; 2.32; 3839; 4487;
80; 2.70; 12414; 13167;
160; 3.90; 46567; 47259;

Jak widać do 20 jest OK ale potem lawinowo wzrasta czas procesu ORM...

Puki co zrobiłem to troche inaczej tzn robie własne złączenie tabel i pobieram bezposrednio obiekty klasy Answer.

w akcji mam:

  1. <?php
  2. $c = new Criteria();
  3. $c->addJoin(AnswerPeer::ID, TestAnswerPeer::ANSWER_ID, Criteria::LEFT_JOIN);
  4. $c->add(TestAnswerPeer::TEST_ID, $this->getId());
  5. $this->collAnswers = AnswerPeer::doSelect($c);
  6. ?>


No i tutaj działa wszystko OK.
Wyglada na to ze problem z wydajności występuje gdy Propel pobiera obiekty TestAnswer i jednoczesnie agregowane przez nie obiekty Answer.

Moj komp:
Athlon XP 1800+
RAM 896 MB
WIN XP SP2

Dam znać jak wypadły testy z uproszczonym schematem bazy danych

Zrobilem osobny projekt ze schematem uproszczonym do tej postaci co w poscie Pawła K.

Niestety problem wydajnosci ciagle występuje.
Czasy minimalnie sie zmniejszyły pownieważ mniej danych jest pobieranych no ale wykonanie trwa i tak grubo ponad 40sekund:/

Ktoś ma jakies pomysły w czym może tkwić problem??

Paweł jakiej wersji Propela uzywasz?questionmark.gif
pawel_k
trochę pogadałem z joebezucha, wykonałem kod na zrzucie jego bazy i wszystko jest ok. przy pierwszym sposobie
  1. <?php
  2. public function executeIndex()
  3. {
  4. $this->test = TestPeer::retrieveByPk(1); // 1*
  5. $c = new Criteria();
  6. $this->test_answers = $this->test->getTestAnswersJoinAnswer($c);  // 2*
  7.  
  8. $arr = array();
  9. foreach ($this->test_answers as $test_answer){
  10. $arr[] = $test_answer->getAnswer();
  11. }
  12. $this->arr = $arr;
  13. }
  14. ?>
czas wykonania wynosi dalej ok 3000ms, nawet nie jest to więcej niż przy uproszczeniu, czasem nawet mniej...

przy drugim sposobie
  1. <?php
  2. public function executeIndex2()
  3. {
  4. $c = new Criteria();
  5. $c->addJoin(AnswerPeer::ID, TestAnswerPeer::ANSWER_ID, Criteria::LEFT_JOIN);
  6. $c->add(TestAnswerPeer::TEST_ID, 1);//$this->getId());
  7. $this->collAnswers = AnswerPeer::doSelect($c);
  8. }
  9. ?>
czas wykonania to ok 250ms
czasu są podawane przez webdebuga.
dla mnie powodem może być widowsowa wersja php...
joebezucha
No i chyba juz wiem w czym problem.

Dotąd aplikację odpalełem pod WAMPem. Większośc z podstron generowana byla przez okolo 1sekunde (dość sporo jak na proste wyswietlenie danych obiektów)

Zainstalowałem najnowsze PHP 5.2.3, Upgrade PEAR (z problemami) No ale czas wykonania problematycznego skryptu byl ciągle bliski 1minuty.
Problem nie istnieje natomiast na platformie LAMP (instalacja na Ubuntu 7.04, PHP 5.2.1)
Większość podstron generowana jest w czasie 250-500ms a problematyczny skrypt około 3-4sekund

Także wyglada na to ze ogołnie pod Windowsem jest slabsza wydajnośc przetwarzania skryptów no a moj przypadek to juz wogóle skrajna sytuacja...

Inna sprawa to tez dziwny problem z upgradem PEAR pod windowsem. Przy upgradzie oraz przy instalacji paczek ciągle wywalał błąd braku pamięci (przekroczenie 8MB). Jednak zmiany w php.ini (memory_limit) nic nie pomagały.

Pomogło dopiero dodanie w pliku pearcmd.php lini:
  1. <?php
  2. ini_set('memory_limit', '100M');
  3. ?>


tak 100MB smile.gif, zgaduje że jest jakis problem z PEAR z jakąs podstawową operacją w stylu czytanie pliku, ktora pod windowsem stała sie potwornie pamięciożerna.

Przy upgradzie PEAR pod Ubuntu takiego problemu nie było!

Także podsumowując zraziłem sie do WAMPa smile.gif
Sh4dow
a moze ktos z was sproboje sprawdzic to przy pomocy xdebuga i wykryc waskie gardlo ?
domis86
Nie wiem jak sie to robi w propelu, ale sprobujcie zrobic tak, zeby wypisalo wszystkie query_stringi jakie robi na bazie.
pawel_k
@domis86 - przecież wypisałem wyżej - robi 2 zapytanie...

@Sh4dow - a możesz powiedzieć jak to sprawdzić? z xdebuga korzystał właściwie tylko jako z lepszego var_dump'a winksmiley.jpg
Sedziwoj
Cytat(pawel_k @ 9.07.2007, 20:55:53 ) *
@Sh4dow - a możesz powiedzieć jak to sprawdzić? z xdebuga korzystał właściwie tylko jako z lepszego var_dump'a winksmiley.jpg

Może to coś pomoże:
http://www.xdebug.org/docs/profiler
splatch
Przyczyną na 90% będzie jedna z metod hydrate, która jest zaimplementowana w sposób nie do końca optymalny. Jakiś czas temu opublikowałem notę z poprawionym generatorem, który tworzy wydajniejszy kod, z racji na zastosowanie identity map.
pawel_k
ehh, teraz przypominam sobie ze niecały rok temu miałem sprawdzić ale przez nawał obowiązków kompletnie wyleciało mi to z głowy za co teraz przepraszam :/ ale mam też pytanie - czy integrowałeś swoją klasę z propelem zintegrowanym z symfony?
To jest wersja lo-fi głównej zawartości. Aby zobaczyć pełną wersję z większą zawartością, obrazkami i formatowaniem proszę kliknij tutaj.
Invision Power Board © 2001-2025 Invision Power Services, Inc.