Greenplum secrets🎩
698 subscribers
38 photos
8 videos
10 files
67 links
The channel about best practice coding for Greenplum / Канал о том как писать оптимальный код в Greenplum. by @smartyru
Download Telegram
Друзья, т.к. рано или поздно бизнес осознает потребность в быстрых витринах (с околонулевой задержкой отклика), вот еще опрос.
Под основной БД понимаем то, что критично для бизнеса, напр. ХД со всей отчетностью, включая отчеты для регуляторов.
Вспомогательная та, без чего бизнес сразу не встанет, но начнет страдать эффективность, надежность и т.д., напр. для последнего варианта это может быть теплый-холодный слой,
который используется как резервная БД в случае катастрофы на основной.

Friends, since sooner or later business will realize the need for fast datamarts (with near-zero latency), here is another vote.
Main DB is what is critical for business, for example, a data warehouse with all reporting, including reports for regulators.
An auxiliary one, without which the business will not immediately stop, but efficiency, reliability, etc. will begin to suffer, for example, for the last option, t
his could be a warm-cold layer, which is used as a backup database in the event of a disaster on the main one.


Итак, опрос (речь исключительно о проде):
So, let's go with survey (we're talking exclusively about PROD):
Greenplum secrets🎩
Вопрос 2 А вы не задумывались, как работает LIMIT в запросе insert into t as select * from s LIMIT n ? В какой момент происходит стоп записи и как именно происходит накачка данными с каждого сегмента ? Question 2 Have you ever wondered how LIMIT works…
Ответ с вероятностью 90% (для CTAS, что сути вопроса не меняет, т.к. оператор создает пустую таблицу, после чего выполняет insert as select с опциональными параметрами (limit, offset) ):
Данные собираются на выделенном сегменте, отличном от QD (Query Dispatcher - координатор, или мастер), после чего редистрибутятся в целевую таблицу.
В принципе, обнаружив этот файл в гите GP
концептуально можно было успокоиться, взглянув на комментарий к плану в оригинале:
For a DML query with an operator that requires a Gather on
single host (such as Limit or Aggregate), ORCA chooses to Gather on segment
instead of master as for DML queris master node need not be involved at all.
The test ensures that the optimization framework enforces a Sigleton Segment
for such operators. The expected plan should look like:

Перевод сути:
Для DML запроса, который требует Gather на
одном хосте (например, Limit или Aggregate), ORCA выбирает Gather на сегменте
вместо главного узла, так как для запроса DML главный узел вообще не должен быть задействован.

