Обновить
2K+
219
Боровиков Кирилл@Kilor

Архитектура ИС: PostgreSQL, Node.js и highload

632
Подписчики
Отправить сообщение

Относительно составного по времени он тут выиграл 5.03 против 5.30, и 410 против 461 по страницам - то есть "копейку". Но выиграл ровно потому, что "двихдиапазонный" поиск по btree заведомо неэффективен.

А вот gist как раз выигрывает за счет более оптимизированного хранения самого дерева индекса - из heap страницы-то читались все разы одни и те же 338 за примерно 0.28-0.30мс.

BitmapAnd обычно стоит избегать, он почти всегда проигрывает составному индексу за редкими исключениями.

https://explain.tensor.ru/archive/explain/f0d4564d9536a996688fe0b96a4a87b2:0:2024-03-15

Эти картинки в большом количестве присутствуют в упоминаемой статье.

Конечно, спасибо, поправил.

Железо надо покупать тогда, когда вы уперлись в какой-то ресурс (производительность CPU или диска, объем памяти) и не знаете способа, как уменьшить его потребление.

Например, 200 постоянно активных сессий будут хотеть много памяти (каждый коннект - процесс PG). Если у вас это количество выросло с 100 до 200, то и памяти понадобится вдвое.

С другой стороны, можно поставить pgbouncer в transaction mode, что сократит кол-во процессов до кол-ва активных транзакций.

А 1Ktps - не метрика производительности, поскольку они могут делать просто SELECT 1;, а это ниочем

Давайте отвечу я, поскольку вопросы больше касаются сервиса, нежели плагина.

  1. Если вас напрягает "сервер где-то в России", то можете настроить отправку из плагина на зеркало explain-postgresql.com "где-то в Amazon" или развернуть self-hosted версию где-то на собственных мощностях и отправлять туда.

  2. Схема БД несложно восстанавливается на основании данных из плана, но она нужна только для принципиальных изменений алгоритма работы запроса, как вот тут, например.

  3. Большинство проблем производительности запроса легко определяется на основании одного лишь плана, и решения там - типовые, в том числе и насчет индексов.

В базовой версии: корректно вычисляет потребление ресурсов на каждом узле с учетом вложенности, подсказывает, как можно устранить неэффективность в запросе, какие индексы стоит добавить в БД и наглядно это все показывает.

В расширенной версии добавляется возможность массового сквозного анализа всего лога сервера PostgreSQL, в том числе в онлайн-режиме.

Результату деления, конечно. Спасибо, поправил.

если в запросе имеется несколько UNION, каждый очередной UNION будет объединять текущую выборку с выборкой, которую получил предыдущий UNION

Формально, X UNION Y UNION Z транслируется как (X UNION Y) UNION Z - так что не вижу смысла тут усложнять описание, про приоритет и так написано.

Это позволяет ускорить получение уникализованной сборки нескольких наборов записей, применив UNION DISTINCT только на самом последнем объединении

Хорошее замечание, но это уже к теме следующей лекции.

Как-то результат не совпадает с опубликованным в статье. Что я не так делаю?

Переписал абзац, поправил слайд. Спасибо!

просто UNION, но его внутри рекурсии стоит использовать с осторожностью ... Непонятно, из-за чего тут требует осторожничать.. или есть ещё какие-то резоны?

Приходилось встречать код примерно с таким смыслом:

WITH RECURSIVE T(i) AS (
  VALUES(1)
UNION
  SELECT
    j
  FROM
    T
  , generate_series(1, T.i + 1) j
  WHERE
    i < 10
)
TABLE T;

Он, конечно, корректно работает, но ни разу не эффективно. Но "простой" UNION маскирует эту проблему.

Психологически, многие дойдя до первого рабочего варианта берут в работу его. Не всегда дочитав туториал до конца. Я даже на работе с таким регулярно сталкиваюсь.

Есть и обратная сторона, тоже сталкиваюсь: "Фу, как это для меня сложно, даже читать дальше не буду!"

