9

Ho una tabella Redshift completamente aspirata da ~ 2 TB con un distkey phash (cardinalità elevata, centinaia di milioni di valori) e tasti di selezione composti (phash, last_seen).Ottimizza la condizione IN grossa per la query Redshift

Quando faccio una query come:

SELECT 
    DISTINCT ret_field 
FROM 
    table 
WHERE 
    phash IN (
     '5c8615fa967576019f846b55f11b6e41', 
     '8719c8caa9740bec10f914fc2434ccfd', 
     '9b657c9f6bf7c5bbd04b5baf94e61dae' 
    ) 
AND 
    last_seen BETWEEN '2015-10-01 00:00:00' AND '2015-10-31 23:59:59' 

Esso restituisce molto rapidamente. Tuttavia, quando aumento il numero di hash oltre 10, Redshift converte la condizione IN da un gruppo di OR a un array, per http://docs.aws.amazon.com/redshift/latest/dg/r_in_condition.html#r_in_condition-optimization-for-large-in-lists

Il problema è quando ho un paio di dozzine di valori phash, la query "ottimizzata" passa da meno di un secondo tempo di risposta a più di mezz'ora. In altre parole, smette di usare la chiave di ordinamento e fa una scansione completa della tabella.

Qualche idea su come posso evitare questo comportamento e mantenere l'uso degli ordinamenti per mantenere la query rapida?

Qui è la differenza tra EXPLAIN < 10 hash e> 10 hash:

Meno di 10 (0,4 secondi):

XN Unique (cost=0.00..157253450.20 rows=43 width=27) 
    -> XN Seq Scan on table (cost=0.00..157253393.92 rows=22510 width=27) 
       Filter: ((((phash)::text = '394e9a527f93377912cbdcf6789787f1'::text) OR ((phash)::text = '4534f9f8f68cc937f66b50760790c795'::text) OR ((phash)::text = '5c8615fa967576019f846b55f11b6e61'::text) OR ((phash)::text = '5d5743a86b5ff3d60b133c6475e7dce0'::text) OR ((phash)::text = '8719c8caa9740bec10f914fc2434cced'::text) OR ((phash)::text = '9b657c9f6bf7c5bbd04b5baf94e61d9e'::text) OR ((phash)::text = 'd7337d324be519abf6dbfd3612aad0c0'::text) OR ((phash)::text = 'ea43b04ac2f84710dd1f775efcd5ab40'::text)) AND (last_seen >= '2015-10-01 00:00:00'::timestamp without time zone) AND (last_seen <= '2015-10-31 23:59:59'::timestamp without time zone)) 

Più di 10 (45-60 minuti):

XN Unique (cost=0.00..181985241.25 rows=1717530 width=27) 
    -> XN Seq Scan on table (cost=0.00..179718164.48 rows=906830708 width=27) 
       Filter: ((last_seen >= '2015-10-01 00:00:00'::timestamp without time zone) AND (last_seen <= '2015-10-31 23:59:59'::timestamp without time zone) AND ((phash)::text = ANY ('{33b84c5775b6862df965a0e00478840e,394e9a527f93377912cbdcf6789787f1,3d27b96948b6905ffae503d48d75f3d1,4534f9f8f68cc937f66b50760790c795,5a63cd6686f7c7ed07a614e245da60c2,5c8615fa967576019f846b55f11b6e61,5d5743a86b5ff3d60b133c6475e7dce0,8719c8caa9740bec10f914fc2434cced,9b657c9f6bf7c5bbd04b5baf94e61d9e,d7337d324be519abf6dbfd3612aad0c0,dbf4c743832c72e9c8c3cc3b17bfae5f,ea43b04ac2f84710dd1f775efcd5ab40,fb4b83121cad6d23e6da6c7b14d2724c}'::text[]))) 
+0

non ci sto capendo quando si dice Redshift esegue sempre una scansione completa della tabella, ma potrebbe utilizzare la chiave di ordinamento per saltare i blocchi. Puoi fornire la spiegazione esatta della query? –

+0

Nessun problema @MarkHildreth - Ho appena modificato il post principale per includere le query 'EXPLAIN'. – Harry

+0

Nota, non molto equivale a SO lettori e utenti (ma è possibile postare la soluzione qui): esiste una mailing list dedicata per le domande sulle prestazioni postgresql. –

risposta

2

Vale la pena provare a impostare sortkeys (last_seen, phash), inserendo prima last_seen.

