В апреле на RailsConf в Фениксе мы обсудили огромное количество советов по использованию Postgres с Rails, и подумали, что будет полезно их записать и поделиться с более широкой аудиторией. Здесь вы найдете некоторые из них, касающиеся отладки и улучшения производительности базы данных вашего Rails приложения.
Управление долгими запросами с помощью таймаутов
Долгие запросы могут оказывать все виды негативных воздействий на вашу базу данных. Независимо от того, работают ли запросы часами или всего несколько секунд, они могут держать блокировки, переполнять WAL, или просто потреблять огромное количество системных ресурсов. C Postgres легче добиться большей стабильности с помощью установки таймаута на запросы. Удобно, что можно установить значение по умолчанию, например, 5 секунд, как показано в примере ниже, и тогда любой запрос, длящийся дольше 5 секунд будет убит:
production:
url: <%= DATABASE_URL %>
variables:
statement_timeout: 5000
Если вам требуется, чтобы запрос выполнялся дольше в пределах сессии, можно задать таймаут, действительный только для текущего соединения:
class MyAnalyticsJob < ActiveJob::Base
queue_as :analytics
def perform
ActiveRecord::Base.connection.execute “SET statement_timeout = 600000” # 10 минут
# ...
ensure
ActiveRecord::Base.connection.execute “SET statement_timeout = 5000” # 5 секунд
end
end
Поиск “плохих” запросов
Rails многое абстрагирует при взаимодействии с вашей базой данных. Это может быть как хорошо, так и плохо. Postgres сам позволяет отслеживать долгие запросы, но по мере роста вашего Rails приложения, этого может оказаться недостаточно. Чтобы узнать происхождение запроса, есть чрезвычайно удобный гем marginalia, который будет логировать, откуда именно пришел ваш запрос. Теперь, когда вы видите неправильный запрос, слишком медленный, или который просто можно убрать, вы точно знаете где в коде это поправить:
Account Load (0.3ms)
SELECT `accounts`.* FROM `accounts`
WHERE `accounts`.`queenbee_id` = 1234567890
LIMIT 1
/*application:BCX,controller:project_imports,action:show*/
Внимание на комментарий — прим.пер.
Высокоуровневый обзор ситуации с запросами к базе
Часто требуется общая картина того, что происходит в вашей базе данных. pg_stat_statements — это расширение Postgres, которое часто предустановленно в облачных средах, таких как Citus Cloud. Оно позволяет видеть, какие запросы выполнялись с момента последнего сброса статистики и как они себя вели.
Например, чтобы увидеть 10 наиболее долго выполняющихся запросов и их среднее время, выполните следующее:
SELECT query, total_time / calls AS avg_time
FROM pg_stat_statements
ORDER BY total_time DESC
LIMIT 10;
Если включить «track_io_timing» в сборщике статистики Postgres, то можно понять что является узким местом — процессор или ввод-вывод. Можно узнать больше о pg_stat_statements здесь (или в другой статье okmeter.io на хабре — прим.пер.).
Использование продвинутых фич
По умолчанию Rails использует файл «schema.rb» для хранения копии схемы базы данных, обычно используемой для инициализации базы данных перед запуском тестов. К сожалению, многие расширенные функции Postgres, такие как функциональные и частичные индексы, а также составные первичные ключи, не могут быть представлены в этом DSL.
Вместо этого имеет смысл перейти на генерируемый и используемый Rails файл «db/structure.sql», который можно сделать следующим образом:
# Use SQL instead of Active Record's schema dumper when creating the database.
# This is necessary if your schema can't be completely dumped by the schema dumper,
# like if you have constraints or database-specific column types
config.active_record.schema_format = :sql
Внутри используется формат Postgres «pg_dump», который иногда может быть чересчур подробным, но зато гарантирует получение полностью восстановленной структуры базы данных. Если вы столкнетесь с проблемой чрезмерно длинных diff-ов, можете взглянуть на activerecord-clean-db-structure.
Следите за тем, чтобы сложные транзакции не блокировали друг друга
Rails любит помещать всё в транзакции, особенно при использовании хуков «before_save» и многоуровневых связей между моделями. Существует одно важное предостережение, которое следует учитывать при транзакциях, которые могут создавать проблемы при масштабировании. Например, в такой транзакции:
BEGIN;
UPDATE organizations SET updated_at = ‘2017-04-27 11:31:03 -0700’ WHERE id = 123;
SELECT * FROM products WHERE store_id = 456;
--- еще всякие statement’ы тут
COMMIT;
Первый оператор UPDATE будет удерживать блокировку на строку "organizations" с id "123", с самого начала и до COMMIT’а транзакции.
Представьте другой запрос к той же самой "organization", пришедший, например, от другого пользователя, и выполняющий аналогичную транзакцию. Как правило, чтобы выполниться, этому другому запросу придется ждать комита первой транзакции, что увеличит время ответа. Чтобы исправить это, можно переупорядочить транзакцию, чтобы UPDATE выполнялся ближе к концу, а также рассмотреть возможность сгруппировать обновления полей временных меток вне транзакции после выполнения основной работы.
Чтобы обнаруживать подобные проблемы зарание, вы можете установить «log_lock_waits = on» в PostgreSQL.
Контролируйте подключения к базе данных
Rails по умолчанию поддерживает пул подключений к базе данных. Когда приходит новый запрос, он берет одно соединение из пула и передает его в приложение. По мере масштабирования вашего Rails приложения это может привести к сотням открытых подключений к базе данных, хотя на самом деле только часть из них выполняет работу. Ключом к этому является использование менеджера соединений для уменьшения активных подключений к базе данных, например, такого как pgBouncer. Менеджер соединений будет открывать соединения, когда транзакции активны, и не пропускать простаивающие соединения, которые не выполняют никакой работы.
От переводчика — а еще вы можете использовать наш мониторинг, который отследит количество соединений к Постгресу и к pgBouncer и много всего другого и поможет предупредить и разрешить проблемные ситуации.