effektives mysql table / index design für 35 millionen zeilen + tabelle mit mehr als 200 entsprechenden spalten (double), von denen jede kombination abgefragt werden kann


17

Ich suche Beratung bei der Gestaltung von Tabellen / Indizes für die folgende Situation:

Ich habe eine große Tabelle (Aktienkursverlaufsdaten, InnoDB, 35 Millionen Zeilen und wachsend) mit einem zusammengesetzten Primärschlüssel (assetid (int), date (date)). Zusätzlich zu den Preisinformationen habe ich 200 Doppelwerte, die jedem Datensatz entsprechen müssen.

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;

Ich habe die 200 Doppelspalten ursprünglich direkt in dieser Tabelle gespeichert, um das Aktualisieren und Abrufen zu vereinfachen. Dies hat einwandfrei funktioniert, da für diese Tabelle nur die Asset-ID und das Datum abgefragt wurden (diese sind religiös in jeder Abfrage für diese Tabelle enthalten) ), und die 200 Doppelspalten wurden nur gelesen. Meine Datenbankgröße betrug ca. 45 Gig

Jetzt muss ich jedoch in der Lage sein, diese Tabelle mit einer beliebigen Kombination dieser 200 Spalten (mit den Namen f1, f2, ... f200) abzufragen. Beispiel:

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

Ich hatte in der Vergangenheit noch nie mit dieser großen Datenmenge zu tun, und mein erster Instinkt war, dass für jede dieser 200 Spalten Indizes benötigt wurden, oder ich musste mit großen Tabellenscans usw. aufwarten. Für mich bedeutete dies, dass Ich brauchte eine Tabelle für jede der 200 Spalten mit Primärschlüssel, Wert und Index der Werte. Also ging ich mit.

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;

Ich füllte und indizierte alle 200 Tabellen. Ich habe die Haupttabelle mit allen 200 Spalten intakt gelassen, da sie regelmäßig nach Asset-ID und Datumsbereich abgefragt wird und alle 200 Spalten ausgewählt werden. Ich dachte mir, dass es am leistungsstärksten wäre, diese Spalten in der übergeordneten Tabelle (nicht indiziert) für Lesezwecke zu belassen und sie dann zusätzlich in ihren eigenen Tabellen indizieren zu lassen (zum Filtern von Joins). Ich lief erklärt auf die neue Form der Abfrage

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 der Tat wurde mein gewünschtes Ergebnis erreicht, erklären Sie mir, dass die gescannten Zeilen für diese Abfrage viel kleiner sind. Allerdings hatte ich einige unerwünschte Nebenwirkungen.

1) Meine Datenbank wurde von 45 Gig auf 110 Gig erweitert. Ich kann die Datenbank nicht mehr im RAM behalten. (Ich habe 256 GB RAM auf dem Weg jedoch)

2) Das nächtliche Einfügen neuer Daten muss jetzt 200-mal statt einmal erfolgen

3) Wartung / Defragmentierung der neuen 200 Tische dauert 200-mal länger als nur die 1-Tabelle. Es kann nicht in einer Nacht abgeschlossen werden.

4) Abfragen gegen die Tabellen f1, etc sind nicht unbedingt performant. beispielsweise:

 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'

Die obige Abfrage zeigt zwar, dass sie <1000 Zeilen enthält, kann jedoch mehr als 30 Sekunden dauern. Ich gehe davon aus, dass die Indizes zu groß sind, um in den Speicher zu passen.

Da das viele schlechte Nachrichten waren, habe ich weiter gesucht und Partitionierung gefunden. Ich habe Partitionen auf dem Haupttisch implementiert, die alle 3 Monate nach dem Datum partitioniert wurden. Monthly schien mir Sinn zu machen, aber ich habe gelesen, dass die Leistung leidet, wenn man über 120 Partitionen hat. Die vierteljährliche Partitionierung wird mich für die nächsten 20 Jahre oder so unterkriegen. Jede Partition ist etwas kleiner als 2 Gig. Ich habe EXPLAIN-Partitionen ausgeführt und alles scheint ordnungsgemäß zu bereinigen. Unabhängig davon war die Partitionierung ein guter Schritt, zumindest zu Analyse- / Optimierungs- / Reparaturzwecken.

Ich habe viel Zeit mit diesem Artikel verbracht

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

