Frage

Ich brauche die Meinung eines echten DBA.Postgres 8.3 benötigt 200 ms, um diese Abfrage auf meinem Macbook Pro auszuführen, während Java und Python dieselbe Berechnung in weniger als 20 ms (350.000 Zeilen) durchführen:

SELECT count(id), avg(a), avg(b), avg(c), avg(d) FROM tuples;

Ist dieses Verhalten bei Verwendung einer SQL-Datenbank normal?

Das Schema (die Tabelle enthält Antworten auf eine Umfrage):

CREATE TABLE tuples (id integer primary key, a integer, b integer, c integer, d integer);

\copy tuples from '350,000 responses.csv' delimiter as ','

Ich habe einige Tests in Java und Python für den Kontext geschrieben und sie zerstören SQL (mit Ausnahme von reinem Python):

java   1.5 threads ~ 7 ms    
java   1.5         ~ 10 ms    
python 2.5 numpy   ~ 18 ms  
python 2.5         ~ 370 ms

Sogar sqlite3 ist mit Postgres konkurrenzfähig, obwohl davon ausgegangen wird, dass alle Spalten Zeichenfolgen sind (zum Vergleich:Selbst wenn man in Postgres nur auf numerische Spalten anstelle von Ganzzahlen umschaltet, führt dies zu einer 10-fachen Verlangsamung.)

Zu den Tunings, die ich erfolglos ausprobiert habe, gehören (ich bin blind einigen Ratschlägen aus dem Internet gefolgt):

increased the shared memory available to Postgres to 256MB    
increased the working memory to 2MB
disabled connection and statement logging
used a stored procedure via CREATE FUNCTION ... LANGUAGE SQL

Meine Frage ist also: Ist meine Erfahrung hier normal und kann ich Folgendes erwarten, wenn ich eine SQL-Datenbank verwende?Ich kann verstehen, dass ACID mit Kosten verbunden sein muss, aber das ist meiner Meinung nach irgendwie verrückt.Ich frage nicht nach Spielgeschwindigkeit in Echtzeit, aber da Java Millionen von Doubles in weniger als 20 ms verarbeiten kann, bin ich ein bisschen neidisch.

Gibt es eine bessere Möglichkeit, einfaches OLAP kostengünstig durchzuführen (sowohl im Hinblick auf das Geld als auch auf die Serverkomplexität)?Ich habe mir Mondrian und Pig + Hadoop angesehen, bin aber nicht besonders begeistert von der Wartung einer weiteren Serveranwendung und bin mir nicht sicher, ob sie überhaupt helfen würden.


Nein, der Python-Code und der Java-Code erledigen sozusagen die gesamte Arbeit intern.Ich erzeuge einfach 4 Arrays mit jeweils 350.000 Zufallswerten und errechne dann den Durchschnitt.Ich beziehe die Generierung nicht in die Timings ein, sondern nur den Mittelungsschritt.Das Timing der Java-Threads verwendet 4 Threads (durchschnittlich einen pro Array), was übertrieben ist, aber definitiv der schnellste.

Das SQLite3-Timing wird vom Python-Programm gesteuert und von der Festplatte ausgeführt (nicht von :memory:).

Mir ist klar, dass Postgres hinter den Kulissen viel mehr tut, aber der größte Teil dieser Arbeit spielt für mich keine Rolle, da es sich hierbei um schreibgeschützte Daten handelt.

Die Postgres-Abfrage ändert das Timing bei nachfolgenden Ausführungen nicht.

Ich habe die Python-Tests erneut ausgeführt, um das Spoolen von der Festplatte einzuschließen.Das Timing verlangsamt sich erheblich auf fast 4 Sekunden.Aber ich vermute, dass Pythons Dateiverwaltungscode weitgehend in C liegt (aber vielleicht nicht in der CSV-Bibliothek?), was für mich ein Hinweis darauf ist, dass Postgres auch nicht von der Festplatte streamt (oder dass Sie Recht haben und ich mich beugen sollte). bevor wer auch immer seine Speicherschicht geschrieben hat!)

War es hilfreich?

Lösung

Postgres leistet viel mehr, als es den Anschein hat (erst einmal die Aufrechterhaltung der Datenkonsistenz!)

Wenn die Werte nicht 100 % genau sein müssen oder die Tabelle nur selten aktualisiert wird, Sie diese Berechnung aber häufig ausführen, sollten Sie sich Materialized Views ansehen, um sie zu beschleunigen.

