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
Секрет 32 (Остерегайтесь функций!)
Юбилейный секрет( для акул разработки в GP ) решил посвятить функциям, куда же без них.
Кому-то он покажется лишним, как зуб мудрости, но тем не менее, поверьте, рано или поздно каждый третий вспомнит про него.
Действительно, далеко не все задачи решаются SQL запросом(ами), иногда приходится прибегать к циклам, ветвлениям и упаси боже рекурсиям.
Пример такой задачи есть у меня, плавали-знаем - корреляция телеком-трафика, когда надо смэтчить 2 плеча 1 звонка: входящее и исходящее, по гибким правилам, где фиксированный ключ -
это тел. номер, и то после его стандартизации, а длит-ть и время вызова - нечеткие ключи, т.к. ни разу не обязаны совпадать.
Его величество, задача из класса гибкого мэтча. Нетривальна, как может показаться на первый взгляд.

Так вот тем, кому придется взять (или уже взяли как мы) на вооружение функции, думаю, пригодится список ниже.
Суть в том, что в случаях нижеперечисленных типов запросов, при выполнении ф-ии используется не GPORCA планировщик, а Legacy(PostgreSQL).
Такие кейсы не поддерживаются оптимизатором GPORCA по умолчанию и при выполнении такого запроса( из ф-ии) в логе Greenplum будет запись
Falling back to Postgres-based planner because GPORCA does not support the following feature: UTILITY command.
Если нет доступа к логам БД, то проверить выбранный планировщик можно в плане запроса

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

♓️ - SP-GiST индексы. GPORCA поддерживает только B-tree, bitmap, GIN, and GiST индексы, игнорируя все другие типы индексов.
♒️ - Использование в SELECT выражениях TABLESAMPLE
♑️ - Использование WITH ORDINALITY в SELECT
♐️ - Multi-level partitioned tables
♏️ - Non-uniform partitioned tables
♎️ - SortMergeJoin (SMJ)
♍️ - Ordered aggregations
♌️ - Multi-argument DISTINCT qualified aggregates, e.g. SELECT corr(DISTINCT a, b) FROM foo
♋️ - Multiple grouping sets specified using a duplicate alias in a null-producing grouping set spec.
Такие запросы откатываются к Legacy планировщику, если только вы напрямую не присвоите псевдоним отдельной переменной, как в примере ниже:
CREATE TABLE foo AS SELECT i AS ai1, i AS ai2 FROM generate_series(1, 3)i;
SELECT ai1, ai2 FROM foo GROUP BY ai2, ROLLUP(ai1) ORDER BY ai1, ai2;

♊️ - Скалярные операторы:
🔸ROW
🔸ROWCOMPARE
🔸FIELDSELECT
♉️ - Aggregate functions that take set operators as input arguments // Не смог сход придумать пример в этом роде, поэтому без перевода - кто знает о чем речь, блесните в коментах!
♈️ - Множественные агрегаты над DISTINCT не поддерживаются GPORCA по дефолту, напр.
SELECT count(DISTINCT a), sum(DISTINCT b) FROM foo

, но дока гласит, что это можно исправить параметром
optimizer_enable_multiple_distinct_aggs ( не проверял )
- percentile_* window functions (ordered-set aggregate functions)
- Inverse distribution functions.
- ф-ии, содержащие символы UNICODE в названиях таблиц и пр. метаданных, либо символы, несовместимые с локалью ОС, где они исполняются
- SELECT, UPDATE, и DELETE запросы, где в локации таблицы используется ONLY
- Per-column collation. GPORCA supports collation only when all columns in the query use the same collation.
Те, кто кодил под Teradata, наверное знают, что такое collation и как неожиданно она может влиять на рез-т сортировки по полю.
- DML and COPY ... FROM operations on foreign tables.
- Unsupported index-related features include:
🔸Index scan on AO tables
🔸Partial dynamic index scan
🔸Partial indexes
🔸Forward and backward dynamic index and dynamic index-only scans on partitioned tables
🔸Indexed expressions (an index defined as an expression based on one or more columns of the table)
🔸Combined indexes
- ф-ии созданные с опцией ON COORDINATOR, ON ALL SEGMENTS.
К слову, узнать режим выполнения ф-ии можно в pg_proc.proexeclocation: m - master only , s - all segments ( Спасибо @reshke за подсказку! Напомню, он
в списке участников в весеннем BigDat-нике, ссылку на который кидал 11 марта)
👍5
☯️Ну что, оказывается не все так просто с функциями в GP?
Мне остается добавить, что мы на проекте по требованиям Архитектора тоже базово предоставляем доступ к данным через функции, где идет отбор последних версий из SCD2 таблиц
запросом вида
select a.* from foo a join (  select account_rk,effective_date, max(version_id) version_id
from foo where version_id<=p_version_id
group by account_rk,effective_date) b
using (account_rk,effective_date,version_id)