Раз уж пишете туториал, то стоило бы рассмотреть все возможности GROUPING SETS, включая и [DISTINCT] ROLLUP.

Я не ставил себе тут цели разбирать все возможности SQL, только показать возможные решения конкретной прикладной задачи.

Причин три:

  • некоторые все еще живут на 9.4 или на форках от еще более ранних версий, где их нет

  • некоторые ORM "не разумеют" GROUPING SETS, а задача-то никуда не делась

  • показать разницу для тех, кто "я так привык, мне и так удобно", и, например, вместо HAVING при группировке используют подзапрос + WHERE

Достаточно просто идти вперед и счетчик всегда под рукой.

Это может быть полезно только для достаточно узкого класса задач вроде вычисления доли строки относительно итога.

Но позже в слое представления не нужно делать лишнее действие в виде чтения первой строки и извлечения из него значения счетчитка в локальную переменную.

Гораздо проще сделать fetchOne в переменную как раз. То есть подобные "преимущества" никак не оправдывают ни лишней работы СУБД, ни раздувания объема resultset, ни избыточных затрат на его разбор на стороне БЛ.

Это не дублирование. Это очень дешевое выполнение той работы которую позже нужно было бы сделать слою представления.

Я по-прежнему не понимаю цели передавать одно и то же значение, например, итого-счетчика в каждой строке, если его можно передать один раз, в одной записи.

Наверное, я не до конца понимаю идею:

WITH preagg AS (
  SELECT
    ts::date dt
  , extract(hour FROM ts) hr
  , count(*)
  FROM
    timefact
  WHERE
    ts BETWEEN '2023-12-01' AND '2023-12-31'
  GROUP BY
    1, 2
)
SELECT
  dt
, hr
, count cnt_cell
, sum(count) OVER (PARTITION BY dt) cnt_day
, sum(count) OVER (PARTITION BY hr) cnt_hour
, sum(count) OVER () cnt_total
FROM
  preagg;

Такой запрос плодит дубли значений счетчиков:

cnt_total имеет одинаковое значение вообще во всех строках
cnt_total имеет одинаковое значение вообще во всех строках

А кроме этого имеет план с множественными пересортировками, о которых я писал выше:

Чтобы получить "агрегаты" - то есть уменьшить количество исходных строк, в PostgreSQL (не в курсе про T-SQL) пришлось бы использовать конструкт вида:

SELECT DISTINCT ON(dt) dt, count(*) OVER (PARTITION BY dt)

И так для каждого способа группировки, что будет идентично CTE + UNION ALL за исключением множественных пересортировок в плане. Очевидно, что исходные строки при этом потеряются.

А без DISTINCT дополнительные значения будут расчитаны для каждой исходной записи, много-много раз, и не совсем понятно, как это соотносится с задачей.

Вот "склеить" выборку исходных данных (плюс пустые поля для ключей агрегации) и к ней итоги - это можно.

У нас есть и "безофисные" удаленные сотрудники. Разве что с Дальним Востоком может быть сложно взаимодействовать из-за разницы часовых поясов.

Ну, это же уже не вызов функции, а выражение сложения. Но немного подправил, чтобы не возникало разночтений.

??? OFFSET нумерует строки, начиная с нуля. ... укацук

Спасибо, поправил.

Только в случае, если функция/оператор являются "самым последним действием" при вычислении выражения. Иначе получим безымянный столбец.

Это не понял. Можно пример?

Считаю, что нужно явно описать случай, когда запись содержит NULL в выражении уникализации/группировки. Для начинающих поведение сервера в этом случае неочевидно.

Согласен, расширил.

Весьма неплохо умеет, но там разложены некоторые "грабли": "PostgreSQL Antipatterns: куда крутить NULLS"

Видеозапись всего курса можно посмотреть уже сейчас, не дожидаясь публикации транскриптов остальных лекций.

Информация

В рейтинге
Не участвует
Откуда
Ярославль, Ярославская обл., Россия
Дата рождения
Зарегистрирован
Активность