Я мог бы использовать некоторую помощь в оптимизации запроса, который сравнивает строки в одной таблице с миллионами записей. Вот определение таблицы:
CREATE TABLE IF NOT EXISTS data.row_check (
id uuid NOT NULL DEFAULT NULL,
version int8 NOT NULL DEFAULT NULL,
row_hash int8 NOT NULL DEFAULT NULL,
table_name text NOT NULL DEFAULT NULL,
CONSTRAINT row_check_pkey
PRIMARY KEY (id, version)
);
Я переделываю наш push-код, и у меня есть испытательный стенд с миллионами записей в 20 таблицах. Я запускаю свои тесты, получаю количество строк и могу определить, когда некоторые из моих вставок изменились. Следующим шагом является проверка каждой строки, а затем сравнение строк на предмет различий между версиями моего кода. Примерно так:
-- Run my test of "version 0" of the push code, the base code I'm refactoring.
-- Insert the ID and checksum for each pushed row.
INSERT INTO row_check (id,version,row_hash,table_name)
SELECT id, 0, hashtext(record_changes_log::text),'record_changes_log'
FROM record_changes_log
ON CONFLICT ON CONSTRAINT row_check_pkey DO UPDATE SET
row_hash = EXCLUDED.row_hash,
table_name = EXCLUDED.table_name;
truncate table record_changes_log;
-- Run my test of "version 1" of the push code, the new code I'm validating.
-- Insert the ID and checksum for each pushed row.
INSERT INTO row_check (id,version,row_hash,table_name)
SELECT id, 1, hashtext(record_changes_log::text),'record_changes_log'
FROM record_changes_log
ON CONFLICT ON CONSTRAINT row_check_pkey DO UPDATE SET
row_hash = EXCLUDED.row_hash,
table_name = EXCLUDED.table_name;
Это получает две строки в row_check для каждой строки в record_changes_log или любой другой таблицы, которую я проверяю. Для двух запусков record_changes_log я получаю более 8.6M строк в row_check. Они выглядят так:
id version row_hash table_name
e6218751-ab78-4942-9734-f017839703f6 0 -142492569 record_changes_log
6c0a4111-2f52-4b8b-bfb6-e608087ea9c1 0 -1917959999 record_changes_log
7fac6424-9469-4d98-b887-cd04fee5377d 0 -323725113 record_changes_log
1935590c-8d22-4baf-85ba-00b563022983 0 -1428730186 record_changes_log
2e5488b6-5b97-4755-8a46-6a46317c1ae2 0 -1631086027 record_changes_log
7a645ffd-31c5-4000-ab66-a565e6dad7e0 0 1857654119 record_changes_log
Я попросил вчера помочь с запросом сравнения, и это привело к следующему:
select v0.table_name,
v0.id,
v0.row_hash as v0,
v1.row_hash as v1
from row_check v0
join row_check v1 on v0.id = v1.id and
v0.version = 0 and
v1.version = 1 and
v0.row_hash <> v1.row_hash
Это работает, нотеперь я надеюсь немного его оптимизировать. В качестве эксперимента я сгруппировал данные по версии и затем построил индекс BRIN, например:
drop index if exists row_check_version_btree;
create index row_check_version_btree
on row_check
using btree(version);
cluster row_check using row_check_version_btree;
drop index row_check_version_btree; -- Eh? I want to see how the BRIN performs.
drop index if exists row_check_version_brin;
create index row_check_version_brin
on row_check
using brin(row_hash);
vacuum analyze row_check;
Я выполнил запрос через объяснение и получил:
Merge Join (cost=1.12..559750.04 rows=4437567 width=51) (actual time=1511.987..14884.045 rows=10 loops=1)
Output: v0.table_name, v0.id, v0.row_hash, v1.row_hash
Inner Unique: true
Merge Cond: (v0.id = v1.id)
Join Filter: (v0.row_hash <> v1.row_hash)
Rows Removed by Join Filter: 4318290
Buffers: shared hit=8679005 read=42511
-> Index Scan using row_check_pkey on ascendco.row_check v0 (cost=0.56..239156.79 rows=4252416 width=43) (actual time=0.032..5548.180 rows=4318300 loops=1)
Output: v0.id, v0.version, v0.row_hash, v0.table_name
Index Cond: (v0.version = 0)
Buffers: shared hit=4360752
-> Index Scan using row_check_pkey on ascendco.row_check v1 (cost=0.56..240475.33 rows=4384270 width=24) (actual time=0.031..6070.790 rows=4318300 loops=1)
Output: v1.id, v1.version, v1.row_hash, v1.table_name
Index Cond: (v1.version = 1)
Buffers: shared hit=4318253 read=42511
Planning Time: 1.073 ms
Execution Time: 14884.121 ms
. ..что я действительно не понял правильную идею ... поэтому я снова запустил ее в JSON и передал результаты в этот замечательный визуализатор плана:
http://tatiyants.com/pev/#/plans
Советы верны, оценка верхнего узла неверна. Результат - 10 строк, оценка - около 443 757 строк.
Я надеюсь узнать больше об оптимизации такого рода вещей, и этот запрос кажется хорошей возможностью. У меня много идей о том, что может помочь:
- CREATE STATISTICS
?
- Переработать запрос, чтобы переместить сравнение куда?
- Использовать лучший индекс? Я попробовал индекс GIN и прямое B-дерево в версии, но ни один из них не был лучше.
- Переработать формат row_check, чтобы переместить два хэша в одну строку вместо разделения их на две строки, сравнить на insert /обновить, пометить несоответствия и добавить частичный индекс для несоответствующих значений.
Конечно, забавно даже пытаться индексировать что-то, где есть только два значения (0 и 1 в случае выше)вот и все. На самом деле, есть ли какая-нибудь хитрая уловка для логических выражений? Я всегда буду сравнивать две версии, поэтому «старая» и «новая», которые я могу выразить, однако, делает жизнь лучше. Я понимаю, что Postgres имеет внутренние индексы только во время поиска / слияния (?) И что у него нет индекса типа растрового изображения. Будет ли какой-то INTERSECT
, который может помочь? Я не знаю, как в Postgres реализованы математические операторы набора для внутренних операций.
Спасибо за любые предложения о том, как переосмыслить эти данные или запрос, чтобы сделать их более быстрыми для сравнений, включающих миллионы или десятки миллионов строк.