it-swarm.com.de

Sehr langsamer (12+ Stunden) großer Tisch schließt sich dem Postgres an

Ich habe Probleme, einen einfachen LEFT JOIN für zwei sehr große Tabellen zu optimieren, deren Fertigstellung und Fortführung bisher> 12 Stunden gedauert hat.

Hier ist der Ausführungsplan:

Gather  (cost=1001.26..11864143.06 rows=8972234 width=133)
  Workers Planned: 7
  ->  Nested Loop Left Join  (cost=1.26..10773657.51 rows=1281748 width=133)
        ->  Parallel Index Scan using var_case_aliquot_aliquot_ind on var_case_aliquot vca  (cost=0.56..464070.21 rows=1281748 width=103)
        ->  Index Scan using genotype_pos_ind on snv_genotypes gt  (cost=0.70..8.01 rows=1 width=65)
              Index Cond: ((vca.chrom = chrom) AND (vca.start = start) AND (vca.end = end) AND ((vca.alt)::text = (alt)::text))
              Filter: (vca.aliquot_barcode = aliquot_barcode)

Hier ist die Abfrage:

SELECT vca.aliquot_barcode,
    vca.case_barcode,
    vca.gene_symbol,
    vca.variant_classification,
    vca.variant_type,
    vca.chrom,
    int4range(vca.start::integer, vca."end"::integer, '[]'::text) AS pos,
    vca.alt,
    gt.called AS mutect2_call,
    gt.ref_count,
    gt.alt_count,
    gt.read_depth,
    gt.called OR
        CASE
            WHEN (gt.alt_count + gt.ref_count) > 0 THEN (gt.alt_count::numeric / (gt.alt_count + gt.ref_count)::numeric) > 0.20
            ELSE false
        END AS vaf_corrected_call
   FROM analysis.var_case_aliquot vca
     LEFT JOIN analysis.snv_genotypes gt ON vca.aliquot_barcode = gt.aliquot_barcode AND vca.chrom = gt.chrom AND vca.start = gt.start AND vca."end" = gt."end" AND vca.alt::text = gt.alt::text

Beide Tabellen sind sehr groß: vca und gt haben 9 Millionen (2 GB) bzw. 1,3 Milliarden Zeilen (346 GB).

Ich habe die vca (MATERIALIZED VIEW) erstellt, um diesen Join auszuführen. Im Wesentlichen handelt es sich um eine Verknüpfungstabelle mit nur den erforderlichen Feldern für eine 1: 1-Verknüpfung mit übereinstimmenden Links und einigen zusätzlichen Metadaten. Alle Felder, die verbunden werden, werden ordnungsgemäß indiziert, wie Sie aus dem Abfrageplan ersehen können.

Die Abfrage selbst ist recht einfach. Fehlt mir etwas, das sie beschleunigen könnte? Ich nehme nicht an, dass es eine Möglichkeit gibt, stattdessen WHERE zu verwenden.

Gibt es etwas, das ich in meinen Postgres-Einstellungen ändern kann, das helfen könnte? Zur Zeit habe ich folgendes:

shared_buffers = 4096MB
effective_cache_size = 20GB
work_mem = 64MB
maintenance_work_mem = 4096MB
max_wal_size = 4GB
min_wal_size = 128MB
checkpoint_completion_target = 0.9
max_worker_processes = 16
max_parallel_workers_per_gather = 8
max_parallel_workers = 16

UPDATE 12/12:

Tabelle DDL:

CREATE TABLE analysis.snv_genotypes (
    aliquot_barcode character(30) NOT NULL,
    chrom character(2) NOT NULL,
    start bigint NOT NULL,
    "end" bigint NOT NULL,
    alt character varying(510) NOT NULL,
    genotype character(3),
    read_depth integer,
    ref_count integer,
    alt_count integer,
    called boolean
);

ALTER TABLE ONLY analysis.snv_genotypes
    ADD CONSTRAINT genotype_pk PRIMARY KEY (aliquot_barcode, chrom, start, "end", alt);
CREATE INDEX called_ind ON analysis.snv_genotypes USING btree (called);
CREATE INDEX genotype_pos_ind ON analysis.snv_genotypes USING btree (chrom, start, "end", alt);

CREATE MATERIALIZED VIEW analysis.var_case_aliquot AS
 SELECT var_case_aliquot.aliquot_barcode,
    var_case_aliquot.case_barcode,
    var_case_aliquot.chrom,
    var_case_aliquot.start,
    var_case_aliquot."end",
    var_case_aliquot.alt,
    var_case_aliquot.gene_symbol,
    var_case_aliquot.variant_classification,
    var_case_aliquot.variant_type,
    var_case_aliquot.hgvs_p,
    var_case_aliquot.polyphen,
    var_case_aliquot.sift
   FROM var_case_aliquot
  WITH NO DATA;