Meine Tabelle ist derzeit mit noch vorhandenem Primärschlüssel partitioniert. In dem Artikel wird erwähnt, dass Primärschlüssel eine partitionierte Tabelle verlangsamen können. Wenn Sie jedoch einen Computer haben, der dies unterstützt, sind Primärschlüssel in der partitionierten Tabelle schneller. Da ich wusste, dass ich eine große Maschine auf dem Weg habe (256 G RAM), ließ ich die Schlüssel an.

so wie ich es sehe, sind hier meine Wahlen

Option 1

1) Entfernen Sie die zusätzlichen 200 Tabellen, und lassen Sie die Abfrage Tabellensuchen durchführen, um die Werte für f1, f2 usw. zu ermitteln. Nicht eindeutige Indizes können die Leistung einer ordnungsgemäß partitionierten Tabelle beeinträchtigen. Führen Sie eine Erklärung aus, bevor der Benutzer die Abfrage ausführt, und lehnen Sie sie ab, wenn die Anzahl der gescannten Zeilen einen von mir festgelegten Schwellenwert überschreitet. Rette mich vor dem Schmerz der riesigen Datenbank. Verdammt, es wird sowieso bald alles in Erinnerung bleiben.

Unterfrage:

klingt es, als hätte ich ein geeignetes Partitionsschema gewählt?

Option 2

Partitionieren Sie alle 200 Tabellen nach demselben 3-Monats-Schema. Genießen Sie die kleineren Zeilenprüfungen und lassen Sie die Benutzer größere Abfragen ausführen. Jetzt, da sie mindestens partitioniert sind, kann ich sie zu Wartungszwecken 1 Partition auf einmal verwalten. Verdammt, es wird sowieso bald alles in Erinnerung bleiben. Entwickeln Sie eine effiziente Methode, um sie jede Nacht zu aktualisieren.

Unterfrage:

Sehen Sie einen Grund, warum ich Primärschlüsselindizes für diese Tabellen f1, f2, f3, f4 ... vermeiden kann, da ich weiß, dass ich bei der Abfrage immer die Asset-ID und das Datum habe? scheint mir kontraintuitiv zu sein, aber ich bin nicht an Datensätze dieser Größe gewöhnt. das würde die Datenbank ein Haufen verkleinern, nehme ich an

Option 3

Löschen Sie die Spalten f1, f2, f3 in der Mastertabelle, um diesen Speicherplatz freizugeben. Mache 200 Joins, wenn ich 200 Features lesen muss, vielleicht wird es nicht so langsam sein, wie es sich anhört.

Option 4

Sie alle haben eine bessere Möglichkeit, dies zu strukturieren, als ich bisher gedacht habe.

* ANMERKUNG: Ich werde bald weitere 50-100 dieser doppelten Werte zu jedem Element hinzufügen, daher muss ich das Design so gestalten, dass ich weiß, dass es kommen wird.

Vielen Dank für jede Hilfe

Update Nr. 1 - 24.03.2013

Ich habe mich an die in den Kommentaren vorgeschlagene Idee gehalten und eine neue Tabelle mit folgendem Aufbau erstellt:

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

Ich habe den Tisch in 3-Monats-Intervallen aufgeteilt.

Ich habe die früheren 200 Tische weggeblasen, so dass meine Datenbank wieder auf 45 Gig war, und habe angefangen, diese neue Tabelle zu füllen. Eineinhalb Tage später war es vollbracht, und meine Datenbank liegt jetzt bei molligen 220 Gigs!

Es erlaubt die Möglichkeit, diese 200 Werte aus der Master-Tabelle zu entfernen, da ich sie von einem Join erhalten kann, aber das würde mir wirklich nur 25 Gigs oder so zurückgeben

Ich bat es, einen Primärschlüssel für AssetID, Datum, Funktion und einen Index für den Wert zu erstellen, und nach 9 Stunden Tuckern hatte es wirklich keine Beule hinterlassen und schien einzufrieren, sodass ich diesen Teil abgeschafft habe.

Ich habe ein paar Partitionen neu erstellt, aber es schien nicht viel Platz zurückzugewinnen.

Diese Lösung scheint also wahrscheinlich nicht ideal zu sein. Nehmen Zeilen deutlich mehr Platz in Anspruch als Spalten, frage ich mich. Könnte dies der Grund sein, warum diese Lösung so viel mehr Platz in Anspruch nimmt?

Ich bin auf diesen Artikel gestoßen:

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

Es gab mir eine Idee. Es sagt:

Zuerst dachte ich über eine RANGE-Partitionierung nach Datum nach, und während ich das Datum in meinen Abfragen verwende, ist es sehr häufig, dass eine Abfrage einen sehr großen Datumsbereich hat, was bedeutet, dass sie leicht alle Partitionen abdecken kann.

