efficace tabella mysql / design dell'indice per 35 milioni di righe + tabella, con oltre 200 colonne corrispondenti (doppie), di cui è possibile eseguire una query su qualsiasi combinazione


17

Sto cercando consigli sulla progettazione di tabelle / indici per la seguente situazione:

Ho una tabella di grandi dimensioni (dati cronologici sui prezzi delle azioni, InnoDB, 35 milioni di righe e in crescita) con una chiave primaria composta (assetid (int), data (data)). oltre alle informazioni sui prezzi, ho 200 valori doppi che devono corrispondere a ciascun record.

CREATE TABLE `mytable` (
`assetid` int(11) NOT NULL,
`date` date NOT NULL,
`close` double NOT NULL,
`f1` double DEFAULT NULL,   
`f2` double DEFAULT NULL,
`f3` double DEFAULT NULL,   
`f4` double DEFAULT NULL,
 ... skip a few 
`f200` double DEFAULT NULL, 
PRIMARY KEY (`assetid`, `date`)) ENGINE=`InnoDB` DEFAULT CHARACTER SET latin1 COLLATE
    latin1_swedish_ci ROW_FORMAT=COMPACT CHECKSUM=0 DELAY_KEY_WRITE=0 
    PARTITION BY RANGE COLUMNS(`date`) PARTITIONS 51;

inizialmente memorizzavo le 200 doppie colonne direttamente in questa tabella per facilitare l'aggiornamento e il recupero, e questo aveva funzionato bene, poiché l'unica query fatta su questa tabella era dall'assetid e dalla data (questi sono religiosamente inclusi in qualsiasi query contro questa tabella ) e le 200 doppie colonne sono state solo lette. La dimensione del mio database era di circa 45 Gig

Tuttavia, ora ho il requisito in cui devo essere in grado di interrogare questa tabella con qualsiasi combinazione di queste 200 colonne (denominate f1, f2, ... f200), ad esempio:

select from mytable 
where assetid in (1,2,3,4,5,6,7,....)
and date > '2010-1-1' and date < '2013-4-5'
and f1 > -0.23 and f1 < 0.9
and f117 > 0.012 and f117 < .877
etc,etc

storicamente non ho mai avuto a che fare con una così grande quantità di dati prima, quindi il mio primo istinto era che erano necessari indici su ciascuna di queste 200 colonne, o avrei finito con scansioni di tabelle di grandi dimensioni, ecc. Per me questo significava che avevo bisogno di una tabella per ciascuna delle 200 colonne con chiave primaria, valore e indicizzazione dei valori. Quindi ci sono andato.

CREATE TABLE `f1` (
`assetid` int(11) NOT NULL DEFAULT '0',
`date` date NOT NULL DEFAULT '0000-00-00',
`value` double NOT NULL DEFAULT '0',
PRIMARY KEY (`assetid`, `date`),
INDEX `val` (`value`)
) ENGINE=`InnoDB` DEFAULT CHARACTER SET latin1 COLLATE latin1_swedish_ci ROW_FORMAT=COMPACT CHECKSUM=0 DELAY_KEY_WRITE=0;

ho riempito e indicizzato tutte e 200 le tabelle. Ho lasciato intatta la tabella principale con tutte le 200 colonne, poiché regolarmente viene interrogata su assetid e intervallo di date e tutte le 200 colonne sono selezionate. Ho pensato che lasciare quelle colonne nella tabella genitore (non indicizzato) per scopi di lettura, e poi averle indicizzate nelle loro stesse tabelle (per il filtro join) sarebbe stato più efficace. Ho eseguito spiegazioni sul nuovo modulo della query

select count(p.assetid) as total 
from mytable p 
inner join f1 f1 on f1.assetid = p.assetid and f1.date = p.date
inner join f2 f2 on f2.assetid = p.assetid and f2.date = p.date 
where p.assetid in(1,2,3,4,5,6,7)
and p.date >= '2011-01-01' and p.date < '2013-03-14' 
and(f1.value >= 0.96 and f1.value <= 0.97 and f2.value >= 0.96 and f2.value <= 0.97) 

In effetti il ​​mio risultato desiderato è stato raggiunto, spiega mi mostra che le righe scansionate sono molto più piccole per questa query. Tuttavia ho finito con alcuni effetti collaterali indesiderati.

