2011-01-03 18 views
5

Immaginate una tabella con la seguente struttura su PostgreSQL 9.0:PostgreSQL: Caricamento dati in schema a stella in modo efficiente

create table raw_fact_table (text varchar(1000)); 

Per ragioni di semplificazione Cito solo una colonna di testo, in realtà ha una dozzina. Questa tabella ha 10 miliardi di righe e ogni colonna ha molti duplicati. La tabella viene creata da un file flat (csv) utilizzando COPY FROM.

per aumentare le prestazioni voglio convertire al seguente struttura schema a stella:

create table dimension_table (id int, text varchar(1000)); 

La tabella dei fatti sarebbe quindi essere sostituito con una tabella dei fatti come la seguente:

create table fact_table (dimension_table_id int); 

mio metodo attuale è essenzialmente eseguire la query seguente per creare la tabella delle dimensioni:

Create table dimension_table (id int, text varchar(1000), primary key(id)); 

poi per creare riempire la tabella della dimensione che uso:

insert into dimension_table (select null, text from raw_fact_table group by text); 

In seguito ho bisogno di eseguire la seguente query:

select id into fact_table from dimension inner join raw_fact_table on (dimension.text = raw_fact_table.text); 

Basta immaginare le prestazioni orribili ottengo confrontando tutte le stringhe per tutte le altre stringhe diverse volte.

Su MySQL potrei eseguire una stored procedure durante il COPY FROM. Questo potrebbe creare un hash di una stringa e tutto il successivo confronto tra stringhe viene eseguito sull'hash invece della lunga stringa raw. Questo non sembra possibile su PostgreSQL, cosa faccio allora?

dati campione non sia un file CSV contenente qualcosa come questo (io uso le virgolette anche intorno interi e raddoppia):

"lots and lots of text";"3";"1";"2.4";"lots of text";"blabla" 
"sometext";"30";"10";"1.0";"lots of text";"blabla" 
"somemoretext";"30";"10";"1.0";"lots of text";"fooooooo" 
+0

Quanto ci vuole? Quanto tempo ti aspettavi che ci volesse? –

+0

Non l'ho mai finito usando la quantità di dati menzionata. Ma su 15 milioni di file ci sono volute diverse ore. Ho già esaminato tutte le cose standard di ottimizzazione del server (work_mem ecc.), Quindi sono in cerca di un metodo diverso per ottenere lo stesso risultato. – David

+0

Invia dati di esempio e DDL. –

risposta

2

Solo per domande: - è necessario convertire i dati in 1 o 2 passaggi? - Possiamo modificare la tabella durante la conversione?

esecuzione di query più semplici di può migliorare le prestazioni (e il carico del server, mentre farlo)

