Ценность уместного комментария

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

У каждого из нас есть задача, над решением которой ты долго и безуспешно бьешься, которую изучил уже и вдоль и поперек, но все равно не можешь получить нужный результат. При этом одни уверены, что твоя задача в принципе не имеет решения, другие - что проблемы вообще нет. Такой задачей для меня была длительность выполнения запросов к хранилищу данных на Greenplum. И как же приятно наконец-то написать, что я эту проблему победил!

Когда пытаешься понять, почему запросы занимают столько времени
Когда пытаешься понять, почему запросы занимают столько времени

Из раза в раз при работе с хранилищем на ожидание результата запроса уходило по несколько минут. Причем не важно, запрос выбирает данные из таблицы на десять строк, или на миллион - время на ожидание будет соизмеримым. А любой простой в таком творческом процессе, как аналитика - это просто пытка!

Я много раз заходил на исправление такого поведения. Начал с тюнинга sql клиента. DBeaver предлагает пользователям целую кучу разных тонких настроек - кажется, я перепробовал их все, но успеха не достиг. Следом перепробовал настройки самого драйвера PostgreSQL - эффект тот же. 

Все это время перед глазами был пример специализированного клиента для Greenplum - Aginity Workbench, в котором из коробки скорость выполнения аналогичных запросов ощутимо выше. Эта прога была дополнительным раздражающим фактором, так как найти причины такого ускорения у меня не получалось - ее исходный код закрыт и работает она только по лицензии, а подключение к greenplum в ней происходит при помощи нативных инструментов, а не обычных jdbc драйверов.

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

В этот момент я испытал целую бурю эмоций - от радости, что наконец-то нашел проблему, до недоумения от самой сути находки. То, что каталог прилично нагружен, я знал и раньше: к хранилищу сейчас подключено уже больше 250 систем-источников, в каждом не одна сотня таблиц, а в самих таблицах может быть двухуровневое партиционирование. Greenplum учитывает каждую партицию как отдельный объект, поэтому системные представления вроде pg_class прилично раздуваются. Но неужели действительно нужно обращаться к каталогу при любом запросе данных?

К счастью, драйвер постгреса имеет открытый исходный код, поэтому можно самостоятельно посмотреть, что же происходит под капотом. Заглянул в код, нашел нужный класс PgResultSetMetaData и его метод, который запрашивает метаданные - fetchFieldMetaData. Содержимое обнадеживает - похоже, без метаданных можно обойтись: fetchFieldMetaData вызывается из нескольких других методов, которые для моих целей не так важны, при этом сами эти методы допускают возврат пустого объекта, что намекает на возможность выпилить из кода вообще все обращения к fetchFieldMetaData, а значит избежать медленного запроса к каталогу.

Подробнее про методы:
  • isAutoIncrement. От запроса метаданных в этом методе легче всего отказаться: в нашем хранилище таких полей нет, да и комментарий "It is believed that PostgreSQL does not support this feature" как бы намекает, что и для других баз он не пригодится.

      public boolean isAutoIncrement(int column) throws SQLException {
        fetchFieldMetaData();
        Field field = getField(column);
        FieldMetadata metadata = field.getMetadata();
        return metadata != null && metadata.autoIncrement;
      }
  • isNullable. При отсутствии метаданных в резалтсете все поля будут считаться nullable - не велика потеря.

      public int isNullable(int column) throws SQLException {
        fetchFieldMetaData();
        Field field = getField(column);
        FieldMetadata metadata = field.getMetadata();
        return metadata == null ? ResultSetMetaData.columnNullable : metadata.nullable;
      }
  • getBaseColumnName. В коде драйвера этот метод используется в updatable резалтсетах при обновлении значений. В мои планы не входит использование этого функционала, но на всякий случай можно будет прикрутить выбрасывание исключений при вызове таких методов.

    public String getBaseColumnName(int column) throws SQLException {
        Field field = getField(column);
        if (field.getTableOid() == 0) {
          return "";
        }
        fetchFieldMetaData();
        FieldMetadata metadata = field.getMetadata();
        return metadata == null ? "" : metadata.columnName;
      }
  • getBaseSchemaName и getBaseTableName. Возвращают название схемы/таблицы, в которой находится запрошенный атрибут. В коде драйвера метод нигде не используется, мне эти сведения тоже не особо нужны.

    public String getBaseSchemaName(int column) throws SQLException {
        fetchFieldMetaData();
        Field field = getField(column);
        FieldMetadata metadata = field.getMetadata();
        return metadata == null ? "" : metadata.schemaName;
      }
    
    public String getBaseTableName(int column) throws SQLException {
        fetchFieldMetaData();
        Field field = getField(column);
        FieldMetadata metadata = field.getMetadata();
        return metadata == null ? "" : metadata.tableName;
      }

Проверяю влияние по коду, комментирую вызов метода fetchFieldMetaData, собираю jar файл драйвера, подсоединяюсь с его помощью к базе, иииии…

Сказать, что это дало результат - это не сказать ничего. Запросы теперь просто летают. Ускорение - в разы. Отправил коллегам на тест - отзывы примерно такие:

Дополнительный бонус, который греет сердце: отказываясь от необязательных запросов метаданных к каталогу pgclass, мы снимаем с него лишнюю нагрузку. Не уверен, что для хранилища это дает какой-либо значимый эффект, но тут как в борьбе за экологию - радует даже то, что ты хоть сколько нибудь улучшаешь ситуацию.

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

P.S. В итоговой версии кода создал новый параметр подключения драйвера runtimeMetaDisable. Вызов метаданных и выбрасывание исключений привязал к его значению. Такой подход более гибок, чем жестко закомментированный вызов метода и позволяет управлять поведением драйвера в зависимости от потребностей. Код выложил на гитхаб. Если у вашей базы тяжелый каталог и вы хотите попробовать драйвер в деле, но не знакомы с миром java и не знаете, как собрать jar файл драйвера - напишите в комментариях!

Источник: https://habr.com/ru/company/rostelecom/blog/535696/


Интересные статьи

Интересные статьи

Один из ключевых сценариев работы в CRM это общение с клиентом в удобном для него канале. По почте, по телефону, по SMS или в мессенджере. Особенно выделяется WhatsApp — интеграцию с ...
Много всякого сыпется в мой ящик, в том числе и от Битрикса (справедливости ради стоит отметить, что я когда-то регистрировался на их сайте). Но вот мне надоели эти письма и я решил отписатьс...
Ранее в одном из наших КП добавление задач обрабатывалось бизнес-процессами, сейчас задач стало столько, что бизнес-процессы стали неуместны, и понадобился инструмент для массовой заливки задач на КП.
Тема статьи навеяна результатами наблюдений за методикой создания шаблонов различными разработчиками, чьи проекты попадали мне на поддержку. Порой разобраться в, казалось бы, такой простой сущности ка...
Если Вы используете в своих проектах инфоблоки 2.0 и таблицы InnoDB, то есть шанс в один прекрасный момент столкнуться с ошибкой MySQL «SQL Error (1118): Row size too large. The maximum row si...