Comments 4
Концептуально - идея прикольная. Нет, серьёзно. Мне нравится.
Но я не совсем понимаю, как вы в реальной системе планируете привязывать SQL-запрос к какому-то идентификатору. Это надо структуру приложения менять - складировать куда-то SQL-запросы, пусть и генерируемые через ORM, чтобы потом это место как-то парсить. А если там query-builder какой-то, у которого итоговый SQL зависит от входных параметров? Из логов выхватывать? Но как их тогда с предыдущими версиями сопоставлять?
Число INNER JOIN тоже, конечно те ещё попугаи. Лучше, чем ничего, но и вредить может. К примеру было 3 отдельных последовательных SQL-запроса не под транзакцией. Объединили их под одну транзакцию? Стало хуже? Лучше? Объединили их с помощью JOIN - стало лучше или хуже? Как себя стал пул соединений чувствовать под нагрузкой от этого? А если таких запросов -> JOIN'ов 10? 20? Как себя ведёт планировщик постгри, переключившись на всякие генетические алгоритмы? Но вы вроде понимаете это и обозначили что JOIN - это просто пример.
Этот подход хорошо сработает для вторичных систем, по большей части состоящих из набора SQL-запросов. Или вы таки применяете его где-то ещё?:)
Но я не совсем понимаю, как вы в реальной системе планируете привязывать SQL-запрос к какому-то идентификатору. Это надо структуру приложения менять - складировать куда-то SQL-запросы, пусть и генерируемые через ORM, чтобы потом это место как-то парсить.
Собирать SQL можно, например, со стороны C# при помощи директивы #if DEBUG или из логов СУБД. Вообще подход актуален для улучшения существующих интеграционных тестов (которые проверяют только правильность значений), собирать такую статистику на основе ручной активности в приложении тоже можно, но сложнее и имеет смысл отдельно рассматривать.
А если там query-builder какой-то, у которого итоговый SQL зависит от входных параметров? Из логов выхватывать? Но как их тогда с предыдущими версиями сопоставлять?
Предполагается, что для интеграционных тестов всегда фиксировано количество тестов (и количество SQL) и входные параметры. Если будет где-то расхождение, и оно будет видно в таблице, то в этом и смысл подхода. Могут быть и "ложные срабатывания" для ожидаемых изменений (например, новые тесты или изменения в параметрах тестов и т.д.). Также от версии к версии придется поддерживать сбор статистики (при необходимости), в "первой версии" сравнение с предыдущей будет невозможно.
Но я не совсем понимаю, как вы в реальной системе планируете привязывать SQL-запрос к какому-то идентификатору.
Это хороший вопрос, больше подход расчитан на существующие интеграционные тесты. Получается, нужно, чтобы количество SQL запросов было условно постоянным и параметры были неслучайные. В таком случае можно обойтись без ID, а оставлять какие-либо условно подсказки/комментарии и т.д. для упрощения поиска генерации этого SQL в C# коде. Достаточно отсортировать все SQL запросы и в качестве ключа взять, например, часть SQL запроса (условно первые 30 символов и последние 30), это лучше, чем ничего, и в некоторых случах может помочь найти исходный C# код. Даже если не поможет — в любом случае, из таблицы с хешами будет виден масштаб изменений. Наконец, тесты можно выполнять по частям, и соответственно, использовать это для описания, т.е., например, брать в качестве ключа первые 30 и последние 30 символов из SQL и название текущей группы запускаемых тестов, например, условный ключ Sales Details Tests: SELECT sales.ordernumber, sale...N sales.productid = product.id
Этот подход хорошо сработает для вторичных систем, по большей части состоящих из набора SQL-запросов. Или вы таки применяете его где-то ещё?:)
Рассмотрена PostgreSQL как одна из наиболее популярных, и там действительно эффективные оптимизации. Если расмотреть, например, ClickHouse, который ещё активно развивается, то в некоторых версиях перенос INNER JOIN в WHERE работает быстрее, несмотря на абсурдность такого SQL, в последних вериях это исправили. Т.е. эти количественные характеристики зависят от SQL диалекта, и иногда имеет смысл вылавливать даже INNER JOIN.
Также подход может быть полезен при рефакторинге — можно проверить, что нет изменений и ничего не сломалось именно с точки зрения производительности, и не нужно гонять бечмарки после каждого изменения.
Условной характеристикой качества SQL запроса будем считать количество INNER JOIN в нем
Берем простой запрос с подзапросом (WITH сгодится). Внутри WITH пишем оконную функцию. Снаружи - WHERE, для отбора например 1 записи. Имеем фуллскан, потому что постгрес не умеет проталкивать предикаты в подзапрос, если там есть оконная функция (не знаю, для каких версий это верно). То есть, в запросе вообще нет JOIN, но он ужасно неэффективен.
Я ваши оговорки чуть ниже этой фразы видел, но это все равно фигня. Методика не останется той же самой, если заменить число джойнов на другую метрику. Если бы план запроса было так просто построить - каждый дурак и писал бы оптимизаторы этого плана. Единственный работающий способ - это попросить у СУБД план запроса при помощи EXPLAIN, опять же с оговорками, что в некоторых случаях время выполнения будет таким же, как для выполнения запроса, а потом этот план научиться анализировать (это не является чем-то невозможным). Вот уже из плана (постгрес умеет отдавать его во вполне разбираемом виде типа JSON или ямл) можно понять, эффективен ли запрос - хотя бы взять кост, который тоже те еще попугаи - но это попугаи на базе данных СУБД, а не выдумок.
То есть, в запросе вообще нет JOIN, но он ужасно неэффективен
В статье нет цели заменить анализ плана выполнения запроса на один показатель. И даже если нет INNER JOIN — то такой кейс не является контрпримером, т.к. ноль INNER JOIN соответствует не оптимальности запроса, а только тому, что нет информации об ухудшении производительности запроса в разрезе INNER JOIN, а по другим критериям — по плану выполения запроса, или, в частности, по подзапросам и WHERE с оконными функциями — ухудшение производительности может быть.
В качестве дополнительной метрики можно использовать количество подзапросов (считать WITH), или при помощи регулярных выражений (при возможности и необходимости) искать как раз позапросы с WHERE и оконными функциями.
Вот уже из плана (постгрес умеет отдавать его во вполне разбираемом виде типа JSON или ямл) можно понять, эффективен ли запрос
В статье описывается подход применительно к тестам, не ставится задача глубокого анализа каждого SQL запроса, как может делать не только СУБД, но и, частично, например, какой-нибудь плагин Rider для разных SQL диалектов.
В контексте тестов всё фокусируется на оценке регресса, деградации, сравнении до и после, а не на детальном анализе запроса: если все SQL совпадают (определяем по совпадению двух Total хешей до и после), то деградации нет, тесты проходят и заканчиваем анализ. Делать выводы на основе нуля INNER JOIN при совпадении Total хешей всех запросов "до и после" никто не собирается.
попугаи на базе данных СУБД, а не выдумок
Всё же это не выдумки, а так как всё относится к тестам, то это больше идеи на основе эспертизы разработчика, что он делает, что меняет в генерации SQL, на что это может повлиять. Так вообще можно назвать любые тесты "выдумками" и их запретить.
Если разработчик видит, что он меняет логику генерации SQL, в которой присутствуют INNER JOIN, и он может случайно увеличить их количество, и это будет считаться багом и проблемой производительности, то в таком случае есть смысл считать количество INNER JOIN, записывать в таблицу, это не более чем метаданные для простейших тестов, упрощающие работу разработчика и позволяющие сразу найти и исправить такого рода баги: был 1 INNER JOIN, стало 2 или 10 — это выглядит, как явный баг, при изменениях в логике с INNER JOIN, и нуждается в проверке и исправлении, как раз с помощью ручного анализа плана выполнения запроса.
Но в подходе из статьи анализировать вручную требуется условно не все подряд 5000 тестов в проекте, а с учетом того, что изменения в SQL тестов фиксируются, вручную необходимо проверять только несколько тестов с именениями в SQL, плюс разработчик может и без анализа плана выполнения запроса понять, что +10 INNER JOIN — это баг, и сразу перейти к исправлению, тогда анализ плана заведомо неоптимального запроса и потерю времени на это можно исключить. Причем интеграционные тесты такие баги могут не выявить, т.к. результаты запросов могут совпасть, но SQL будет разный. То же самое и с другой метрикой, например, количеством подзапросов WITH, с несколькими метриками одновременно, и т.д.
Непрямой контроль за изменениями в производительности приложения через генерируемый SQL и его характеристики