И хотя такой типовой SQL запрос не попадает ни в одну из перечисленных категорий трансформаций, видимо, именно опция создания ф-ии ON ALL SEGMENTS приводит к тому, что
такая ф-я выполняется Postgre оптимизатором, что крайне неэффективно, как по времени выполнения, так и по размеру спилла(10-20 TB).
Сам ее SQL запрос отрабатывает без него и на порядок быстрее.
Translation of last published secret:
Secret 32 ​​(Beware of functions!)
I decided to dedicate the anniversary secret (for GP development sharks) to functions, where would we be without them.
It may seem superfluous to some, like a wisdom tooth, but nevertheless, believe me, sooner or later every third person will remember about it.
Indeed, not all tasks are solved by SQL query(s), sometimes you have to resort to cycles, branches and, God forbid, recursion.
I have an example of such a task, we've been there before - correlation of telecom traffic, when it is necessary to match 2 legs of 1 call:
incoming and outgoing, according to flexible rules, where the fixed key is the phone number, and then after its standardization,
and the duration and time of the call are fuzzy keys, because they are never required to match.
His Majesty, a task from the flexible match class. Non-trivial, as it may seem at first glance.

So for those who will have to take (or have already taken, like us) the functions into service, I think the list below will be useful.

The point is that in cases of the following types of queries, when executing the function, not the GPORCA planner is used, but Legacy (PostgreSQL).
Such cases are not supported by the GPORCA optimizer by default and when executing such a query (from the function), the Greenplum log will contain the entry
Falling back to Postgres-based planner because GPORCA does not support the following feature: UTILITY command.
If there is no access to the DB logs, then you can check the selected planner in the query plan.

♓️ - SP-GiST indexing method. GPORCA supports only B-tree, bitmap, GIN, and GiST indexes. GPORCA ignores indexes created with unsupported methods.
♒️ - The SELECT command's TABLESAMPLE clause
♑️ - The optional WITH ORDINALITY clause of a function call inside the FROM clause of a SELECT command.
♐️ - Multi-level partitioned tables
♏️ - Non-uniform partitioned tables
♎️ - SortMergeJoin (SMJ)
♍️ - Ordered aggregations
♌️ - Multi-argument DISTINCT qualified aggregates, e.g. SELECT corr(DISTINCT a, b) FROM foo
♋️ - Multiple grouping sets specified using a duplicate alias in a null-producing grouping set spec.
Such queries fall back to the Postgres-based planner unless you directly coerce the alias to a separate variable as shown in the example below:
CREATE TABLE foo AS SELECT i AS ai1, i AS ai2 FROM generate_series(1, 3)i;
SELECT ai1, ai2 FROM foo GROUP BY ai2, ROLLUP(ai1) ORDER BY ai1, ai2;

♊️ - These scalar operators:
🔸ROW
🔸ROWCOMPARE
🔸FIELDSELECT
♉️ - Aggregate functions that take set operators as input arguments // Indeed any examples of that case are welcome in comments
♈️ - Multiple Distinct Qualified Aggregates, such as below are not supported by default
🔥1
Translation of last published secret, part 2 of 3:
SELECT count(DISTINCT a), sum(DISTINCT b) FROM foo

