2017-12-21 2 views
1

Ich arbeite an einem Projekt, das Daten in Batches verarbeitet und eine PostgreSQL-Datenbank (9.6, aber ich könnte upgraden) füllt. Die aktuelle Funktionsweise besteht darin, dass der Prozess in getrennten Schritten abläuft und jeder Schritt Daten zu einer Tabelle hinzufügt, die er besitzt (selten schreiben zwei Prozesse in derselben Tabelle, wenn sie dies tun, schreiben sie in eine andere Spalte).Eine PostgreSQL-Datenbank aufteilen/teilen

Wie die Daten sind, werden die Daten bei jedem Schritt immer feinkörniger. Als vereinfachtes Beispiel habe ich eine Tabelle, die die Datenquellen definiert. Es gibt sehr wenige (in den Zehner-/niedrigen Hunderten), aber jede dieser Datenquellen erzeugt Stapel von Datenproben (Stapel und Proben sind separate Tabellen, um Metadaten zu speichern). Jede Charge erzeugt typischerweise etwa 50.000 Proben. Jeder dieser Datenpunkte wird dann Schritt für Schritt verarbeitet, und jeder Datenabtastwert erzeugt mehr Datenpunkte in der nächsten Tabelle.

Dies funktionierte gut, bis wir zu einer 1,5mil Reihen in der Probentabelle kamen (das ist aus unserer Sicht nicht viele Daten). Das Filtern nach einem Batch beginnt langsam zu werden (ungefähr 10 ms für jede Probe, die wir abrufen). Und es fängt an, ein großer Flaschenhals zu werden, weil die Ausführungszeit, um die Daten für einen Stapel zu erhalten, 5-10min dauert (der Abruf ist ms).

Wir haben B-Tree-Indizes für alle Fremdschlüssel, die für diese Abfragen beteiligt sind.

Da unsere Berechnungen auf die Batches abzielen, muss ich während der Berechnung normalerweise nicht zwischen Batches abfragen (dies ist der Moment, in dem die Abfragezeit sehr schmerzt). Aus Gründen der Datenanalyse müssen Ad-hoc-Abfragen über mehrere Stapel hinweg möglich bleiben.

Also eine sehr einfache Lösung wäre, eine individuelle Datenbank für jeden Stapel zu generieren, und irgendwie Abfrage über diese Datenbanken, wenn ich muss. Wenn ich nur eine Charge in jeder Datenbank hätte, wäre die Filterung für eine einzelne Charge offensichtlich sofort und mein Problem wäre gelöst (vorerst). Dann würde ich jedoch mit Tausenden von Datenbanken enden und die Datenanalyse wäre schmerzhaft.

In PostgreSQL gibt es eine Möglichkeit, so zu tun, dass ich separate Datenbanken für einige Abfragen habe? Idealerweise möchte ich das für jede Charge tun, wenn ich eine neue Charge "registriere".

Außerhalb der Welt von PostgreSQL, gibt es eine andere Datenbank, die ich für meinen Anwendungsfall versuchen sollte?

Edit: DDL/Schema

In unserer aktuellen Implementierung sample_representation ist die Tabelle, die alle Verarbeitungsergebnisse abhängen. Ein Stapel ist wirklich definiert durch ein Tupel von (batch.id, representation.id). Die Abfrage, die ich und versuchte, wie oben langsam beschrieben ist (10 ms für jede Probe, Hinzufügen von bis zu etwa 5 min für 50k Proben)

SELECT sample_representation.id, sample.sample_pos 
FROM sample_representation 
JOIN sample ON sample.id = sample_representation.id_sample 
WHERE sample_representation.id_representation = 'representation-uuid' AND sample.id_batch = 'batch-uuid' 

Aktuell haben wir irgendwo um 1,5 s sample s, 2 representation s, 460 batch es (von denen 49 verarbeitet wurden, den anderen sind keine Proben zugeordnet), was bedeutet, dass jede Charge im Durchschnitt 30.000 Proben hat. Manche haben ungefähr 50k.

Das Schema ist unten. Mit allen Tabellen sind einige Metadaten verknüpft, aber in diesem Fall werde ich nicht danach gefragt. Die eigentlichen Beispieldaten werden separat auf der Festplatte und nicht in der Datenbank gespeichert, falls dies einen Unterschied macht.

Rendered Schema

create table batch 
(
    id uuid default uuid_generate_v1mc() not null 
     constraint batch_pk 
      primary key, 
    path text not null 
     constraint unique_batch_path 
      unique, 
    id_data_source uuid 
) 
; 
create table sample 
(
    id uuid default uuid_generate_v1mc() not null 
     constraint sample_pk 
      primary key, 
    sample_pos integer, 
    id_batch uuid 
     constraint batch_fk 
      references batch 
       on update cascade on delete set null 
) 
; 
create index sample_sample_pos_index 
    on sample (sample_pos) 
; 
create index sample_id_batch_sample_pos_index 
    on sample (id_batch, sample_pos) 

; 
create table representation 
(
    id uuid default uuid_generate_v1mc() not null 
     constraint representation_pk 
      primary key, 
    id_data_source uuid 
) 
; 
create table data_source 
(
    id uuid default uuid_generate_v1mc() not null 
     constraint data_source_pk 
      primary key 
) 
; 
alter table batch 
    add constraint data_source_fk 
     foreign key (id_data_source) references data_source 
      on update cascade on delete set null 