(Beachten Sie, dass ich in Postgres keine materialisierten Ansichten verwendet habe. Sie sehen etwas kitschig aus, passen aber möglicherweise zu Ihrer Situation.)

Materialisierte Ansichten

Berücksichtigen Sie auch den Aufwand für die tatsächliche Verbindung zum Server und den Hin- und Rückweg, der erforderlich ist, um die Anfrage an den Server und zurück zu senden.

Ich würde 200 ms für so etwas als ziemlich gut erachten. Ein schneller Test auf meinem Oracle-Server, der gleichen Tabellenstruktur mit etwa 500.000 Zeilen und ohne Indizes, dauert etwa 1 bis 1,5 Sekunden, was fast ausschließlich darauf zurückzuführen ist, dass Oracle die Daten aussaugt von der Festplatte.

Die eigentliche Frage ist: Sind 200 ms schnell genug?

-------------- Mehr --------------------

Ich war daran interessiert, dieses Problem mithilfe materialisierter Ansichten zu lösen, da ich nie wirklich damit gespielt habe.Das ist in Oracle.

Zuerst habe ich ein MV erstellt, das jede Minute aktualisiert wird.

create materialized view mv_so_x 
build immediate 
refresh complete 
START WITH SYSDATE NEXT SYSDATE + 1/24/60
 as select count(*),avg(a),avg(b),avg(c),avg(d) from so_x;

Es ist zwar erfrischend, es werden jedoch keine Zeilen zurückgegeben

SQL> select * from mv_so_x;

no rows selected

Elapsed: 00:00:00.00

Sobald es aktualisiert wird, ist es VIEL schneller als die Rohabfrage

SQL> select count(*),avg(a),avg(b),avg(c),avg(d) from so_x;

  COUNT(*)     AVG(A)     AVG(B)     AVG(C)     AVG(D)
---------- ---------- ---------- ---------- ----------
   1899459 7495.38839 22.2905454 5.00276131 2.13432836

Elapsed: 00:00:05.74
SQL> select * from mv_so_x;

  COUNT(*)     AVG(A)     AVG(B)     AVG(C)     AVG(D)
---------- ---------- ---------- ---------- ----------
   1899459 7495.38839 22.2905454 5.00276131 2.13432836

Elapsed: 00:00:00.00
SQL> 

Wenn wir in die Basistabelle einfügen, ist das Ergebnis nicht sofort im MV sichtbar.

SQL> insert into so_x values (1,2,3,4,5);

1 row created.

Elapsed: 00:00:00.00
SQL> commit;

Commit complete.

Elapsed: 00:00:00.00
SQL> select * from mv_so_x;

  COUNT(*)     AVG(A)     AVG(B)     AVG(C)     AVG(D)
---------- ---------- ---------- ---------- ----------
   1899459 7495.38839 22.2905454 5.00276131 2.13432836

Elapsed: 00:00:00.00
SQL> 

Aber warten Sie etwa eine Minute, dann wird das MV im Hintergrund aktualisiert und das Ergebnis wird so schnell wie möglich zurückgegeben.

SQL> /

  COUNT(*)     AVG(A)     AVG(B)     AVG(C)     AVG(D)
---------- ---------- ---------- ---------- ----------
   1899460 7495.35823 22.2905352 5.00276078 2.17647059

Elapsed: 00:00:00.00
SQL> 

Das ist nicht ideal.Erstens ist es nicht in Echtzeit, Einfügungen/Aktualisierungen sind nicht sofort sichtbar.Außerdem wird eine Abfrage ausgeführt, um das MV zu aktualisieren, unabhängig davon, ob Sie es benötigen oder nicht (dies kann auf einen beliebigen Zeitrahmen oder bei Bedarf abgestimmt werden).Dies zeigt jedoch, wie viel schneller ein MV dem Endbenutzer erscheinen kann, wenn man mit Werten leben kann, die nicht ganz auf die Sekunde genau sind.

Andere Tipps

Ich würde sagen, Ihr Testschema ist nicht wirklich nützlich.Um die Datenbankabfrage zu erfüllen, durchläuft der Datenbankserver mehrere Schritte:

  1. Analysieren Sie die SQL
  2. einen Abfrageplan ausarbeiten, d.e.Entscheiden Sie, welche Indizes (falls vorhanden) verwendet, optimiert usw. werden sollen.
  3. Wenn ein Index verwendet wird, durchsuchen Sie ihn nach Zeigern auf die tatsächlichen Daten und gehen Sie dann zur entsprechenden Stelle in den Daten oder
  4. Wenn kein Index verwendet wird, scannen den ganzen Tisch um zu bestimmen, welche Zeilen benötigt werden
  5. Laden Sie die Daten von der Festplatte an einen temporären Ort (hoffentlich, aber nicht unbedingt, in den Speicher).
  6. Führen Sie die Berechnungen count() und avg() durch