They can be enabled with the optimizer_enable_multiple_distinct_aggs
- percentile_* window functions (ordered-set aggregate functions)
- Inverse distribution functions.
- Queries that contain UNICODE characters in metadata names, such as table names, and the characters are not compatible with the host system locale.
- SELECT, UPDATE, and DELETE commands where a table name is qualified by the ONLY keyword.
- Per-column collation. GPORCA supports collation only when all columns in the query use the same collation.
Those who've coded for Teradata probably know what collation is and how unexpectedly it can affect the result of sorting by a field.
- DML and COPY ... FROM operations on foreign tables.
- Unsupported index-related features include:
🔸Index scan on AO tables
🔸Partial dynamic index scan
🔸Partial indexes
🔸Forward and backward dynamic index and dynamic index-only scans on partitioned tables
🔸Indexed expressions (an index defined as an expression based on one or more columns of the table)
🔸Combined indexes
- Queries that run functions that are defined with the ON COORDINATOR or ON ALL SEGMENTS attribute.
By the way, you can find out the function execution mode in pg_proc.proexeclocation: m - master only, s - all segments
(Thanks to @reshke for the tip! Let me remind you, he is on the list of participants in the spring BigData meetup, the link to which I posted on March 11)
Translation of last published secret, part 3 of 3:
☯️Well, it turns out that not everything is so simple with functions in GP?
I have to add that on the project, according to the Architect's requirements, we also provide basic access to data through functions, where the latest versions are selected from SCD2 tables
with a request like
select a.* from foo a join (  select account_rk,effective_date, max(version_id) version_id
from foo where version_id<=p_version_id
group by account_rk,effective_date) b
using (account_rk,effective_date,version_id)

And although such a typical SQL query does not fall into any of the listed categories of transformations, apparently, it is the option of creating the ON ALL SEGMENTS function that leads to the fact that such a function is executed by the Postgre optimizer, which is extremely inefficient, both in terms of execution time and the size of the spill (10-20 TB). Its SQL query itself is processed without it and an order of magnitude faster.
Секрет 33 ( UNION + функция = полная деструкция )

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

Засветился вчера запрос вида

SELECT response_id
FROM foo$v r, stg.foo a WHERE r.contact_id = a.contact_id
UNION
SELECT response_id FROM foo_fn(0, :p_version_max).

Спилл 38 TB. Отличились! В этом году - это топ!

foo$v и foo_fn - это вью и ф-я, которые смотрят на одну и ту же партицированную помесячно табл-у из 62 секций,
так называемые аксессоры(в наших термиах), полного и интервального среза.
foo - временная табл-а.

Опустил константные фильтры каждого подзапроса, которые не влияют на суть происходящего.
А суть в том, что если материализовать каждый подзапрос в отдельную табл-у t1 и t2, то исходный запрос

SELECT response_id FROM t1
UNION
SELECT response_id FROM t2

выполняется без спилла.
При этом, запросы создания t1 и t2 спилл не создают ( суммарно 0.1 TB для t1 из 1.9 млрд строк, 0.05 TB для t2 из 1.2 млрд строк на кластер из primary 1056 сегментов).

Диагноз проблемы: Запрос исполнил легаси оптмизатор (PostgreSQL), а не GPORCA, ибо в запросе есть функция из класса Legacy т.к. создана ON ALL SEGMENS.
explain analyze зверушки выполнить не смог под моей РГ, но explain ( тут не привожу, 480 строк - ту мач ) выявил Broascast Motion в первом подазпросе( тот, что без ф-ии ),
что видимо и объясняет наличие исходного спилла. При чем, что интересно, Broascast появляется при обращении к вью, и вот этот датасает
уже претерпевает Redistribute при join-е с stg.foo, т.к. ключи обоих датасетов не согласованы.

Если же выполнить

explain analyze
SELECT response_id
FROM foo$v r, stg.foo a
WHERE r.contact_id = a.contact_id

то в плане Broascast-а нет, только Redistribute.
😱3
Translation of last published secret
Secret 33 ( UNION + function = complete destruction )

Well, 33 cows are on the playground, new game was brought in -).
I'll continue with secret 32, since the topic is not covered, especially since I came across a fresh example on the topic of why functions are mines.

A query of the type below appeared yesterday

SELECT response_id
FROM foo$v r, stg.foo a WHERE r.contact_id = a.contact_id
UNION
SELECT response_id FROM foo_fn(0, :p_version_max).

Spill 38 TB. Distinguished! This year - this is the top!

foo$v and foo_fn are view and function that source at the same monthly partitioned table of 62 sections,
the so-called accessors (in our terms), full and interval cut.
foo is a temporary table.

I omitted the constant filters of each subquery, which do not affect the essence of what is happening.
And the point is that if you materialize each subquery into a separate table t1 and t2, then the original query
 SELECT response_id FROM t1
UNION
SELECT response_id FROM t2

