Очень медленные (более 12 часов) большие таблицы объединяются в postgres - PullRequest
0 голосов
/ 13 декабря 2018

Я изо всех сил пытаюсь оптимизировать простую LEFT JOIN для двух очень больших таблиц, которые до сих пор занимали> 12 часов, чтобы завершить и продолжить.

Вот план выполнения:

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)

Вот запрос:

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

Обе таблицы очень велики: vca и gt имеют 9 миллионов (2 ГБ) и 1,3 миллиарда строк (346 ГБ) соответственно.

Я создал vca (MATERIALIZED VIEW) с единственной целью - выполнить это соединение.По сути, это таблица соединения, содержащая только обязательные поля для соответствующего левого соединения 1: 1, а затем дополнительные метаданные.Все поля, к которым присоединяются, правильно проиндексированы, как вы можете видеть из плана запроса.

Сам запрос достаточно прост, есть что-то, чего я пропускаю, что могло бы ускорить его?Я не думаю, что есть какой-то способ использовать WHERE вместо этого?

Есть ли что-то, что я могу настроить в моих настройках postgres, которые могут помочь?В настоящее время у меня есть следующее:

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

ОБНОВЛЕНИЕ 12/12:

Таблица 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 здесь: https://rextester.com/JRJH43442

ОБНОВЛЕНИЕ 12/13:

Для пояснения я использую Postgres 10.5 на CentOS 7.3 с 16 ядрами и 32 ГБ памяти.Запрос теперь выполняется в течение 24+ часов без какого-либо результата.

При проверке состояния кажется, что wait_event_type равно IO.Означает ли это, что запрос царапает / пишет на пустом месте?Может ли это объяснить медлительность?

+------------------+---------------+---------------+---------------+---------------+-----------------+--------------+--------+-------------+--------------+
| 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        |
+------------------+---------------+---------------+---------------+---------------+-----------------+--------------+--------+-------------+--------------+

У меня много доступных ресурсов:

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

Я думаю, может помочь выделение дополнительной памяти?Есть ли способ оптимизировать запросы, которым требуется больше памяти, чем доступно для них?

1 Ответ

0 голосов
/ 13 декабря 2018

Из комментария к этому посту:

Ваш запрос использует genotype_pos_ind и фильтрует по aliquot_barcode.Попробуйте удалить (временно) genotype_pos_ind, и если это не сработает, найдите способ принудительного использования индекса.

Ваш запрос должен использовать вместо этого genotype_pk.

Из чегоВы сказали, что может быть много записей с одинаковыми значениями для aliquot_barcode, chrom, start и end, поэтому СУБД потребуется много времени для фильтрации каждого aliquot_barcode.

И если это все еще слишком долго для вас, вы можете попробовать мой старый ответ, который я сохраню для дальнейших ссылок:



К сожалению, я выигралне могу оптимизировать ваш запрос: слишком много вещей, чтобы принять во внимание.Построить результат с 9 миллионами записей из 13 полей может быть слишком много: может произойти перестановка, ваша ОС не позволит выделять столько памяти, а также будет делать JOIN и т. Д. (написано до реального ответа...)

Я использовал для оптимизации некоторый запрос, состоящий из пятнадцати таблиц из 10 миллионов записей.SELECT такого размера никогда не будет выполнимо в разумные сроки (менее 10 часов).

У меня нет СУБД для проверки того, что я говорю.Кроме того, я не делал SQL в течение полугода: p Поиск того, почему это занимает так много времени (как вы и просили), будет слишком трудоемким, поэтому вот еще одно решение исходной проблемы.


Я принял решение создать временную таблицу:

  1. Создать временную таблицу: tmp_analysis с теми же полями, что и у SELECT + некоторые служебные поля:

Поле идентификатора (tmp_ID, большое целое число), логическое значение, чтобы проверить, была ли запись обновлена ​​(tmp_updated), и отметка времени, чтобы проверить, когда она была обновлена ​​(tmp_update_time).И, конечно, все поля с одинаковыми типами данных из исходных SELECT (из vca и gt)

Вставьте все свои записи из vca:

Используйте null (или любое другое значение по умолчанию, если вы не можете) для полей из gt на данный момент.Установите tmp_updated в false.Используйте простой count() для первичного ключа.

Обновите все эти записи полями из gt.

Используйте WHERE вместо 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

Я сказал, что вы должны использовать EXISTS из соображений производительности, но я ошибся, так как не думаю, что вы можете получить поля из условия EXISTS.Возможно, есть способ сказать Postgresql, что это отношения один на один, но я не уверен.Во всяком случае, индекс

Очевидно, SELECT ваша tmp_analysis таблица для получения ваших записей!

Некоторые примечания для этого:

  1. Если это занимает слишком много времени:

Используйте поле tmp_ID, чтобы ограничить количество обновлений, например, до 10 000, и проверьте план выполнения 3-го запроса (UPDATE): необходимо выполнить полное сканирование натаблица временных таблиц и сканирование индекса на gt (на genotype_pk).Если нет, проверьте свои индексы и поищите, как принудительно использовать индексы PGSL.Вы должны использовать WHERE tmp_ID < 10000 вместо LIMIT 10000.IIRC, LIMIT выполнит весь запрос и просто выдаст вам часть результата.

Если это все еще занимает слишком много времени:

Сегментируйте запрос с помощью tmp_ID и (как вы сказали) используйте оператор цикла в UPDATE для запросас 100 000 или менее записей одновременно (снова используйте where tmp_ID < x AND tmp_ID > y).Проверьте план выполнения еще раз: полное сканирование должно быть ограничено tmp_id до сканирования индекса.Не забудьте добавить индекс для этого поля (если это еще не первичный ключ).

Если вам понадобится повторить это позже:

Используйте BEGIN/END TRANSACTION для инкапсуляции всех запросов и опцию TEMPORARY TABLE для CREATE TABLE tmp_analysis, чтобы вам не приходилось чиститьtmp_analysis после выполнения запроса.

Если у вас все еще есть проблема с циклами:

Используйте транзакции внутри цикла и остановите его, если он снова зависнет.Затем вы можете восстановить его позже с меньшим размером петли.

Если вы хотите немного уменьшить время выполнения:

Вы можете выполнить шаги 1 и 2 в одном запросе с INSERT .. AS .. SELECT, но я не помню, как установить тип данныхдля полей с gt, поскольку они будут установлены в ноль.Обычно это должно быть немного быстрее в целом.

Если вам интересно:

И запрос без цикла по-прежнему занимает более 10 часов, остановите его и проверьте tmp_update_time, чтобы увидеть, как изменяется время выполнения, возможно, оно даст вамподсказка о том, почему оригинальный запрос не сработал.В PGSQL есть несколько параметров конфигурации для ограничения использования ОЗУ, использования диска, потоков.Ваша ОС может устанавливать собственные ограничения и проверять замену диска, использование кэша ЦП и т. Д. (Я думаю, что вы уже сделали это, но я не проверял)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...