Wenn Sie also ein Array in Python erstellen und den Durchschnitt ermitteln, werden im Grunde alle diese Schritte bis auf den letzten übersprungen.Da Festplatten-I/O zu den teuersten Vorgängen gehört, die ein Programm ausführen muss, stellt dies einen großen Fehler im Test dar (siehe auch die Antworten zu diese Frage Ich habe hier schon mal nachgefragt).Selbst wenn Sie die Daten in Ihrem anderen Test von der Festplatte lesen, ist der Prozess völlig anders und es ist schwer zu sagen, wie relevant die Ergebnisse sind.

Um weitere Informationen darüber zu erhalten, wo Postgres seine Zeit verbringt, würde ich die folgenden Tests vorschlagen:

  • Vergleichen Sie die Ausführungszeit Ihrer Abfrage mit einer SELECT-Anweisung ohne die Aggregationsfunktionen (d. h.e.Schnitt Schritt 5)
  • Wenn Sie feststellen, dass die Aggregation zu einer erheblichen Verlangsamung führt, versuchen Sie, ob Python dies schneller erledigt, indem Sie die Rohdaten über das einfache SELECT aus dem Vergleich abrufen.

Um Ihre Abfrage zu beschleunigen, reduzieren Sie zunächst den Festplattenzugriff.Ich bezweifle sehr, dass es die Aggregation ist, die die Zeit braucht.

Dafür gibt es mehrere Möglichkeiten:

  • Zwischenspeichern Sie Daten (im Speicher!) für den späteren Zugriff, entweder über die eigenen Funktionen der Datenbank-Engine oder mit Tools wie memcached
  • Reduzieren Sie die Größe Ihrer gespeicherten Daten
  • Optimieren Sie die Verwendung von Indizes.Manchmal kann dies bedeuten, dass die Verwendung von Indizes ganz übersprungen werden muss (schließlich handelt es sich auch um den Zugriff auf die Festplatte).Ich glaube mich zu erinnern, dass es bei MySQL empfohlen wird, Indizes zu überspringen, wenn man davon ausgeht, dass die Abfrage mehr als 10 % aller Daten in der Tabelle abruft.
  • Wenn Ihre Abfrage Indizes sinnvoll nutzt, weiß ich, dass es bei MySQL-Datenbanken hilfreich ist, Indizes und Daten auf separaten physischen Festplatten abzulegen.Ich weiß jedoch nicht, ob das für Postgres gilt.
  • Es kann auch zu komplexeren Problemen kommen, z. B. zum Auslagern von Zeilen auf die Festplatte, wenn der Ergebnissatz aus irgendeinem Grund nicht vollständig im Speicher verarbeitet werden kann.Aber ich würde diese Art der Recherche aufgeben, bis ich auf ernsthafte Leistungsprobleme stoße, die ich nicht anders beheben kann, da dafür Kenntnisse über viele kleine Details im Hintergrund Ihres Prozesses erforderlich sind.

Aktualisieren:

Mir ist gerade aufgefallen, dass Sie offenbar keine Verwendung für Indizes für die obige Abfrage haben und höchstwahrscheinlich auch keine verwenden, daher waren meine Ratschläge zu Indizes wahrscheinlich nicht hilfreich.Entschuldigung.Dennoch würde ich sagen, dass nicht die Aggregation das Problem ist, sondern der Festplattenzugriff.Ich werde das Indexmaterial auf jeden Fall drin lassen, es könnte noch von Nutzen sein.

Ich habe den Test mit MySQL unter Angabe von ENGINE = MEMORY erneut durchgeführt und es ändert sich nichts (immer noch 200 ms).SQLite3, das eine In-Memory-Datenbank verwendet, liefert ebenfalls ähnliche Timings (250 ms).

Die Mathematik Hier sieht korrekt aus (zumindest die Größe, denn so groß ist die SQLite-Datenbank :-)