; 
alter table representation 
    add constraint data_source_fk 
     foreign key (id_data_source) references data_source 
      on update cascade on delete set null 
; 
create table sample_representation 
(
    id uuid default uuid_generate_v1mc() not null 
     constraint sample_representation_pk 
      primary key, 
    id_sample uuid 
     constraint sample_fk 
      references sample 
       on update cascade on delete set null, 
    id_representation uuid 
     constraint representation_fk 
      references representation 
       on update cascade on delete set null 
) 
; 
create unique index sample_representation_id_sample_id_representation_uindex 
    on sample_representation (id_sample, id_representation) 
; 
create index sample_representation_id_sample_index 
    on sample_representation (id_sample) 
; 
create index sample_representation_id_representation_index 
    on sample_representation (id_representation) 
; 
+0

'... 50k Proben. Jeder dieser Datenpunkte wird dann Schritt für Schritt verarbeitet ... 'Sie meinen: Sie holen einen Punkt, dann den nächsten Punkt, dann den nächsten Punkt, bis zu 50K mal? Warum nicht alle auf einmal in einem großen Schwung? – joop

+0

"* In PostgreSQL gibt es eine Möglichkeit, so zu tun, dass ich separate Datenbanken für einige Abfragen * habe" - Sie könnten [shard] (https://en.wikipedia.org/wiki/Shard_ (database_architecture)) die Daten zu anderen Datenbankserver und greifen Sie transparent auf einen Server zu, indem Sie [fremde Tabellen] (https://www.postgresql.org/docs/current/static/sql-alterforefreignable.html) –

+0

@joop verwenden, um diese 50k Datensätze abzurufen (die ungefähr 1 Datenstunde) dauert etwa 5-10 Minuten in einem Durchlauf. (mit 'SELECT tblA.propery_a, tblB.propery_b VON tblA JOIN tblB ON tblB.id_tblA = tblA.id WHERE tblB.batch_id = 'einige-uuid' UND tblA.some_fk = 'andere-uuid'') –

Antwort

0

Nach etwa das Hantieren, fand ich eine Lösung. Aber ich bin immer noch nicht sicher, warum die ursprüngliche Abfrage dauert wirklich so viel Zeit:

SELECT sample_representation.id, sample.sample_pos 
FROM sample_representation 
JOIN sample ON sample.id = sample_representation.id_sample 
WHERE sample_representation.id_representation = 'representation-uuid' AND sample.id_batch = 'batch-uuid' 

Alles ist indiziert, aber die Tische sind relativ groß und mit 1,5 Millionen Zeilen in sample_representation und in sample. Ich vermute, was passiert, ist, dass zuerst die Tabellen verbunden und dann mit WHERE gefiltert werden. Aber selbst wenn man eine große Ansicht als Folge des Joins erstellt, sollte es nicht so lange dauern ?!

In jedem Fall habe ich versucht, ein CTE anstelle von zwei "massiven" Tabellen zu verwenden. Die Idee war, früh zu filtern und danach zu verbinden:

WITH sel_samplerepresentation AS (
    SELECT * 
    FROM sample_representation 
    WHERE id_representation='1437a5da-e4b1-11e7-a254-7fff1955d16a' 
), sel_samples AS (
    SELECT * 
    FROM sample 
    WHERE id_video='75c04b9c-e4b9-11e7-a93f-132baa27ac91' 
) 
SELECT sel_samples.sample_pos, sel_samplerepresentation.id 
FROM sel_samplerepresentation 
JOIN sel_samples ON sel_samples.id = sel_samplerepresentation.id_representation 

Diese Abfrage dauert auch ewig. Hier ist der Grund klar. sel_samples und sel_samplerepresentation haben jeweils 50k Datensätze. Der Join findet in einer nicht indizierten Spalte der CTEs statt.

Da es keine Indizes für CTEs, ich sie neu formuliert als materialisierte Ansichten, für die ich Indizes hinzufügen:

CREATE MATERIALIZED VIEW sel_samplerepresentation AS (
    SELECT * 
    FROM sample_representation 
    WHERE id_representation='1437a5da-e4b1-11e7-a254-7fff1955d16a' 
); 

CREATE MATERIALIZED VIEW sel_samples AS (
    SELECT * 
    FROM sample 
    WHERE id_video = '75c04b9c-e4b9-11e7-a93f-132baa27ac91' 
); 

CREATE INDEX sel_samplerepresentation_sample_id_index ON sel_samplerepresentation (id_sample); 
CREATE INDEX sel_samples_id_index ON sel_samples (id); 

SELECT sel_samples.sample_pos, sel_samplerepresentation.id 
FROM sel_samplerepresentation 
JOIN sel_samples ON sel_samples.id = sel_samplerepresentation.id_sample; 

DROP MATERIALIZED VIEW sel_samplerepresentation; 
DROP MATERIALIZED VIEW sel_samples; 

Dies ist eher ein Hack als eine Lösung, aber diese Abfragen Ausführung nimmt 1s! (ab 8min)

Verwandte Themen