CREATE INDEX var_case_aliquot_aliquot_ind ON analysis.var_case_aliquot USING btree (aliquot_barcode);
CREATE INDEX var_case_aliquot_pos_ind ON analysis.var_case_aliquot USING btree (chrom, start, "end", alt);

Ausführlichere DDL hier: https://rextester.com/JRJH43442

UPDATE 12/13:

Zur Verdeutlichung verwende ich Postgres 10.5 auf CentOS 7.3 mit 16 Kernen und 32 GB Speicher. Die Abfrage wurde nun mehr als 24 Stunden ohne Ergebnis ausgeführt.

Nach dem Status zu sehen scheint, dass wait_event_typeIO ist. Bedeutet dies, dass die Abfrage im Scratch-Space abrubbelt/schreibt? Könnte das die Langsamkeit erklären?

+------------------+---------------+---------------+---------------+---------------+-----------------+--------------+--------+-------------+--------------+
| application_name | backend_start | xact_start    | query_start   | state_change  | wait_event_type | wait_event   | state  | backend_xid | backend_xmin |
+------------------+---------------+---------------+---------------+---------------+-----------------+--------------+--------+-------------+--------------+
| psql             | 12/12/18 8:42 | 12/12/18 8:42 | 12/12/18 8:42 | 12/12/18 8:42 | IO              | DataFileRead | active | 22135       | 22135        |
+------------------+---------------+---------------+---------------+---------------+-----------------+--------------+--------+-------------+--------------+

Ich habe viele verfügbare Ressourcen:

$ free -h
              total        used        free      shared  buff/cache   available
Mem:            31G        722M        210M        5.0G         30G         25G
Swap:          3.7G        626M        3.1G

Ich denke, es könnte helfen, mehr Speicher zur Verfügung zu stellen. Gibt es eine Möglichkeit, Abfragen zu optimieren, die mehr Speicher benötigen, als ihnen zur Verfügung stehen?

6
Floris

Aus dem Kommentar dieses Beitrags:

Ihre Anfrage verwendet genotype_pos_ind und filtert nach aliquot_barcode. Versuchen Sie, (vorübergehend) genotype_pos_ind zu löschen. Wenn dies nicht funktioniert, suchen Sie, wie Sie die Verwendung des Index erzwingen können.

Ihre Anfrage sollte stattdessen genotype_pk verwenden.

Nach dem, was Sie gesagt haben, kann es eine Menge Datensätze mit denselben Werten für aliquot_barcode, chrom, start und end geben, so dass das RDBMS eine lange Zeit benötigt, um jeden aliquot_barcode zu filtern.

Und wenn es Ihnen noch zu lang ist, können Sie meine ältere Antwort ausprobieren, die ich für weitere Hinweise aufbewahre:



Leider kann ich Ihre Anfrage nicht optimieren: Es sind zu viele Dinge zu berücksichtigen. Das Erstellen eines Ergebnisses mit 9 Millionen Datensätzen aus 13 Feldern kann zu viel sein: Es kann zu einem Austausch kommen, das Betriebssystem lässt nicht so viel Speicherzuweisung zu und JOIN usw .. (schriftlich vor der eigentlichen Antwort ...)

Ich habe eine Abfrage optimiert, die aus fünfzehn Tabellen mit ungefähr 10 Millionen Datensätzen bestand. SELECT dieser Größe wäre niemals in angemessener Zeit (weniger als 10 Stunden) machbar.

Ich habe kein RDBMS, um zu testen, was ich sage. Außerdem habe ich seit einem halben Jahr kein SQL mehr ausgeführt: p Es ist zu zeitaufwändig, herauszufinden, warum dies so lange dauert (wie Sie gefragt haben). Hier finden Sie eine andere Lösung für das ursprüngliche Problem.


Die Lösung, die ich gewählt habe, bestand darin, eine temporäre Tabelle zu erstellen:

  1. Erstellen Sie die temporäre Tabelle: tmp_analysis mit denselben Feldern wie Ihre SELECT + einigen Hilfsfeldern:

Ein ID-Feld (tmp_ID, ein großes int), ein Boolescher Wert, um zu überprüfen, ob der Datensatz aktualisiert wurde (tmp_updated), und ein Zeitstempel, um zu überprüfen, wann er aktualisiert wurde (tmp_update_time). Und natürlich alle Felder mit denselben Datentypen aus Ihrer ursprünglichen SELECT (aus vca und gt)

  1. Fügen Sie alle Ihre Datensätze aus vca ein:

Verwenden Sie null (oder einen anderen Standardwert, falls dies nicht möglich ist) für Felder aus gt. Setzen Sie tmp_updated auf false. Verwenden Sie eine einfache count() für den Primärschlüssel.

  1. Aktualisieren Sie alle diese Datensätze mit Feldern von gt.