is executed without a spill.
At the same time, the queries for creating t1 and t2 do not create a spill (in total 0.1 TB for t1 from 1.9 billion rows, 0.05 TB for t2 from 1.2 billion rows per cluster
of 1056 primary segments).

Problem diagnosis: The query was executed by the legacy optimizer (PostgreSQL), not GPORCA, because the query contains a function from the Legacy class,
since it was created with directive ON ALL SEGMENTS.
explain analyze of the animal could not be executed under my RG(Resource Group), but explain (I will not cite it here, 480 rows - that's too much)
detected Broascast Motion in the first subquery (the one without the function),
which apparently explains the presence of the original spill. What's interesting is that Broascast appears when accessing the view, and this dataset
is already Redistributed when joining with stg.foo, since the keys of both datasets are not consistent.

To be clear, If you execute just 1st subQ
 explain analyze
SELECT response_id
FROM foo$v r, stg.foo a
WHERE r.contact_id = a.contact_id

GPORCA eliminates Broadcast, only Redistribute still in deal
ФИНАНСОВЫЕ РЕЗУЛЬТАТЫ ГРУППЫ ARENADATA ПО МСФО ЗА 2024 ГОД: РАСТУЩАЯ ПРИБЫЛЬ ПРИ ОТСУТСТВИИ ДОЛГА

отчет

Во втором квартале 2025 года Совет директоров Группы Arenadata рассмотрит вопрос о выплате дивидендов за отчетный период.
🔥1
BI_Database_Naming_Standards.pdf
474.6 KB
#DB#naming#convention# Стандарты наименования объектов БД - сохраню ка тут на память. В Инете уже потерли. Добротная вещица! Standards for naming database objects - I'll save them here as a keepsake. They've already been erased from the Internet. A brilliant thing!
👍4🔥1
На заметку
Наткнулся в комьюнити Greenplum Russia на пост:
Там как-то ЦТЕшками заставили гпорку с ума сойти. Если убрать условие where, то план ожидаем, если с условием, то зачем-то материализуются tab_1 и tab_2 полностью на мастере FROM tab.tab_1 a
LEFT JOIN tab.tab_2 b ON SPLIT_PART(b.number, '_', 1) = a.number
WHERE a.number::bigint NOT IN (
SELECT number FROM cte_1
UNION ALL
SELECT number FROM cte_2

Улыбнуло, это прям таки оскар в номинации - самый яркий антипаттерн.
Кстати, если убрать WHERE, в общем случае получим 2 Redistribute в плане для LEFT JOIN, 1й перераспределит b по SPLIT_PART(b.number, '_', 1), 2й - a по a.number
👍3👏1
Вопрос 2
А вы не задумывались, как работает LIMIT в запросе
insert into t as select * from s LIMIT n ?
В какой момент происходит стоп записи и как именно происходит накачка данными с каждого сегмента ?

Question 2
Have you ever wondered how LIMIT works in the query
insert into t as select * from s LIMIT n ?
At what point does the recording stop and how exactly does the data pumping from each segment occur?
На заметку
#table #create #time
Сегодня потребовалось узнать дату создания табл-ы. Сходу не нагуглил, так что сохраню здесь.
Замечу, что 1) фиксируется момент начала транзакции CREATE, а не конца; 2) после DROP это инфо уже недоступно
Today I needed to know the date of table creation. I couldn't find it right away on Google, so I'll save it here.
Note that the moment of the beginning of the CREATE transaction is recorded, not the end. Since DROP table the info is no longer available
.
select statime, *
from pg_stat_last_operation o,
pg_class c
where c.relname = 'foo'
and o.objid = c.oid
🔥12
Вопрос 3
В таблице foo (AOCO zstd1) 109.2 млрд строк, равномерно размазанных на 1188 одноранговых секций ( ключ секции - document_date( DATE ), отличен от ключа дистрибуции ).
select count(*) from foo висит вечно = не работает.
После analyze foo тот же запрос вернет рез-т за 1.5 мин.
Что изменилось после analyze, что запрос подсчета строк смог отработать.
Question 3
The foo table (AOCO zstd1) has 109.2 billion rows, evenly spread across 1188 partitions (the partition key is document_date( DATE ), differs from distribution key).
select count(*) from foo hangs forever = does not work.
After analyze foo the same query will return the result in 1.5 minutes.
What changed after analyze that the row count query was able to complete.
1 апрельский квест
Таблица foo распределена по полю hash_key. Сколько Redistribute выполнит запрос:
April 1 Quest Table foo is distributed by hash_key field. How many Redistribute will execute the query: select gp_segment_id, count(*) FROM foo group by 1
Anonymous Poll
63%
0
37%
1
🐳3
Greenplum secrets🎩
Вопрос 3 В таблице foo (AOCO zstd1) 109.2 млрд строк, равномерно размазанных на 1188 одноранговых секций ( ключ секции - document_date( DATE ), отличен от ключа дистрибуции ). select count(*) from foo висит вечно = не работает. После analyze foo тот же запрос…
Ответ: Вот он - принцип MapReduce в чистом виде. На планах ниже видно, что без analyze агрегация, грубо говоря суммирование маркеров каждой строки,
выполняется на мастере,
тогда как после analyze на мастер поступают готовые агрегаты с каждого сегмента.
В первом случае, мастер видимо не может выполнить эту работу (для 100 ГБ интерконнекта прокачать 212 ГБ данных таблицы с сегментов - не проблема),
т.к. агрегация идет на одном ядре, куда стекаются потоки от всех 348 сегментов кластера.

