Forwarded from MarketTwits
Секрет 9(Осторожно сиквенс!)
Как-то мне поручили аудит DWH на сотни TB, точнее его etl потоков.
В выгрузке в стэйж из источника для генерации уникального ключа строки
было обнаружено массовое использование сиквенсов, своего в каждом потоке.
Для архитекторов у меня теперь было 2 новости - плохая и хорошая.
Плохая в том, что сиквенсы использовались некэшированные, а это - дорогое удовольствие.
Проведем эксперимент.
Secret 9 (Beware of sequence!)
Once I was assigned to audit DWH for hundreds of TB, or rather its etl streams.
In the unloading to stage from the source for generating a unique row key,
massive use of sequences was discovered, one for each stream.
For architects, I now had 2 news - bad and good.
The bad news is that sequences were used uncached, and this is an expensive pleasure.
Let's conduct an experiment.
А теперь сгенерируем 1 млн значений сиквенса, использующего кэш.
Now let's generate 1 million sequence values using the cache.
1й вариант медленнее в 190 раз!
Но есть нюанс.
Если вам нужен диапазон ключей без дырок, то быстрый вариант подойдет не всегда
Проверим этот факт, запросив сиквенс в двух разных сессиях.
Option 1 is 190 times slower!
But there is a nuance.
If you need a range of keys without holes, then the fast option will not always work
Let's check this fact by requesting a sequence in two different sessions.
Также, стоит отметить, что на каждый созданный сиквенс выделяется место, например в нашем кластере из 720 нод он весит 23 MB.
Таким образом, если под каждый поток или etl выгрузку создается новый сиквенс без его последующего удаления,
что имело место в нашем DWH ( Don't ask me - why ), то это плохое проектирование, плата за которое очень высока.
В рамках моего аудита я обнаружил 737 000 таких зомби весом в 15 TB, что также засоряет и каталог.
Also, it is worth noting that space is allocated for each created sequence, for example, in our cluster of 720 nodes it weighs 23 MB.
Thus, if a new sequence is created for each stream or etl unload without its subsequent deletion,
which was the case in our DWH (Don't ask me - why), then this is bad design, the price for which is very high.
As part of my audit, I found 737,000 such zombies weighing 15 TB, which also clutters the catalog.
Как-то мне поручили аудит DWH на сотни TB, точнее его etl потоков.
В выгрузке в стэйж из источника для генерации уникального ключа строки
было обнаружено массовое использование сиквенсов, своего в каждом потоке.
Для архитекторов у меня теперь было 2 новости - плохая и хорошая.
Плохая в том, что сиквенсы использовались некэшированные, а это - дорогое удовольствие.
Проведем эксперимент.
Secret 9 (Beware of sequence!)
Once I was assigned to audit DWH for hundreds of TB, or rather its etl streams.
In the unloading to stage from the source for generating a unique row key,
massive use of sequences was discovered, one for each stream.
For architects, I now had 2 news - bad and good.
The bad news is that sequences were used uncached, and this is an expensive pleasure.
Let's conduct an experiment.
create sequence public.sq_1;
create table public.sq_1_tst
as select nextval('public.sq_1') sq, generate_series(1,1000000) n;
1,000,000 rows affected in 3 m 11 s 203 ms
А теперь сгенерируем 1 млн значений сиквенса, использующего кэш.
Now let's generate 1 million sequence values using the cache.
create sequence public.sq_1000 cache 1000;
create table public.sq_1000_tst
as select nextval('public.sq_1000') sq, generate_series(1,1000000) n;
1,000,000 rows affected in 1 s 156 ms
1й вариант медленнее в 190 раз!
Но есть нюанс.
Если вам нужен диапазон ключей без дырок, то быстрый вариант подойдет не всегда
Проверим этот факт, запросив сиквенс в двух разных сессиях.
Option 1 is 190 times slower!
But there is a nuance.
If you need a range of keys without holes, then the fast option will not always work
Let's check this fact by requesting a sequence in two different sessions.
SQL> select pg_backend_pid(), nextval('public.sq_1000')
pg_backend_pid | nextval
---------------+--------
11309 | 1001001
SQL> select pg_backend_pid(), nextval('public.sq_1000')
pg_backend_pid | nextval
---------------+--------
12208 | 1002001Также, стоит отметить, что на каждый созданный сиквенс выделяется место, например в нашем кластере из 720 нод он весит 23 MB.
Таким образом, если под каждый поток или etl выгрузку создается новый сиквенс без его последующего удаления,
что имело место в нашем DWH ( Don't ask me - why ), то это плохое проектирование, плата за которое очень высока.
В рамках моего аудита я обнаружил 737 000 таких зомби весом в 15 TB, что также засоряет и каталог.
Also, it is worth noting that space is allocated for each created sequence, for example, in our cluster of 720 nodes it weighs 23 MB.
Thus, if a new sequence is created for each stream or etl unload without its subsequent deletion,
which was the case in our DWH (Don't ask me - why), then this is bad design, the price for which is very high.
As part of my audit, I found 737,000 such zombies weighing 15 TB, which also clutters the catalog.
❤3👍1
Секрет 10( Ускоряем count(distinct) )
Наверное многие знают, что подсчет уникальных значений - одна из самых дорогих операций для БД
Однако, оказывается, зачастую ее можно ускорить.
Например, если надо посчитать count(distinct) по полю, входящему в хэш таблицы,
у меня для вас хорошие новости.
Существует альтернатива, не требующая больших доработок кода- все, что нужно знать - ключ распределения таблицы,
в которой считаем уникальные кортежи.
Синтезируем датасет из 1 млрд триплетов, который и будет его хэшом , в 3 шага.
Создадим 1000 уникальных значений
Secret 10 (Speeding up count(distinct))
Many people probably know that counting unique values is one of the most expensive operations for a database.
However, it turns out that it can often be accelerated.
For example, if you need to calculate count(distinct) by a field included in the table hash, I have good news for you.
There is an alternative that does not require major code modifications - all you need to know is the distribution key of the table,
in which we count unique tuples.
We synthesize a dataset of 1 billion triplets, which will be its hash, in 3 steps.
Let's create 1000 unique values
Размножим его до 1 млн строк (1000 x1000):
Let's multiply it to 1 million rows (1000x1000):
Ради интереса проверим, сколько уникальных n2 получилось ?
For the sake of interest, let's check how many unique n2 we got?
select count(distinct n2) from public.tst2 -- 248 083 ( А вы думали 1 млн Did you think 1 million? )
И наконец размножим сет до 1 млрд строк ( 1 000 000 x 1000 ), не забыв про ключ распределения:
And finally, we will multiply the set to 1 billion rows (1,000,000 x 1000), not forgetting about the distribution key
Для тех, кому понравился предыдущий чек, уникальных значений n3 получилось 32 136 768
Проверим число строк в итоговом датасете
For those who liked the previous check, the unique values of n3 were 32,136,768.
Let's check the number of rows in the final dataset
и его хэш and its hash
Все готово для гонок.
Замерим старый добрый подход
All set for racing.
Let's measure the good old school approach
И альтернативу, где используется дедупликация для подачи в count уже уникального набора
And we'll run an alternative that uses deduplication to feed count with an already unique set
Если глянуть на explain analyze на фото, то можно заметить, что планы почти одинаковы, но в быстром варианте (справа) есть дополнительный Agregate,
который делается на нодах и только потом сокращенная выборка пересылается на мастер (Gather Motion), где идет финальный подсчет числа элементов.
Будем считать, что на фото выполнен повторный прогон бенчмарка в режиме explain analyze, где быстрый вариант показал почти то же самое время, а традиционный сильно ускорился, но все равно в 7 раз медленнее.
If you look at the explain analyze in the photo, you can see that the plans are almost the same..
Наверное многие знают, что подсчет уникальных значений - одна из самых дорогих операций для БД
Однако, оказывается, зачастую ее можно ускорить.
Например, если надо посчитать count(distinct) по полю, входящему в хэш таблицы,
у меня для вас хорошие новости.
Существует альтернатива, не требующая больших доработок кода- все, что нужно знать - ключ распределения таблицы,
в которой считаем уникальные кортежи.
Синтезируем датасет из 1 млрд триплетов, который и будет его хэшом , в 3 шага.
Создадим 1000 уникальных значений
Secret 10 (Speeding up count(distinct))
Many people probably know that counting unique values is one of the most expensive operations for a database.
However, it turns out that it can often be accelerated.
For example, if you need to calculate count(distinct) by a field included in the table hash, I have good news for you.
There is an alternative that does not require major code modifications - all you need to know is the distribution key of the table,
in which we count unique tuples.
We synthesize a dataset of 1 billion triplets, which will be its hash, in 3 steps.
Let's create 1000 unique values
create table public.tst1 WITH (appendonly=true,orientation=column,compresstype=zstd,compresslevel=1)
as select generate_series(1,1000) n1;
Размножим его до 1 млн строк (1000 x1000):
Let's multiply it to 1 million rows (1000x1000):
create table public.tst2 WITH (appendonly=true,orientation=column,compresstype=zstd,compresslevel=1)
as select t1.n1, t1.n1*t2.n1 n2 from
public.tst1 t1, public.tst1 t2
Ради интереса проверим, сколько уникальных n2 получилось ?
For the sake of interest, let's check how many unique n2 we got?
select count(distinct n2) from public.tst2 -- 248 083 ( А вы думали 1 млн Did you think 1 million? )
И наконец размножим сет до 1 млрд строк ( 1 000 000 x 1000 ), не забыв про ключ распределения:
And finally, we will multiply the set to 1 billion rows (1,000,000 x 1000), not forgetting about the distribution key
create table public.tst3 WITH (appendonly=true,orientation=column,compresstype=zstd,compresslevel=1)
as select t1.n1, t1.n2, t1.n2*t2.n1 n3 from
public.tst2 t1, public.tst1 t2
distributed by (n1,n2,n3);
Для тех, кому понравился предыдущий чек, уникальных значений n3 получилось 32 136 768
Проверим число строк в итоговом датасете
For those who liked the previous check, the unique values of n3 were 32,136,768.
Let's check the number of rows in the final dataset
SQL> select reltuples from pg_class c, pg_namespace s where relname = 'tst3'
and s.nspname = 'public'
and s.oid = c.relnamespace;
reltuples
--------------
1 000 000 000
и его хэш and its hash
SQL>select pg_get_table_distributedby('public.tst3'::regclass)
pg_get_table_distributedby
------------------------------------------
DISTRIBUTED BY (n1, n2, n3)Все готово для гонок.
Замерим старый добрый подход
All set for racing.
Let's measure the good old school approach
SQL>select count(distinct n1) from public.tst3
count
--------
1 000
execution: 6 m 30 s 459 ms
И альтернативу, где используется дедупликация для подачи в count уже уникального набора
And we'll run an alternative that uses deduplication to feed count with an already unique set
SQL> select count(*)
from (
select n1
from (
select n1, n2, n3
from dbg.tst3
group by 1, 2, 3) a
group by 1) b;
count
---------
1 000
execution: 15 s 386 ms
Если глянуть на explain analyze на фото, то можно заметить, что планы почти одинаковы, но в быстром варианте (справа) есть дополнительный Agregate,
который делается на нодах и только потом сокращенная выборка пересылается на мастер (Gather Motion), где идет финальный подсчет числа элементов.
Будем считать, что на фото выполнен повторный прогон бенчмарка в режиме explain analyze, где быстрый вариант показал почти то же самое время, а традиционный сильно ускорился, но все равно в 7 раз медленнее.
If you look at the explain analyze in the photo, you can see that the plans are almost the same..
❤8
Секрет Полишинеля или Будет всё как ты захочешь!
А почему собственно кубик Рубика ?
Почему не пирамидка (как на фото 1)?
В детстве у меня была пирамидка и совсем не помню какой-либо сложности в ней.
Или почему не мини 2x2x2 ?
Если бы мой канал был про Oracle, MS SQL или Postgres, то его аватаркой вполне мог бы стать тот что на фото 2,
который собрать не так просто, как кажется. И если в Oracle я погружался 16 лет, то
в 2x2x2 - 2 недели, прежде чем понял что к чему.
Segreto di Pulcinella or Everything will be as you want it to be!
And why a Rubik's cube is logo for the channel ?
Why not a pyramid (as in photo 1)?
As a child, I had a pyramid and I don't remember any difficulty in it.
Or why not a mini 2x2x2?
If my channel was about Oracle, MS SQL or Postgres, then its avatar could well be the one in photo 2,
which is not as easy to assemble as it seems. And if I immersed myself in Oracle for 16 years, then in 2x2x2 - 2 weeks, before I understood what was what.
А почему собственно кубик Рубика ?
Почему не пирамидка (как на фото 1)?
В детстве у меня была пирамидка и совсем не помню какой-либо сложности в ней.
Или почему не мини 2x2x2 ?
Если бы мой канал был про Oracle, MS SQL или Postgres, то его аватаркой вполне мог бы стать тот что на фото 2,
который собрать не так просто, как кажется. И если в Oracle я погружался 16 лет, то
в 2x2x2 - 2 недели, прежде чем понял что к чему.
Segreto di Pulcinella or Everything will be as you want it to be!
And why a Rubik's cube is logo for the channel ?
Why not a pyramid (as in photo 1)?
As a child, I had a pyramid and I don't remember any difficulty in it.
Or why not a mini 2x2x2?
If my channel was about Oracle, MS SQL or Postgres, then its avatar could well be the one in photo 2,
which is not as easy to assemble as it seems. And if I immersed myself in Oracle for 16 years, then in 2x2x2 - 2 weeks, before I understood what was what.
Из курса "Теория систем" в магистратуре МИФИ мне особенно врезался в память один постулат - чем сложнее система, тем
сложнее вывести ее из равновесия или разрушить.
Кубик Рубика (3x3x3) - образцово-показательный пример сложной системы, разрушить энтропию которой,
т.е. собрать случайным образом невозможно.
Ну действительно, найти случайными поворотами 1 единственное положение из
43 252 003 274 489 856 000 может только Бог, имея бесконечный запас времени.
К слову, кубик можно собрать из любого положения за 22 хода - режим Бога -)
Я верю, что рост экспертизы в нашем ремесле происходит квантовыми скачками.
Собрать 1-ю грань кубика любой размерности очень легко, дальше начинаются игры разума.
Моим перsым открытием после тщетного ковыряния трешки было - а почему бы не взяться за мини, который я в итоге решил
после неожиданно родившейся на исходе 2й недели следующей гипотезы.
А что, если включить бритву Оккамы, отсекая лишнее: фиксируем намертво 1 кубик за начало координат( без возможности поворота),
потом решаем тот, что будет на главной диагонали и далее
выставляем остальные по диагоналям граней. В итоге осталось собрать 2 кубика, которые были дезориентированы на одном ребре.
Для их сборки, как выяснилось, существует формула сборки ( 1 из тысяч алгоритмов перестановок конкретных кубиков ), но об этом я узнал потом, когда подружил эту сладкую парочку.
Про паттерны проектирования в разработке кода я тоже узнал не сразу.
Сейчас понимаю, если бы не разборка собранного кубика в уме по определенным циклам, я бы долго возился с этой парой.
Моей следующей гипотезой стала - а не есть ли трешка по сути матрешка из мини(углы) , креста внутри ( реберные кубики ) и окошек - центральные кубики,
которые не связаны между собой.
После победы над мини, я без проблем собрал каркас (углы), решив уже известную задачу и стал интуитивно собирать ребра, как пойдет, главное, не ломая углы.
При этом я был почти уверен, что если собрать три ортогональные грани, то противоположные автоматом сложатся корректно.
Таки мне удалось собрать "половину" куба, но это был провал. Гипотеза не подтвердилась. Остальные 3 грани не сложились,
т.к. эти 3 кубика было уже не решить ( см. foto_3 ). По крайне мере, с ходу не наГуглил как ее решить.
From the course "Systems Theory" in the MEPhI master's program, one postulate especially stuck in my memory - the more complex the system, the more difficult it is to unbalance or destroy it.
The Rubik's Cube (3x3x3) is an exemplary example of a complex system, the entropy of which cannot be destroyed,
i.e., assembled randomly.
Well, indeed, only God can find 1 unique position out of
43,252,003,274,489,856,000 by random turns, having an infinite supply of time.
By the way, the cube can be assembled from any position in 22 moves - God mode -)
I believe that the growth of expertise in our craft occurs in quantum leaps.
It is very easy to assemble the 1st face of a cube of any dimension, then the mind games begin.
My first discovery after futilely picking at 3x3x3 was - why not take on the mini (2x2x2), which I eventually solved
after the following hypothesis was unexpectedly born at the end of the 2nd week.
What if we turn on Occam's razor, cutting off the excess: we fix 1 cube tightly at the origin of coordinates (without the possibility of rotation),
then we solve the one that will be on the main diagonal and then
we set the rest along the diagonals of the faces. As a result, it remained to solve 2 cubes that were disoriented on one edge.
As it turned out, there is an assembly formula for assembling them (1 of thousands of algorithms for permuting specific cubes), but I learned about this later,
when I lined up this sweet couple just right.
In my career as a developer I also did not immediately learn about design patterns in code development.
Now I understand that if it were not for disassembling the assembled cube in my mind according to certain cycles, I would have fiddled with this pair for a long time.
сложнее вывести ее из равновесия или разрушить.
Кубик Рубика (3x3x3) - образцово-показательный пример сложной системы, разрушить энтропию которой,
т.е. собрать случайным образом невозможно.
Ну действительно, найти случайными поворотами 1 единственное положение из
43 252 003 274 489 856 000 может только Бог, имея бесконечный запас времени.
К слову, кубик можно собрать из любого положения за 22 хода - режим Бога -)
Я верю, что рост экспертизы в нашем ремесле происходит квантовыми скачками.
Собрать 1-ю грань кубика любой размерности очень легко, дальше начинаются игры разума.
Моим перsым открытием после тщетного ковыряния трешки было - а почему бы не взяться за мини, который я в итоге решил
после неожиданно родившейся на исходе 2й недели следующей гипотезы.
А что, если включить бритву Оккамы, отсекая лишнее: фиксируем намертво 1 кубик за начало координат( без возможности поворота),
потом решаем тот, что будет на главной диагонали и далее
выставляем остальные по диагоналям граней. В итоге осталось собрать 2 кубика, которые были дезориентированы на одном ребре.
Для их сборки, как выяснилось, существует формула сборки ( 1 из тысяч алгоритмов перестановок конкретных кубиков ), но об этом я узнал потом, когда подружил эту сладкую парочку.
Про паттерны проектирования в разработке кода я тоже узнал не сразу.
Сейчас понимаю, если бы не разборка собранного кубика в уме по определенным циклам, я бы долго возился с этой парой.
Моей следующей гипотезой стала - а не есть ли трешка по сути матрешка из мини(углы) , креста внутри ( реберные кубики ) и окошек - центральные кубики,
которые не связаны между собой.
После победы над мини, я без проблем собрал каркас (углы), решив уже известную задачу и стал интуитивно собирать ребра, как пойдет, главное, не ломая углы.
При этом я был почти уверен, что если собрать три ортогональные грани, то противоположные автоматом сложатся корректно.
Таки мне удалось собрать "половину" куба, но это был провал. Гипотеза не подтвердилась. Остальные 3 грани не сложились,
т.к. эти 3 кубика было уже не решить ( см. foto_3 ). По крайне мере, с ходу не наГуглил как ее решить.
From the course "Systems Theory" in the MEPhI master's program, one postulate especially stuck in my memory - the more complex the system, the more difficult it is to unbalance or destroy it.
The Rubik's Cube (3x3x3) is an exemplary example of a complex system, the entropy of which cannot be destroyed,
i.e., assembled randomly.
Well, indeed, only God can find 1 unique position out of
43,252,003,274,489,856,000 by random turns, having an infinite supply of time.
By the way, the cube can be assembled from any position in 22 moves - God mode -)
I believe that the growth of expertise in our craft occurs in quantum leaps.
It is very easy to assemble the 1st face of a cube of any dimension, then the mind games begin.
My first discovery after futilely picking at 3x3x3 was - why not take on the mini (2x2x2), which I eventually solved
after the following hypothesis was unexpectedly born at the end of the 2nd week.
What if we turn on Occam's razor, cutting off the excess: we fix 1 cube tightly at the origin of coordinates (without the possibility of rotation),
then we solve the one that will be on the main diagonal and then
we set the rest along the diagonals of the faces. As a result, it remained to solve 2 cubes that were disoriented on one edge.
As it turned out, there is an assembly formula for assembling them (1 of thousands of algorithms for permuting specific cubes), but I learned about this later,
when I lined up this sweet couple just right.
In my career as a developer I also did not immediately learn about design patterns in code development.
Now I understand that if it were not for disassembling the assembled cube in my mind according to certain cycles, I would have fiddled with this pair for a long time.
Аналогично, в Greenplum разработчик задает корректные ключи распределения таблиц без перекосов и думает, что пора выкатываться в пром, но реальность сложнее
и начинает проявляться уже при join-е 3х таблиц по несогласованным с их хэшами ключам, если одна таблица-блудница
должна сопрягаться с всякими другими по требованиям бизнеса по разным ключам в разных запросах или в одном и том же.
Или другой весьма поучительный пример потенциальной бомбы на тему foto_3, когда мы грузим ключи банковских проводок в хаб Data-Vault.
Мы блестяще построили защиту от дублирования старых, уже загруженных ранее документов, но увы забыли, что когда объем хаба вырастет вдвое,
то и время поиска старых документов в хабе вырастет вдвое.
А почему ? Потому что ситом для просеивания проводок является весь хаб целиком, а не множество, размер которого можно контролировать, например кодом источника и датой проводки.
Решение в итоге не масштабируется - кубик не собран.
Вернемся к нашим баранам. В итоге, моя гипотеза матрешек кубика была состоятельна,
но мне пришлось воспользоваться подсказкой, что после каркаса надо собирать строго определенные
слои, точнее крайние, что расходится с традиционной системой послойной сборки, где углы, однако, не фиксируются.
И вот наконец, остался средний слой и собран кубик ... но снова грабли - вылетели те самые центральные окошки.
Я сломал ребра и стал собирать снова, но чтобы окошки не гуляли и в итоге пришел к конфигурации
с двумя нерешенными кубиками ( как на foto_4 ), на разворот которых требуется 12 ходов, на что тоже есть своя формула,
половину которой я подсмотрел.
Тут как видим, здание было не собрано даже на половину, исходя из того, что максимальная трудоемкость решения любой конфигурации - 22 хода,
а мне до цели осталось 12.
В итоге - успех, но какой ценой. Энтропия как мера хаоса любой информационной системы - это тот же кубик,
которую можно обуздать, если знать стратегию, которая упрощает решение проблемы шаг за шагом -
углы ( проблема решена на кубике меньшей размерности ) => ребра двух крайних граней => срединный слой, в котором
все конфигурации зверушки изучены и описаны формулами.
Известно, что на ошибки проектирования тратится 90% бюджета проекта.
Если, я донес хотя бы до 1 человека, как важно определиться с концепцией вашего здания DWH и знать типовые шаблоны проектирования кода,
которые тут иногда публикуются, буду считать, что этот лонгрид был не зря.
Similarly, in Greenplum, the developer sets the correct table distribution keys without skew and thinks that it's time to roll out to production, but the reality is more complicated
and begins to manifest itself already when joining 3 tables by keys that are not consistent with their hashes, if one harlot table
must be matched with many others according to business requirements by different keys in different queries or in the same one.
Or another very instructive example of a potential bomb on the topic of foto_3, when we load bank transaction keys into the Data-Vault hub.
We brilliantly built protection against duplication of old, previously loaded documents, but alas, we forgot that when the hub volume doubles,
then the time to search for old documents in the hub will also double.
And why? Because the sieve for sifting transactions is the entire hub as a whole, and not a set, the size of which can be controlled, for example, by the source code and transaction date.
The solution ultimately does not scale - the cube is not assembled.
Let's get back to our sheep. Ultimately, my hypothesis of the cube's nesting dolls was consistent,
but I had to use the hint that after the frame, strictly defined
layers must be assembled, or rather the outer ones, which diverges from the traditional layer-by-layer assembly system, where the corners, however, are not fixed.
And finally, the middle layer remained and the cube was assembled ... but again a rake - those very central windows flew out.
и начинает проявляться уже при join-е 3х таблиц по несогласованным с их хэшами ключам, если одна таблица-блудница
должна сопрягаться с всякими другими по требованиям бизнеса по разным ключам в разных запросах или в одном и том же.
Или другой весьма поучительный пример потенциальной бомбы на тему foto_3, когда мы грузим ключи банковских проводок в хаб Data-Vault.
Мы блестяще построили защиту от дублирования старых, уже загруженных ранее документов, но увы забыли, что когда объем хаба вырастет вдвое,
то и время поиска старых документов в хабе вырастет вдвое.
А почему ? Потому что ситом для просеивания проводок является весь хаб целиком, а не множество, размер которого можно контролировать, например кодом источника и датой проводки.
Решение в итоге не масштабируется - кубик не собран.
Вернемся к нашим баранам. В итоге, моя гипотеза матрешек кубика была состоятельна,
но мне пришлось воспользоваться подсказкой, что после каркаса надо собирать строго определенные
слои, точнее крайние, что расходится с традиционной системой послойной сборки, где углы, однако, не фиксируются.
И вот наконец, остался средний слой и собран кубик ... но снова грабли - вылетели те самые центральные окошки.
Я сломал ребра и стал собирать снова, но чтобы окошки не гуляли и в итоге пришел к конфигурации
с двумя нерешенными кубиками ( как на foto_4 ), на разворот которых требуется 12 ходов, на что тоже есть своя формула,
половину которой я подсмотрел.
Тут как видим, здание было не собрано даже на половину, исходя из того, что максимальная трудоемкость решения любой конфигурации - 22 хода,
а мне до цели осталось 12.
В итоге - успех, но какой ценой. Энтропия как мера хаоса любой информационной системы - это тот же кубик,
которую можно обуздать, если знать стратегию, которая упрощает решение проблемы шаг за шагом -
углы ( проблема решена на кубике меньшей размерности ) => ребра двух крайних граней => срединный слой, в котором
все конфигурации зверушки изучены и описаны формулами.
Известно, что на ошибки проектирования тратится 90% бюджета проекта.
Если, я донес хотя бы до 1 человека, как важно определиться с концепцией вашего здания DWH и знать типовые шаблоны проектирования кода,
которые тут иногда публикуются, буду считать, что этот лонгрид был не зря.
Similarly, in Greenplum, the developer sets the correct table distribution keys without skew and thinks that it's time to roll out to production, but the reality is more complicated
and begins to manifest itself already when joining 3 tables by keys that are not consistent with their hashes, if one harlot table
must be matched with many others according to business requirements by different keys in different queries or in the same one.
Or another very instructive example of a potential bomb on the topic of foto_3, when we load bank transaction keys into the Data-Vault hub.
We brilliantly built protection against duplication of old, previously loaded documents, but alas, we forgot that when the hub volume doubles,
then the time to search for old documents in the hub will also double.
And why? Because the sieve for sifting transactions is the entire hub as a whole, and not a set, the size of which can be controlled, for example, by the source code and transaction date.
The solution ultimately does not scale - the cube is not assembled.
Let's get back to our sheep. Ultimately, my hypothesis of the cube's nesting dolls was consistent,
but I had to use the hint that after the frame, strictly defined
layers must be assembled, or rather the outer ones, which diverges from the traditional layer-by-layer assembly system, where the corners, however, are not fixed.
And finally, the middle layer remained and the cube was assembled ... but again a rake - those very central windows flew out.
👏3
Кому интересно, моя финальная конфигурация выше представлена в позиции 16 (номер алгоритма), которую можно крутить стрелочками под ней:
https://hlavolam.maweb.eu/corners-first-method. Только осторожно, можно залипнуть на процессе ( как в анекдоте про слона и воробья) !
В заключение, может у кого-то возник вопрос, а кубик большей размерности, скажем 4x4x4 разве не сложнее ?
Думаю, если считать его парные реберные кубики одним реберным трешки, а центр из квадрата 2х2
- одним большим монолитным окошком классического кубика и собрать сначала их, то получим топологически ту же трешку, но это не точно -).
p.s.
В прошлую Пт, 27.09 был митап от T-банка и у меня только один вопрос к несравненному ведущему, который пытался держать
энтропию жаркой дискуссии в разумных границах: почему по его мнению, цитирую
"Greenplum до определенной степени масштабировать сначала легко, потом очень трудно" ?
https://www.youtube.com/live/EXRLqI9mx54 (сказано в 1:50:17)
Возможно, если я получу ответ, который убедит меня в истинности этого утверждения, опровергнув мой 5 летний опыт в GP,
я подумаю о замене логотипа, ну скажем, на интерференционную щель, демонстрирующую квантовую запутанность
или на шахматы, которые не решены до сих пор.
For those interested, my final configuration above is shown in position 16 (the algorithm number), which can be rotated with the arrows below it:
https://hlavolam.maweb.eu/corners-first-method. Just be careful, you can get stuck on the process (like in the joke about the elephant and the sparrow)!
In conclusion, maybe someone has a question, isn't a cube of a larger dimension, say 4x4x4, more difficult?
I think if we consider its paired edge cubes as one edge cube for the three, and the center from the 2x2 square
- one large monolithic window of the classic cube and assemble them first, we will get topologically the same 3x3x3, but this is not certain -).
p.s.
Last Fri, 09/27 there was a meetup from T-bank and I have only one question for the incomparable host, who tried to keep
the entropy of the heated discussion within reasonable limits: why in his opinion, I quote
"Greenplum is easy to scale to a certain extent at first, then very difficult"?
https://www.youtube.com/live/EXRLqI9mx54 (said at 1:50:17)
Maybe if I get an answer that convinces me of the truth of this statement, disproving my 5 years of experience in GP,
I will think about replacing the logo, say, with an interference slit demonstrating quantum entanglement
or with chess, which has not been solved yet.
https://hlavolam.maweb.eu/corners-first-method. Только осторожно, можно залипнуть на процессе ( как в анекдоте про слона и воробья) !
В заключение, может у кого-то возник вопрос, а кубик большей размерности, скажем 4x4x4 разве не сложнее ?
Думаю, если считать его парные реберные кубики одним реберным трешки, а центр из квадрата 2х2
- одним большим монолитным окошком классического кубика и собрать сначала их, то получим топологически ту же трешку, но это не точно -).
p.s.
В прошлую Пт, 27.09 был митап от T-банка и у меня только один вопрос к несравненному ведущему, который пытался держать
энтропию жаркой дискуссии в разумных границах: почему по его мнению, цитирую
"Greenplum до определенной степени масштабировать сначала легко, потом очень трудно" ?
https://www.youtube.com/live/EXRLqI9mx54 (сказано в 1:50:17)
Возможно, если я получу ответ, который убедит меня в истинности этого утверждения, опровергнув мой 5 летний опыт в GP,
я подумаю о замене логотипа, ну скажем, на интерференционную щель, демонстрирующую квантовую запутанность
или на шахматы, которые не решены до сих пор.
For those interested, my final configuration above is shown in position 16 (the algorithm number), which can be rotated with the arrows below it:
https://hlavolam.maweb.eu/corners-first-method. Just be careful, you can get stuck on the process (like in the joke about the elephant and the sparrow)!
In conclusion, maybe someone has a question, isn't a cube of a larger dimension, say 4x4x4, more difficult?
I think if we consider its paired edge cubes as one edge cube for the three, and the center from the 2x2 square
- one large monolithic window of the classic cube and assemble them first, we will get topologically the same 3x3x3, but this is not certain -).
p.s.
Last Fri, 09/27 there was a meetup from T-bank and I have only one question for the incomparable host, who tried to keep
the entropy of the heated discussion within reasonable limits: why in his opinion, I quote
"Greenplum is easy to scale to a certain extent at first, then very difficult"?
https://www.youtube.com/live/EXRLqI9mx54 (said at 1:50:17)
Maybe if I get an answer that convinces me of the truth of this statement, disproving my 5 years of experience in GP,
I will think about replacing the logo, say, with an interference slit demonstrating quantum entanglement
or with chess, which has not been solved yet.
YouTube
T-Meetup: GreenPlum
Дайджесты, статьи и анонсы митапов: https://t.iss.one/kod_zheltyi
Мы Вконтакте: https://vk.com/kod_zheltyi
Блог на Хабре: https://habr.com/ru/companies/tbank/articles/
Больше о жизни ИТ-команды в Тинькофф: https://t.iss.one/t_crew
Мы Вконтакте: https://vk.com/kod_zheltyi
Блог на Хабре: https://habr.com/ru/companies/tbank/articles/
Больше о жизни ИТ-команды в Тинькофф: https://t.iss.one/t_crew
👍1
Секрет 11 (Мал, да удал)
Часто в ETL встает задача удаления дублей ( и всегда на собеседованиях с моим участием ).
Убежден, что задачу, которую можно решить на стороне БД, эффективнее решать именно там(Привет адептам Информатики!), поэтому совет, который лично мне был неочевиден в первый год погружения в GP, будет не лишним.
Те, кто плавал по морям Oracle, прекрасно знает, что уникальным ключом строки таблицы у Ларри Эллисона является служебное поле rowid.
Значит, удалить дубли, скажем, из таблицы звонков в Oracle, концептуально(но не всегда оптимально) можно так:
Secret 11 (Small but mighty)
Often in ETL the task of removing duplicates arises (and always in interviews with my participation).
I believe that the task that can be solved on the DB side is more effectively solved there (Hello, adherents of Informatica!),
so the advice that was not obvious to me personally in the first year of immersion in GP will not be redundant.
Those who came from Oracle world know very well that the unique key of a table row in Larry Ellison is the service field 'rowid'.
So, removing duplicates, say, from the call table in Oracle, can be conceptually (but not always optimally) done like this:
В Postgresql аналогом rowid служит поле ctid векторного вида, например: (0,37)
Таким образом, при миграции кода с Oracle на PG достаточно заменить rowid на ctid (в части задачи дедупликации).
Однако, для GP такой подход не сработает, потому что ctid уникален внутри сегмента, но не кластера.
Более того, такой подход чреват катастрофой, т.к. очистит вам почти все данные в таблице.
Уникальный ключ в GP представлен тандемом служебных полей ctid и gp_segment_id, последнее из которых
представляет уникальный идентификатор конкретного сегмента S, т.е. является константой для части таблицы,
которая лежит в S, независимо от топологии ее хранения, будь то AOCO или AORO.
Для наглядности, проведем эксперимент:
In Postgresql, the rowid equivalent is the vector-type ctid field, for example: (0.37)
Thus, when migrating code from Oracle to PG, it is enough to replace rowid with ctid (in terms of the deduplication task).
However, this approach will not work for GP, because ctid is unique within a segment, but not a cluster.
Moreover, this approach is fraught with disaster, since it will clear almost all the data in the table.
A unique key in GP is represented by a tandem of the service fields ctid and gp_segment_id, the latter of which
represents a unique identifier of a specific segment S, i.e. is a constant for the part of the table
that lies in S, regardless of its storage topology, be it AOCO or AORO.
For clarity, let's conduct an experiment:
Ожидаемо, 1й запрос вернул число уникальных ключей << 1000, т.к. мы смотрели только на часть. ключа.
В заключение, отметим, что gp_segment_id является тем самым полем, на основе которого считается коэффициент перекоса в распределении таблицы по узлам кластера, но это уже совсем другая история.
As expected, the 1st query returned the number of unique keys << 1000, since we only looked at part of the key
In conclusion, we note that gp_segment_id is the very field on the basis of which the skew coefficient in the distribution of the table across the cluster nodes is calculated, but that is a completely different story.
Часто в ETL встает задача удаления дублей ( и всегда на собеседованиях с моим участием ).
Убежден, что задачу, которую можно решить на стороне БД, эффективнее решать именно там(Привет адептам Информатики!), поэтому совет, который лично мне был неочевиден в первый год погружения в GP, будет не лишним.
Те, кто плавал по морям Oracle, прекрасно знает, что уникальным ключом строки таблицы у Ларри Эллисона является служебное поле rowid.
Значит, удалить дубли, скажем, из таблицы звонков в Oracle, концептуально(но не всегда оптимально) можно так:
Secret 11 (Small but mighty)
Often in ETL the task of removing duplicates arises (and always in interviews with my participation).
I believe that the task that can be solved on the DB side is more effectively solved there (Hello, adherents of Informatica!),
so the advice that was not obvious to me personally in the first year of immersion in GP will not be redundant.
Those who came from Oracle world know very well that the unique key of a table row in Larry Ellison is the service field 'rowid'.
So, removing duplicates, say, from the call table in Oracle, can be conceptually (but not always optimally) done like this:
delete
from scott.calls
where rowid not in
(select min(rowid) from scott.calls group by phone_num)
В Postgresql аналогом rowid служит поле ctid векторного вида, например: (0,37)
Таким образом, при миграции кода с Oracle на PG достаточно заменить rowid на ctid (в части задачи дедупликации).
Однако, для GP такой подход не сработает, потому что ctid уникален внутри сегмента, но не кластера.
Более того, такой подход чреват катастрофой, т.к. очистит вам почти все данные в таблице.
Уникальный ключ в GP представлен тандемом служебных полей ctid и gp_segment_id, последнее из которых
представляет уникальный идентификатор конкретного сегмента S, т.е. является константой для части таблицы,
которая лежит в S, независимо от топологии ее хранения, будь то AOCO или AORO.
Для наглядности, проведем эксперимент:
In Postgresql, the rowid equivalent is the vector-type ctid field, for example: (0.37)
Thus, when migrating code from Oracle to PG, it is enough to replace rowid with ctid (in terms of the deduplication task).
However, this approach will not work for GP, because ctid is unique within a segment, but not a cluster.
Moreover, this approach is fraught with disaster, since it will clear almost all the data in the table.
A unique key in GP is represented by a tandem of the service fields ctid and gp_segment_id, the latter of which
represents a unique identifier of a specific segment S, i.e. is a constant for the part of the table
that lies in S, regardless of its storage topology, be it AOCO or AORO.
For clarity, let's conduct an experiment:
create table public.foo as
select generate_series(1, 1000) n;
SQL> select count(distinct ctid)
from public.foo;
count
-----
67
SQL> select count(distinct ctid::text || '.' || gp_segment_id)
from public.foo
count
-----
1000
Ожидаемо, 1й запрос вернул число уникальных ключей << 1000, т.к. мы смотрели только на часть. ключа.
В заключение, отметим, что gp_segment_id является тем самым полем, на основе которого считается коэффициент перекоса в распределении таблицы по узлам кластера, но это уже совсем другая история.
As expected, the 1st query returned the number of unique keys << 1000, since we only looked at part of the key
In conclusion, we note that gp_segment_id is the very field on the basis of which the skew coefficient in the distribution of the table across the cluster nodes is calculated, but that is a completely different story.
Пришло время спросить вас, а вы используете секционировани в вашем Greenplum ? It's time to ask you, do you use partitions in your Greenplum?
Anonymous Poll
70%
Y
16%
N
12%
Нет, но планирую. No, but I plan to.
1%
Да, но пожалел об этом. Yes, but I regretted it
Секрет 12(Секунда часик бережет)
Через меня проходит много кода, поэтому поделюсь часто используемыми командами, которые настолько облегчают жизнь
что для каждой я создал snippet в DataGrip(макрос), чтобы не набивать ее каждый раз.
Secret 12 (A second saves an hour)
I handle a lot of code, so I'll share frequently used commands that make life so much easier
that I created a snippet in DataGrip (macro) for each one so I don't have to type it in every time.
1) ключ распределения табл-ы (далее хэш) table distribution key (hereinafter hash)
select pg_get_table_distributedby('public.foo'::regclass)
Важно отметить, если при создании табл-ы из другой (CTAS) хэш не указан,
то получим DISTRIBUTED RANDOMLY, но если создается пустая через DDL
It is important to note that if the hash is not specified when creating a table from another (CTAS), then we will get DISTRIBUTED RANDOMLY, but if an empty one is created through DDL
, e.g.
create table t( a int, b bool), то хэшу GP назначит 1 колонку__then GP will assign first column to the hash*/: DISTRIBUTED BY(a)
Ежели 1) вернул NULL, значит перед нами вью __If 1) returned NULL, then we have a view
2) select pg_get_viewdef('public.foo') - вывод тела вью без хедера /*get view body without header*/(CREATE OR REPLACE ...),
т.е. ее /*only*/ SELECT ...
3) Код функции: — Function code
select pg_get_functiondef('public.fn_foo'::regproc)
Если ошибка, значит ф-я перегружена, т.е. существуют одноименные функции с разным набором параметров( словил 1 раз за 5 лет ).
If there is an error, it means the function is overloaded, i.e. there are functions with the same name with a different set of parameters (I caught it once in 5 years).
В этом случае есть вариант с перечислением типов параметров In this case we use the option with enumeration of parameter types
, e.g.
select pg_get_functiondef('public.foo(text, text, text, text, text, boolean)'::regprocedure)
либо вариант с oid, используемый ниже or the oid-specified variant below
4) Иногда надо найти ф-ии, скажем любые 5 в схеме etl, в тексте которых есть 'DROP CASCADE' :
Sometimes you need to find functions, say any 5 in the 'etl' scheme containing 'DROP CASCADE'
Как ни странно, простой вариант, без GROUP BY, я не нашел.
Кто знает - делитесь!
Oddly enough, I haven't found a simple option without GROUP BY.
If anyone knows, please share!
5) Часто надо узнать быстро число строк в табл-е , e.g. public.foo:
В relname передаем табл-у без схемы, и в первой паре колонок получаем число строк и схему табл-ы.
Хотя reltuples не всегда 100% точное, для анализа вполне хватает.
Если оно = 0, то вот это точно - либо по табл-е нет статистики, либо она партицированная.
Намеренно даю вариант, который может вернуть данные из разных схем, т.к. он быстрее в использовании.
6)
Как мы знаем (см. Секрет 1,6), GP частенько тупит, когда запрос идет к пустой табл-е, сопряженной с другими сущностями.
Есть сведения, что выключение оптимизатора GPORCA может ускорять выполнение запроса, как бы нам и хотелось.
В таком случае, если времянка идет по конвейеру перекладки данных, чтобы проверить число строк, только что записанных в нее, есть быстрый способ
проверить сессионную переменную
GET DIAGNOSTICS v_cnt = ROW_COUNT; ( аналог sql%rowcount в Oracle ).
Если v_cnt = 0, то можно отключить GPORCA, включив Postgres query optimizer:
SET optimizer=off
и либо выполнить следующий SQL запрос
либо просто пропустить его, если известно, что результат будет одинаков.
К слову, если перед выполнением каждого запроса необходимо логировать текущий режим оптимизатора(Legacy или GPORCA),
то select current_setting('optimizer') в помощь.
Через меня проходит много кода, поэтому поделюсь часто используемыми командами, которые настолько облегчают жизнь
что для каждой я создал snippet в DataGrip(макрос), чтобы не набивать ее каждый раз.
Secret 12 (A second saves an hour)
I handle a lot of code, so I'll share frequently used commands that make life so much easier
that I created a snippet in DataGrip (macro) for each one so I don't have to type it in every time.
1) ключ распределения табл-ы (далее хэш) table distribution key (hereinafter hash)
select pg_get_table_distributedby('public.foo'::regclass)
Важно отметить, если при создании табл-ы из другой (CTAS) хэш не указан,
то получим DISTRIBUTED RANDOMLY, но если создается пустая через DDL
It is important to note that if the hash is not specified when creating a table from another (CTAS), then we will get DISTRIBUTED RANDOMLY, but if an empty one is created through DDL
, e.g.
create table t( a int, b bool), то хэшу GP назначит 1 колонку__then GP will assign first column to the hash*/: DISTRIBUTED BY(a)
Ежели 1) вернул NULL, значит перед нами вью __If 1) returned NULL, then we have a view
2) select pg_get_viewdef('public.foo') - вывод тела вью без хедера /*get view body without header*/(CREATE OR REPLACE ...),
т.е. ее /*only*/ SELECT ...
3) Код функции: — Function code
select pg_get_functiondef('public.fn_foo'::regproc)
Если ошибка, значит ф-я перегружена, т.е. существуют одноименные функции с разным набором параметров( словил 1 раз за 5 лет ).
If there is an error, it means the function is overloaded, i.e. there are functions with the same name with a different set of parameters (I caught it once in 5 years).
В этом случае есть вариант с перечислением типов параметров In this case we use the option with enumeration of parameter types
, e.g.
select pg_get_functiondef('public.foo(text, text, text, text, text, boolean)'::regprocedure)
либо вариант с oid, используемый ниже or the oid-specified variant below
4) Иногда надо найти ф-ии, скажем любые 5 в схеме etl, в тексте которых есть 'DROP CASCADE' :
Sometimes you need to find functions, say any 5 in the 'etl' scheme containing 'DROP CASCADE'
with a as
(
select string_agg(pg_get_functiondef(p.oid), ',' order by 1) fn
from pg_proc p,
pg_namespace n
where p.pronamespace = n.oid
and n.nspname = 'etl'
group by p.oid
)
select fn from a
where upper(fn) like '%DROP%CASCADE%'
limit 5
Как ни странно, простой вариант, без GROUP BY, я не нашел.
Кто знает - делитесь!
Oddly enough, I haven't found a simple option without GROUP BY.
If anyone knows, please share!
5) Часто надо узнать быстро число строк в табл-е , e.g. public.foo:
select reltuples, s.nspname, *
from pg_class c,
pg_namespace s
where relname = 'foo'
and s.oid = c.relnamespace;
В relname передаем табл-у без схемы, и в первой паре колонок получаем число строк и схему табл-ы.
Хотя reltuples не всегда 100% точное, для анализа вполне хватает.
Если оно = 0, то вот это точно - либо по табл-е нет статистики, либо она партицированная.
Намеренно даю вариант, который может вернуть данные из разных схем, т.к. он быстрее в использовании.
6)
Как мы знаем (см. Секрет 1,6), GP частенько тупит, когда запрос идет к пустой табл-е, сопряженной с другими сущностями.
Есть сведения, что выключение оптимизатора GPORCA может ускорять выполнение запроса, как бы нам и хотелось.
В таком случае, если времянка идет по конвейеру перекладки данных, чтобы проверить число строк, только что записанных в нее, есть быстрый способ
проверить сессионную переменную
GET DIAGNOSTICS v_cnt = ROW_COUNT; ( аналог sql%rowcount в Oracle ).
Если v_cnt = 0, то можно отключить GPORCA, включив Postgres query optimizer:
SET optimizer=off
и либо выполнить следующий SQL запрос
либо просто пропустить его, если известно, что результат будет одинаков.
К слову, если перед выполнением каждого запроса необходимо логировать текущий режим оптимизатора(Legacy или GPORCA),
то select current_setting('optimizer') в помощь.
👍4
5) Often you need to quickly check presence of data in a table, e.g. public.foo:
In relname we pass a table without a schema, and in the first pair of columns we get the number of rows and the table schema.
Although reltuples is not always 100% accurate, it is quite sufficient for analysis.
If it = 0, then this is for sure - either there are no statistics for the table, or it is partitioned.
I intentionally give the option that can return data from different schemas, because it is faster to use.
6)
As we know (see Secret 1.6), GP often slows down when a query goes to an empty table joined with other entities.
There is information that disabling the GPORCA optimizer can speed up query execution, as we would like.
In this case, if the temporary one goes along the data transfer pipeline, in order to check the number of rows just written to it, there is a quick way
to check the session variable
GET DIAGNOSTICS v_cnt = ROW_COUNT; (analogous to sql%rowcount in Oracle).
If v_cnt = 0, then you can disable GPORCA by enabling Postgres query optimizer:
SET optimizer=off
and either execute the following SQL query
or simply skip it if you know that the result will be the same.
By the way, if you need to log the current optimizer mode (Legacy or GPORCA) before executing each query, then select current_setting('optimizer') will help.
select reltuples, s.nspname, *
from pg_class c,
pg_namespace s
where relname = 'foo'
and s.oid = c.relnamespace;
In relname we pass a table without a schema, and in the first pair of columns we get the number of rows and the table schema.
Although reltuples is not always 100% accurate, it is quite sufficient for analysis.
If it = 0, then this is for sure - either there are no statistics for the table, or it is partitioned.
I intentionally give the option that can return data from different schemas, because it is faster to use.
6)
As we know (see Secret 1.6), GP often slows down when a query goes to an empty table joined with other entities.
There is information that disabling the GPORCA optimizer can speed up query execution, as we would like.
In this case, if the temporary one goes along the data transfer pipeline, in order to check the number of rows just written to it, there is a quick way
to check the session variable
GET DIAGNOSTICS v_cnt = ROW_COUNT; (analogous to sql%rowcount in Oracle).
If v_cnt = 0, then you can disable GPORCA by enabling Postgres query optimizer:
SET optimizer=off
and either execute the following SQL query
or simply skip it if you know that the result will be the same.
By the way, if you need to log the current optimizer mode (Legacy or GPORCA) before executing each query, then select current_setting('optimizer') will help.
Секрет 13 (Контора пишет)
Как-то прилетела задачка протестировать список из 100 запросов, предоставив на выходе план и время выполнения каждого.
Т.к. лень - двигатель прогресса, я автоматизировал основную задачу - получить то что нужно для конкретного запроса.
Для этого создал ф-ю, которая возвращает план запроса в JSON
Далее создал журнал для результата
И проверил, что все работает
и что любая метрика легко доступна из узла плана через jsonpath, e.g.
Сам план представляет массив из 1 элемента
Дальше выгрузил список SQL запросов из XLS в табл-у, по которой пробежался в цикле методом выше и задача была решена.
Как-то прилетела задачка протестировать список из 100 запросов, предоставив на выходе план и время выполнения каждого.
Т.к. лень - двигатель прогресса, я автоматизировал основную задачу - получить то что нужно для конкретного запроса.
Для этого создал ф-ю, которая возвращает план запроса в JSON
CREATE OR REPLACE FUNCTION public.estimate_cost(query text)
RETURNS json
LANGUAGE plpgsql
AS $function$
DECLARE
query_explain text;
explanation json;
BEGIN
query_explain :=e'EXPLAIN (ANALYZE, FORMAT JSON) ' || query;
EXECUTE query_explain INTO explanation;
RETURN explanation;
END;
$function$
Далее создал журнал для результата
create table public.tst_plan (query_plan json, id int) DISTRIBUTED BY (id)
И проверил, что все работает
insert into public.tst_plan
select *, 1 from public.estimate_cost_0('select generate_series(1,10000)')
и что любая метрика легко доступна из узла плана через jsonpath, e.g.
SQL> select (query_plan->0)->>'Execution Time' as exec_tm from public.tst_plan where id = 1
exec_tm
-------
1.444
Сам план представляет массив из 1 элемента
select query_plan from public.tst_plan where id = 1
query_plan
—————————————————————————————-
[
{
"Plan": {
"Node Type": "Result",
"Startup Cost": 0.00,
"Total Cost": 0.00,
"Plan Rows": 1,
"Plan Width": 4,
"Actual Startup Time": 0.055,
"Actual Total Time": 0.770,
"Actual Rows": 10000,
"Actual Loops": 1,
"Plans": [
{
"Node Type": "Result",
"Parent Relationship": "Outer",
"Startup Cost": 0.00,
"Total Cost": 0.00,
"Plan Rows": 1,
"Plan Width": 1,
"Actual Startup Time": 0.024,
"Actual Total Time": 0.024,
"Actual Rows": 1,
"Actual Loops": 1
}
]
},
"Planning Time": 2.391,
"Triggers": [
],
"Slice statistics": [
{
"Slice": 0,
"Executor Memory": 57512
}
],
"Statement statistics": {
"Memory used": 311296
},
"Settings": {
"Optimizer": "Pivotal Optimizer (GPORCA)"
},
"Execution Time": 1.444
}
]
Дальше выгрузил список SQL запросов из XLS в табл-у, по которой пробежался в цикле методом выше и задача была решена.
👍7❤2
Secret 13 (Office writes)
Once I was given a task to test a list of 100 queries, providing a plan and execution time for each.
Since laziness is the engine of progress, I automated the main task - to get what is needed for a specific query.
For this, I created a function that returns a query plan in JSON
Then I created a log for the result
and checked that everything works.
and that any metric is easily accessible from the plan node via jsonpath, e.g.
The plan itself is an array of 1 element.
Then I unloaded the list of SQL queries from XLS into a table, which I ran through in a loop using the method above, and the problem was solved.
Once I was given a task to test a list of 100 queries, providing a plan and execution time for each.
Since laziness is the engine of progress, I automated the main task - to get what is needed for a specific query.
For this, I created a function that returns a query plan in JSON
CREATE OR REPLACE FUNCTION public.estimate_cost(query text)
RETURNS json
LANGUAGE plpgsql
AS $function$
DECLARE
query_explain text;
explanation json;
BEGIN
query_explain :=e'EXPLAIN (ANALYZE, FORMAT JSON) ' || query;
EXECUTE query_explain INTO explanation;
RETURN explanation;
END;
$function$
Then I created a log for the result
create table public.tst_plan (query_plan json, id int) DISTRIBUTED BY (id)
and checked that everything works.
insert into public.tst_plan
select *, 1 from public.estimate_cost_0('select generate_series(1,10000)')
and that any metric is easily accessible from the plan node via jsonpath, e.g.
SQL> select (query_plan->0)->>'Execution Time' as exec_tm from public.tst_plan where id = 1
exec_tm
-------
1.444
The plan itself is an array of 1 element.
select query_plan from public.tst_plan where id = 1
query_plan
—————————————————————————————-
[
{
"Plan": {
"Node Type": "Result",
"Startup Cost": 0.00,
"Total Cost": 0.00,
"Plan Rows": 1,
"Plan Width": 4,
"Actual Startup Time": 0.055,
"Actual Total Time": 0.770,
"Actual Rows": 10000,
"Actual Loops": 1,
"Plans": [
{
"Node Type": "Result",
"Parent Relationship": "Outer",
"Startup Cost": 0.00,
"Total Cost": 0.00,
"Plan Rows": 1,
"Plan Width": 1,
"Actual Startup Time": 0.024,
"Actual Total Time": 0.024,
"Actual Rows": 1,
"Actual Loops": 1
}
]
},
"Planning Time": 2.391,
"Triggers": [
],
"Slice statistics": [
{
"Slice": 0,
"Executor Memory": 57512
}
],
"Statement statistics": {
"Memory used": 311296
},
"Settings": {
"Optimizer": "Pivotal Optimizer (GPORCA)"
},
"Execution Time": 1.444
}
]
Then I unloaded the list of SQL queries from XLS into a table, which I ran through in a loop using the method above, and the problem was solved.