Jetzt teile ich den Bereich auch nach Datum auf, erlaube aber auch die Suche nach einem großen Datumsbereich, was die Effektivität meiner Partitionierung verringert. Ich werde immer einen Datumsbereich haben, wenn ich suche, aber ich werde auch immer eine Liste von assetids haben. Vielleicht sollte meine Lösung darin bestehen, nach Asset-ID und Datum zu partitionieren, wobei ich die normalerweise gesuchten Asset-ID-Bereiche identifiziere (die ich mir ausdenken kann, es gibt Standardlisten, S & P 500, Russell 2000 usw.). Auf diese Weise würde ich fast nie den gesamten Datensatz betrachten.

Andererseits bin ich sowieso primär auf assetid und date fixiert, also würde das vielleicht nicht viel helfen.

Über weitere Gedanken / Kommentare würde ich mich freuen.


2
Ich verstehe nicht, warum Sie 200 Tische brauchen. Eine einzelne Tabelle mit der (value_name varchar(20), value double)Lage wäre , zu speichern alles ( zu value_namesein f1, f2...)
a_horse_with_no_name

Vielen Dank. Der Grund, warum ich sie einzeln platzierte, war, die Grenze von 50 Indizes für eine Tabelle zu überschreiten. Ich hatte darüber nachgedacht, sie in 5 Tabellen mit jeweils 40 Werten einzufügen, aber ich füge ungefähr 17000 Datensätze pro Tag ein und wusste nicht, wie die Einfügungsleistung bei einer Tabelle mit 40 Indizes aussehen würde. Beachten Sie, dass jede Kombination von assetid, date ihre eigenen Werte für f1, f2 ... erhält. Schlagen Sie eine einzelne Tabelle mit (assetid, date, value_name, value), mit dem Primärschlüssel assetid, date, möglicherweise index on (value_name, value) vor? Diese Tabelle hätte 35 mil * 200 = 7 Milliarden Zeilen, aber vielleicht würde eine gute Partitionierung funktionieren?
Dyeryn

Aktualisierter Beitrag mit meinen Erfahrungen mit dieser Methode
dyeryn

Ich habe die endgültige Lösung in der Entwicklung, ich werde aktualisieren, wenn ich fertig bin. Es handelt sich im Wesentlichen um die hier vorgeschlagene Einzeltabellenlösung mit spezifischer Partitionierung und logischem Sharding.
Dyeryn

Könnte eine andere Speicher-Engine helfen? Versuchen Sie statt InnoDb vielleicht InfiniDB? Spaltendaten, Zugriffsmuster sehen wie große Stapelaktualisierungen, bereichsbezogene Lesevorgänge und minimale Tabellenpflege aus.
chaotisch

Antworten:


1

Zufälligerweise untersuche ich auch einen Client-Support, bei dem wir die Struktur der Schlüsselwertpaare aus Gründen der Flexibilität entworfen haben und die Tabelle derzeit mehr als 1,5 Byte Zeilen enthält und die ETL viel zu langsam ist. Nun, es gibt viele andere Dinge in meinem Fall, aber haben Sie über diesen Entwurf nachgedacht. Sie haben eine Zeile mit allen 200 Spalten, deren aktueller Wert in 200 Zeilen im Schlüssel-Wert-Paar-Design konvertiert wird. Je nachdem, für welche AssetID und welches Datum in wie vielen Zeilen tatsächlich alle 200 f1 bis f200 Werte vorhanden sind, können Sie mit diesem Entwurf Platzvorteile erzielen. Wenn Sie sagen, dass 30% der Spalten NULL sind, sparen Sie Platz. weil im Schlüssel-Wert-Paar-Design, wenn die Wert-ID NULL ist, diese Zeile nicht in der Tabelle enthalten sein muss. In der vorhandenen Spaltenstruktur nimmt jedoch auch NULL Platz ein. (Ich bin nicht 100% sicher, aber wenn Sie mehr als 30 Spalten NULL in der Tabelle haben, dann nehmen NULL 4 Bytes). Wenn Sie diesen Entwurf sehen und davon ausgehen, dass alle 35M-Zeilen Werte in allen 200 Spalten enthalten, wird Ihre aktuelle Datenbank sofort zu 200 * 35M = 700M-Zeilen in der Tabelle. Der Tabellenbereich wird jedoch nicht sehr hoch sein, was bei allen Spalten in einer einzelnen Tabelle der Fall ist, da wir nur die Spalten in der Zeile transponieren. In dieser Transponierungsoperation haben wir eigentlich keine Zeilen, in denen die Werte NULL sind. Sie können also tatsächlich eine Abfrage für diese Tabelle ausführen und feststellen, wie viele Nullen vorhanden sind, und die Zieltabellengröße schätzen, bevor Sie sie tatsächlich implementieren. Der Tabellenbereich wird jedoch nicht sehr hoch sein, was bei allen Spalten in einer einzelnen Tabelle der Fall ist, da wir nur die Spalten in Zeilen transponieren. In dieser Transponierungsoperation haben wir eigentlich keine Zeilen, in denen die Werte NULL sind. Sie können also tatsächlich eine Abfrage für diese Tabelle ausführen und feststellen, wie viele Nullen vorhanden sind, und die Zieltabellengröße schätzen, bevor Sie sie tatsächlich implementieren. Der Tabellenbereich wird jedoch nicht sehr hoch sein, was bei allen Spalten in einer einzelnen Tabelle der Fall ist, da wir nur die Spalten in Zeilen transponieren. In dieser Transponierungsoperation haben wir eigentlich keine Zeilen, in denen die Werte NULL sind. Sie können also tatsächlich eine Abfrage für diese Tabelle ausführen und feststellen, wie viele Nullen vorhanden sind, und die Zieltabellengröße schätzen, bevor Sie sie tatsächlich implementieren.