1) il mio database è passato da 45 Gig a 110 Gig. Non riesco più a mantenere il db nella RAM. (Ho 256Gig di RAM sulla strada comunque)

2) gli inserti notturni di nuovi dati ora devono essere eseguiti 200 volte anziché una volta

3) la manutenzione / deframmentazione delle nuove 200 tabelle richiede 200 volte di più rispetto alla sola 1 tabella. Non può essere completato in una notte.

4) le query contro le tabelle f1, ecc. Non sono necessariamente performanti. per esempio:

 select min(value) from f1 
 where assetid in (1,2,3,4,5,6,7) 
 and date >= '2013-3-18' and date < '2013-3-19'

la query sopra, mentre spieghi mostra che sembra <1000 righe, il completamento può richiedere più di 30 secondi. Presumo che ciò sia dovuto al fatto che gli indici sono troppo grandi per adattarsi alla memoria.

Dato che erano molte cattive notizie, ho guardato oltre e ho trovato il partizionamento. Ho implementato le partizioni nella tabella principale, partizionata alla data ogni 3 mesi. Il mensile mi è sembrato sensato, ma ho letto che una volta ottenute più di 120 partizioni, la performance ne risente. il partizionamento trimestrale mi lascerà sotto per i prossimi 20 anni circa. ogni partizione è leggermente inferiore a 2 Gig. ho corso a spiegare le partizioni e tutto sembra potare correttamente, quindi indipendentemente dal fatto che ritengo che il partizionamento sia stato un buon passo, almeno per scopi di analisi / ottimizzazione / riparazione.

Ho trascorso molto tempo con questo articolo

http://ftp.nchu.edu.tw/MySQL/tech-resources/articles/testing-partitions-large-db.html

la mia tabella attualmente è partizionata con la chiave primaria ancora su di essa. L'articolo menziona che le chiavi primarie possono rallentare una tabella partizionata, ma se si dispone di una macchina in grado di gestirla, le chiavi primarie sulla tabella partizionata saranno più veloci. Sapendo che ho una grande macchina in arrivo (256 G RAM), ho lasciato le chiavi accese.

così come la vedo io, ecco le mie opzioni

opzione 1

1) rimuovere le 200 tabelle extra e lasciare che la query esegua le scansioni delle tabelle per trovare i valori f1, f2 ecc. indici non univoci possono effettivamente compromettere le prestazioni su una tabella adeguatamente partizionata. eseguire una spiegazione prima che l'utente esegua la query e negarli se il numero di righe analizzate supera una certa soglia definita. salvami il dolore del gigantesco database. Cavolo, presto sarà presto tutto in memoria.

sub-domanda:

sembra che abbia scelto uno schema di partizione appropriato?

opzione 2

Partiziona tutte le 200 tabelle usando lo stesso schema di 3 mesi. goditi le scansioni delle righe più piccole e consenti agli utenti di eseguire query più grandi. ora che sono partizionate almeno posso gestirle 1 partizione alla volta per scopi di manutenzione. Cavolo, presto sarà presto tutto in memoria. Sviluppa un modo efficiente per aggiornarli di notte.

sub-domanda:

Vedi un motivo per cui potrei evitare gli indici delle chiavi primarie su queste tabelle f1, f2, f3, f4 ..., sapendo che ho sempre assetid e data durante le query? mi sembra poco intuitivo ma non sono abituato a set di dati di queste dimensioni. questo ridurrebbe un po 'il database presumo

Opzione 3

Rilasciare le colonne f1, f2, f3 nella tabella principale per recuperare quello spazio. faccio 200 join se ho bisogno di leggere 200 funzionalità, forse non sarà lento come sembra.

Opzione 4

Tutti voi avete un modo migliore di strutturarlo di quanto non abbia pensato finora.

* NOTA: presto aggiungerò altri 50-100 di questi doppi valori a ciascun articolo, quindi devo progettare sapendo che sta arrivando.

Grazie per qualsiasi aiuto

Aggiornamento n. 1 - 24/03/2013

Sono andato con l'idea suggerita nei commenti che ho ricevuto di seguito e ho creato una nuova tabella con la seguente configurazione:

create table 'features'{
  assetid int,
  date    date,
  feature varchar(4),
  value   double
}

Ho partizionato la tabella a intervalli di 3 mesi.