Il motivo della lentezza potrebbe essere dovuto al fatto che la colonna principale per la chiave di ordinamento è phash che sembra un carattere casuale. Come dice il documento DEVS redshift dev, le colonne timestamp dovrebbero essere la colonna principale per la chiave di ordinamento se si utilizza quella per le condizioni.

Se i dati recenti viene richiesto più di frequente, specificare la colonna timestamp come la colonna principale per la chiave di ordinamento. - Choose the Best Sort Key - Amazon Redshift

Con questo ordine di chiave di ordinamento, tutte le colonne verranno ordinati per last_seen, allora phash. (What does it mean to have multiple sortkey columns?)

Una nota è che è necessario ricreare la tabella per modificare la chiave di ordinamento. This ti aiuterà a farlo.

+0

Soluzione semplice, ma questo l'ha risolto! Ancora non fiammeggiante veloce, ma apparentemente gli ordinamenti sono orribilmente inefficienti su stringhe casuali. – Harry

3

È possibile provare a creare tabella/sottoquery temporanea:

SELECT DISTINCT t.ret_field 
FROM table t 
JOIN (
    SELECT '5c8615fa967576019f846b55f11b6e41' AS phash 
    UNION ALL 
    SELECT '8719c8caa9740bec10f914fc2434ccfd' AS phash 
    UNION ALL 
    SELECT '9b657c9f6bf7c5bbd04b5baf94e61dae' AS phash 
    -- UNION ALL 
) AS sub 
    ON t.phash = sub.phash 
WHERE t.last_seen BETWEEN '2015-10-01 00:00:00' AND '2015-10-31 23:59:59'; 

In alternativa si fa ricerca in blocchi (se Query Optimizer di unire a uno, utilizzare tabella ausiliaria per memorizzare i risultati intermedi):

SELECT ret_field 
FROM table 
WHERE phash IN (
     '5c8615fa967576019f846b55f11b6e41', 
     '8719c8caa9740bec10f914fc2434ccfd', 
     '9b657c9f6bf7c5bbd04b5baf94e61dae') 
    AND last_seen BETWEEN '2015-10-01 00:00:00' AND '2015-10-31 23:59:59' 
UNION 
SELECT ret_field 
FROM table 
WHERE phash IN () -- more hashes) 
    AND last_seen BETWEEN '2015-10-01 00:00:00' AND '2015-10-31 23:59:59' 
UNION 
-- ... 

Se Query Optimizer di unire a quella che si può provare ad usare tabella temporanea per intermedi risultati

EDIT:

SELECT DISTINCT t.ret_field 
FROM table t 
JOIN (SELECT ... AS phash 
     FROM ... 
) AS sub 
    ON t.phash = sub.phash 
WHERE t.last_seen BETWEEN '2015-10-01 00:00:00' AND '2015-10-31 23:59:59'; 
+0

