2016-04-17 12 views
1

Ich arbeite derzeit an einem Projekt, das eine sehr große Menge an Daten aus einem Netzwerk von drahtlosen Modems im Feld sammelt. Wir haben eine Tabelle ‚Lesungen‘, der wie folgt aussieht:Effiziente Einfügungen mit doppelten Prüfungen für große Tabellen in Postgres

CREATE TABLE public.readings (
    id INTEGER PRIMARY KEY NOT NULL DEFAULT nextval('readings_id_seq'::regclass), 
    created TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT now(), 
    timestamp TIMESTAMP WITHOUT TIME ZONE NOT NULL, 
    modem_serial CHARACTER VARYING(255) NOT NULL, 
    channel1 INTEGER NOT NULL, 
    channel2 INTEGER NOT NULL, 
    signal_strength INTEGER, 
    battery INTEGER, 
    excluded BOOLEAN NOT NULL DEFAULT false 
); 
CREATE UNIQUE INDEX _timestamp_modemserial_uc ON readings USING BTREE (timestamp, modem_serial); 
CREATE INDEX ix_readings_timestamp ON readings USING BTREE (timestamp); 
CREATE INDEX ix_readings_modem_serial ON readings USING BTREE (modem_serial); 

Es ist wichtig für die Integrität des Systems, das wir nie zwei Lesungen aus dem gleichen Modem mit dem gleichen Zeitstempel haben, daher den eindeutigen Index.

Unsere Herausforderung besteht im Moment darin, eine performante Methode zum Einfügen von Messwerten zu finden. Wir müssen oft Millionen von Zeilen einfügen, wenn wir historische Daten einbringen, und wenn wir sie zu einer bestehenden Basis von 100 Millionen plus Lesungen hinzufügen, kann dies etwas langsam werden.

Unser aktueller Ansatz besteht darin, Chargen von 10.000 Messwerten in eine temporäre Tabelle zu importieren, bei der es sich im Wesentlichen um eine nicht indizierte Kopie der Messwerte handelt. Wir führen dann die folgende SQL es in der Haupttabelle zu verschmelzen und Duplikate entfernen:

INSERT INTO readings (created, timestamp, modem_serial, channel1, channel2, signal_strength, battery) 
SELECT DISTINCT ON (timestamp, modem_serial) created, timestamp, modem_serial, channel1, channel2, signal_strength, battery 
FROM temporary_readings 
WHERE NOT EXISTS(
    SELECT * FROM readings 
    WHERE timestamp=temporary_readings.timestamp 
    AND modem_serial=temporary_readings.modem_serial 
) 
ORDER BY timestamp, modem_serial ASC; 

Das funktioniert gut, aber dauert ~ 20 Sekunden pro 10.000 Zeilenblock einzufügen. Meine Frage ist zweifach:

  1. Ist dies der beste Weg, um das Problem anzugehen? Ich bin relativ neu in Projekten mit diesen Leistungsanforderungen, daher bin ich gespannt, ob es bessere Lösungen gibt.
  2. Welche Schritte kann ich ergreifen, um den Einfügeprozess zu beschleunigen?

Vielen Dank im Voraus!

+0

Können Sie mehr über Ihren Anwendungsfall beschreiben - müssen Sie die Messwerte in Echtzeit deduplizieren oder erstellen Sie ein Warehouse für Analysen? – wrschneider

+0

Hat die 'temporary_reads'-Tabelle eine Struktur oder Einschränkungen (wie eine PK- oder UNIQUE-Einschränkung)? – wildplasser

Antwort

3

Ihre Anfrage Idee ist in Ordnung. Ich würde versuchen, es für 100.000 Zeilen im Batch zu timing, um zu beginnen, eine Vorstellung von einer optimalen Batch-Größe zu bekommen.

Allerdings verlangsamt die distinct on Dinge. Hier sind zwei Ideen.

Die erste ist anzunehmen, dass Duplikate in Chargen ziemlich selten sind. Wenn dies der Fall ist, versuchen Sie, die Daten ohne distinct on einzufügen. Wenn das fehlschlägt, führen Sie den Code erneut mit der distinct on. Dies kompliziert die Einfügelogik, aber es kann die durchschnittliche Einfügung viel kürzer machen.

Die zweite besteht darin, einen Index auf temporary_readings(timestamp, modem_serial) (kein eindeutiger Index) zu erstellen. Postgres wird diesen Index für die Einfügelogik nutzen - und manchmal einen Index erstellen und ihn schneller verwenden als alternative Ausführungspläne. Wenn dies funktioniert, können Sie größere Batch-Größen versuchen.

Es gibt eine dritte Lösung, die on conflict verwenden soll. Dies würde ermöglichen, dass die Einfügung selbst doppelte Werte ignoriert. Dies ist jedoch nur in Postgres 9.5 verfügbar.

+0

Danke Gordon! Tolle Vorschläge - Ich werde ein wenig experimentieren, um die distinkte on-Klausel zu entfernen und Indizes hinzuzufügen, wie Sie es vorschlagen. Ich werde dir berichten, wie ich gehe :-) –

1

Das Hinzufügen zu einer Tabelle, die bereits 100 Millionen indizierte Datensätze enthält, wird langsam sein, unabhängig davon, was Sie wahrscheinlich durch einen neuen Blick auf Ihre Indizes etwas beschleunigen können.

CREATE UNIQUE INDEX _timestamp_modemserial_uc ON readings USING BTREE (timestamp, modem_serial); 
CREATE INDEX ix_readings_timestamp ON readings USING BTREE (timestamp); 
CREATE INDEX ix_readings_modem_serial ON readings USING BTREE (modem_serial); 

Im Moment haben Sie drei Indizes, aber sie sind auf dem gleichen Satz von Spalten. Können Sie nicht nur mit dem eindeutigen Index umgehen? Ich weiß nicht, wie Ihre anderen Abfragen aussehen, aber Ihre WHERE NOT EXISTS-Abfrage kann diesen eindeutigen Index verwenden.

Wenn Sie Abfragen haben, die nur mit der WHERE-Klausel das Feld modem_serial filtern.Ihr eindeutiger Index wird wahrscheinlich nicht verwendet. Wenn Sie jedoch die Spalten in diesem Index spiegeln, wird es sein!

CREATE UNIQUE INDEX _timestamp_modemserial_uc ON readings USING BTREE (timestamp, modem_serial); 

Um von den manual zu zitieren:

Ein B-Baum-Index mehrspaltigen mit Abfragebedingungen verwendet werden, das jede Teilmenge des Index Spalten beinhalten , aber der Index ist am effizient, wenn es sind Einschränkungen für die führenden (ganz links) Spalten.

Verwandte Themen