Comments 12
Продолжаю с нетерпением ждать публикации доклада
Статистический анализ результатов бенчмарков
При нагрузочном тестировании PostgreSQL бенчмарки замеряют время исполнения запроса (latency). Для более объективного результата запрос выполняется большое количество раз — из этого получается некоторый набор latency. Для оценки производительности PostgreSQL на данном запросе можно использовать стандартные методы, такие как медиана или среднее, но мы предлагаем использовать более комплексный подход. Как показала практика, такие выборки часто бывают мультимодальными и состоят из различных распределений с некоторыми параметрами.
В таких случаях стандартных методов становится недостаточно, необходимо анализировать составляющие по отдельности. Результатом работы является инструмент, позволяющий автоматически проводить статистический анализ результата бенчмарка с учетом особенностей каждого набора данных, в том числе выявлять мультимодальность, количество и границы преобладания каждой моды, а также параметры распределений.
На моей памяти - первый доклад о применении математических методов в DBA PostgreSQL.
Доклад то я с интересом заслушал. Но поскольку онлайн режим - не удалось пообщаться с авторами.
Но на следующей конференции , может повезёт. Обсудим общие разработки.
так доклады можно смотреть в личном кабинете pgconf не дожидаясь публикации
А можно подробнее: вот этот математический подход к оценке времени исполнения запроса, он учитывает то, что только первый запрос из серии, скорее всего, потребует подгрузки индекса с диска, а вот все остальные уже, часто, работают строго в оперативке, что искажает результаты?
@santjagocorkez если это вопрос по моему комментарию , я бы и сам хотел бы узнать подробности .
Поэтому и ожидаю публикации от авторов .
А вообще , если тема математического подхода к анализу производительности интересна , лучше обсуждать не здесь, а в комментариях на дзен канале
Или в телеграме
:-) Ну как бы "производительность БД" "по факту" не интересна никому...
Всех "по факту" интересует "производительность прикладного приложения".
Да есть связи между ними, но они "совсем не простые"... :-)
К примеру БД нагружается (выразимся так) "различными прикладными задачами". И у прикладных "задач" есть различные "требования" по времени ответа (от БД в том числе). Если в БД нет "встроенных" средств "приотеризации" - это уже "не есть хорошо". Вот и получаем ситуацию когда один (для приложения не приоритетный) запрос в БД (который БД обрабатывает как бы быстро и оптимально) "убивает" производительность других приоритетных для приложения запросов. Для PG это достаточно актуально.
А можете привести план, который у вас получается после создания индекса (id, last)? Индекс точно используется?
да, используется:
EXPLAIN (analyze, buffers) update g1 set cnt = cnt + 1,
last = now() where ctid =
(select ctid from g1 where id = 10
order by last desc limit 1);
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------
Update on g1 (cost=12.38..16.40 rows=0 width=0) (actual time=0.985..1.017 rows=0 loops=1)
Buffers: shared hit=1266
InitPlan 1
-> Limit (cost=12.37..12.38 rows=1 width=14) (actual time=0.363..0.385 rows=1 loops=1)
Buffers: shared hit=118
-> Sort (cost=12.36..12.37 rows=1 width=14) (actual time=0.356..0.367 rows=1 loops=1)
Sort Key: g1_1.last DESC
Sort Method: quicksort Memory: 25kB
Buffers: shared hit=118
-> Index Scan using g1_pkey on g1 g1_1 (cost=0.27..12.31 rows=1 width=14) (actual time=0.336..0.344 rows=1 loops=1)
Index Cond: (id = 10)
Buffers: shared hit=118
-> Tid Scan on g1 (cost=0.00..4.02 rows=1 width=22) (actual time=0.386..0.395 rows=1 loops=1)
TID Cond: (ctid = (InitPlan 1).col1)
Buffers: shared hit=119
Planning Time: 0.132 ms
Execution Time: 1.056 ms
(17 rows)
postgres=# \d g1
Table "public.g1"
Column | Type | Collation | Nullable | Default
--------+-----------------------------+-----------+----------+---------
id | integer | | not null |
cnt | bigint | | |
last | timestamp without time zone | | |
Indexes:
"g1_pkey" PRIMARY KEY, btree (id)
"g1_idx" btree (id, last)
Тест с одной строкой с планами https://dba1.ru/pmt/hotprune.txt
На одной строке выбирается Index Scan. Seq Scan быстрее, но он не выбирается
Без индекса Seq Scan: Execution Time: 5.612 ms
Index Scan: Execution Time: 6.476 ms
Bitmap Heap Scan: Execution Time: 7.472 ms
Не не не. Тут у вас используется индекс g1_pkey
. Зачем он вам? Если есть индекс g1_idx(id, last)
, то primary key индекс g1_pkey(id)
уже не нужен. Иными словами, в "инженерном решении" нужно пересоздать первичный ключ как (id, last)
, а не просто добавить ещё один индекс.
Обратите внимание на то, что Михаил в докладе явно проговаривал и показывал, что план должен использовать добавленный индекс, и не просто использовать, а "брать последнюю запись". В плане должно быть Index Scan Backward
, а у вас по факту "взять все записи, отсортировать, взять последнюю"
Добейтесь, пожалуйста, плана с Index Scan Backward
, а потом уже делайте выводы помогает ли решение Михаила или нет.
Спасибо! Индекс g1 используется первичным ключём. Если первичным ключём сделать (id, last) то тогда потребуется как-то гарантировать уникальность id. В докладе для этого используется advisory lock. Без ограничения целостности по id это опасно. Если данные разъедутся и их сложно будет собрать. Возможно можно сохранить индекс по id и добиться плана с Index Scan Backward
при наличии индекса по id, тогда нет проблем. Цель статьи не про инженерное решение, а в том, что оптимизация на уровне индекса устанилоа бы эту проблему.
alter table g1 drop constraint g1_pkey;
ALTER TABLE
EXPLAIN (analyze, buffers) update g1 set cnt = cnt + 1, last = now() where ctid = (select ctid from g1 where id = 10 order by last desc limit 1); QUERY PLAN
Update on g1 (cost=1119.64..1123.65 rows=0 width=0) (actual time=0.135..0.166 rows=0 loops=1)
Buffers: shared hit=10
InitPlan 1
-> Limit (cost=746.55..1119.63 rows=1 width=14) (actual time=0.057..0.074 rows=1 loops=1)
Buffers: shared hit=4
-> Index Scan Backward using g1_idx on g1 g1_1 (cost=0.39..373.47 rows=1 width=14) (actual time=0.047..0.051 rows=1 loops=1)
Index Cond: (id = 10)
Buffers: shared hit=4
-> Tid Scan on g1 (cost=0.00..4.02 rows=1 width=22) (actual time=0.082..0.091 rows=1 loops=1)
TID Cond: (ctid = (InitPlan 1).col1)
Buffers: shared hit=5
Planning Time: 0.874 ms
Execution Time: 0.201 ms
(13 rows)
progress: 6390.0 s, 5649.4 tps, lat 1.719 ms stddev 0.860, 0 failed
progress: 6395.0 s, 5749.6 tps, lat 1.687 ms stddev 0.880, 0 failed
progress: 6400.0 s, 5892.2 tps, lat 1.647 ms stddev 0.783, 0 failed
Скорость с инженерным решением действительно большая. Позже отредактирую статью добавив это!
c одной блокировкой advisory lock скорость низкая:
progress: 2425.0 s, 1040.4 tps, lat 9.596 ms stddev 2.415, 0 failed
progress: 2430.0 s, 1052.8 tps, lat 9.475 ms stddev 2.203, 0 failed
progress: 2435.0 s, 1023.8 tps, lat 9.775 ms stddev 3.013, 0 failed
С блокировками по идентификатору товара: SELECT pg_advisory_lock(:b_id);
progress: 15.0 s, 3594.3 tps, lat 2.747 ms stddev 1.263, 0 failed
то тогда потребуется как-то гарантировать уникальность id
Самый простой вариант проверить — создать индекс по (id+0): create unique index g1_pkey on g1((id+0));
Это, конечно, недёшево будет, ведь каждое обновление будет создавать новые строки в g1_pkey. Но по-быстрому сравнить можно.
Было бы хорошо, если бы в PostgreSQL можно было создавать unique/primary key по неуникальным индексам (в OracleDB так можно).
Да, мысль тем или иным образом в индексной строке отмечать, что она ведёт на устаревшую hot цепочку интересная. В целом, это может и autovacuum делать (ну, чтобы не напрягать пользовательские процессы).
Некий минус, что сейчас, наверное, btree индекс ничего не знает про HOT. Это как бы деталь реализации heap table, поэтому добавка завязок между nbtree и heapam выглядит как некий косяк.
Хотя, конечно, ускорение DML (update, delete, select for update) было бы крайне полезно.
Да, мысль тем или иным образом в индексной строке отмечать, что она ведёт на устаревшую hot цепочку интересная.
👍 именно это я и "рекламирую" 🙂 Я рад, что Михаил осветил эту проблему. 🤝 Править код индекса btree сложно, поэтому и написал, что Лубенникова долго это изучала и ей это проще всех. Про серверный процесс я написал исходя из того, что код патча, который ввел очистку блоков индекса серверным процессом наверное можно было бы использовать, чтобы вставить признак, аналогичный dead. Место в заголовке записи наверное есть (для vars, nulls, dead есть). То есть задача не такая неподъемная. Оптимальнее это было бы сделать в коде вакуума, он в каждом цикле почти впустую сканирует такие таблицы (я привел пример лога по таблицам системного каталога)
По следам PgConf: обзор проблемы #1 из доклада «Как PostgreSQL может сделать больно, когда не ожидаешь»