Questo in realtà funziona per continuare a usare l'indice (grazie!), Ma ho bisogno di restituire l'elenco dei valori di 'phash' da un'altra subquery ... non è manuale/codificato. C'è un modo per usare/abusare di UNION ALL da un altro risultato di una subquery? :( – Harry

+0

@Harry È possibile modificare 'UNION ALL' con qualsiasi cosa restituisca phash – lad2025

+0

Ho provato prima quello' EDIT', e ha lo stesso effetto di scansione della tabella.E non riesco a romperlo in pezzi perché l'hash tutto torna da Redshift in un grande batch – Harry

2

non si ha realmente bisogno DISTINCT? Questo operatore potrebbe essere costoso.

Vorrei provare a utilizzare LATERAL JOIN. Nella query sotto la tabella Hashes c'è una colonna phash - questa è la tua grande partita di hash. Potrebbe essere una tabella temporanea, una (sotto) query, qualsiasi cosa.

SELECT DISTINCT T.ret_field 
FROM 
    Hashes 
    INNER JOIN LATERAL 
    (
     SELECT table.ret_field 
     FROM table 
     WHERE 
      table.phash = Hashes.phash 
      AND table.last_seen BETWEEN '2015-10-01 00:00:00' AND '2015-10-31 23:59:59' 
    ) AS T ON true 

E 'molto probabile che implementa ottimizzatore LATERAL JOIN come un ciclo nidificato. Passerebbe attraverso tutte le righe su Hashes e per ogni riga eseguirà lo SELECT FROM table. L'interno SELECT dovrebbe utilizzare l'indice che si ha su (phash, last_seen). Per giocare sul sicuro includere ret_field nell'indice e renderlo un indice di copertura: (phash, last_seen, ret_field).


c'è un punto molto valido per la risposta di @Diego: invece di mettere costanti phash valori nella query, metterli in una tabella temporanea o permanente.

Mi piacerebbe estendere la risposta di @Diego e aggiungere che è importante che questa tabella con hash abbia indice indice univoco.

Quindi, creare una tabella Hashes con una colonna phash che ha esattamente lo stesso tipo di quello principale table.phash. È importante che i tipi corrispondano. Rendi questa colonna una chiave primaria con un indice cluster univoco. Scarica i tuoi dozzine di valori phash nella tabella Hashes.

Poi la query diventa semplice INNER JOIN, non laterale:

SELECT DISTINCT T.ret_field 
FROM 
    Hashes 
    INNER JOIN table ON table.phash = Hashes.phash 
WHERE 
    table.last_seen BETWEEN '2015-10-01 00:00:00' AND '2015-10-31 23:59:59' 

È comunque importante che table ha indice (phash, last_seen, ret_field).

L'ottimizzatore deve essere in grado di sfruttare il fatto che entrambe le tabelle unite sono ordinate per colonna phash e che è univoca nella tabella Hashes.

+0

Ho provato ogni variazione possibile per i join laterali e ottengo continuamente un errore di sintassi. Sei sicuro che siano supportati su Redshift? – Harry

+0

@Harry, no, non sono sicuro che Redshift abbia 'LATERAL JOIN'. Ho visto il tag Postgres e non ho prestato attenzione al tag Redshift. Sfortuna. Redshift ha stored procedure e cursori? Di solito i cursori sono più lenti di SQL dichiarativo quando fanno la stessa cosa. Ma in questo caso l'SQL dichiarativo non sta facendo la ricerca dell'indice per ogni 'phash', quindi il ciclo esplicito per ogni' phash' con l'aggiunta di risultati in una tabella temporanea potrebbe essere più veloce nel complesso. –

1

è possibile sbarazzarsi degli "OR" inserendo i dati desiderati in una tabella temporanea e unendoli con la tabella attuale.

Ecco un esempio (sto usando un CTE perché con lo strumento Im usando è difficile da catturare il piano quando si dispone di più di un'istruzione SQL - ma andare con una tabella temporanea se potete)

select * 
from <my_table> 
where checksum in 
(
'd7360f1b600ae9e895e8b38262cee47936fb6ced', 
'd1606f795152c73558513909cd59a8bc3ad865a8', 
'bb3f6bb3d1a98d35a0f952a53d738ddec5c72c84', 
'b2cad5a92575ed3868ac6e405647c2213eea74a5' 
) 

VERSUS

with foo as 
(
    select 'd7360f1b600ae9e895e8b38262cee47936fb6ced' as my_key union 
    select 'd1606f795152c73558513909cd59a8bc3ad865a8' union 
    select 'bb3f6bb3d1a98d35a0f952a53d738ddec5c72c84' union 
    select 'b2cad5a92575ed3868ac6e405647c2213eea74a5' 
) 
select * 
from <my_table> r 
    join foo f on r.checksum = F.my_key 

ed ecco il piano, come si può vedere sembra più complesso, ma questo è a causa del CTE, non sembrerebbe che modi su una tabella temporanea:

enter image description here

1

Hai provato a utilizzare l'unione per tutti i valori di phash?

Proprio così: "non si ferma con il SortKey e fa una scansione completa della tabella"

SELECT ret_field 
FROM table 
WHERE phash = '5c8615fa967576019f846b55f11b6e41' -- 1st phash value 
and last_seen BETWEEN '2015-10-01 00:00:00' AND '2015-10-31 23:59:59' 

UNION 

SELECT ret_field 
FROM table 
WHERE phash = '8719c8caa9740bec10f914fc2434ccfd' -- 2nd phash value 
and last_seen BETWEEN '2015-10-01 00:00:00' AND '2015-10-31 23:59:59' 

UNION 

SELECT ret_field 
FROM table 
WHERE phash = '9b657c9f6bf7c5bbd04b5baf94e61dae' -- 3rd phash value 
and last_seen BETWEEN '2015-10-01 00:00:00' AND '2015-10-31 23:59:59' 

-- and so on... 

UNION 

SELECT ret_field 
FROM table 
WHERE phash = 'nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn' -- Nth phash value 
and last_seen BETWEEN '2015-10-01 00:00:00' AND '2015-10-31 23:59:59' 
Problemi correlati