Egy érthetetlenül lassú query

Sziasztok.

Van egy szép nagy programom, egy összetettre sikerült PostgreSQL adatbázissal: https://github.com/csikfer/lanview2
Elboldogulok úgy általában az SQL-lel, de nem vagyok guru a témában.
Van egy lekérdezés a programba, ami irreálisan lassú. A lekérdezés 4 másodperc, miközben egy teljesen üres táblát kérdez le. Azt érzem, hogy a query messze nem optimális, de a semmin mit lehet molyolni 4 másodpercig, még ha balfasz módon fogalmaztam is meg a kérdést? És persze akkor is sok lesz ez a (több mint) 4 másodperc, ha lesz adat is a táblában, márpedig lehet benne sok rekord.
A query :

SELECT NULL,* FROM ONLY view_alarms WHERE array_length(ack_user_ids, 1) IS NULL AND is_group_place(place_id, ?) AND user_is_any_groups_member(?,online_group_ids);

A view_alarm egy VIEW tábla, amin szerintem van mit finomítani:

CREATE VIEW view_alarms AS
SELECT
a.alarm_id AS view_alarm_id,
a.host_service_id,
hs.node_id,
n.place_id,
a.superior_alarm_id,
a.begin_time,
a.end_time,
a.first_status,
a.max_status,
a.last_status,
alarm_message(host_service_id, max_status) AS msg,
COALESCE(hs.offline_group_ids, s.offline_group_ids) AS offline_group_ids,
COALESCE(hs.online_group_ids, s.online_group_ids) AS online_group_ids,
ARRAY(SELECT user_id FROM user_events WHERE alarm_id = a.alarm_id AND event_type = 'notice') AS notice_user_ids,
ARRAY(SELECT user_id FROM user_events WHERE alarm_id = a.alarm_id AND event_type = 'view') AS view_user_ids,
ARRAY(SELECT user_id FROM user_events WHERE alarm_id = a.alarm_id AND event_type = 'acknowledge') AS ack_user_ids,
ARRAY(SELECT user_id FROM user_events WHERE alarm_id = a.alarm_id AND event_type = 'sendmessage') AS msg_user_ids,
ARRAY(SELECT user_id FROM user_events WHERE alarm_id = a.alarm_id AND event_type = 'sendmail') AS mail_user_ids
FROM alarms AS a
JOIN host_services AS hs USING(host_service_id)
JOIN services AS s USING(service_id)
JOIN nodes AS n USING(node_id)
WHERE NOT a.noalarm;

Az alarms rekordból lehet viszonylag sok is, és fontos lenne, hogy max. 1-2 másodperc alatt lefusson. Elvileg a lekérdezés eredménye néhány sor, de nem kéne kiakadnia a programnak, ha pl. pár száz sor lenne.
A VIEW-ben hozzácsapom a táblához a felhasználói eseményeket (Megjelent a programban a riasztás, megnézte a részleteit, nyugtázta) ezek mint ID tömbök kerülnek a VIEW-ba. Valamint bekerül a VIEW-be a riasztás helye, milyen szolgáltatás, melyik eszközön riasztott.
A query-ben szűrök arra, hogy a riasztás a megfelelő zónában volt-e, az aktuális felhasználó benne van-e a csoportban, melyeknek on-line riasztást kell adni, és hogy nyugtázta-e már valaki ezt a riasztást.
Hogyan lehetne optimalizálni a fenti lekérdezést (további infók a linkben)?

Hozzászólások

Jó ez az EXPLAIN, feltéve, hogy értelmezni tudja a kedves felhasználó, én pl. nem nagyon igazodom el rajta.
Azért próba-cseresznye alapon módosítgattam a lekérdezést, valószínűleg szerencsém volt, mert az első csere: "array_length(ack_user_ids, 1) IS NULL" helyett "COALESCE(array_length(ack_user_ids, 1) = 0, true)" feltételt beírva megszűnt a 4 másodperc feletti tökölés (a kiindulási alarms tábla még mindig üres).
Eredetileg itt a "array_length(ack_user_ids, 1) > 0" feltétel szerepelt, csak éppen nem működött, mer a föggvény nem nullát, hanem NULL-t adott vissza.
Azt még elfogadom (bár nem erre számítottam jelen esetben), hogy az array_lenght NULL-t ad vissza üres tömbnél. De azt már végkép nem értem, hogy az eredeti query miért tökölt ennyi ideig, miközben a kiindulási tábla teljesen üres.

