Piszę system, w którym konieczne jest generowanie skomplikowanych raportów, wykorzystujących duże ilości rekordów bazy danych (kilka - kilkanaście tys.). Dodatkowo przy raportach trzeba zbierać informacje z wielu różnych tabel (równie dużych). Napisałem odpowiednią procedurę MySQL aby szybko generowała raport. Niestety wykonuje się ona około 2-4 minut. Cała aplikacja w czasie wykonywania procedury wisi - żaden inny użytkownik nie może z niej korzystać dopóki procedura całkowicie się nie wykona. Próbowałem zrobić odpowiadającą tej procedurze metodę w samym kontrolerze, ale dzieje się to samo. Zarówno procedura jak i metoda muszą przejść po kilku tysiącach elementów i zebrać z bazy danych potrzebne informacje dla każdego z tych elementów. Nie wiem jak sobie poradzić z tym żeby serwer nie zawieszał się podczas generowania takiego raportu.
Działa moja teoria: każda aplikacja po osiągnięciu odpowiedniego stopnia złożoności zaczyna potrzebować możliwości odpalenia dłuższych procesów w tle
Zacznij od tego – bardzo fajny plugin ułatwiający delegację tego typu zadań z aplikacji railsowe do najpopularniejszych obecnie backendów zajmujących się “czarną robotą”: http://playtype.net/past/2008/2/6/starling_and_asynchrous_tasks_in_ruby_on_rails/
W dużym skrócie interesuje cię (zapytania do googla) “background tasks in rails”, “asynchronous tasks in rails” z naciskiem na biblioteki: spawn, starling, rabbitmq, backgroundrb.
A tak poważnie, przenieś wykonywanie na backgroundrb, starling/workling albo napisz własnego demona, ale na pewno nie wykonuj tego w obrębie aplikacji.
A ja bym zaczął od sprawdzenia czemu tak długo się to wykonuje. Kilkanaście tysięcy rekordów to żadna duża ilość. Być może wystarczy poprawcować nad indeksami w bazie, żeby było dużo szybciej.
Napisał że musi zbierać rekordy z kilku dużych tabel, czyli złączenia. Tuning konfiguracji MySQL (właśnie, googlnij ściągnij i odpal “mysql tuning primer”) oczywiście może pomóc, ale jeśli zapytanie ma szansę zająć więcej niż 1-2 sekundy, to sorry Winnetou, ale wynosimy Cię poza aplikację.
Optymalizacja samego zapytania na bazie. Sprawdź czy klucze obce mają indeksy(migracja rails automatycznie ich nie zakłada), Dodaj indeksy dla kolumn filtrujących zapytanie.
Jeżeli jest to możliwe użyj cachowania wyników. W zależności od charakterystyki danych:
zadanie crona wypełniające dodatkową tabelę raportu, odpalane co pewien czas
użyj memcached lub inny cache, cache będzie napełniany przez pierwsze zapytanie, metody inwalidacji cache’a musisz opracować w zależności od rodzaju danych
Co do zawieszania aplikacji to odpal dodatkowy proces mongrela (nie wiem jak wygląda to w przypadku passangera). Wywołania rails są asynchroniczne i pojedynczy serwer jest blokowany do czasu zakończenia zapytania.
Ała.
Nie, nie i zdecydowanie nie jest to dobre podejście do tematu.
“Uda się” jeśli generowanie raportu będzie trwało 20 sekund, ale przy 30 już może (Apacz albo przeglądarka), zależnie od konfiguracji, dostać timeout error.
Naprawdę, jeśli coś ma (może) trwać dłużej niż sekundę-dwie, to musi być wykonane asynchronicznie w osobnym procesie.
Owszem, optymalizacja generowania raportu to dobry pomysł, ale ja zakładam, że osoba go robiąca wiedziała co robi i o indeksach słyszała.
Skoro serwer przyjmuje tylko 1 żądanie to wnioskuję że używasz mongrela/thina i odpalasz tylko 1 proces. Albo musisz odpalać kilka procesów tych serwerów (np. na portach 3000-3005) i odpowiednio ustawić proxowanie w apache/nginx lub (IMHO łatwiej) użyć passengera i ustawić mu maksymalną ilość procesów > 1 (domyślnie tak właśnie jest). Aby jednak długo trwający proces nie kolejkował nadchodzących żądań musisz włączyć opcję PassengerUseGlobalQueue.
Bardzo pouczający artykuł. Rekordy są cache’owane w tabeli (cache’owanie jest kolejkowane offline) w wersji przygotowanej do szybkiego odczytania przez javascript.
Wszystko działa super. Używam Starling + Workling, ale mam pewne obawy.
Potrzebuję odpalać w tle metody, które generują raporty. Raport generuje sie kilkanascie - kilkadziesiąt sekund. Co jeżeli wielu użytkowników zacznie generować wiele raportów jednoczesnie? Czy starling umożliwi rownoczesne dzialanie metod dla kazdego uzytkownika? Jaki to ma wpływ na obciążenie systemu?
Zakladajac że Starling tylko kolejkuje zadania, czy jest jakiś sposób, aby umożliwić sprawne generowanie raportów przez wielu użytkowników jednocześnie? (bez odpalania wielu wątków). W sytuacji kiedy tworzy się kolejka, przy kilkudziesięciu użytkownikach, ktorzy jenoczesnie zaczeli generowac raporty, ostatni uzytkownik bedzie czekal nawet kilkanascie minut na swoj raport.
Komputery to takie urządzenia, które potrafią wykonać określoną liczbę operacji na sekundę. Jeśli komputerowi wykonującemu X operacji na sekundę każesz wykonać 5X operacji, zajmie mu owa seria co najmniej 5 sekund. Niezależnie od tego, jak owe operacje poustawiasz czy poprzeplatasz.
A teraz jeszcze prościej i mniej złośliwie:
Jeśli zrobienie 100 raportów ma zająć 10 minut, to naprawdę lepiej robić je po kolei na 100% mocy każdy – dzięki temu ostatni owszem, wypadnie po 10 minutach, ale pierwszy już po kilku sekundach. Jeśli odpalisz wszystkie na raz (wątki, procesy, cokolwiek) będą się robiły dłużej niż 10 minut (narzut na przełączanie zadań) i każdy z nich wyleci dopiero w ostatniej minucie. Co z tego że w ciągu ostatniej minuty uzyskasz nagle 100 raportów, skoro w tym momencie każdy użytkownik na swój raport czekać musiał pełne 10 minut?
Mam nadzieję że to przekonuje Cię co do wyższości kolejkowania nad multizadaniowością
A te raporty to są generowane z bieżących danych? W sensie jak użytkownik sobie generuje raport i potem znowu za pół godziny to one się jakoś bardzo różnią?
Nie myślałeś może, żeby stworzyć jakiś zupełnie oddzielny proces co by generował te raporty po kolei dla wszystkich użytkowników i je gdzie zapisywał na dysku albo w bazie, a potem jak użytkownik by chciał raport to poprsotu byś go odczytywał z dysku czy tam z bazy.
Mógłbyś też spróbować robić jakieś wstępne przetwarzanie danych w osobnym procesie, tak żeby przy tworzenie raportu nie trzeba było wszystkiego liczyć od nowa, tylko odczytywać gotowe dane.
Nie no oczywiście raporty są zapisywane do bazy. Mógłbym rzeczywiście pomyśleć nad jakimś mechanizmem ktory by mi porównywał zmiany z poprzednim raportem i wykorzystywał stare dane, ale nie wiem czy sprawdzanie powtórzonych danych nie zajeło by wiecej czasu niz generowanie wszystkiego od nowa.
Czy możliwe jest ustawienie (korzystajac ze Starling/Workling) oddzielnej kolejki dla każdego Workera? Z tym, że musiało by być sprecyzowane że dla tego Workera jest ta kolejka i żadna inna. Czy możliwe jest coś takiego?