Answer: Here it is - the MapReduce principle in its purest form. The plans below show that without analyze, aggregation, roughly speaking,
summing up the markers of each row, is performed exclusively on the master,
while after analyze, the master receives ready aggregates from each segment.
In the first case, the master apparently can't do this work (for 100 GB of interconnect, pumping 212 GB of table data from segments is not a problem),
since aggregation occurs on one core, where flows from all 348 cluster segments flow.


План A: Запрос без статы: A:No statistics on table
Aggregate  (cost=0.00..431.00 rows=1 width=8) (actual time=1072.009..1072.009 rows=1 loops=1)
-> Gather Motion 348:1 (slice1; segments: 348) (cost=0.00..431.00 rows=1 width=1) (actual time=0.997..695.980 rows=7000000 loops=1)
-> Seq Scan on foo (cost=0.00..431.00 rows=1 width=1) (actual time=0.980..4.703 rows=21999 loops=1)
Planning time: 11.125 ms
(slice0) Executor memory: 909K bytes.
" (slice1) Executor memory: 298K bytes avg x 348 workers, 298K bytes max (seg0)."
Memory used: 524288kB
Optimizer: Pivotal Optimizer (GPORCA)
Execution time: 1081.252 ms

План Б: Оптимальный запрос: B:There is statistics
Aggregate  (cost=0.00..442.05 rows=1 width=8) (actual time=266.551..266.551 rows=1 loops=1)
-> Gather Motion 348:1 (slice1; segments: 348) (cost=0.00..442.05 rows=1 width=8) (actual time=5.237..266.416 rows=348 loops=1)
-> Aggregate (cost=0.00..442.05 rows=1 width=8) (actual time=6.519..6.519 rows=1 loops=1)
-> Seq Scan on foo (cost=0.00..442.01 rows=20115 width=1) (actual time=0.498..5.115 rows=21999 loops=1)
Planning time: 14.683 ms
(slice0) Executor memory: 429K bytes.
" (slice1) Executor memory: 298K bytes avg x 348 workers, 298K bytes max (seg0)."
Memory used: 524288kB
Optimizer: Pivotal Optimizer (GPORCA)
Execution time: 289.056 ms


Для иллюстрации планы даны для гораздо меньшей таблицы, но к сути вопроса это не относится.
For the sake of simplicity, the plans are given for a much smaller table, but it doesn't matter.

p.s.
Для Legacy оптимизатора такой проблемы не существует -)
После set optimizer = off запрос отрабатывает по плану Б.
For the Legacy optimizer there is no such problem -)
After set optimizer = off the query is processed according to plan B.
🐳3👍1
꧁꧂Не успели отшипеть пузырики в новогодних бокалах шампанского, под звон которых я думал о том, будет ли год 2025 так же щедр на секреты
нашей любимой зеленой сливы, как позади уже 1й квартал.
На удивление, Грин-плюмбум ( отсылка к фильму "Плюмбум, или опасная игра" ) не перестает удивлять своей архитекторой, интригуя
каждую новую неделю чем-то новым и неожиданным.
Все таки дополнительное измерение сложности в виде соединения считалок в Сеть - это интереснейшая тема,
которая наделяет проект Аренадаты потенциалом органического роста, заставля держать нас в уме главный девиз синергии 1+1 > 2