Ich kaufe das Argument der Festplattenursachen-Langsamkeit einfach nicht ab, da alles darauf hindeutet, dass sich die Tabellen im Speicher befinden (die Postgres-Leute warnen alle davor, zu sehr zu versuchen, Tabellen im Speicher anzuheften, da sie schwören, dass das Betriebssystem das besser kann als der Programmierer). )

Um das Timing zu verdeutlichen: Der Java-Code liest nicht von der Festplatte, was es zu einem völlig unfairen Vergleich macht, wenn Postgres von der Festplatte liest und eine komplizierte Abfrage berechnet, aber das ist eigentlich nebensächlich, die Datenbank sollte intelligent genug sein, um eine kleine Abfrage zu liefern Tabelle in den Speicher kopieren und meiner Meinung nach eine gespeicherte Prozedur vorkompilieren.

UPDATE (als Antwort auf den ersten Kommentar unten):

Ich bin mir nicht sicher, wie ich die Abfrage testen würde, ohne eine Aggregationsfunktion auf eine faire Weise zu verwenden, denn wenn ich alle Zeilen auswähle, würde es jede Menge Zeit kosten, alles zu serialisieren und zu formatieren.Ich behaupte nicht, dass die Langsamkeit auf die Aggregationsfunktion zurückzuführen ist, es könnte aber auch nur ein Overhead durch Parallelität, Integrität und Freunde sein.Ich weiß einfach nicht, wie ich die Aggregation als einzige unabhängige Variable isolieren soll.

Das sind sehr detaillierte Antworten, aber sie werfen meist die Frage auf, wie ich diese Vorteile nutzen kann, ohne Postgres zu verlassen, da die Daten problemlos in den Speicher passen, gleichzeitige Lesevorgänge, aber keine Schreibvorgänge erfordern und immer wieder mit derselben Abfrage abgefragt werden.

Ist es möglich, den Abfrage- und Optimierungsplan vorab zu kompilieren?Ich hätte gedacht, dass die gespeicherte Prozedur dies tun würde, aber es hilft nicht wirklich.

Um Festplattenzugriffe zu vermeiden, muss die gesamte Tabelle im Speicher zwischengespeichert werden. Kann ich Postgres dazu zwingen?Ich denke jedoch, dass dies bereits der Fall ist, da die Abfrage nach wiederholten Ausführungen in nur 200 ms ausgeführt wird.

Kann ich Postgres mitteilen, dass die Tabelle schreibgeschützt ist, sodass jeder Sperrcode optimiert werden kann?

Ich denke, es ist möglich, die Kosten für die Abfrageerstellung mit einer leeren Tabelle abzuschätzen (Zeiten liegen zwischen 20 und 60 ms).

Ich kann immer noch nicht verstehen, warum die Java/Python-Tests ungültig sind.Postgres leistet einfach nicht viel mehr Arbeit (obwohl ich mich immer noch nicht mit dem Aspekt der Parallelität befasst habe, sondern nur mit dem Caching und der Abfragekonstruktion).

AKTUALISIEREN:Ich glaube nicht, dass es fair ist, die vorgeschlagenen SELECTS zu vergleichen, indem man 350.000 durch die Treiber- und Serialisierungsschritte in Python zieht, um die Aggregation auszuführen, und auch nicht, die Aggregation wegzulassen, da der Overhead bei der Formatierung und Anzeige schwer vom Timing zu trennen ist.Wenn beide Engines mit Speicherdaten arbeiten, sollte es ein Vergleich zwischen Äpfeln sein. Ich bin mir jedoch nicht sicher, wie ich garantieren kann, dass dies bereits geschieht.

Ich kann nicht herausfinden, wie ich Kommentare hinzufügen kann. Vielleicht habe ich nicht genug Reputation?

Ich bin selbst ein MS-SQL-Typ und wir würden es verwenden DBCC PINTABLE um eine Tabelle zwischenzuspeichern und STATISTIK EINSTELLEN IO um zu sehen, dass aus dem Cache und nicht von der Festplatte gelesen wird.

Ich kann auf Postgres nichts finden, was PINTABLE nachahmen könnte, aber pg_buffercache scheint Details darüber zu geben, was sich im Cache befindet. Vielleicht möchten Sie das überprüfen und sehen, ob Ihre Tabelle tatsächlich zwischengespeichert wird.