Az az alaptábla mindig is üres volt, vagy kiürült?

Nem tudom, hogy a pg hogyan viselkedik (vagy hogy egyáltalán viselkedik-e bárhogyan), ha a lekérdezések módját optimalizáló motor által látott statisztikák jelentősen eltérnek a tábla valódi tartalma által képviselt értékektől.

Üres táblára vonatkozó korrekt statisztika esetén ui. tényleg minden okod megvan feltételezni, hogy az optimalizáló azt teszi, amit egy tartalom nélküli táblával lekérdezéskor tennie érdemes: semmit. Ha viszont azt hiszi, hogy lesznek adatai, ezért egy rakás eshetőségre fel kell készülnie, akkor fel is készül (meglepően időrabló folyamat), és csak a tényleges végrehajtáskor szembesül azzal, hogy feleslegesen izgult.

A TRUNCATE nem járható út, ha az adatbázis tele van idegen kulcsokkal.
Persze lehet valamilyen áthidaló megoldás, de azt én nem ismerem.
Indexek hiányoztak, azokat pótoltam (de nem ettől lett gyorsabb, mert indexek később lettek).
Jelenleg 2 riasztással, és 4 felhasználói eseménnyel, rendesen működik, ami ugye nem sokat jelent, de majd ha lesz több riasztás meglátjuk.

Probléma:
Úgy általánosságban nem használunk *-ot lekérdezésekben, maximum, amikor feltérképezzük a táblát.
Másrészt valószínűleg az ARRAY-ben szereplő lekérdezéseket futtatja le a buta először, az eredményeket letárolja temp táblákban (ha van elég intelligens a query optimizer, akkor összehúzza egy lekérdezésbe). Ha elég népes a user_events tábla, esetleg nincs index az event_type mezőn (talán az alarm_id + event_type mezőn még jobb lenne), vagy van, de rossz a selectivitása, akkor azon bizony el fog molyolni a storage engine anélkül, hogy egyetlen rekordot is visszaadna.
Majdnem jó megoldás:
Az ARRAY függvényekben lévő lekérdezést át kellene írni úgy, hogy mindegyik függvényben ugyanaz a lekérdezés legyen: SELECT user_id, event_type FROM user_events WHERE alarm_id = a.alarm_id, majd egy case when-nel elágaztatni event_type-ra szűrve. Pontosan most nem tudom kifejteni, hogy hogyan, de az a lényeg, hogy ezt a lekérdezést már csak egyszer fogja lefuttatni az öt helyett és cache-be vágja az eredményt.
Egész jó megoldás:
A user_events táblát joinolod a lekérdezésben és megint csak case when-nel szűröd az event_type-ot. Az nem tudom, mennyivel jobb, ha a user_events táblát ötször joinolod hozzá a join feltételben szűrve event_type-ra, ebben az esetben nem kell a case when ugye. Explain-t futtass mindkét verzióra és használd a jobbat.

A teljesen tuti megoldást valszeg egy postgre guru fogja megmondani.

-----------
"Pontban 0:00-kor nem nagyon szoktak véletlen dolgok történni"

"Úgy általánosságban nem használunk *-ot lekérdezésekben"

Hogyhogy nem? Ennyire ront a lekérdezés sebességén? Én elég gyakran használom, mert vannak sok oszlopos táblák, ahonnan kell minden, egyszerűen indokolatlannak tűnt felsorolni az oszlopneveket.

Szerk: Ezt találtam hirtelen a témában: http://use-the-index-luke.com/blog/2013-08/its-not-about-the-star-stupid

A *-gal prod rendszerben az a baj, hogy nem garantált a mezők visszatérési sorrendje. Ami azt jelenti, hogy létrehozol egy táblát A, B, C mezőkkel, akkor a select *-os lekérdezés is A, B, C sorrendben adja vissza a mezőket. Ezt forráskódba illesztve működik is, amikor sorszámmal hivatkozol rájuk (column[1], column[2], column[3]). Majd amikor egy D mezővel ki kell egészíteni a táblát és valami jól szituált DBA újrakreálja a táblát és a következőképpen sorolja fel a mezőket: D, C, B, A, akkor a select * is ebben sorrendben tér vissza. Ami azt jelenti, hogy az alkalmazásodban column[1] értéke a D mező lesz, column[2] értéke C és így tovább. Érdemes rá egy egyszerű példa alkalmazást írni, hogy reprezentatívabb legyen a különbség.
Performancia szempontjából annyit ront a történeten, hogy a storage engine időnként elballag az (általam rajongott) rendszer adatbázisához, ahol az objektumokról tárol meta információkat és lekérdezi a mezőket. Ez akkor lehet problémás, ha éppen fut egy (vagy több) create utasítás, addig ugyanis a meta adatok lockolva vannak.

