Frage

Ich habe eine einfache Zeitreihentabelle

movement_history (
    data_id serial,
    item_id character varying (8),
    event_time timestamp without timezone,
    location_id character varying (7),
    area_id character varying (2)
);

Mein Frontend-Entwickler sagt mir, dass die Kosten zu hoch sind, wenn er wissen möchte, wo sich ein Artikel zu einem bestimmten Zeitpunkt befindet, weil er die Tabelle sortieren muss.Er möchte, dass ich für das nächste Ereignis ein weiteres Zeitstempelfeld hinzufüge, damit er nicht sortieren muss.Das kostet jedoch mehr als das Doppelte meines Codes, um eine neue Bewegung einzufügen, da ich den vorherigen Eintrag für den Artikel abfragen, aktualisieren und dann die neuen Daten einfügen muss.

Meine Einsätze übertreffen natürlich bei weitem seine Anfragen in der Häufigkeit.Und ich habe noch nie eine Zeitreihentabelle gesehen, die einen Eintrag für die Zeit des nächsten Ereignisses enthielt.Er sagt mir, dass mein Tisch kaputt ist, weil seine seltene Abfrage eine Sortierung erfordert.Irgendwelche Vorschläge?

Ich weiß nicht, welche Abfrage er verwendet, aber ich würde das tun:

select * from movement_history 
where event_time <= '1-15-2015'::timestamp  
and item_id = 'H665AYG3' 
order by event_time desc limit 1;

Wir haben derzeit ungefähr 15K Artikel, die höchstens einmal am Tag in die Datenbank eingegeben werden.Wir werden jedoch bald 50K Artikel mit Sensordaten haben, die alle 1 bis 5 Minuten aktualisiert werden.

Ich sehe nicht, dass seine Abfrage sehr oft ausgeführt wird, aber eine andere Abfrage, um den aktuellen Status der Paletten zu erhalten, wird sein.

select distinct on (item_id) * 
from movement_history 
order by item_id, event_time desc;

Auf diesem Server wird derzeit 9.3 ausgeführt, er könnte jedoch bei Bedarf auch auf 9.4 ausgeführt werden.

War es hilfreich?

Lösung

Erstellen Sie einen Index auf (item_id, event_time).

Es springt zur angegebenen item_id, springt zur angegebenen event_time für diese item_id und geht dann um eins zurück.Keine Sortierung erforderlich.

Andere Tipps

Widersprüchliche Lösungen

Sie würden einen mehrspaltigen Index benötigen wie @jjanes bereitgestellt.Während Sie dabei sind, Sie können machen (item_id, event_time) der Primärschlüssel, um den Index automatisch bereitzustellen.

Aber das widerspricht der Schreibleistung wie @Michael erklärt:Sie verdoppeln die Kosten für 50K of items ... updated every 1 to 5 minutes machen gelegentlich SELECT abfragen billiger.Das sind ungefähr 1 Mio.reihen pro Stunde.

Partitionierung

Wenn Sie keine widersprüchlicheren Anforderungen haben, könnte der Kompromiss lauten Partitionierung wo die aktuell partition hat noch keinen Index.Auf diese Weise erhalten Sie top Schreibleistung und (fast) top Leseleistung.

Die übergeordnete Tabelle könnte sein movement_history, die aktuelle Partition movement_history_current.Keine Indizes, nur eine Einschränkung ist zulässig ausschluss von Einschränkungen.Könnte standardmäßig tägliche Partitionen sein.Aber die Zeitintervalle können sein alles, muss nicht einmal regelmäßig sein.Wir können damit arbeiten und jederzeit eine neue Partition starten.

Wenn Sie aktuelle Daten in diese Abfrage aufnehmen müssen, gehen Sie wie folgt vor:

  1. So starten Sie eine neue Partition in einer Transaktion:

    • Benennen Sie die aktuelle Partition um, indem Sie etw anhängen.zum Namen, wie movement_history_20150110_20150115 (oder genauer) und passen Sie die Einschränkung an event_time.
    • Erstellen Sie eine neue Partition mit dem immer gleichen Namen movement_history_current und eine Einschränkung auf event_time das überschneidet sich nicht mit dem letzten und mit offenes Ende.
    • Abhängig von Ihren Zugriffsmustern müssen Sie sich möglicherweise mit gleichzeitigem Schreibzugriff befassen...
  2. Fügen Sie eine PK hinzu (item_id, event_time) auf die haue historische Teilung.Nicht in der gleichen Transaktion.Das Erstellen des Index in einem Stück ist viel billiger als schrittweise hinzuzufügen.

    2a.Um Ratschläge für Ihre zweite Anfrage unten zu integrieren:

    REFRESH MATERIALIZED VIEW mv_last_movement 
    
  3. Abfrage ausführen.Tatsächlich können Sie die Abfrage ausführen jeder Zeit.Wenn es die aktuelle Partition oder eine Partition enthält, die den Index noch nicht hat, ist es für diese Partition langsamer.

Archivieren Sie von Zeit zu Zeit die ältesten Partitionen.Sichern und löschen Sie einfach die Tabelle.Stört den laufenden Betrieb nicht sehr, das ist das Schöne an der Partitionierung.

Lesen Sie zuerst das Handbuch.Es gibt Vorbehalt für Vererbung und Partitionierung.

Ihre zweite Anfrage