Но точность не давала мне покоя - а как именно GP выбирает сегмент(далее QE - Query Executor) для промежуточного Motion (CPhysicalMotionGather(segment) в тест-плане выше).
Сразу скажу, что моя гипотеза, что для этого в сорцах GP шлепнут тривиальный rand() % Num_Segments оказалась верна только идейно, но реализована хитрее.
Размотав клубок вызовов с createas.c(точка входа CTAS) до execMain.c, я обнаружил функцию FillSliceGangInfo(Slice *slice, int numsegments),
которая назначает список сегментов исполнения каждому слайсу запроса, фрагмент ниже для наглядности:
switch (slice->gangType)
{
...
case GANGTYPE_ENTRYDB_READER:
slice->gangSize = 1;
slice->segments = list_make1_int(-1);
break;
case GANGTYPE_SINGLETON_READER:
{
int gp_segment_count = getgpsegmentCount();
slice->gangSize = 1;
/*
* numsegments might be larger than the number of gpdb actual segments for foreign table.
* For example, for gp2gp, when remote gpdb cluster has more segments than local gpdb,
* numsegments will be larger than getgpsegmentCount().
*
* So we need to use the minimum of numsegments and getgpsegmentCount() here.
*/
slice->segments = list_make1_int(gp_session_id % Min(numsegments, gp_segment_count));
break;

из которого видно, что если слайс выполняется на QD, то по ветке ENTRYDB_READER получим список сегментов = -1 ( что соотв-ет мастер=узлу с gp_segment_configuration.content = -1), для нашего же случая получим по ветке SINGLETON_READER также список из 1 сегмента,
номер которого дает формула gp_session_id MOD min(numsegments, gp_segment_count) или остаток целочисленного деления sid сессии на число сегментов в кластере.
По сути тот же random(), но более хитрый, чтобы поддержать корректно запросы к внешним таблицам, где на удаленном GP gp_segment_count может быть больше чем, на локальном, о чем любезно сообщил автор кода в комментарии выше.
3
#Не реклама#эксперимент
Друзья, сегодня проснувшись меня посетила гениальная мысль про эволюцию денег.
Сначала их не было - был натуральный обмен: я тебе сало, ты мне молоко. Список обмена ограниченный, и цепочка обмена, чтобы получить что-то нужное
могла быть нетривиальной.
Потом кто-то умный придумал камушки, бусинки, и в итоге деньги, которые расширили наши возможности в приобретении благ цивилизации.
Потом кто-то еще более умный придумал отключить SWIFT или даже заблокировать ваши активы в виде иностранных акций (лично я застрял почти на 7 млн руб в акциях США
из-за неправомерных действий СПБ Биржи и ЦБ РФ в ноябре 2023 г, из-за которых у ~500 000 граждан РФ в заморозке десятки млрд $)
и мы столкнулись с проблемой, что купить можно стало не все..
Но хорошая новость в том, что кто-то придумал Интернет, а на его основе биткойн, который стер все границы в оплате и сделал
средством платежа, почти не зависящим от воли регуляторов и жирных банков. Таким образом, я не согласен с мнением некоторых,
что крипта - это пирамида типа МММ, хотя полно фейковых проектов, если не сказать, что 90% как минимум и есть скам.

Это была преамбула, а теперь суть.

Никогда не думал, что буду писать об этом, т.к. категорически негативно отношусь к кредитным картам,
но мой любимый Т-банк так настойчиво предлагает мне поучаствоdать в его акции, что я решил проверить,
насколько мягко он стелит бонусами.

Нет, речь пойдет не про GP Тинька, а про деньги -)

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

У Т-банк есть кредитка Платинум, которая дает 100 000 руб в пользование на 30 дн без % и комиссий, и часто это по фану,
если надо купить какой-то актив или ту же крипту на просадке, здесь и сейчас.

Короче, откровенно говоря, я хочу проверить, все ли так с акцией, которую предлагает Т-банк - уже 4 000 руб за каждого( начиналось с 1000 руб емнип), кто
оформит кредитку по моей реферальной ссылке.

Если вы еще здесь, вот условия акции: -)
📌
Бонус начисляется в течение 7 дней после выполнения условий.

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

Не считаем покупкой:
- Оплату сотовой связи, интернета, услуг ЖКХ, брокера, рекламных компаний, брокерских компаний, букмекерских контор, лотерей.
- Платежи и переводы через мобильное приложение и личный кабинет Т-Банка.
- Оплата в сервисах YOULA, FUNPAY, CloudTips, tips и donat.