-----------
"Pontban 0:00-kor nem nagyon szoktak véletlen dolgok történni"

Köszi a választ. Akkor ez mégsem annyira bad practice, hogy eltemetnek érte, csak ésszel kell használni. Nyilván a teljesítmény kérdését ott kidobtam az ablakon, hogy dbal-t használok, az némi absztrakción keresztől megoldja nekem, hogy ne sorszámmal, hanem mezőnévvel hivatkozzak az egyes mezőkre.

Köszönöm a választ.
Azt én is látom/láttam, hogy az ARRAY-kban lévő kifejezések, így együtt messze nem optimálisak, azért lettek így megcsinálva, mert az általad felsorolt módokon nem tudtam összerakni az SQL kódot.
Az user_events és alarms JOIN-olásával próbálkoztam először, de a GROUP BY -ál elakadtam.
A nagyon rossz válaszidőn egyenlőre sikerült javítani (lásd egy másik válasz). Ha lesz egy kicsit nyugalmasabb időszakom, és időm utána nézni a szükséges hogyan-oknak, akkor átírom a javaslataid szerint.
Ja, a hiányzó indexeket az beírom az adatbázisba, az is gyorsítani fog.

Olyan nincs, hogy optimalizálok 4mp-ről 1-2mp-re. Ez hosszú távon 40mp->20mp optimalizálás lesz, amit megint optimalizálni kellene.

Az igazi optimalizálás 4->0.1sec lenne, amely működik akkor is, ha 400mp-re nő a rossz SQL (400->0.1). A visszaadott eredményhalmaz méretével, főleg a sorok számával (és a BLOB-ok méretével) egyenes arányos SELECT-eket kell mindig írni.

Ezt a hozzászólást nem nagyon értem, ill. nem világos mire válasz.
Egy üres kiindulási táblán alapuló kicsit szerencsétlen query 4 sec felet futott le. Kicserélve egy látszólag futási idő szempontjából irreleváns feltételt (lásd fentebb) lett belőle 11 msec. Ennek nyilván oka van, de az nem derült ki, hogy mi.

Az ilyen esetek után kezdtük pessimizernek hívni az optimizert. Sajnos drágább motor esetén is előfordul, hogy látszik az anomália, az explain meg is mutatja, hogy hol ered, csak arra nincs válasz, hogy miért.
Az egyik mindenkiben mély nyomot hagyott, aki be volt vonva (5 égtáj felől mindenki, aki a témában expert titulussal létezik): a majdnem minden kipróbálása után egyik kolléga minden elvárás nélkül, csak kínjában ökörködve a where elejére írta, hogy
1=1 and
mire 20-szoros lett a tempó.

(Bár a pg esetén nagyon távol állok attól, hogy azt merjem mondani, hogy "ennek itt nincs ésszerű oka".)

megjegyzem, ha hasonló problémába futtok bele, akkor a #postgresql csatornára kell írni, és ott segíteni fognak a fejlesztők (akik a postgresql-t írják). Nekünk volt
2 elég komoly bugbejelentésünk (az egyikre konkrétan segfault-ot dobott a szerver), és segítettek minimál tesztesetet összeállítani, és viszonylag rövid idő alatt
jött a javítás is.

(Nem, pg-t utoljára kb. 15 éve startoltam. Nagyobb, _sokkal_ fizetősebb, az rdbs piacon szempont és ízlés szerint az 1. v. 2. helyre tett motoron is létező jelenség, hogy az esetek töredék százalékában az optimalizáló olyat csinál, amit a fejlesztőkkel is törpölni feljogosított konzulens sem tud megindokolni. Heuristic strikes back.)

Atyaisten! A PostgreSQL 2016-ban még mindig nem tud tárolt eljárásokat, vagy direkt szeretsz lassú kódot írni? :P

(bocs, nem bírtam ki)
--
"Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live." John F. Woods

Get dropbox account now!