Мы, студенты из МИФИ, Даниил и Александр, пришли на стажировку в Сбербанк в департамент SberData, который занимается развитием внутренней корпоративной аналитической платформы (КАП).Это современная платформа с удобными инструментами созданная для закрытия полного спектра потребностей Сбера в работе с данными, таких как хранение, интеграция, разнообразная аналитика, отчетность, моделирование и контроль качества данных. Все эти направления было бы трудно развивать без отдельного R&D подразделения, в составе которого мы и работаем. Сегодня мы хотим поделиться нашим исследованием в области проектирования алгоритмов в выявлении «токсичных» SQL‑запросов с помощью машинного обучения. Почему же запросы называются именно «токсичные»? Они затрачивают на своё выполнение слишком большое количество ресурсов, а именно времени. На самом деле не только время, но для упрощения мы будем считать только время, так как это ключевой параметр.
Статья посвящена исследованию существующих подходов и их апробации на открытых данных. В качестве общедоступных данных были выбраны данные из таких бенчмарков, как TPC‑H и BIRD. Помимо этого, в статье рассматриваются некоторые трудности, с которыми мы столкнулись при работе над задачей, например, генерация данных и SQL‑запросов, а также миграция между диалектами SQL. В конце статьи мы опишем оригинальный подход, к которому по итогу пришли. В следующей статье мы расскажем о применении полученного опыта для реальной промышленной системы.
Наше решение задачи можно разделить на несколько пунктов:
Обзор существующих методов
Рассмотрим три различных подхода:
1.1 Одним из таких являются графовые нейронные сети — архитектура нейронный сети позволяет работать с данными, представленными в виде матрицы смежности, за счет графовой свертки. В данной работе (Query Performance Prediction for Concurrent Queries using Graph Embedding) план выполнения запроса представляется в виде графа, вершинами которого являются операторы запроса и их параметры, ребра же отражают связи между этими операторами. Этот метод дает возможность рассматривать сразу несколько запросов, что позволяет учитывать конкуренцию за ресурсы при одновременном выполнении нескольких запросов. Метод обеспечивает высокую точность предсказания, но требует больших объемов данных для эффективного обучения. Пример построения нейронной сети для набора операторов представлен на рисунке ниже.
1.2 Затем мы обратили внимание на идею (Plan‑Structured Deep Neural Network Models for Query Performance Prediction) рассмотрения каждого оператора запроса, как отдельного элемента. Предлагается использовать модульную архитектуру глубокой нейронной сети, где каждый оператор плана запроса отображается отдельным модулем. Эти модули объединяются в единую сеть, которая соответствует структуре плана запроса и позволяет точно прогнозировать задержки на каждом этапе выполнения запроса. Такой подход позволяет гибко и точно моделировать сложные SQL‑запросы, учитывая их структуру и ресурсоемкость. Процесс работы модели (полный цикл) показан на рисунке ниже.
1.3 Эти методы используются для предсказания времени выполнения SQL‑запросов на основе анализа их планов выполнения, однако стоит учитывать, что в реальной системе не всегда есть доступ к плану выполнения запроса. По этой причине на фоне остальных подходов выделяется метод, основанный на векторизации текста запроса, используя такие методы, как TF‑IDF и Bag of Words (BoW). Например, в исследовании используются методы векторизации текста в сочетании с различными моделями, для прогнозирования времени выполнения запросов и их ресурсов. Архитектура модели, представленная в статье, показана на рисунке ниже.
Таким образом опишем плюсы и минусы каждого метода:
Подход |
Особенности |
Минусы |
Query Performance Prediction for Concurrent Queries using Graph Embedding |
Учитывает выполнение нескольких запросов одновременно. Представление плана запроса в виде графа. |
Необходим план запроса Требуется большое количество данных |
Plan‑Structured Deep Neural Network Models for Query Performance Prediction |
Модульное представление плана запросов |
Необходим план запроса |
Forecasting SQL Query Cost at Twitter |
Не используется план запросов. Векторизируется текст запросов. |
Векторизация текста не позволяет учитывать порядок операторов в запросе |
Описание выбранных моделей и метрик
Ниже мы будем применять понятие плана выполнения запроса — детализированное описание последовательности операций, которые СУБД (система управления базами данных) выполняет для получения результата SQL‑запроса. Для того чтобы не вмешиваться в промышленные системы, мы будем использовать текст запроса. Но из плюсов нам будет доступно только два параметра из плана запроса — cost и row. Cost (оценка стоимости) указывает на оценку стоимости выполнения каждой операции в плане запроса. Row показывает количество строк, которое, по оценке СУБД, будет прочитано.
Для выбранных нами признаков опишем используемые алгоритмы векторизации и модели регрессии.
Алгоритмы векторизации:
BoW (Bag of Words): преобразует текст в числовые признаки, подсчитывая частоту каждого слова в документе;
TF‑IDF (Term Frequency‑Inverse Document Frequency): преобразует текст в числовые признаки, учитывая частоту слова в документе и обратную частоту слов в корпусе текстов;
BERT (Bidirectional Encoder Representations from Transformers): используется для создания векторных представлений текстов. Модель предобучена (bert‑base‑uncased) на больших данных и способна улавливать контекстные зависимости между словами в предложении. Тексты запросов были преобразованы в токены с помощью BERT Tokenizer, а затем токены использовались для извлечения признаков с помощью BERT Model.
Модели регрессии:
Lasso — регуляризованный алгоритм линейной регрессии, использующий штраф L1;
Ridge — алгоритм, аналогичный Lasso, но использующий штраф L2;
Random forest — случайный лес, используемый для задачи регрессии;
Neural network — классическая полносвязная многослойная нейронная сеть, реализованная на Pytorch;
SVR — метод опорных векторов для задачи регрессии.
Метрики:
-
MAPE — среднее абсолютное отклонение (погрешность предсказания в долях / процентах):
-
Accuracy — для задачи регрессии, если предсказанное значение совпадает с истинным с погрешностью %, то считается, как верное предсказание. Далее используется окно в 25 % погрешности.
В этих формулах— это ответ модели, а - это истинное значение.
Тестирование моделей на TPC-H
Сразу получить доступ к данным у нас не вышло, и первым шагом решили потренироваться на «кошечках». Первоначально для обучения моделей и предварительной оценки был выбран бенчмарк TPC‑H, который в основном используется для сравнения производительности аналитических систем и хранилищ данных. Решили взять для теста именно его, так как в нем уже есть более пяти связанных по ключу таблиц и генерировать данные для теста нет необходимости. Во время тестирования моделей столкнулись с тем, что SQL‑запросов, содержащихся в выбранном бенчмарке, не хватает, поэтому был разработан модуль для их генерации при помощи языковых моделей, который будет описан ниже. Для проверки работы моделей сгенерировали данные — среднее выполнение SQL‑запросов до десятков секунд. При тестировании были получены результаты, представленные в таблице ниже.
Regressor |
MAPE (%) |
Accuracy (%) |
||
TF‑IDF |
BERT |
TF‑IDF |
BERT |
|
Lasso |
120,76 |
120,76 |
13,33 |
13,33 |
SVR |
148,57 |
125,61 |
10,00 |
13,33 |
Random Forest |
90,55 |
101,49 |
20,00 |
3,33 |
Neural Network |
86,12 |
171,51 |
0,00 |
16,67 |
Видно, что показатели точности предсказаний довольно низкие, а значит, можно сделать вывод о том, что данных для обучения мало, поэтому утверждать об успехе или провале нельзя. Из‑за большого разнообразия слов в запросах размерность признакового пространства для этого примера превышает количество обучающих данных и модель не может достигнуть хорошей обобщающей способности. В связи с этим дальше необходимо выбрать новый бенчмарк, в котором будет больше таблиц и получится увеличить количество SQL‑запросов.
Модуль генерации SQL-запросов
Из‑за недостатка SQL‑запросов решили написать свой небольшой модуль по генерации, в который можно найти по ссылке. Принцип работы основан на большой языковой модели — GigaChat. Для наших задач он отлично справился с генерацией дополнительных SQL запросов. Вот алгоритм действия:
Идет обращение в GigaChat с указанием на генерацию некоторого числа SQL‑запросов;
Из полученного текста извлекаются SQL‑запросы;
Они валидируются на БД малого объема для проверки корректности синтаксиса.
При разработке были выявлены следующие особенности:
лучше указывать ограничения в генерируемых запросах за раз, тогда их разнообразие будет выше (подбирается эмпирическим методом);
можно делать проверку на уникальность, ответы языковой модели могут повторяться.
Получилось набрать около 300 уникальных запросов в дополнение к уже имевшимся. Но практика показала, что этого все же недостаточно, и было принято решение найти базу данных с большим количеством таблиц для увеличения количества обучающей выборки.
Тестирование моделей на BIRD
С целью получения набора данных с изначально большим числом запросов и таблиц на следующем этапе был выбран бенчмарк BIRD, предназначенный для решения задачи text2sql. Он включает в себя множество баз данных, к каждой из которых есть определенное число запросов. Таблица с топ-7 БД по количеству запросов представлена ниже.
Название БД |
Количество запросов |
Количество таблиц |
works_cycles |
473 |
65 |
public_review_platform |
380 |
15 |
retail_world |
360 |
8 |
movie_3 |
314 |
16 |
mondial_geo |
292 |
34 |
soccer_2016 |
257 |
21 |
retails |
242 |
8 |
Наш окончательный выбор пал на базу public_review_platform из‑за большого количества запросов. При этом количество таблиц в ней небольшое — это позволит легко мигрировать на postgres с изначального sqlite, на котором сделан весь бенчмарк BIRD. Подробнее про миграцию будет ниже.
Также на этом шаге мы решили добавить дополнительную предобработку запросов, чтобы повысить точность, а именно:
Форматирование (приведение к единому регистру и замена множественных пробелов);
Удаление комментариев (как однострочных, так и многострочных);
-
Замена алиасов на полные названия таблиц;
-
Было:
SELECT u.user_id, u.user_average_stars, r.review_stars FROM users u JOIN reviews r ON u.user_id = r.user_id WHERE r.review_length = 'Short' AND r.review_votes_useful = 'Medium';
-
Стало:
select users.user_id, users.user_average_stars, reviews.review_stars from users join reviews on users.user_id = reviews.user_id where reviews.review_length = 'short' and reviews.review_votes_useful = 'medium';
-
Добавление дополнительных признаков из плана запроса (примерная стоимость запроса и количество обрабатываемых строк).
Более того, к базе данных были сгенерированы дополнительные запросы с помощью модуля генерации запросов, описанного выше. Тем самым число запросов увеличилось до 600, и, помимо этого, размер базы данных был расширен с 100 Мб до 15 Гб суммарно. Затем на полученных данных было проведено тестирование, результаты которого представлены ниже.
TF‑IDF
Regressor |
MAPE (%) (с cost и rows) |
MAPE (%) (без cost и rows) |
Accuracy (%) (с cost и rows) |
Accuracy (%) (без cost и rows) |
XGBoost |
33,30 |
136,86 |
80,49 |
67,07 |
SVR |
98,81 |
435,06 |
62,20 |
64,63 |
Random Forest |
32,22 |
66,99 |
76,83 |
43,90 |
Ridge |
277,41 |
118,75 |
46,34 |
34,15 |
Neural Network |
109,40 |
142,47 |
47,56 |
46,34 |
BoW
Regressor |
MAPE (%) (с cost и rows) |
MAPE (%) (без cost и rows) |
Accuracy (%) (с cost и rows) |
Accuracy (%) (без cost и rows) |
Lasso |
89,94 |
89,94 |
24,39 |
14,39 |
SVR |
1509,46 |
139,86 |
54,39 |
49,51 |
Random Forest |
45,29 |
70,62 |
64,15 |
50,73 |
XGBoost |
77,87 |
130,64 |
58,05 |
53,17 |
Neural Network |
117,44 |
167,93 |
43,41 |
37,32 |
BERT
Regressor |
MAPE (%) (с cost и rows) |
MAPE (%) (без cost и rows) |
Accuracy (%) (с cost и rows) |
Accuracy (%) (без cost и rows) |
Lasso |
89,94 |
201,7 |
43,90 |
24,39 |
SVR |
258,3 |
594,61 |
64,63 |
62,20 |
Random Forest |
56,20 |
68,40 |
46,34 |
37,80 |
XGBoost |
35,48 |
223,24 |
76,83 |
47,56 |
Neural Network |
191,81 |
93,61 |
53,66 |
39,02 |
Исходя из анализа таблиц, были выделены наилучшие модели регрессии, в частности, случайный лес и градиентный бустинг показали наиболее высокие результаты. Это объясняется тем, что случайный лес является ансамблевым (бэггинг) методом также, как и градиентный бустинг, который позволяет лучше обобщать данные и обеспечивать более стабильные результаты.
При рассмотрении различных методов векторизации текста было выявлено, что мешок слов в среднем оказался хуже, поскольку метод просто считает количество вхождений слов, не учитывая их взаимосвязи и контекст. В противоположность этому, TF‑IDF показал высокие результаты, так как учитывает частоту встречаемости каждого слова в документах, что предотвращает переоценку часто встречающихся терминов (например, операторов SQL) и позволяет выделять редкие, но значимые слова, такие как названия таблиц или столбцов. BERT также показал высокие результаты, но дальнейшее тестирование проводилось на векторизаторе TF‑IDF из‑за его высокой скорости работы. Тем не менее, итоговую модель можно запустить на BERT, так как он способен учитывать семантику слов, что повышает точность модели.
Max Features |
Regressor |
MAPE (%) |
Accuracy Reg (%) |
128 |
Random Forest |
38,06 |
76,83 |
XGBoost |
33,3 |
80,49 |
|
SVR |
98,81 |
62,20 |
|
256 |
Random Forest |
29,16 |
75,61 |
Catboost |
82,96 |
62,20 |
|
XGBoost |
33,31 |
84,15 |
|
512
|
XGBoost |
33,31 |
84,15 |
Random Forest |
37,10 |
71,95 |
|
SVR |
126,91 |
63,41 |
|
1024 |
Random Forest |
27,67 |
76,83 |
XGBoost |
33,31 |
84,15 |
|
Linear |
234,78 |
65,85 |
|
2048 |
Random Forest |
28,01 |
76,83 |
XGBoost |
33,31 |
84,15 |
|
SVR |
126,91 |
63,41 |
|
4096 |
Random Forest |
31,28 |
78,05 |
XGBoost |
33,31 |
84,15 |
|
Catboost |
82,96 |
62,20 |
|
8192 |
Random Forest |
37,13 |
73,17 |
XGBoost |
33,31 |
84,15 |
|
SVR |
126,91 |
63,41 |
При анализе зависимости точности моделей от максимального количества признаков в векторизаторе TF‑IDF было замечено, что с увеличением количества признаков точность моделей возрастает. Это связано с тем, что больший размер вектора позволяет учитывать большее количество столбцов таблиц и ключевых слов запросов. Однако избыточный размер вектора перестает приносить дополнительные преимущества, и точность моделей стабилизируется. Поэтому размерность вектора следует подбирать, исходя из среднего и максимального количества таблиц и столбцов в системе. Для нашего случая оптимальным значением максимального размера вектора оказалось 4096, но при необходимости более быстрой работы можно использовать и размер, равный 1024. Выбор размерности объясняется размером «словаря», который используется в запросах, в частности, с количеством уникальных названий таблиц и столбцов. Забегая вперед, скажем, что на реальных данных необходим вектор большего размера, поскольку мощность словаря больше.
Во время проведения исследования на данном бенчмарке мы встретились с некоторыми трудностями, а именно генерацией данных и миграцией с SQLite на PostgreSQL, для решения которых были разработаны определенные модули. Опишем подробнее ситуации ниже.
Модуль генерации данных в БД
Не для всех бенчмарков созданы модули по генерации данных, поэтому пришлось написать инструмент, решающий нашу проблему, который можно изучить по ссылке. Для полного функционала обобщили генерацию на произвольную структуру БД. Основные особенности модуля представлены ниже:
Ввод схемы БД для генерации данных;
Поддержка внешних ключей для корректной генерации (происходит наивная проверка среди уже сгенерированных данных);
Для каждого столбца можно задать значения для генерации (например, список городов);
Запись в файл или построчная запись в PostgreSQL.
Миграция с SQLite на PostgreSQL
Бенчмарк BIRD в качестве диалекта использует SQLite, типы данных которого отличаются от PostgreSQL. Просто использовать SQLite нежелательно, и мы мигрировали в PostgreSQL. Поэтому нам необходимо было выполнить следующие шаги для миграции (их можно выполнить при помощи ручного анализа и регулярных выражений для замены):
Перевести описание таблиц в корректный формат;
Изменить все типы данных в соответствии с содержанием (например, некоторые колонки «текст» перевести во «время»);
Перевести табличные файлы описания в.sql файлы;
Внести изменения данных в запросы (например, «11AM» перевести в «11:00:00»).
Выводы
Для PostgreSQL получилось достичь результатов, с которыми уже можно продолжать работать на реальных СУБД (на бенчмарке BIRD: ~30% отклонения / 75–80% точности). Лучшими векторизаторами оказались TF‑IDF и BERT, но последний более ресурсозатратный. А лучшую точность показали регрессоры градиентный бустинг (XGBoost) и случайный лес (Random Forest) за счет их ансамблевой структуры.
Основные преимущества модели:
Базируется на тексте запроса, который несложно получить в системе;
Хорошая интерпретируемость.
Недостатки и возможные нюансы при дальнейшей работе:
При большом количестве таблиц вектор будет слишком большим (вероятно, необходимо уменьшить признаковое пространство).
Авторы: Усков Даниил(@Dusik207) и Диков Александр(@DikovAlexandr) под кураторством Максима Радионова(@tr1ggers), Дмитриева Игоря (@indmitriev) и Владимира Зворыгина(@Wocher), участники профессионального сообщества Сбера DWH/BigData.
Комментарии (3)
sshikov
23.10.2024 17:15На самом деле не только время, но для упрощения мы будем считать только время, так как это ключевой параметр.
Если бы авторы немного вникли в то, как выполняет запросы например Spark, то поняли бы, что время вообще не параметр само по себе, потому что его можно разменять на ресурсы. Т.е., вы можете выделить на выполнение запроса скажем, 10 ядер CPU, и 10 гигабайт памяти, а можете - 1000 ядер и 10 терабайт. И результаты по времени выполнения будут совершенно разные. Время актуально при условии, что ресурсы фиксированы, а это далеко не всегда так.
Ну и как резонно выше замечено, на выполнение запросов влияет множество факторов, и кроме наличия индексов можно еще назвать наличие актуальных статистик, про которое (в отличие от наличия индекса) мы вообще ничего не знаем.
RedWolf
23.10.2024 17:15В постгресе можно подкрутить настройки памяти (work_mem), и вместо адского торможения на диске, запрос будет летать в памяти.
arseniiv
Все хорошо, но в исследовании как-то учитывались индексы на таблицах? Ведь, токсичные запросы могут возникать когда нехватает или плохо построены индексы.