Единственно, при добавлении столбца, индекса и подзапроса. tps становятся сразу в ~1,5 раза ниже и падают с такой же скоростью как исходный update (без лишнего столбца, индекса, подзапроса). А при добавлении advisory lock сразу в пять раз ниже, поэтому в докладе не приведены tps решений.
Как показала практика, утверждение неверное. Полезно сначала проверять, и только после проверки тиражировать мысль. Обращались к Михаилу за рецензией статьи? Или, быть может, ещё к кому-нибудь, кто хорошо разбирается и мог бы посмотреть почему ваши результаты отличаются от результатов Михаила. А так, получается, что вы зря ругаете рабочие решения.
На OUTER JOIN таблица, по которой строится хэш, не меняется (при изменении порядка таблицы LEFT меняется на RIGHT):
Переформулируйте, пожалуйста. Я несколько раз прочитал и не понял что вы имеете ввиду. Если вы имеете ввиду, что я всё верно сказал, так и скажите. Если вы до сих пор думаете, что я неправ, то подумайте ещё раз. Я серьёзно.
Хинт SWAP_JOIN_INPUTS не влияет на результат, поэтому ваша фраза "LEFT меняется на RIGHT" крайне странная. Разумеется, результат запроса сохраняется, и как было в вашем случае departments d left outer join locations , так и остаётся. А таблица, по которой строится хэш как раз меняется. При любом типе HASH JOIN в OracleDB хэш-таблица строится по первой таблице (верхней в плане). По планам, которые вы показываете как раз видно, что в первом случае хэш-таблица строится по departments, а во втором — по locations.
то тогда потребуется как-то гарантировать уникальность id
Самый простой вариант проверить — создать индекс по (id+0): create unique index g1_pkey on g1((id+0));
Это, конечно, недёшево будет, ведь каждое обновление будет создавать новые строки в g1_pkey. Но по-быстрому сравнить можно.
Было бы хорошо, если бы в PostgreSQL можно было создавать unique/primary key по неуникальным индексам (в OracleDB так можно).
Да, мысль тем или иным образом в индексной строке отмечать, что она ведёт на устаревшую hot цепочку интересная. В целом, это может и autovacuum делать (ну, чтобы не напрягать пользовательские процессы).
Некий минус, что сейчас, наверное, btree индекс ничего не знает про HOT. Это как бы деталь реализации heap table, поэтому добавка завязок между nbtree и heapam выглядит как некий косяк.
Хотя, конечно, ускорение DML (update, delete, select for update) было бы крайне полезно.
Не не не. Тут у вас используется индекс g1_pkey. Зачем он вам? Если есть индекс g1_idx(id, last), то primary key индекс g1_pkey(id) уже не нужен. Иными словами, в "инженерном решении" нужно пересоздать первичный ключ как (id, last), а не просто добавить ещё один индекс.
Обратите внимание на то, что Михаил в докладе явно проговаривал и показывал, что план должен использовать добавленный индекс, и не просто использовать, а "брать последнюю запись". В плане должно быть Index Scan Backward, а у вас по факту "взять все записи, отсортировать, взять последнюю"
Добейтесь, пожалуйста, плана с Index Scan Backward, а потом уже делайте выводы помогает ли решение Михаила или нет.
политкорректным вопросом "Когда в PGpro Enterprise появится патч?"
Вопрос "когда что-либо появится в PGpro Enterprise?" некорректен. Единственным ответом может быть лишь "обращайтесь в пресс-службу". Разработчики далеко не единственные, кто влияют на даты, поэтому вопросы про сроки в закрытых проектах почти лишены смысла.
Корректным был бы вопрос "а можно где-нибудь посмотреть на патч?"
Фёдор моментально ответил: планировщик не смог переставить порядок, наверняка было левое соединение
Вы неверно поняли. Моментальным ответом было "патч решал только замену nested loops на hash join для конкретного запроса, а уж какой там hash join получался Фёдор вообще не смотрел".
И, как обычно бывает, в таких случаях притянул "в оракле переставляет" (и там, конечно же, не переставляет)
Проверяйте, пожалуйста, факты при написании статьи. Ссылайтесь на документацию, подтверждающие ваши слова. Хотя бы в случаях, когда вы пишете, что кто-то "прав или не прав".
The optimizer uses the smaller of two data sets to build a hash table on the join key in memory
Более того, ответ Фёдора про "реляционную алгебру" был явно мимо. Разумеется, реляционная алгебра ни коем образом не мешает реализовать два оба варианта hash join inner (именно про inner join говорилось в докладе!). В inner случае задача hash join полностью симметрична, и то, по какому из входов строить хэш-таблицу полностью отдаётся на откуп оптимизатору.
Может показаться, что в outer join уж точно невозможно переставить таблицы местами, но это не так. Ещё раз повторю, что OracleDB это давным-давно делает (см. HASH JOIN OUTER vs HASH JOIN RIGHT OUTER), да и из общих соображений понятно, что, скажем, left outer join можно реализовать двумя путями.
Например, для select c.id, o.id from clients c left outer join orders o on (c.id = o.client_id)
а) Хэшируем orders. Пробегаемся по таблице clients, и либо находим запись в хэш-таблице и выводим в результат данные (c.id, o.id), либо не находим запись в хэш-таблице и выводим только часть про clients (c.id, null)
a) Хэшируем clients. Идём по orders. Для каждой строки из второго источника либо находим запись в хэш-таблице (и тогда выводим её как результат c.id, o.id), либо не находим (и тогда игнорируем). В конце пробегаемся по тем записям хэш-таблицы, которые ни разу не использовались и дополняем их к результатам outer (c.id, null). Да, при таком подходе нужно запоминать какие записи в хэш-таблице мы встречались, а какие нет, но никакая реляционная алгебра не запрещает подобный подход. Более того, если таблица clients гораздо меньше orders, то это намного эффективнее, нежели строить хэш-таблицу по огромной таблице orders
Почти в любой системе количество входных данных таково, что полный перебор всех возможных вариантов никогда не закончится. И рандомизация (особенно, если она с учётом граничных значений, coverage guided, и так далее) может тестировать новые и новые данные с каждым запуском. Всех случаев не проверишь но 1-5 минут рандома на каждый pull request и рано или поздно ошибка найдётся.
В Java language specification отдельно описано, что if (false) это годный код, и компилятор не должен ругаться на его недостижимость: https://docs.oracle.com/javase/specs/jls/se7/html/jls-14.html#jls-14.21
Думаю, ни там ни там не надо. Разумеется, "правильного ответа" тут нет, и как партия решит, так пусть и будет. Но я всё равно думаю, что подкрашивать условие if(false) это странно. Вполне нормально взять и написать while(false), if(false) и т.п. для отладки. Если оно более блеклым цветом отметит "никогда не выполняющиеся ветки кода", то это другой разговор. А подкрашивать "if(false) is always false" -- ну, зачем?
Не, объявить val debug=false и бранчеваться -- самая тема. Поэтому, да, соглашусь, что ругаться на if (debug) не стоит. Поэтому же и считаю, что ругаться на if (false) не должно. Тут же по-человечески написано false. Зачем лишний раз сообщать, что "condition is always false"? :)
Тема статьи какая? "Вот нормальный код, анализатор пытался его обругать, но не надо было". А тут смотришь -- и между строк написано "тут должно было быть isNullOrEmpty".
Я другое спрошу (213.4631.20, 6 October). Почему на if (false) ругается, что "condition is always false", а на val x = false; if (x) уже нет? Бага получается? Я бы на if (false) и if (true) не ругался.
Ну представь, что там исключение кидается. Лучше будет?
Исключение прекрасно кинется в when else ветке. Ну, тут я полностью соглашусь, что корень проблемы не в "expression is always true", а в том, что тут возникло дублирование логики, и, вполне возможно, это дублирование следует убрать, чтобы перечни статусов не разошлись (ну, вдруг добавят ветку в when, а if забудут поправить?). Но, если бы у меня в том when "low" возникло предупреждение "condition is always true", то я бы порадовался и сказал: "а, действительно, как так получилось, что always true" и пошёл бы и убрал if или что там.
А смысл? Предупреждения должны нести смысл. Если над каждым придётся думать, то смысла в них никакого не будет. Так-то в каждой строке можно предупреждение придумать: "вы точно уверены, что тут переменная x, а не y?"
Возможно, что 99.42% всех таких примеров сведутся к тому, что незачем там return внутри let использовать, и оно должно ругаться словами "убирайте return" или "убирайте let"
Если присмотреться к http запросам на сайте deepwiki, то в ответ приходит markdown в чистом виде, так что внешний экспорт можно написать
Как показала практика, утверждение неверное. Полезно сначала проверять, и только после проверки тиражировать мысль. Обращались к Михаилу за рецензией статьи? Или, быть может, ещё к кому-нибудь, кто хорошо разбирается и мог бы посмотреть почему ваши результаты отличаются от результатов Михаила. А так, получается, что вы зря ругаете рабочие решения.
Переформулируйте, пожалуйста. Я несколько раз прочитал и не понял что вы имеете ввиду. Если вы имеете ввиду, что я всё верно сказал, так и скажите. Если вы до сих пор думаете, что я неправ, то подумайте ещё раз. Я серьёзно.
Хинт
SWAP_JOIN_INPUTS
не влияет на результат, поэтому ваша фраза "LEFT меняется на RIGHT" крайне странная. Разумеется, результат запроса сохраняется, и как было в вашем случаеdepartments d left outer join locations
, так и остаётся. А таблица, по которой строится хэш как раз меняется. При любом типе HASH JOIN в OracleDB хэш-таблица строится по первой таблице (верхней в плане). По планам, которые вы показываете как раз видно, что в первом случае хэш-таблица строится по departments, а во втором — по locations.Самый простой вариант проверить — создать индекс по (id+0):
create unique index g1_pkey on g1((id+0));
Это, конечно, недёшево будет, ведь каждое обновление будет создавать новые строки в g1_pkey. Но по-быстрому сравнить можно.
Было бы хорошо, если бы в PostgreSQL можно было создавать unique/primary key по неуникальным индексам (в OracleDB так можно).
Да, мысль тем или иным образом в индексной строке отмечать, что она ведёт на устаревшую hot цепочку интересная. В целом, это может и autovacuum делать (ну, чтобы не напрягать пользовательские процессы).
Некий минус, что сейчас, наверное, btree индекс ничего не знает про HOT. Это как бы деталь реализации heap table, поэтому добавка завязок между nbtree и heapam выглядит как некий косяк.
Хотя, конечно, ускорение DML (update, delete, select for update) было бы крайне полезно.
Не не не. Тут у вас используется индекс
g1_pkey
. Зачем он вам? Если есть индексg1_idx(id, last)
, то primary key индексg1_pkey(id)
уже не нужен. Иными словами, в "инженерном решении" нужно пересоздать первичный ключ как(id, last)
, а не просто добавить ещё один индекс.Обратите внимание на то, что Михаил в докладе явно проговаривал и показывал, что план должен использовать добавленный индекс, и не просто использовать, а "брать последнюю запись". В плане должно быть
Index Scan Backward
, а у вас по факту "взять все записи, отсортировать, взять последнюю"Добейтесь, пожалуйста, плана с
Index Scan Backward
, а потом уже делайте выводы помогает ли решение Михаила или нет.Вопрос "когда что-либо появится в PGpro Enterprise?" некорректен. Единственным ответом может быть лишь "обращайтесь в пресс-службу". Разработчики далеко не единственные, кто влияют на даты, поэтому вопросы про сроки в закрытых проектах почти лишены смысла.
Корректным был бы вопрос "а можно где-нибудь посмотреть на патч?"
Вы неверно поняли. Моментальным ответом было "патч решал только замену nested loops на hash join для конкретного запроса, а уж какой там hash join получался Фёдор вообще не смотрел".
Проверяйте, пожалуйста, факты при написании статьи. Ссылайтесь на документацию, подтверждающие ваши слова. Хотя бы в случаях, когда вы пишете, что кто-то "прав или не прав".
Документация Oracle напрямую говорит, что база данных строит хэш-таблицу по наименьшему входному множеству. В Oracle это работает и для inner join, и для outer join, и для anti join и для semi join. Хинтом SWAP_JOIN_INPUTS можно повлиять на то, по какому источнику будет строиться хэш-таблица.
Более того, ответ Фёдора про "реляционную алгебру" был явно мимо. Разумеется, реляционная алгебра ни коем образом не мешает реализовать два оба варианта hash join inner (именно про inner join говорилось в докладе!). В inner случае задача hash join полностью симметрична, и то, по какому из входов строить хэш-таблицу полностью отдаётся на откуп оптимизатору.
Может показаться, что в outer join уж точно невозможно переставить таблицы местами, но это не так. Ещё раз повторю, что OracleDB это давным-давно делает (см.
HASH JOIN OUTER
vsHASH JOIN RIGHT OUTER
), да и из общих соображений понятно, что, скажем, left outer join можно реализовать двумя путями.Например, для
select c.id, o.id from clients c left outer join orders o on (c.id = o.client_id)
а) Хэшируем orders. Пробегаемся по таблице clients, и либо находим запись в хэш-таблице и выводим в результат данные (
c.id, o.id
), либо не находим запись в хэш-таблице и выводим только часть про clients (c.id, null
)a) Хэшируем clients. Идём по orders. Для каждой строки из второго источника либо находим запись в хэш-таблице (и тогда выводим её как результат
c.id, o.id
), либо не находим (и тогда игнорируем). В конце пробегаемся по тем записям хэш-таблицы, которые ни разу не использовались и дополняем их к результатам outer (c.id, null
). Да, при таком подходе нужно запоминать какие записи в хэш-таблице мы встречались, а какие нет, но никакая реляционная алгебра не запрещает подобный подход. Более того, если таблица clients гораздо меньше orders, то это намного эффективнее, нежели строить хэш-таблицу по огромной таблице ordersА можете привести план, который у вас получается после создания индекса (id, last)? Индекс точно используется?
"6. Рандомизация в тестах". Вам там property-based testing https://en.m.wikipedia.org/wiki/Software_testing#Property_testing и фаззинг https://ru.m.wikipedia.org/wiki/%D0%A4%D0%B0%D0%B7%D0%B7%D0%B8%D0%BD%D0%B3 показывают, а вы отнекиваетесь, что "не надо писать рандомизированные тесты". Надо. Надо их писать. Вот пример с недавнего Гейзенбага: https://youtu.be/fbhuzSpyUJc.
Почти в любой системе количество входных данных таково, что полный перебор всех возможных вариантов никогда не закончится. И рандомизация (особенно, если она с учётом граничных значений, coverage guided, и так далее) может тестировать новые и новые данные с каждым запуском. Всех случаев не проверишь но 1-5 минут рандома на каждый pull request и рано или поздно ошибка найдётся.
Если Котлин, то при чём тут Си?
На самом деле, статья и начинается со слов, что в Java анализатор уже написан и отлажен, поэтому с Java сравнивать логично.
Если код закомментировать, то он может перестать компилироваться, поэтому if false зачастую лучше, чем комментирование кода
В Java language specification отдельно описано, что if (false) это годный код, и компилятор не должен ругаться на его недостижимость: https://docs.oracle.com/javase/specs/jls/se7/html/jls-14.html#jls-14.21
Думаю, ни там ни там не надо. Разумеется, "правильного ответа" тут нет, и как партия решит, так пусть и будет. Но я всё равно думаю, что подкрашивать условие if(false) это странно. Вполне нормально взять и написать while(false), if(false) и т.п. для отладки. Если оно более блеклым цветом отметит "никогда не выполняющиеся ветки кода", то это другой разговор. А подкрашивать "if(false) is always false" -- ну, зачем?
Не, объявить val debug=false и бранчеваться -- самая тема. Поэтому, да, соглашусь, что ругаться на if (debug) не стоит. Поэтому же и считаю, что ругаться на if (false) не должно. Тут же по-человечески написано false. Зачем лишний раз сообщать, что "condition is always false"? :)
"condition is always false". И quick-fix там -- "delete expression"
Хотя посмотрел на Java -- там if (false) ругается (почему бы?). Наверное, если уж делать, то однообразно.
Тема статьи какая? "Вот нормальный код, анализатор пытался его обругать, но не надо было". А тут смотришь -- и между строк написано "тут должно было быть isNullOrEmpty".
Я другое спрошу (213.4631.20, 6 October). Почему на if (false) ругается, что "condition is always false", а на val x = false; if (x) уже нет? Бага получается? Я бы на if (false) и if (true) не ругался.
Ты меня раскусил
Исключение прекрасно кинется в when else ветке. Ну, тут я полностью соглашусь, что корень проблемы не в "expression is always true", а в том, что тут возникло дублирование логики, и, вполне возможно, это дублирование следует убрать, чтобы перечни статусов не разошлись (ну, вдруг добавят ветку в when, а if забудут поправить?). Но, если бы у меня в том when "low" возникло предупреждение "condition is always true", то я бы порадовался и сказал: "а, действительно, как так получилось, что always true" и пошёл бы и убрал if или что там.
А смысл? Предупреждения должны нести смысл. Если над каждым придётся думать, то смысла в них никакого не будет. Так-то в каждой строке можно предупреждение придумать: "вы точно уверены, что тут переменная x, а не y?"
Возможно, что 99.42% всех таких примеров сведутся к тому, что незачем там return внутри let использовать, и оно должно ругаться словами "убирайте return" или "убирайте let"