Ho spazzato via le precedenti 200 tabelle in modo che il mio database fosse tornato a 45 Gig e ho iniziato a riempire questa nuova tabella. Un giorno e mezzo dopo, è stato completato e il mio database ora si trova a 220 concerti paffuti !

Permette la possibilità di rimuovere questi 200 valori dalla tabella principale, in quanto posso ottenerli da un join, ma ciò mi restituirebbe solo 25 concerti o forse così

Gli ho chiesto di creare una chiave primaria su assetid, data, funzionalità e un indice sul valore, e dopo 9 ore di chugging non ha davvero fatto un'ammaccatura e sembrava congelarsi, quindi ho ucciso quella parte.

Ho ricostruito un paio di partizioni ma non sembrava recuperare molto / alcuno spazio.

Quindi quella soluzione sembra che probabilmente non sarà l'ideale. Le righe occupano molto più spazio delle colonne, mi chiedo, potrebbe essere questo il motivo per cui questa soluzione ha richiesto molto più spazio?

Mi sono imbattuto in questo articolo:

http://www.chrismoos.com/2010/01/31/mysql-partitioning-tables-with-millions-of-rows

mi ha dato un'idea. Dice:

Inizialmente, ho pensato al partizionamento RANGE per data e, mentre sto usando la data nelle mie query, è molto comune che una query abbia un intervallo di date molto ampio, e ciò significa che potrebbe facilmente estendersi su tutte le partizioni.

Ora sto anche partizionando l'intervallo per data, ma consentirò anche ricerche per ampio intervallo di date, il che ridurrà l'efficacia del mio partizionamento. Avrò sempre un intervallo di date quando cerco, ma avrò sempre anche un elenco di assetidi. Forse la mia soluzione dovrebbe essere quella di partizionare per assetid e data, in cui identifico intervalli di assetid tipicamente cercati (che posso trovare, ci sono elenchi standard, S&P 500, Russell 2000, ecc.). In questo modo non guarderei quasi mai l'intero set di dati.

D'altronde, sono principalmente a chiave su assetid e date comunque, quindi forse non sarebbe di grande aiuto.

Altri pensieri / commenti sarebbero apprezzati.


2
Non riesco a capire perché hai bisogno di 200 tabelle. Un unico tavolo con (value_name varchar(20), value double)sarebbe in grado di memorizzare tutto ( value_nameessendo f1, f2, ...)
a_horse_with_no_name

Grazie. il motivo per cui li ho messi singolarmente era di ottenere il limite di 50 indici su un tavolo. Avevo pensato di metterli in 5 tabelle, 40 valori ciascuno, ma sto inserendo circa 17000 record al giorno per ciascuno e non sapevo come sarebbe stata la prestazione di inserimento su una tabella con 40 indici. si noti che ogni combinazione di assetid, date ottiene i propri valori f1, f2 ... Stai suggerendo una singola tabella con (assetid, date, value_name, value), con assetid chiave primaria, date, forse index on (value_name, value)? che tabella avrebbe 35 mil * 200 = 7 miliardi di righe ma forse partizionerebbe bene funzionerebbe?
Dyeryn,

post aggiornato con le mie esperienze provando questo metodo
dyeryn

ho la soluzione finale in fase di sviluppo, aggiornerò al termine. è essenzialmente la soluzione a tabella singola qui proposta con partizionamento specifico e sharding logico.
Dyeryn,

Potrebbe essere utile un motore di archiviazione diverso? Invece di InnoDb forse prova InfiniDB? I dati a colonna, i modelli di accesso sembrano grandi aggiornamenti in batch, letture basate sull'intervallo e minima manutenzione della tabella.
disordinato

Risposte:


1