Надеюсь, даэже если такие именитые вендоры как Oracle, Teradata вернутся на наш рынок, они не дождутся смерти
Greenplum, который составит им на тот момент еще более серьезную конкуренцию
и те, кто едут на нем, на нем же и продолжать свой путь в Big Data.

Друзья, Всем лучи добра и хорошей комфортной езды без слива, тьфу без спилла -)☮️
🐳6👏2
Greenplum secrets🎩
1 апрельский квест
Таблица foo распределена по полю hash_key. Сколько Redistribute выполнит запрос:
April 1 Quest Table foo is distributed by hash_key field. How many Redistribute will execute the query: select gp_segment_id, count(*) FROM foo group by 1
Неожиданно, но корректный ответ - 1. Надеюсь, что если тут есть Архитекторы или Продакты Арены, они инициируют доработку, т.к.
Redistribute тут не нужен, т.к. служебное поле gp_segment_id - уже готовый ключ для локальной агрегации на каждом сегменте.
Я бы сказал, это нужная доработка, т.е. ускорит расчет перекоса в таблицах, что является важной рутинной операцией.

Surprisingly, but the correct answer is 1. I hope that if there are Arena Architects or Product owners here, they will initiate the revision, because
Redistribute is nonsense here, because the hidden field gp_segment_id is a ready-made key for local aggregation on each segment.
I would say that this is a necessary revision, i.e. it will speed up the calculation of the skew in the tables, which is an important routine operation.
👍1🐳1
Как вы проверяете наличие данных в таблице ?
How do you check if data exists in a table?
Anonymous Poll
46%
select count(*) from x
54%
select 1 from x limit 1
Greenplum secrets🎩
Как вы проверяете наличие данных в таблице ?
How do you check if data exists in a table?
Думаю, что вариант с select count(*) лучше.
Как мы знаем из плана запроса, о чем писал выше, в случае собранной по табл-е статистике, что как правило имеет место,
count(*) делает агрегацию пред агрегатов, посчитанных на сегментах,
а как работает LIMIT для меня до сих пор загадка.

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

I think that the option with select count(*) is better.
As we know from the query plan, which I wrote about above, in the case of statistics collected from the table, which usually takes place,
count(*) makes an aggregation of pre-aggregates calculated on segments,
and how LIMIT works is still a mystery to me.

The conclusion is also confirmed by the statistics I collected from running 5000 different AirFlow flows - since we had both options in production, there was an opportunity to compare.

The difference is noticeable, if we talk about the average run time of both versions of the code in the interval of 2 weeks.
👍4
Greenplum secrets🎩
Думаю, что вариант с select count(*) лучше. Как мы знаем из плана запроса, о чем писал выше, в случае собранной по табл-е статистике, что как правило имеет место, count(*) делает агрегацию пред агрегатов, посчитанных на сегментах, а как работает LIMIT для…
Опровержение!
Оказывается, не все так просто.
Поставил эксперимент.
Взял топ 98 самых больших таблиц на проме: 38 партицированных, 60 - нет. Общий вес 78 TB.
Проверил наличие данных для каждой в цикле,
результат - безоговорочная победа метода LIMIT 1, который показал суммарное время = 2.8 ceк, тогда как COUNT(*) дал 1455 сек.

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

А теперь самое интересное.
Я решил проверить, а можно ли ускорить получение первой произвольной строки через LIMIT 1, добавив отсечку по gp_segment_id.

Запилил такую ф-ю:

CREATE OR REPLACE FUNCTION is_tbl_empty(p_tbl_nm text, p_segments_cnt int, p_start_segment_id int)
RETURNS boolean
LANGUAGE plpgsql
AS $function$
declare
v_ int;
begin
for c in 0 .. p_segments_cnt - 1
loop
execute 'select 1 from ' || p_tbl_nm || ' where gp_segment_id = $1 limit 1' into v_ using mod( coalesce(p_start_segment_id,0)+c, p_segments_cnt) ;
if v_ = 1 then
return false;
end if;
end loop;
return true;
end;
$function$