Die zweite Abfrage, die Sie in einer Bearbeitung hinzugefügt haben, ist die weit größeres Problem für die Leistung.Ich spreche von Größenordnungen:

select distinct on (item_id) * from movement_history
order by item_id, event_time desc;

Sobald Sie mit dem Einfügen von 1 Mio. beginnen.zeilen pro Stunde verschlechtert sich die Leistung für diese Abfrage schnell.Du hast es zu tun mit viele, viele zeilen pro Element, DISTINCT ON ist nur gut für wenig zeilen pro Element.Detaillierte Erklärung für DISTINCT ON und schnellere Alternativen:

Ich schlage immer noch vor partitionierung wie in meiner ersten Antwort.Erzwingen Sie jedoch in angemessenen Abständen eine neue Partition, damit die aktuelle Partition nicht zu groß wird.

Erstellen Sie außerdem eine "materialisierte Ansicht" verfolgt den neuesten Status für jeden Artikel.Es ist kein Standard MATERIALIZED VIEW weil die definierende Abfrage eine Selbstreferenz hat.Ich nenne es mv_last_movement und es hat den gleichen Zeilentyp wie movement_history.

Aktualisieren Sie immer dann, wenn eine neue Partition gestartet wird (siehe oben).
Unter der Annahme der Existenz eines item Tabelle:

CREATE TABLE item (
  item_id varchar(8) PRIMARY KEY  -- should really be a serial 
  -- more columns?
);

Wenn Sie noch keine haben, erstellen Sie sie.Oder verwenden Sie die beschriebene alternative rekursive CTE-Technik in der oben verlinkten Antwort.

Initiieren mv_last_movement einmal:

CREATE TABLE mv_last_movement AS
SELECT m.*
FROM   item i
,      LATERAL (
   SELECT *
   FROM   movement_history_current  -- current partition
   WHERE  item_id = i.item_id  -- lateral reference
   ORDER  BY event_time DESC
   LIMIT  1
   ) m;

ALTER TABLE mv_last_movement ADD PRIMARY KEY (item_id);

Dann zum Aktualisieren (in einer einzigen Transaktion!):

BEGIN;

CREATE TABLE mv_last_movement2 AS
WÄHLEN SIE m.*
VON Punkt i
, SEITLICH (
   ( -- Klammern erforderlich
   WÄHLEN *
   FROM movement_history_current -- aktuelle Partition
   WOBEI item_id = i.item_id -- seitliche Referenz
   SORTIEREN NACH event_time ABSTEIGEND
   LIMIT 1 - wird auf diese AUSWAHL angewendet, nicht unbedingt benötigt, aber billiger
   )
   UNION ALL - falls nicht gefunden, auf den letzten vorherigen Status zurückgreifen
   WÄHLEN *
   VON mv_last_movement - Ihre materialisierte Ansicht
   WOBEI item_id = i.item_id -- seitliche Referenz
   LIMIT 1 - wird auf die gesamte UNION-Abfrage angewendet
   ) m;

DROP TABLE mv_last_movement;
ALTER TABLE mv_last_movement2 RENAME mv_last_movement;
ALTER TABLE mv_last_movement ADD PRIMARY KEY (item_id);

COMMIT;

Oder ähnliches.Mehr Details hier:

Dieselbe Abfrage von oben (fette Hervorhebung) ersetzt auch Ihre ursprüngliche Abfrage, die oben zitiert wurde.

Auf diese Weise müssen Sie nicht den gesamten Verlauf nach Elementen ohne aktuelle Zeilen durchsuchen, was extrem teuer wäre.

Warum UNION ALL ... LIMIT 1?

Mehr Beratung

  • varchar für PK / FK-Spalten ist dies ineffizient, insbesondere für große Tabellen mit 1 Million Zeilen pro Stunde.Verwenden integer schlüssel stattdessen.

  • Verwenden Sie immer das ISO-Format für Datums- und Zeitstempelliterale, oder Ihre Abfragen hängen von den Gebietsschemaeinstellungen ab: '2015-15-01' statt '1-15-2015'.

  • Hinzufügen NOT NULL einschränkungen, bei denen die Spalte nicht NULL sein darf.

  • Optimieren Sie Ihr Tabellenlayout, um Platzverlust beim Auffüllen zu vermeiden

Oft Software-Design ist ein Kompromiss zwischen den konkurrierenden Anforderungen.Es ist wichtig, die relativen Verdienste zu verstehen, sowohl für das System als Ganzes als auch jedes Fall lokal.Sie sagen beispielsweise, schreibt, dass Sie überlastet werden.Das würde, dass das System insgesamt als Ganzes vorschlägt, sollte für Schreibvorgänge optimiert werden.Was sind diejenigen, für die sie jedoch lesen - verhindern sie eine Fahrzeugkollision oder einen Herzstillstand?Vielleicht sollten diese Systeme zum Lesen optimiert werden.

Haben Sie einen Index in der Zeitspalte?Dann sollte eine Abfrage wie der select top (1) .. where time < parameter .. sorted desc diesen Index verwenden.Im Wesentlichen sortieren Sie die Daten für alle Abfragen.

Die Ironie ist, dass jedes Schreiben diesen Index aufrechterhalten muss und die Kosten jedes Mal verdoppelt werden.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit dba.stackexchange
scroll top