per coincidenza sto anche esaminando uno dei supporti client in cui abbiamo progettato la struttura di coppie chiave-valore per la flessibilità e attualmente la tabella ha più di 1,5 miliardi di righe e ETL è troppo lento. bene ci sono molte altre cose nel mio caso, ma hai pensato a quel design. avrai una riga con tutte e 200 le colonne presenti nel valore presente, quella riga verrà convertita in 200 righe nel disegno della coppia chiave-valore. otterrai questo vantaggio in termini di spazio con questo design a seconda di un determinato AssetID e data quante righe ha effettivamente tutti i valori da 200 f1 a f200 presenti? se dici che anche il 30% delle colonne ha un valore NULL di quello è il tuo risparmio di spazio. perché nel design della coppia chiave-valore se il valore ID NULL quella riga non deve essere nella tabella. ma nel design della struttura di colonne esistente anche NULL occupa spazio. (Non sono sicuro al 100%, ma se hai più di 30 colonne NULL nella tabella, allora NULL prende 4bytes). se vedi questo disegno e supponi che tutte le 35 M righe abbiano valori in tutte le 200 colonne, allora il tuo db attuale diventerà 200 * 35 M = 700 M righe nella tabella immediatamente. ma non sarà molto elevato nello spazio tabella quello che avevi con tutte le colonne in una singola tabella poiché stiamo solo trasponendo le colonne in riga. in questa operazione di trasposizione in realtà non avremo righe in cui i valori sono NULL. quindi puoi effettivamente eseguire query su questa tabella e vedere quanti null ci sono e stimare la dimensione della tabella target prima di implementarla effettivamente. ma non sarà molto elevato nello spazio tabella quello che avevi con tutte le colonne in una singola tabella poiché stiamo solo trasponendo le colonne in riga. in questa operazione di trasposizione in realtà non avremo righe in cui i valori sono NULL. quindi puoi effettivamente eseguire query su questa tabella e vedere quanti null ci sono e stimare la dimensione della tabella target prima di implementarla effettivamente. ma non sarà molto elevato nello spazio tabella quello che avevi con tutte le colonne in una singola tabella poiché stiamo solo trasponendo le colonne in riga. in questa operazione di trasposizione in realtà non avremo righe in cui i valori sono NULL. quindi puoi effettivamente eseguire query su questa tabella e vedere quanti null ci sono e stimare la dimensione della tabella target prima di implementarla effettivamente.

il secondo vantaggio è la lettura delle prestazioni. come hai detto, il nuovo modo di interrogare i dati è una qualsiasi combinazione di questa colonna da f1 a f200 nella clausola where. con una coppia di valori chiave, il design da f1 a f200 è presente in una colonna, ad esempio "FildName" e i loro valori sono presenti nella seconda colonna, ad esempio "FieldValue". puoi avere un indice CLUSTER su entrambe le colonne. la tua query sarà UNIONE di quelle Selezioni.

DOVE (FiledName = 'f1' e FieldValue TRA 5 E 6)

UNIONE

(FiledName = 'f2' e FieldValue TRA 8 E 10)

eccetera.....

Ti darò alcuni numeri di prestazione dal vero server di produzione. abbiamo 75 colonne di prezzo per ciascun TICKER di sicurezza.


1

Nel trattare questo tipo di dati in cui è necessario inserire molte righe e sono anche necessarie prestazioni di query analitiche molto buone (sto assumendo che qui sia il caso), è possibile che un RDBMS colonnare sia adatto . Dai un'occhiata a Infobright CE e InfiniDB CE (entrambi i motori di archiviazione colonnare collegati a MySQL) e anche Vertica CE (più PostgreSQL simile invece di MySQL) ... tutte queste edizioni della community sono gratuite (anche se Vertica non lo è open source, scala gratuitamente a 3 nodi e 1 TB di dati). Gli RDBMS a colonne offrono in genere tempi di risposta di "grandi query" che sono 10-100 volte migliori rispetto a quelli basati su riga e tempi di caricamento di 5-50 volte migliori. Devi usarli correttamente o puzzano (non fare operazioni a fila singola ... fai tutte le operazioni in un approccio globale), ma usato correttamente oscillano davvero. ;-)

HTH, Dave Sisk


1
Abbiamo quasi un miliardo di righe di dati di tipo clickstream (non molto diversi dai dati ticker stock) in un'installazione Vertica a 3 nodi ... possiamo caricare un intero giorno di dati in circa 15 secondi e otteniamo tempi di risposta alle query in la gamma da 500 millisecondi. Nel tuo caso, sembra certamente che valga la pena dare un'occhiata.
Dave Sisk,

Posso garantire lo stesso. Nella mia ultima azienda avevamo un cluster Vertica a 8 nodi con circa lo stesso numero di righe e query aggregate semplici sull'intero set restituite in 1-3 secondi (in media). Il costo era di circa 1/4 del nostro precedente cluster Greenplum.
bma
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.