Eine kurze Berechnung des Umschlags lässt mich vermuten, dass Sie von der Festplatte aus blättern.Unter der Annahme, dass Postgres 4-Byte-Ganzzahlen verwendet, haben Sie (6 * 4) Bytes pro Zeile, sodass Ihre Tabelle mindestens (24 * 350.000) Bytes ~ 8,4 MB groß ist.Unter der Annahme eines dauerhaften Durchsatzes von 40 MB/s auf Ihrer Festplatte rechnen Sie mit etwa 200 ms zum Lesen der Daten (was wie erwähnt, sollte dort sein, wo fast die ganze Zeit verbracht wird).

Sofern ich meine Berechnungen nicht irgendwo vermasselt habe, sehe ich nicht ein, wie es möglich ist, dass Sie 8 MB in Ihre Java-App einlesen und in den von Ihnen angezeigten Zeiten verarbeiten können – es sei denn, diese Datei ist bereits entweder auf dem Laufwerk oder auf Ihrem Laufwerk zwischengespeichert Betriebssystem.

Ich glaube nicht, dass Ihre Ergebnisse allzu überraschend sind – wenn überhaupt, ist Postgres so schnell.

Läuft die Postgres-Abfrage ein zweites Mal schneller, nachdem sie die Möglichkeit hatte, die Daten zwischenzuspeichern?Um etwas fairer zu sein, sollte Ihr Test für Java und Python zunächst die Kosten für die Erfassung der Daten abdecken (idealerweise das Laden von der Festplatte).

Wenn dieses Leistungsniveau in der Praxis ein Problem für Ihre Anwendung darstellt, Sie aber aus anderen Gründen ein RDBMS benötigen, können Sie es sich ansehen zwischengespeichert.Sie hätten dann einen schnelleren zwischengespeicherten Zugriff auf Rohdaten und könnten die Berechnungen im Code durchführen.

Verwenden Sie TCP für den Zugriff auf Postgres?In diesem Fall bringt Nagle Ihr Timing durcheinander.

Eine weitere Funktion, die ein RDBMS im Allgemeinen für Sie übernimmt, ist die Bereitstellung von Parallelität, indem es Sie vor dem gleichzeitigen Zugriff eines anderen Prozesses schützt.Dies geschieht durch das Platzieren von Sperren, was einen gewissen Mehraufwand mit sich bringt.

Wenn Sie es mit völlig statischen Daten zu tun haben, die sich nie ändern, und vor allem, wenn Sie sich grundsätzlich in einem „Einzelbenutzer“-Szenario befinden, bringt Ihnen die Verwendung einer relationalen Datenbank nicht unbedingt große Vorteile.

Sie müssen die Caches von Postgres so weit vergrößern, dass der gesamte Arbeitssatz in den Speicher passt, bevor Sie eine Leistung erwarten können, die mit der Leistung im Speicher mit einem Programm vergleichbar ist.

Vielen Dank für die Oracle-Timings, das ist genau das, was ich suche (allerdings enttäuschend :-)

Materialisierte Ansichten sind wahrscheinlich eine Überlegung wert, da ich denke, dass ich die interessantesten Formen dieser Abfrage für die meisten Benutzer vorab berechnen kann.

Ich denke nicht, dass die Abfrage-Roundtripzeit sehr hoch sein sollte, da ich die Abfragen auf demselben Computer ausführe, auf dem Postgres ausgeführt wird, sodass dadurch keine große Latenz entsteht?

Ich habe auch die Cache-Größen überprüft und es scheint, dass Postgres sich beim Caching auf das Betriebssystem verlässt. BSD wird ausdrücklich als ideales Betriebssystem dafür erwähnt, daher denke ich, dass Mac OS bei der Integration der Tabelle ziemlich klug sein sollte Erinnerung.Sofern nicht jemand spezifischere Parameter im Sinn hat, liegt meiner Meinung nach ein spezifischeres Caching außerhalb meiner Kontrolle.

Am Ende kann ich wahrscheinlich Antwortzeiten von 200 ms in Kauf nehmen, aber zu wissen, dass 7 ms ein mögliches Ziel sind, macht mich unzufrieden, da selbst 20–50 ms Zeiten es mehr Benutzern ermöglichen würden, aktuellere Abfragen zu stellen und diese loszuwerden jede Menge Caching und vorberechnete Hacks.

Ich habe gerade die Timings mit MySQL 5 überprüft und sie sind etwas schlechter als Postgres.Abgesehen von einigen größeren Caching-Durchbrüchen kann ich wohl davon ausgehen, dass ich mich auf die relationale Datenbankroute begebe.

Ich wünschte, ich könnte einige Ihrer Antworten positiv bewerten, aber ich habe noch nicht genug Punkte.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top