J'ai du mal à optimiser un simple LEFT JOIN
par rapport à deux tables très volumineuses qui ont duré plus de 12 heures et qui ont duré jusqu'à maintenant.
Voici le plan d'exécution:
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)
Voici la requête:
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
Les deux tables sont très grandes: vca
et gt
ont respectivement 9 millions (2 Go) et 1,3 milliard de lignes (346 Go).
J'ai créé la vca
(MATERIALIZED VIEW
) dans le seul but d'effectuer cette jointure. Il s'agit essentiellement d'une table de jointure avec uniquement les champs requis pour une jointure gauche 1: 1 correspondante, suivie de métadonnées supplémentaires. Tous les champs joints sont correctement indexés, comme vous pouvez le constater dans le plan de requête.
La requête elle-même est assez simple, y at-il quelque chose qui me manque qui pourrait l'accélérer? Je suppose qu'il n'y a pas moyen d'utiliser WHERE
à la place?
Y a-t-il quelque chose que je peux modifier dans mes paramètres postgres qui pourrait aider? Actuellement, j'ai les éléments suivants:
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
MISE À JOUR 12/12:
Tableau 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);
DDL plus complet ici: https://rextester.com/JRJH43442
MISE À JOUR 12/13:
Pour clarifier, j'utilise Postgres 10.5 sur CentOS 7.3 avec 16 cœurs et 32 Go de mémoire. La requête est en cours d'exécution depuis plus de 24 heures sans résultat.
En vérifiant l'état, il semble que wait_event_type
est IO
. Cela signifie-t-il que la requête est en train de gratter/écrire dans l'espace de travail? Cela pourrait-il expliquer la lenteur?
+------------------+---------------+---------------+---------------+---------------+-----------------+--------------+--------+-------------+--------------+
| 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 |
+------------------+---------------+---------------+---------------+---------------+-----------------+--------------+--------+-------------+--------------+
J'ai beaucoup de ressources disponibles:
$ free -h
total used free shared buff/cache available
Mem: 31G 722M 210M 5.0G 30G 25G
Swap: 3.7G 626M 3.1G
Je suppose que rendre plus de mémoire disponible pourrait aider? Existe-t-il un moyen d'optimiser les requêtes nécessitant plus de mémoire que ce dont elles disposent?
Du commentaire de ce post:
Votre requête utilise
genotype_pos_ind
et filtre suraliquot_barcode
. Essayez de supprimer (temporairement)genotype_pos_ind
et si cela ne fonctionne pas, cherchez comment forcer l'utilisation de l'index.
Votre requête devrait utiliser genotype_pk
à la place.
D'après ce que vous avez dit, il pourrait y avoir beaucoup d'enregistrements avec les mêmes valeurs pour aliquot_barcode
, chrom
, start
et end
, de sorte que le SGBDR prendra alors beaucoup de temps pour filtrer chaque aliquot_barcode
.
Et si c'est encore trop long pour vous, vous pouvez essayer ma réponse plus ancienne, que je garderai pour plus de références:
Malheureusement, je ne pourrai pas optimiser votre requête: il y a trop d'éléments à prendre en compte. Construire un résultat avec 9 millions d'enregistrements de 13 champs peut être trop: un échange peut se produire, votre système d'exploitation n'autorisera pas autant d'allocation de mémoire, tout en faisant (écrit avant la vraie réponse ...)JOIN
, etc.
J'avais l'habitude d'optimiser une requête composée d'une quinzaine de tables d'environ 10 millions d'enregistrements. SELECT
de cette taille ne serait jamais réalisable dans un délai raisonnable (moins de 10 heures).
Je n'ai pas de SGBDR pour tester ce que je dis. En outre, je n’ai pas utilisé de code SQL depuis six mois: p Trouver la raison pour laquelle cela prend tant de temps (comme vous l’aviez demandé) consomme trop de temps, c’est donc une autre solution au problème initial.
La solution que j'ai adoptée a été de créer une table temporaire:
tmp_analysis
, avec les mêmes champs que votre SELECT
+ quelques champs d’utilitaire:Un champ ID (tmp_ID
, un gros int), un booléen pour vérifier si l'enregistrement a été mis à jour (tmp_updated) et un horodatage pour vérifier quand il a été mis à jour (tmp_update_time
) . Et bien sûr tous les champs, avec les mêmes types de données , à partir de votre SELECT
d'origine (de vca
et gt
)
vca
:Utilisez null
(ou toute autre valeur par défaut si vous ne le pouvez pas) pour les champs de gt
pour le moment. Définissez tmp_updated
sur false. Utilisez une simple count()
pour la clé primaire.
Utilisez une WHERE
plutôt qu'une 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
J'ai dit que vous devriez utiliser EXISTS
pour des raisons de performances, mais je me suis trompé car je ne pense pas que vous puissiez récupérer des champs à l'intérieur de la condition EXISTS
. Il y aurait peut-être un moyen de dire à Postgresql que c'est une relation de un à un, mais je ne suis pas sûr. Quoi qu'il en soit, index
SELECT
votre table tmp_analysis
pour obtenir vos enregistrements!Quelques notes pour cela:
Utilisez le champ tmp_ID
pour limiter le nombre de mises à jour à 10 000 par exemple et vérifiez le plan d'exécution de la 3ème requête (UPDATE
): Vous devriez avoir une analyse complète de la table de la table temporaire et une analyse d'index sur gt
(sur genotype_pk
). Sinon, vérifiez vos index et cherchez comment forcer l'utilisation de l'index par PGSL. Vous devriez utiliser WHERE tmp_ID < 10000
plutôt que LIMIT 10000
. IIRC, LIMIT
exécutera la requête entière et vous donnera une partie du résultat.
Segmentez la requête à l'aide de tmp_ID
et (comme vous l'avez dit) utilisez une instruction boucle sur la UPDATE
pour interroger avec 100 000 enregistrements ou moins à la fois (utilisez à nouveau where tmp_ID < x AND tmp_ID > y
). Vérifiez à nouveau le plan d'exécution: l'analyse complète doit être limitée par le tmp_id
avant l'analyse d'index. N'oubliez pas d'ajouter un index sur ce fild (s'il ne s'agit pas déjà de la clé primaire).
Utilisez BEGIN/END TRANSACTION
pour encapsuler toutes les requêtes et l'option TEMPORARY TABLE
sur CREATE TABLE tmp_analysis
pour ne pas avoir à nettoyer tmp_analysis après l'exécution de la requête.
Utilisez des transactions dans la boucle et arrêtez-la si elle se fige à nouveau. Ensuite, vous pourrez le restaurer plus tard avec une taille de boucle plus petite.
Vous pouvez effectuer les étapes 1 et 2 dans une requête avec un INSERT .. AS .. SELECT
, mais je ne me souviens pas comment définir le type de données pour les champs à partir de gt
, car ils seront définis sur null. Normalement, cela devrait être un peu plus rapide dans son ensemble.
Et la requête sans boucle prend encore plus de 10 heures, arrêtez-la et consultez tmp_update_time pour voir comment les temps d'exécution évoluent. Cela vous donnera peut-être une idée de la raison pour laquelle la requête d'origine n'a pas fonctionné. Il existe plusieurs options de configuration sur PGSQL pour limiter l'utilisation de RAM, l'utilisation du disque, les threads. Votre système d'exploitation peut mettre ses propres limites et vérifier l'échange de disque, l'utilisation du cache de la CPU, etc. (je pense que vous avez déjà fait cela, mais je n'ai pas vérifié)