Un approccio potrebbe essere:

  1. generare dimension_table (se ho ben capito, non c'è bisogno problemi di prestazioni con questo) (magari con un campo booleano temporaneo aggiuntivo ...)
  2. ripetizione: scegliere una voce precedentemente non selezionata da dimension_table, selezionare ogni riga da raw_fact_table che la contiene e inserirli in fact_table. Mark record di dimension_table come fatto, e dopo ... È possibile scrivere questo come una stored procedure, e può convertire i dati in background, mangiare risorse minime ...

O un altro (probabilmente migliore):

  1. creare fact_table come OGNI record da raw_fact_table AND one dimension_id.(Quindi compresi dimension_text e le righe dimension_id)
  2. creare dimension_table
  3. creare un dopo inserto trigger per fact_table quali:
    • ricerche per dimension_text in fact_table
    • se non trovato, crea un nuovo record nella dimension_table
    • aggiornamenti dimension_id a questo ID
  4. in un ciclo simle, inserire ogni record da raw_fact_table a fact_table
+0

Grazie per i suggerimenti. Non ho specificato questo, ma la mia unica preoccupazione è quella di elaborare tutti i dati il ​​più velocemente possibile, quindi l'esecuzione di qualcosa in background non ha senso nel mio setup (so che è molto saggio in altre situazioni). Il problema con il secondo approccio è che i trigger non vengono attivati ​​su COPY FROM. Quindi credo che non ci sia alcuna ragione per il trigger. Il tuo approccio è comunque valido usando un cursore. Sono però incerto sulle prestazioni: http://stackoverflow.com/questions/4776127/postgres-surprising-performance-on-updates-using-cursor – David

+0

Per quanto riguarda il tuo primo approccio, non ho cronometrato le prestazioni della generazione di la tabella delle dimensioni (dovrei farlo). Mi piace il tuo approccio con il pensiero al contrario. Creerò un nuovo commento con un approccio basato sul tuo. – David

+0

crea tabella dimension_table (id seriale, testo varchar (1000), raw_fact_table_id bigint [], chiave primaria (id)); ------------------- inserisci in dimension_table (testo , raw_fact_table_id) (seleziona il testo, array_agg (raw_fact_table.id) dal gruppo raw_fact_table per testo); in seguito si dovrebbe trovare un modo per aggiornare raw_fact_table in base agli id ​​in raw_fact_table_id. Cosa ne pensi? – David

2

Stai omettendo alcuni dettagli lì alla fine, ma non vedere che c'è necessariamente un problema. Non è evidente che tutte le stringhe siano effettivamente confrontate con tutte le altre stringhe. Se si fa un join, PostgreSQL potrebbe scegliere un algoritmo di join più intelligente, come un hash join, che potrebbe darti lo stesso hash che stai implementando nella tua soluzione MySQL. (Anche in questo caso, i dati sono confusa su quello.)

+0

Grazie per la risposta. Ho aggiornato la domanda ora, con i dettagli mancanti. – David

6

Basta immaginare le prestazioni orribili ottengo confrontando tutte le stringhe a tutti gli altri corde più volte.

Quando lo fai da un po ', smetti di immaginare le prestazioni e inizi a misurarlo. "L'ottimizzazione prematura è la radice di tutto il male."

Cosa significa "miliardi" per te? Per me, negli Stati Uniti, significa 1.000.000.000 (o 1e9). Se questo è vero anche per te, probabilmente stai considerando tra 1 e 7 terabyte di dati.

Il mio metodo attuale è quella essenzialmente eseguire la seguente query per creare la tabella dimensioni:

Create table dimension_table (id int, text varchar(1000), primary key(id)); 

Come farai in forma 10 miliardi di righe in una tabella che utilizza un numero intero per un chiave primaria? Diciamo anche che metà delle righe sono duplicati. Come funziona quell'aritmetica quando lo fai?

Non immaginare. Leggi prima. Quindi prova.

Leggi Data Warehousing with PostgreSQL. Sospetto che queste diapositive di presentazione ti diano alcune idee.

Leggere anche Populating a Database e considerare quali suggerimenti implementare.

Test con un milione (1e6) righe, in seguito a un processo "divide et impera". Cioè, non provare a caricare un milione alla volta; scrivi una procedura che la suddivide in blocchi più piccoli. Esegui

EXPLAIN <sql statement> 

Hai detto di stimare almeno il 99% di righe duplicate. In generale, ci sono due modi per sbarazzarsi dei dupes

  1. All'interno di un database, non necessariamente la stessa piattaforma utilizzata per la produzione.
  2. Al di fuori di un database, nel filesystem, non necessariamente lo stesso file system utilizzato per la produzione.

Se si dispone ancora dei file di testo che è stato caricato, è consigliabile provare prima all'esterno del database. Questo awk one-liner genererà linee univoche da ciascun file. È relativamente economico, in quanto fa un solo passaggio sui dati.

awk '!arr[$0]++' file_with_dupes > file_without_dupes 

Se avete davvero 99% gonzi, entro la fine di questo processo, si dovrebbe avere ridotto i tuoi 1 a 7 terabyte fino a circa 50 concerti. E, fatto ciò, è anche possibile numerare ciascuna linea univoca e creare un file delimitato da tabulazioni prima di copiarlo nel data warehouse. Questo è un altro uno-liner:

awk '{printf("%d\t%s\n", NR, $0);}' file_without_dupes > tab_delimited_file 

Se dovete fare questo sotto Windows, mi piacerebbe usare Cygwin.

Se si deve fare questo in un database, proverei a evitare di utilizzare il database di produzione o il server di produzione. Ma forse sono troppo cauto. Spostare diversi terabyte è una cosa costosa da fare.

Ma mi piacerebbe testare

SELECT DISTINCT ... 

prima di utilizzare GROUP BY. Potrei essere in grado di eseguire alcuni test su un set di dati di grandi dimensioni, ma probabilmente non questa settimana. (Di solito non lavoro con file di dimensioni terabyte, è piuttosto interessante, se puoi aspettare.)

+0

I AM sta ottenendo prestazioni orribili e chiedo consigli specifici per risolvere il problema specifico. Raw_fact_table non ha un numero intero per la chiave primaria. Solo le tabelle delle dimensioni in quanto vi sono 99. XX% duplicati nel fact_table. Ho già implementato tutti i consigli dei collegamenti che mi hai inviato. – David

+0

La "T" in "ETL" ti sta uccidendo. Il 99% dei duplicati significa che stai mirando a qualcosa di circa 100.000.000 di righe. Modificherò la mia risposta. –

+0

Per semplificare l'esempio ho solo detto che raw_fact_table ha una colonna di testo. In effetti ne ha una dozzina, quindi il tuo metodo per rimuovere i duplicati non funzionerebbe, grazie per averlo fatto notare, però. Aggiornerò la domanda Raw_fact_table ha anche valori interi e valori doppi. – David

1

ho un visto diversi modi di risolvere il tuo problema C'è funzione md5 in PostgreSQL md5 (stringa) Calcola l'hash MD5 di stringa, restituendo il risultato in esadecimale

inserto in dimension_table (selezionare nulla, md5 (testo), testo dal gruppo raw_fact_table da testo)

aggiungere campo MD5 in raw_fact_table così select id in fact_table dalla dimensione interna join raw_fact_table on (dimension.md5 = raw_fact_table.md5);

Indici su MD5 depositate potrebbe aiutare pure

Oppure è possibile calcolare MD5 al volo durante il caricamento dei dati. Ad esempio il nostro strumento ETL Advanced ETL può farlo per te. Inoltre può caricare i dati in più tabelle contemporaneamente.

C'è un certo numero di tutorial on-line disponibile sul nostro sito web Per esempio questo dimostra il caricamento lento dimensione modificabile

http://www.dbsoftlab.com/online-tutorials/advanced-etl-processor/advanced-etl-processor-working-with-slow-changing-dimension-part-2.html

+0

Non credo che si possa eseguire il calcolo di MD5 durante l'esecuzione di COPY FROM (che è il metodo consigliato per caricare i dati). Se ciò significa che il tuo strumento non usa COPY FROM, allora credo che sia inutile in quanto il caricamento senza questo richiederebbe anni. Devo dire che sono molto scettico riguardo una soluzione ETL senza codice. Va bene finché ho solo bisogno di fare le cose standard, ma se mai dovessi imbattermi in un caso speciale, non ho codice per ricollegarlo. – David

+0

Totalmente d'accordo con te COPY FROM è il modo più veloce per caricare i dati in PostgreSQL. Questo è il motivo per cui lo usiamo internamente in Advanced ETL Processor. Dalla documentazione PostgreSQL: COPIA TABLE_NAME da stdin (STDIN Specifica che l'input viene dal l'applicazione client.) –

+0

Abbiamo fatto del nostro meglio per rendere il più velocemente possibile. Per ogni database utilizziamo il modo a digiuno per caricare i dati. (percorso diretto/convenzionale per Oracle, bcp per SQL Server, copia da PostgreSQL, ecc.) Abbiamo effettivamente stampato codice critico e contrassegnato ed eliminato tutte le parti inefficaci. Di quanto abbiamo usato profiler e prestazioni ottimizzate ulteriormente. Stiamo migliorando costantemente. (Dai un'occhiata al nostro forum di supporto e prendi nota del tempo necessario per risolvere il problema o introdurre una nuova funzione piuttosto che confrontarla con i grandi giocatori). –

2
-- add unique index 
CREATE UNIQUE INDEX uidx ON dimension_table USING hash(text); 
-- for non case-sensitive hash(upper(text)) 

prova hash (testo); e btree (testo) per vedere quale è più veloce

Problemi correlati