Verwenden Sie eine WHERE anstelle einer JOIN:

UPDATE tmp_analysis as tmp -- I don't think you need to use a schema to call tmp_analysis
    SET tmp_update = true,
    tmp_update_time = clock_timestamp(),
    tmp.mutect2_call = gt.called
    gt.ref_count,
    gt.alt_count,
    gt.read_depth,
    gt.called = -- ... (your CASE/WHEN/ELSE/END should work here)
FROM 
    analysis.snv_genotypes gt
WHERE --JOIN should work too
    tmp.aliquot_barcode = gt.aliquot_barcode AND 
    tmp.chrom = gt.chrom AND 
    vca.start = gt.start AND 
    tmp."end" = gt."end" AND 
    tmp.alt::text = gt.alt::text

Ich habe gesagt, dass Sie EXISTS aus Leistungsgründen verwenden sollten, aber ich habe mich geirrt, da ich nicht glaube, dass Sie Felder aus der EXISTS-Bedingung heraus abrufen können. Es könnte eine Möglichkeit geben, Postgresql mitzuteilen, dass es sich um eine Eins-zu-Eins-Beziehung handelt, aber ich bin mir nicht sicher. Wie auch immer, Index

  1. Offensichtlich SELECT Ihre tmp_analysis Tabelle, um Ihre Aufzeichnungen zu erhalten!

Einige Anmerkungen dazu:

  1. Wenn es zu lange dauert:

Verwenden Sie das Feld tmp_ID, um beispielsweise die Anzahl der Aktualisierungen auf 10 000 zu begrenzen und den Ausführungsplan der dritten Abfrage (UPDATE) zu überprüfen: Die temporäre Tabellentabelle sollte vollständig und die Indexsuche für gt (auf genotype_pk) durchgeführt werden. Wenn nicht, überprüfen Sie Ihre Indizes und suchen Sie, wie Sie die Indexverwendung durch PGSL erzwingen können. Sie sollten WHERE tmp_ID < 10000 anstelle von LIMIT 10000 verwenden. IIRC, LIMIT führt die gesamte Abfrage aus und gibt Ihnen nur einen Teil des Ergebnisses.

  1. Wenn es noch zu lange dauert:

Segmentieren Sie die Abfrage mit tmp_ID und verwenden Sie (wie Sie sagten) eine loop-Anweisung für die UPDATE, um mit 100.000 oder weniger Datensätzen gleichzeitig abzufragen (verwenden Sie erneut where tmp_ID < x AND tmp_ID > y). Überprüfen Sie den Ausführungsplan erneut: Der vollständige Scan sollte vor dem Index-Scan durch den tmp_id begrenzt werden. Fälschen Sie nicht, einen Index für dieses Feld hinzuzufügen (falls dies nicht bereits der Primärschlüssel ist).

  1. Wenn Sie dies später noch einmal aufrufen müssen:

Verwenden Sie BEGIN/END TRANSACTION, um alle Abfragen zu kapseln, und die Option TEMPORARY TABLE für CREATE TABLE tmp_analysis, damit Sie tmp_analysis nach dem Ausführen der Abfrage nicht bereinigen müssen.

  1. Wenn Sie immer noch ein Problem mit Schleifen haben:

Verwenden Sie Transaktionen innerhalb der Schleife und stoppen Sie sie, wenn sie erneut einfrieren. Dann können Sie es später mit einer kleineren Schleifengröße wiederherstellen.

  1. Wenn Sie die Ausführungszeit etwas verkürzen möchten:

Sie können Schritt 1 und 2 in einer Abfrage mit einem INSERT .. AS .. SELECT ausführen, aber ich kann mich nicht erinnern, wie der Datentyp für Felder aus gt festgelegt wird, da sie auf null gesetzt werden. Normalerweise sollte dies insgesamt etwas schneller sein.

  1. Wenn Sie neugierig sind:

Und die Abfrage ohne Schleife dauert immer noch mehr als 10 Stunden. Halten Sie sie an und überprüfen Sie die Zeit tmp_update_time, um festzustellen, wie sich die Ausführungszeiten entwickeln. Vielleicht gibt sie Ihnen einen Hinweis darauf, warum die ursprüngliche Abfrage nicht funktioniert hat. Es gibt mehrere Konfigurationsoptionen in PGSQL, um die Verwendung von RAM, die Festplattennutzung und Threads einzuschränken. Ihr Betriebssystem hat möglicherweise eigene Beschränkungen und überprüft das Austauschen von Festplatten, die CPU-Cache-Auslastung usw. (Ich glaube, Sie haben dies bereits getan, aber ich habe es nicht überprüft).

1
Asoub