Der zweite Vorteil ist die Leseleistung. Wie Sie bereits erwähnt haben, ist diese neue Art der Datenabfrage eine beliebige Kombination dieser Spalte f1 bis f200 in der where-Klausel. Wenn das Schlüsselwertpaar Design f1 bis f200 in einer Spalte vorhanden ist, sagen wir "FildName", und wenn ihre Werte in der zweiten Spalte vorhanden sind, sagen wir "FieldValue". Sie können für beide Spalten einen CLUSTERED-Index festlegen. Ihre Anfrage wird UNION dieser Selects sein.

WO (FiledName = 'f1' und FieldValue ZWISCHEN 5 UND 6)

UNION

(FiledName = 'f2' und FieldValue ZWISCHEN 8 UND 10)

etc.....

Ich gebe Ihnen einige Leistungszahlen vom tatsächlichen Prod-Server. Wir haben 75 Preisspalten für jeden Wertpapier-TICKER.


1

Wenn Sie mit dieser Art von Daten arbeiten, bei denen Sie viele Zeilen einfügen und eine wirklich gute Leistung bei analytischen Abfragen benötigen (ich gehe davon aus, dass dies hier der Fall ist), stellen Sie möglicherweise fest, dass ein spaltenbasiertes RDBMS gut passt . Werfen Sie einen Blick auf Infobright CE und InfiniDB CE (beides in MySQL integrierte spaltenbasierte Speicher-Engines) sowie auf Vertica CE (mehr PostgreSQL anstelle von MySQL) ... all diese Community-Editionen sind kostenlos (Vertica jedoch nicht) Open Source, es skaliert auf 3 Knoten und 1 TB Daten (kostenlos). Spalten-RDBMS bieten in der Regel Antwortzeiten für große Abfragen, die 10 bis 100 Mal besser sind als zeilenbasiert, und Ladezeiten, die 5 bis 50 Mal besser sind. Man muss sie richtig verwenden oder sie stinken (keine einreihigen Operationen ausführen ... alle Operationen in einem Bulk-Ansatz ausführen), aber bei richtiger Verwendung rocken sie wirklich. ;-)

HTH, Dave Sisk


1
In einer 3-Knoten-Vertica-Installation befinden sich fast eine Milliarde Zeilen mit Clickstream-Daten (die sich nicht von den Börsenticker-Daten unterscheiden). Wir können Daten im Wert von ganzen Tagen in etwa 15 Sekunden laden und erhalten die Antwortzeiten für Abfragen in der Bereich von 500 Millisekunden. In Ihrem Fall klingt es sicherlich so, als wäre dies einen Blick wert.
Dave Sisk

Ich kann dafür bürgen. In meiner letzten Firma hatten wir einen Vertica-Cluster mit 8 Knoten mit ungefähr der gleichen Anzahl von Zeilen und einfachen aggregierten Abfragen für den gesamten Satz, die in durchschnittlich 1-3 Sekunden zurückgegeben wurden. Es war ungefähr 1/4 der Kosten unseres früheren Greenplum-Clusters.
bma
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.