2013-04-15 5 views
5

Ho una tabella in un database PostgreSQL chiamato feeds_up. Sembra:Record più recente, precedente alla data, per categoria: ottimizzazione

| feed_url | isup | hasproblems | observed timestamp with tz | id (pk)| 
|----------|------|-------------|-------------------------------|--------| 
| http://b.| t | f   | 2013-02-27 16:34:46.327401+11 | 15235 | 
| http://f.| f | t   | 2013-02-27 16:31:25.415126+11 | 15236 | 

Ha qualcosa come 300k righe, che cresce a ~ 20 righe ogni cinque minuti. Ho una query che viene eseguita molto spesso (carico di ogni pagina)

select distinct on (feed_url) feed_url, isUp, hasProblems 
    from feeds_up 
    where observed <= '2013-02-27T05:38:00.000Z' 
    order by feed_url, observed desc; 

Ho messo un esempio di tempo lì, quel momento è parametrizzato. L'analisi di spiegazione è su explain.depesz.com. Ci vogliono circa 8s. Pazzo!

Ci sono solo circa 20 valori univoci per feed_url, quindi questo sembra davvero inefficiente. Pensavo di essere stupido e provare un ciclo FOR in una funzione.

CREATE OR REPLACE FUNCTION feedStatusAtDate(theTime timestamp with time zone) RETURNS SETOF feeds_up AS 
$BODY$ 
DECLARE 
    url feeds_list%rowtype; 
BEGIN 
FOR url IN SELECT * FROM feeds_list 
LOOP 
    RETURN QUERY SELECT * FROM feeds_up 
    WHERE observed <= theTime 
    AND feed_url = url.feed_url 
    ORDER BY observed DESC LIMIT 1; 
END LOOP; 
END; 
$BODY$ language plpgsql; 

select * from feedStatusAtDate('2013-02-27T05:38:00.000Z'); 

che richiede solo 307ms!

L'utilizzo di un ciclo FOR in SQL mi fa sbagliare, come posso fare una bella query, come la prima, efficiente? È possibile? O è questo il genere di cose in cui un ciclo FOR è davvero il migliore?

ETA

versione

Postgres: PostgreSQL 9.1.5 su i686-pc-linux-gnu, compilato da gcc (SUSE Linux) 4.3.4 [gcc-4_3-ramo di revisione 152.973], a 32 bit

Indici su feeds_up:

CREATE INDEX feeds_up_url 
    ON feeds_up 
    USING btree 
    (feed_url COLLATE pg_catalog."default"); 

CREATE INDEX feeds_up_url_observed 
    ON feeds_up 
    USING btree 
    (feed_url COLLATE pg_catalog."default", observed DESC); 

CREATE INDEX feeds_up_observed 
    ON public.feeds_up 
    USING btree 
    (observed DESC); 
+0

Solo FYI @Cathy ha provato ad aumentare 'work_mem' a 20 MB con il seguente risultato: http://explain.depesz.com/s/UJw (dai commenti su una risposta che ho eliminato). L'ordinamento non si riversa più sul disco ma la query non è significativamente più veloce. Creare un indice 'CREATE INDEX feeds_up_feed_url_observed ON feed_up (feed_url, osservato DESC);' anche non ha funzionato; l'indice non è usato. –

+0

Quale versione di PostgreSQL, tra l'altro? 'SELEZIONA versione()'. –

+0

@CraigRinger 9.1.5, farò una modifica. – Cathy

risposta

1

Partendo dal presupposto che "id" è di serie e sempre sequenziale, si potrebbe semplificare, trovando il MAX (id) per ogni FEED_URL in una sottoquery e poi tirare il resto dei dati come segue:

SELECT fu.feed_url, fu.isup, fu.hasproblems, fu.observed 
FROM feeds_up fu 
JOIN 
(
    SELECT feed_url, max(id) AS id FROM feeds_up 
    WHERE observed <= '2013-03-27T05:38:00.000Z' 
    GROUP BY feed_url 
) AS q USING (id); 
ORDER BY fu.feed_url, fu.observed desc; 

Ho fatto un test rapido e questo funziona in modo molto efficiente utilizzando solo un indice su "osservato".

UPDATE:

Per utilizzare "osservato" invece di "id" (dal momento che i record non possono inserire in ordine) è possibile modificare al di sopra di query come segue:

SELECT DISTINCT ON (fu.feed_url) fu.feed_url, fu.isup, fu.hasproblems, fu.observed 
FROM feeds_up fu 
JOIN 
(
    SELECT feed_url, max(observed) as observed FROM feeds_up 
    WHERE observed <= '2013-03-27T05:38:00.000Z' 
    GROUP BY feed_url 
) AS q USING (feed_url, observed) 
ORDER BY fu.feed_url, fu.observed desc; 

Sul mio sistema questo ha funzionato in quasi alla stessa ora con l'indice su "osservato". YMMV

+0

Questo è proprio quello che stavo cercando! Meno di 100ms con una cache calda. – Cathy

0

Se si parla di ottimizzazione si dovrebbe descrivere quello indexs avete.

penso quello che è assolutamente obbligatorio un indice in "osservato"

Un altro indice sarebbe "FEED_URL, ha osservato"

Infine uno in "FEED_URL", potrebbe essere utile, ma io non sono così certo se questo farebbe più caldo che bene. Ovviamente lo svantaggio di tutto questo sarà la performance su insert, ma per quello avrei bisogno di conoscere un po 'meglio il problema.

Hai considerato un partition da "feed_url" (dal momento che dici che ne hai solo pochi)? Altrimenti "osservato" per data (mese)?

+0

Ho modificato per aggiungere gli indici che ho. Come dice @CraigRinger sopra, ho provato a creare un indice combinato, ma il pianificatore di query non lo ha usato (Sì, ho analizzato 'vuoto). – Cathy

+0

Non penso che una partizione di feed_url possa essere d'aiuto, dato che ne prendo sempre uno (a meno che non fraintenda il partizionamento?). Sono disposto a provare il partizionamento con "osservato", (non riesco a trovare alcuna istruzione per un tavolo esistente?) Anche se sembra un po 'estremo per un tavolo con circa 200.000 file al mese. – Cathy

Problemi correlati