[Полный список операций-исключений с МСС-кодами](
https://cdn.tbank.ru/static/documents/promo-baf-common.pdf)

Особенности участия:
- Если у приглашенного друга уже есть любая кредитная карта, или с момента ее закрытия прошло меньше 1 года, то друг не сможет участвовать в акции.
- Нельзя пригласить в акцию самого себя.
- Бонус фиксируется в момент, когда приглашенный друг создаст заявку.


Мой бонус за каждую оформленную карту - 4000 руб ( но это не точно, согласно п. 2.1.2 у банка ряд поощрительных опций, отличных от денежной формы )
Ваш бонус - пожизненное бесплатное обслуживание кредитки ( что скорее всего чистый маркетинг ) + 1/2 моего бонуса = 2000 руб от меня переводом по СБП ( но это не точно )

За всех, кто поставит палец вниз на опусе буду очень рад, значит у вас все о.к.
🤡12
Greenplum secrets🎩
Вопрос 2 А вы не задумывались, как работает LIMIT в запросе insert into t as select * from s LIMIT n ? В какой момент происходит стоп записи и как именно происходит накачка данными с каждого сегмента ? Question 2 Have you ever wondered how LIMIT works…
Translation of the answer to Question 2
Answer with 90% probability (for CTAS, which does not change the essence of the question, since the operator creates an empty table, and then performs insert as select with optional parameters (limit, offset) ):
Data is collected on a dedicated segment, different from QD (Query Dispatcher - coordinator, or master), and then redistributed to the target table.
Basically, having found this file in GP sources
conceptually, one could calm down, looking at the comment to the plan in the original:
For a DML query with an operator that requires a Gather on
single host (such as Limit or Aggregate), ORCA chooses to Gather on segment
instead of master as for DML queris master node need not be involved at all.
The test ensures that the optimization framework enforces a Sigleton Segment
for such operators. The expected plan should look like:


But the accuracy bothered me - how exactly does GP choose a segment (hereinafter QE - Query Executor) for an intermediate Motion (CPhysicalMotionGather(segment) in the test plan above.
I will say right away that my hypothesis that for this purpose the GP uses a trivial rand() % Num_Segments turned out to be correct only in concept,
but the implementation is more tricky.
Going through the chain of call from createas.c (the CTAS entry point) to execMain.c, I found the function FillSliceGangInfo(Slice *slice, int numsegments),
which assigns a list of execution segments to each slice of the request, the fragment below is for clarity:

switch (slice->gangType)
{
...
case GANGTYPE_ENTRYDB_READER:
slice->gangSize = 1;
slice->segments = list_make1_int(-1);
break;
case GANGTYPE_SINGLETON_READER:
{
int gp_segment_count = getgpsegmentCount();
slice->gangSize = 1;
/*
* numsegments might be larger than the number of gpdb actual segments for foreign table.
* For example, for gp2gp, when remote gpdb cluster has more segments than local gpdb,
* numsegments will be larger than getgpsegmentCount().
*
* So we need to use the minimum of numsegments and getgpsegmentCount() here.
*/
slice->segments = list_make1_int(gp_session_id % Min(numsegments, gp_segment_count));
break;



from which it is clear that if the slice is executed on QD, then on the ENTRYDB_READER branch we will get a list of segments = -1 (which corresponds to the master=node with gp_segment_configuration.content = -1),
for our case we will also get on the SINGLETON_READER branch a list of 1 segment, the number of which is given by the formula gp_session_id MOD min(numsegments, gp_segment_count)
or the remainder of the integer division of the session sid by the number of segments in the cluster.
In essence, the same random(), but more artful, in order to correctly support queries to external tables, where on the remote GP gp_segment_count may be greater
than on the local one, which the author of the code kindly reported in the comment above.
HighLoad++ открыл доступ к докладам спикеров, что очень радует.
Посмотрел на одном дыхании

Удачная попытка опровергнуть CAP теорему при определенных бизнес-сценариях.

CAP-теорема утверждает, что в распределенной системе невозможно одновременно полностью обеспечить три свойства:
- Consistency (Согласованность) — все узлы видят одинаковые данные в одно и то же время.
- Availability (Доступность) — каждый запрос получает ответ, независимо от состояния системы.
- Partition Tolerance (Устойчивость к разделениям сети) — система продолжает работать при недоступности части узлов
🔥3
Аналогично докладу с HighLoad, выше автор дает интересные эвристики для определения медианы множества - задача в общем случае в GP требующая больших вычислительных ресурсов, т.к. сортировка сета выполняется на мастере.
Секрет 37 (Готовь сани летом, а телегу зимой)
Неожиданно и случайно новый созрел секрет
Поговорим про проактивность, ведь как известно 90% расходов на бюджет ИТ продукта приходится на ошибки проектирования.

Бывший коллега по Ламоде, канал которого ("Инжиниринг Данных") у меня в топе, сегодня написал то, что, уверен, многим в ИТ созвучно, цитирую, т.к. лучше не скажешь:

... увидел хорошие термины про тип работы - deep work vs shallow work.
Deep work - глубокое погружение в работу, которое позволяет сосредоточиться на проблеме, изучить необходимые технологии и процессы. Обычно такая работа требует как минимум несколько часов без отвлечений, и по окончании процесса вы получаете удовлетворение. От такой напряжённой работы вы не так устаете и не выгораете.
Shallow work, напротив, - это работа урывками, когда часто меняется контекст между задачами и проектами.
Даже хорошо спланированную работу в формате deep work можно легко превратить в shallow work. Достаточно начать реагировать на сообщения в мессенджере от коллег, менеджеров, друзей. Или участвовать в частых митингах.
Вот и получается: вроде день прошёл, а результата ноль.


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

Это была лирика, теперь к сути.

Возвращаясь к Секрету 1, наверное, многие из нас по своей ленивой природе выделяют дельту именно так, как в ветке bad code:
SELECT *
FROM foo t
where t.key not in (select pk from big_tbl)

На самом деле, если в целевой табл-е, т.е. big_tbl есть партиции, скажем по списку src_cd и известно входящее множество кодов источника в таблице дельты foo, то запрос
SELECT *
FROM foo t
where t.key not in (select pk from big_tbl where src_cd IN (<prt_list>)

избавит вас от лишних проблем.

В нашем случае, словили спилл 27 TB на аналогичном запросе от фреймворка, где не было отсечки по партициям.
Хоть наш запрос получает только 1ю строчку из запроса выше через LIMIT 1, чтобы определить признак наличия новых данных, сути это не меняет.
С отсечкой ожидаемо запрос проблем не вызывает.

Отмечу, что вариант рефакторинга, представленный в ветке good code Секрета 1 остается рабочим в любом случае и без отсечки партиций, хоть и работает заметно дольше чем not in с отсечкой, но почти без спилла.

Если развить тему загрузки дельты, то вероятно у кого-то используются и такие запросы вида ( утрирую, чтобы не затуманить суть )
UPDATE big_tbl tgt
SET version_id = :1
FROM dlt
WHERE 1 = 1
AND (dlt.account_rk = tgt.account_rk)


У нас таких сотни.
Так вот, самое забавное, что время такого запроса не особо зависит от числа строк в dlt, если она мала по сравнению с размером tgt, как и положено дельте.
И заметил я это благодаря именно новой метрике, которая появилась с сего дня в автоматически сгенеренных тикетах.

Вот реальный пример одного и того же запроса выше, где число строк в dlt отлично на 2 порядка, но время update меняется незначительно:
41k строк - 00:03:49
5.5 mln строк - 00:04:26

Вес big_tbl 4 TB

А причина ? Все та же, в запросе нет отсечки по src_cd, которая совсем не будет лишней для секционированной big_tbl :
UPDATE big_tbl tgt
SET version_id = :1
FROM dlt
WHERE 1 = 1
AND (dlt.account_rk = tgt.account_rk)
AND tgt.src_cd IN (<prt_list>)


Остается сказать, что для запросов UPDATE , которые заимели отсечку на партицию время сократилось с минут до секунд.
📌
Мораль - если ваши целевые таблицы сегодня не партицированы, используйте если уместно в запросах их обновления отсечку на входящее множество. Завтра, они будут нарезаны на секции, как это имело место в случае нашего ХД.
👍21
#true MPP
Секрет 38 (Случай сортировки без мастера)
Я тут подумал, GP - это про BigData. А где много данных, там не обходится без статистики,
которой до сих пор не уделялось должного внимания.
В продолжение находки
загорелся идеей проверить теорию автора на практике.

Вообще, медиана в отличие от среднего часто используется для более адекватной интерпретации реальности.
Напр., средняя зарплата трудоспособного населения РФ в 2024 может сильно отличаться от медианы за счет
наличия олигархата в хвосте распределения.
Иначе говоря, если из 76 млн человек у 50% зарплата не выше 50к RUR, то и медиана будет не выше 50к,
при средней 88к.

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

Теперь к сути.

Напомню, что расчет медианы требует сортировки множества.
Например, если у вас в таблице ряд натуральнях чисел от 1 до 1000, то ф-я
select percentile_disc(0.5) within group(order by n) as median from tst

вернет 500, а ее аналог percentile_cont(0.5) - 500.5 = среднее между верхней границей левого множества и нижней границей правого.

Я проверил, вычисление медианы действительно выполняется на мастере, создав сет из 100 млн чисел, взяв 100 000 раз первые 1000:
create table tst
as
select generate_series(1, 1000) n;

insert into tst
select a.*
from tst a
join generate_series(1, 100000) on 1 = 1

Намеренно создал его DISTRIBUTED RANDOMLY, почему скажу позже

Расчет медианы дал план:
explain analyze 
select percentile_disc(0.5) within group(order by n) as median from tst
Aggregate (cost=3743.50..3743.51 rows=1 width=4) (actual time=73917.174..73917.175 rows=1 loops=1)
-> Gather Motion 372:1 (slice1; segments: 372) (cost=0.00..3741.00 rows=1000 width=4) (actual time=0.539..8346.740 rows=100001000 loops=1)
-> Seq Scan on tst (cost=0.00..11.00 rows=3 width=4) (actual time=0.118..18.218 rows=270117 loops=1)
Planning time: 0.876 ms
(slice0) Executor memory: 34184K bytes.
" (slice1) Executor memory: 218K bytes avg x 372 workers, 218K bytes max (seg0)."
Memory used: 983040kB
Optimizer: Postgres query optimizer
Execution time: 74008.427 ms

median=500
Теперь рассчитаем локальные медианы и возьмем их среднее, т.к. на каждой ноде имеем случайную выборку из исходной :
select avg(median) from
(
select percentile_disc(0.5) within group(order by n) as median from dbg.tst
group by gp_segment_id) a;
Execution time: 2332.995 ms

median=500.4919354838709677

Ускорение в 31.2 раза, что есть круто, но до тех пор, пока мы не вспомним, что у нас 372 сегмента, и e;t не совсем ясно почему рез-т
оказался на порядок медленнее чем кратность уменьшенного объема данных на каждом узле.

Проверил расчет для множества из 1 млрд, размножив x10 существующий сет
insert into tst
select a.*
from tst a
join generate_series(1, 10) on 1 = 1

Время расчета 15 m 12 s 474 ms и 28 s 268 ms для MPP подхода соовт-но.
Ускорение x 32.35.
Рез-ты также почти совпали : 500 и 500.4704301075268817 соотв-но. Кстати, для данного теста MPP все вычисленные
медианы на нодах лежат в границах от 499(min) до 501(max)

📌Видно, что расчет медианы хорошо скейлится при кратном увеличении выборки и коэф. ускорения стабильно держится в интервале 31 - 32
К слову, в нашем случае, объем случайной выборки на каждом шарде не превышает 0,27% (=1/372 узла), тогда как автор в оригинале рекомендует не опускаться ниже 2%,
но в его случае рез-том является медиана только подвыборки, я же ем всего слона по частям.

📌Остается, добавить, что в книге Канемана "Шум" приводится масса примеров когда "мудрость толпы" оказывается ближе к истине чем отдельные индивиды.
Думаю, дал еще один достойный пример для его книги, где - толпа - это множество узлов кластера, в котором погрешность оценки ответа на поставленный вопрос
каждым из них нивелируется при усреднении до почти безукоризненного уровня за такую смешную цену! -)

Всех с праздником и хорошего отдыха!
5
Greenplum secrets🎩
Секрет 37 (Готовь сани летом, а телегу зимой) Неожиданно и случайно новый созрел секрет Поговорим про проактивность, ведь как известно 90% расходов на бюджет ИТ продукта приходится на ошибки проектирования. Бывший коллега по Ламоде, канал которого ("Инжиниринг…
Translation
Secret 37 (Plan ahead during the right seasons)
Unexpectedly and by chance, a new secret has matured.
Let's talk about proactivity, because as we know, 90% of the costs of the IT product budget are due to design errors.

A former colleague at Lamoda, whose channel ("Инжиниринг Данных") is at the top of my list, wrote today something that I am sure many in IT resonate with, I quote, because it is the best I could say:

... I saw good terms about the type of work - deep work vs. shallow work.
Deep work is a deep immersion in work that allows you to focus on the problem, learn the necessary technologies and processes. Usually, such work requires at least several hours without distractions, and at the end of the process you get satisfaction. You are not so tired and do not burn out from such intense work.
**Shallow work**, __on the contrary, is work in fits and starts, when the context between tasks and projects often changes.
Even well-planned work in the deep work format can easily be turned into shallow work. It is enough to start responding to messages in the messenger from colleagues, managers, friends. Or participate in frequent meetings.
And so it turns out: it seems like a day has passed, but there is no result.


Due to my job, a lot of code passes through me, the performance of which I supervise, so I always have a bucket of Vaseline for developers at hand.
It so happened that today I just made another function that formats a lot of problematic code into a lot of tickets, and since now this is done by a script,
I added the metric of the execution time of a non-optimal SQL query to the ticket.

These were lyrics, now to the point.

Returning to Secret 1, probably many of us, due to our lazy nature, highlight the delta exactly as in the bad code branch:
SELECT *
FROM foo t
where t.key not in (select pk from big_tbl)

In deed, if the target table, i.e. big_tbl has partitions, say by the list by src_cd and the incoming set of source codes in the delta table foo is known, then the query
SELECT *
FROM foo t
where t.key not in (select pk from big_tbl where src_cd IN (<prt_list>)

will save you from unnecessary problems.

In our case, we got a 27 TB spill on a similar query from the framework, where there was no cutoff by partitions.
Although our query gets only the 1st line from the query above through LIMIT 1 to determine the presence of new data, this does not change the essence.
As expected, the query does not cause problems with the cutoff.

Just to be clear the refactoring option presented in the good code branch of Secret 1 remains working in any case and without partition prunning,
although it works noticeably longer than NOT IN with the cutoff, but almost without spills.

Looking deeply into delta loading topic, then probably someone uses such queries as (I exaggerate so as not to obscure the essence)
UPDATE big_tbl tgt
SET version_id = :1
FROM dlt
WHERE 1 = 1
AND (dlt.account_rk = tgt.account_rk)

We have hundreds of them on prod.
So, the funniest thing is that the time of such a query does not really depend on the number of rows in dlt, if it is small compared to the size of tgt, as it should be for delta.
And I noticed this thanks to the new metric that appeared today in automatically generated tickets.

Here is a real example of the same query above, where the number of rows in dlt differs by 2 orders of magnitude, but the update time changes insignificantly:
41k rows - 00:03:49
5.5 mln rows - 00:04:26
big_tbl size is 4 TB
Greenplum secrets🎩
Секрет 37 (Готовь сани летом, а телегу зимой) Неожиданно и случайно новый созрел секрет Поговорим про проактивность, ведь как известно 90% расходов на бюджет ИТ продукта приходится на ошибки проектирования. Бывший коллега по Ламоде, канал которого ("Инжиниринг…
And the reason? It's the same, the query doesn't have a src_cd cutoff, i.e. partition prunning which wouldn't be out of place for a partitioned big_tbl:
UPDATE big_tbl tgt
SET version_id = :1
FROM dlt
WHERE 1 = 1
AND (dlt.account_rk = tgt.account_rk)
AND tgt.src_cd IN (<prt_list>)

It remains to say that for UPDATE queries that have a partition cutoff, the time has been reduced from minutes to seconds.
📌
The moral is that if your target tables are not partitioned today, use a cutoff for the incoming set in your update queries if appropriate.
Tomorrow, they will be cut into sections, as was the case with our DW.
Greenplum secrets🎩
#true MPP Секрет 38 (Случай сортировки без мастера) Я тут подумал, GP - это про BigData. А где много данных, там не обходится без статистики, которой до сих пор не уделялось должного внимания. В продолжение находки загорелся идеей проверить теорию автора…
Translation
#true MPP
Secret 38 (A case of sorting without a master mode)
I believe, conceptually GP is about BigData. And where there is a lot of data, there is no way around statistics,
which has not been given due attention until now at the channel.
Continuing with the discovery
I got the idea to test the author's theory in practice.

In general, the median, unlike the average, is often used for a more adequate interpretation of reality.
For example, the average salary of the working population of the Russian Federation in 2024 may differ greatly from the median due to the presence of oligarchy at the tail
of the distribution.
In other words, if out of 76 million people, 50% have a salary no higher than 50k RUR, then the median will not be higher than 50k,
with an average of 88k.

Statistical formulas, including the median, are also widely used in AI algorithms:
-- Outlier processing:
-- in time series processing and filtering
-- In image processing algorithms for smoothing without strong blurring of edges
etc.

Now to the point.

Let me remind you that calculating the median requires sorting the set.
For example, if you have a series of natural numbers from 1 to 1000 in your table, then the function
select percentile_disc(0.5) within group(order by n) as median from tst

will return 500, and its analogue percentile_cont(0.5) - 500.5 = the average between the upper boundary of the left set and the lower boundary of the right set.

I checked that the median calculation is indeed performed on the master by creating a set of 100 million numbers, repeating 100,000 times the first 1000:
create table tst
as
select generate_series(1, 1000) n;

insert into tst
select a.*
from tst a
join generate_series(1, 100000) on 1 = 1

I intentionally created it DISTRIBUTED RANDOMLY, explain why later.

Calculating the median gave me the plan:
explain analyze
select percentile_disc(0.5) within group(order by n) as median from tst

Aggregate (cost=3743.50..3743.51 rows=1 width=4) (actual time=73917.174..73917.175 rows=1 loops=1)
-> Gather Motion 372:1 (slice1; segments: 372) (cost=0.00..3741.00 rows=1000 width=4) (actual time=0.539..8346.740 rows=100001000 loops=1)
-> Seq Scan on tst (cost=0.00..11.00 rows=3 width=4) (actual time=0.118..18.218 rows=270117 loops=1)
Planning time: 0.876 ms
(slice0) Executor memory: 34184K bytes.
" (slice1) Executor memory: 218K bytes avg x 372 workers, 218K bytes max (seg0)."
Memory used: 983040kB
Optimizer: Postgres query optimizer
Execution time: 74008.427 ms

median=500
Now we calculate local medians and take their average, because on each node we have a random sample from the original set:
select avg(median) from
(
select percentile_disc(0.5) within group(order by n) as median from dbg.tst
group by gp_segment_id) a;
Execution time: 2332.995 ms

median=500.4919354838709677

Acceleration by 31.2 times, which is cool, but until we remember that we have 372 segments, and it is not entirely clear why the result
turned out to be an order of magnitude slower than the reduced data volume on each node.

I checked the calculation for a set of 1 billion by multiplying the existing set x10
insert into tst
select a.*
from tst a
join generate_series(1, 10) on 1 = 1

Exec time is 15 m 12 s 474 ms and 28 s 268 ms for the MPP approach respectively.
Acceleration x 32.35.
The results also almost matched: 500 and 500.4704301075268817 respectively. By the way, for this MPP test, all calculated
medians on nodes lie within the range from 499(min) to 501(max)

📌It is clear that the median calculation scales well with a multiple increase in the sample and the acceleration is stably kept in the range of x31 - 32.
By the way, in our case, the volume of a random sample on each shard does not exceed 0.27% (= 1/372 nodes), while the author in the original recommends not to fall below 2%,
but in his case the result is the median of only a subsample, while I eat the whole elephant in parts.
Greenplum secrets🎩
#true MPP Секрет 38 (Случай сортировки без мастера) Я тут подумал, GP - это про BigData. А где много данных, там не обходится без статистики, которой до сих пор не уделялось должного внимания. В продолжение находки загорелся идеей проверить теорию автора…
📌It remains to add that in Kahneman's book "Noise: A Flaw in Human Judgment" there are many examples when the "wisdom of the crowd" turns out to be closer to the truth than individuals point of view.
I think I gave another worthy example for his book, where - the crowd is a set of cluster nodes, in which the error in estimating the answer to the question posed by each of them is leveled out when averaging to an almost flawless level for such a ridiculous price! -)

Happy weekend to everyone and have a good rest!
1
This media is not supported in your browser
VIEW IN TELEGRAM
Друзья, Ровно через неделю, в Пт, 20 июн, поговорим тут в прямом эфире с автором о черных дырах базах данных, in-memory и не очень. Приглашенный гость Director, R&D Arenadata; founder & CTO Picodata - Константин Осипов. Начало в 19.00 мск
🔥7
На заметку
#alter #table
В продолжение,
предположим, вы создали таблицу foo AO/(CO|RO) и собрали стат-ку, потом добавили столбец в который либо внесли данные либо оставили as is.
При любом запросе к таблице получим предупреждение:

Continuing with,
let's assume you created a table foo with AO/(CO|RO) option and collected statistics, then added a column and either partially updated it or left it as is.
For any query to the table, we will receive a warning


One or more columns in the following table(s) do not have statistics: foo

Возникает вопрос - как узнать какие колонки в табл-е без статы, ответ :
The question arises - how to find out what columns are in a table without statistics, the answer:
SELECT
att.attname AS column_name,
att.attnum AS column_id,
rel.relname AS table_name
FROM
pg_attribute att
JOIN
pg_class rel ON att.attrelid = rel.oid
WHERE
rel.relname = 'foo' -- имя вашей таблицы
AND att.attnum > 0 -- исключая системные колонки
AND NOT att.attisdropped -- исключая удалённые колонки
AND NOT EXISTS (
SELECT 1
FROM pg_stats stats
WHERE
stats.schemaname = 'public' -- ваша схема
AND stats.tablename = rel.relname
AND stats.attname = att.attname
)
2🔥2
Live stream scheduled for
Live stream started
Live stream finished (1 hour)