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
Forwarded from Smartyru
🔥2
Секрет 36 (Инвариантность сжатия данных)
Secret 36 (Invariance of data compression)
И снова здравствуйте, продолжим тему кто, как и сколько жмет -)
Вернувшиссь из отпуска, обнаружил что новый прод подвезли, и т.к. старый пока остался, мой первый шаг был проверить -
как конфигурация кластера влияет на степень сжатия таблицы.

Hello again, let's continue the topic of who, how and how much compresses -)
Having returned from vacation, I discovered that a new product had been delivered, and since the old one was still there, my first step was to check -
how the cluster configuration affects the compression ratio of the table.



📌Число сегментов на хост сократилось вдвое.
+ на сегмент стало RAM x 2.5
Версия ADB осталась та же.

📌The number of segments per host was halved.
+ per segment there is RAM x 2.5
The ADB version remains the same.


Я взял уже знакомую нам табл-у account_balance и пересоздал ее на old и new кластере с опцией AO/CO zstd1, отсортировав по 5 полям -
account_rk, effective_date, currency_rk, department_rk, account_sum, где department_rk - филиал банка:
I took the already familiar account_balance table and recreated it on the old and new clusters with the AO/CO zstd1 option, sorting it by 5 fields -
account_rk, effective_date, currency_rk, department_rk, account_sum, where department_rk is the bank branch:


create table account_balance_ordered   WITH (appendonly=true,orientation=column,compresstype=zstd,compresslevel=1)
as select * from idl.account_balance
order by 1,2,3,4,5
DISTRIBUTED BY (account_rk)

Результат:
Размер account_balance_ordered в обоих случаях составил 37% от исходной, или 77 ГБ.
Result:
The size of account_balance_ordered in both cases was 37% of the original, or 77 GB.


📌Таким образом, конфигурация кластера не влияет на размер отсортированного сета, упакованного в zstd

Надеялся, что результат компрессии будет лучше для new, ведь на каждый сегмент теперь приходится почти втрое больше строк для одномоментной компрессии, но видимо
оная жмет данные исключительно по каждой группе с одинаковым номером л.с ( account_rk - первое поле в ключе сортировки ), потому и отсутствие эффекта.

📌Thus, the cluster configuration does not affect the size of the sorted set packed in zstd
I hoped that the compression result would be better for new, because each segment now has almost three times more lines for one-time compression, but apparently
it compresses data exclusively for each group with the same hp number (account_rk - the first field in the sorting key), hence the lack of effect.


p.s.
Ожидаемо, на new спиллов > 10 TB стало меньше в разы, что очень радует.
As expected, on new,the number of spill > 10 TB queries has decreased significantly > 10 TB, which makes me very happy.
5
Друзья, продолжим тему KYC, т.к. мне очень интересно узнать,кто тут тусуется. Ниже пара опросов.
Guys, let's go further with KYC coz I really wonder who are you. Below are two votes
Oops, опрос с телефона не создать. Ладно, пишем в комментах, как давно у вас случился первый "секс" с Greenplum.
Damn, there is no chance to create vote from mobile app. Well, just share in comments how long have you known Greenplum?
Как давно у вас случился первый "секс" с Greenplum, г ? How long have you known Greenplum, yrs?
Anonymous Poll
6%
<0.5
23%
<1
38%
< 3
21%
<5
10%
<7
0%
<10
0%
<13
0%
<17
0%
<20
2%
<23
Сколько вам лет ? How old are you ?
Anonymous Poll
3%
<10
0%
<15
0%
<20
16%
<25
24%
« 30
17%
< 35
14%
<40
13%
<45
8%
<50
5%
>50
Вопрос 4
А кто-то знает, почему GP запрос возвращает местами полные дубли ? Это же нарушение 12 правил Кодда.
Anybody knows why the query below returns surprisingly complete duplicates? Isn't this a violation of Codd's 12 rules?
select * from pg_partitition_columns
😱3
На заметку
Пример интервала по стандарту ISO 8601
P0DT5H33M39.000000S = 5 ч 33 м 39 с
- P — обозначает начало периода (Period).
- 0D — 0 дней.
- T — разделитель даты и времени.
- 5H — 5 часов.
- 33M — 33 минуты.
- 39.000000S — 39 секунд с шестью знаками после запятой.
Note:
Example of time interval according to ISO 8601 standard
P0DT5H33M39.000000S = 5 h 33 m 39 s

p.s.
Убил час, чтобы вкурить, что это за строка, а ИИ убил меня, ответив за 3 сек, что это за строка
I spent an hour trying to figure out what this line was, and the AI ​​killed me by answering in 3 seconds what this line was
Друзья, т.к. рано или поздно бизнес осознает потребность в быстрых витринах (с околонулевой задержкой отклика), вот еще опрос.
Под основной БД понимаем то, что критично для бизнеса, напр. ХД со всей отчетностью, включая отчеты для регуляторов.
Вспомогательная та, без чего бизнес сразу не встанет, но начнет страдать эффективность, надежность и т.д., напр. для последнего варианта это может быть теплый-холодный слой,
который используется как резервная БД в случае катастрофы на основной.

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