которая возвращает true для пустой табл-ы ( иначе false ), читая первый сегмент, заданный параметром p_start_segment_id
Если табл-а пустая, придется прочитать все p_segments_cnt сегментов.
Но как правило, в реальности имеем дело с новыми даныыми каждый день, поэтому вероятность, что в каждом сегменте будут данные почти 1.

Читать по порядку от 0 до p_segments_cnt - 1 не стоит, т.к. повышаем вероятность нагрузки на сегменты с наименьшими номерами, поэтому функции в
p_start_segment_id лучше передавать random(0, p_segments_cnt).

Неожиданно, данный метод показал лучший результат лишь для 91 из 98 таблиц списка - суммарно затратив 0.9 сек против 4.8 с на все табл-ы .
Для остальных 7 таблиц ( из которых 1 оказалась партицированной) ф-я дала рез-т сильно хуже, в итоге суммарно ф-я затратила 18.7 с против 5 сек у LIMIT 1.

Я проверил для отдельной табли-ы без партиций из этих 7, нет ли в ней перекоса - особо не выявлено.
Вместо p_start_segment_id = 0 я задал для функции номер сегмента с наименьшим числом запиесей ( 87 ) рез-т почти не изменился.
Рез-т воспроизводится. Эти 7 таблиц раскиданы случайно в общем наборе, упорядоченном по убыванию размера таблиц.
Обяснения у меня нет. Но, надеюсь, это не деградация дисков.
🔥5
Секрет 34 ( Порядок бьет класс, v/2.0 или кэшбэк от 11 до 45% на ровном месте )

Как мы знаем из https://t.iss.one/mpp_secrets/52 размер таблицы зависит от выбранного ключа дистрибуции.
Оказывается, размер таблицы зависит и от порядка строк в табл-е
и это можно использовать в нашу пользу, если отсортировать данные по ключу дистрибуции.

Проведем тест.
Для краткости отмечу, что все таблицы ниже создаются колоночными со сжатием zstd1, т.е.
WITH (appendonly=true,orientation=column,compresstype=zstd,compresslevel=1),

🔸Тест 1: 101 млн записей, низкая энтропия данных
Синтетика:
create table t1 as
select a.n, current_date + a.n dt
from (select generate_series(1, 1000000) n) a distributed by(n);

insert into t1
select a.n + trunc(random() * 1000), a.dt
from t1 a
join (select generate_series(1, 100) n) b on 1 = 1;

Сортировка:
create table t1_ordered as
select *
from t1
order by 1 distributed by(n);

select count(*) from t1 -- 101 000 000

Рез-т:
select pg_relation_size('t1') -- 468 795 552
select pg_relation_size('t1_ordered') -- 258 197 512
Экономия места: 45%

🔸Тест 2: 101 млн записей, высокая энтропия данных
Синтетика:
create table t2 as
select a.n, current_date + a.n dt, random() rnd
from (select generate_series(1, 1000000) n) a distributed by(n);

insert into t2
select a.n + trunc(random() * 100000000), a.dt, random() rnd
from t3 a
join (select generate_series(1, 100) n) b on 1 = 1;

Сортировка:
create table t2_ordered as
select *
from t2
order by 1 distributed by(n);

Чек идентичности данных:
select count(*) from t2 -- 101 000 000
select h, sum(cnt) from (
select textin(record_out(t2)) as h, 1 as cnt from t3
union all
select textin(record_out(t2_ordered)) as h, -1 as cnt from t3_ordered) foo
group by h
having sum(cnt) <> 0;

=> 0 строк ( сеты совпадают )

Рез-т:
select pg_relation_size('t2') -- 1 206 135 536
select pg_relation_size('t2_ordered') -- 1 075 361 608
Экономия места: 11%

Напомню, энтропия R - мера хаоса системы, а в данном контексте - % уникальных значений в столбце, т.е.
t2.rnd имеет экстремальную кардинальность ввиду его определения ( 101 млн случайных чисел ),
у dt - R в 100 раз ниже,
R ключа дистрибуции почти такая же как у rnd( ~100 млн случайных чисел) в тесте №2 и в 100 раз меньше - в 1-м тесте ( не более 1.001 млн случайных чисел )

🔸Кого заинтересовал рецепт, делитесь своими результатами - спасем планету от лишнего мусора!
Рассмотрены крайние случаи.
🤔3👍2❤‍🔥1