Выполнение запросов Rails вызывает всплески базы данных

У меня проблема с моим Rails-приложением, когда некоторые случайные запросы занимают около 5 секунд или дольше. В большинстве случаев запросы очень просты (select * from x where id = ?), и поля даже индексируются.

Вот еще информация о настройке:

  • Puma 3.5.0 за противоположным прокси-сервером nginx
    • 4 рабочих с минимум 4, максимум 8 потоков каждый.
  • Ruby v2.2.3, Rails v4.2.4
  • База данных PostgreSQL 9.4
    • Пул потоков, настроенный на максимальное количество 60 соединений
  • Приложения для мониторинга
  • 8 ГБ оперативной памяти, 4 процессора, SSD.

Я узнал об этом при просмотре производительности запросов в Appsignal. Я заметил, что большинство запросов заканчиваются за несколько мс, а затем время от времени, все еще в одном запросе, есть несколько запросов, на которые требуется 5+ секунд. И странная часть состоит в том, что она ВСЕГДА занимает 5,.. секунд. Вот картина этого в действии: Производительность по протоколу

Вещи, которые я пробовал:

  • Увеличьте пул потоков, чтобы убедиться, что потоки рабочей среды puma имеют достаточно объектов соединения.
  • Установите 'reaping_frequency' в 10 секунд, чтобы убедиться, что не используются мертвые соединения.
  • Увеличение числа работников/потоков puma

Я замечаю это в приложении, так как есть некоторые страницы, на которые требуется много времени для загрузки (у меня есть вызов функции, который занимает около 1 минуты), и каким-то образом это блокирует новые запросы. Это странно для меня, так как есть 4 рабочих с 8 потоками = 32 потока, которые могут обрабатывать другие запросы.

Я выполнил объяснение по запросу на рисунке выше, это результат:

Limit  (cost=0.28..8.30 rows=1 width=150)
  ->  Index Scan using index_addresses_on_addressable_id_and_addressable_type on addresses  (cost=0.28..8.30 rows=1 width=150)
        Index Cond: ((addressable_id = 1) AND ((addressable_type)::text = 'University'::text))
        Filter: (deleted_at IS NULL)
Total query runtime: 13 ms

И это схема таблицы адресов:

# Table name: addresses
#
#  id               :integer          not null, primary key
#  street           :string
#  zip_code         :string
#  city             :string
#  country          :string
#  addressable_id   :integer
#  addressable_type :string
#  created_at       :datetime         not null
#  updated_at       :datetime         not null
#  street_number    :string
#  latitude         :float
#  longitude        :float
#  mobile           :string
#  phone            :string
#  email            :string
#  deleted_at       :datetime
#  name             :string`

Здесь мой конфигурационный файл Puma:

#!/usr/bin/env puma

directory '/home/deployer/apps/qeystate/current'
rackup "/home/deployer/apps/qeystate/current/config.ru"
environment 'staging'   
pidfile "/home/deployer/apps/qeystate/shared/tmp/pids/puma.pid"
state_path "/home/deployer/apps/qeystate/shared/tmp/pids/puma.state"
stdout_redirect '/home/deployer/apps/qeystate/shared/log/puma_access.log', '/home/deployer/apps/qeystate/shared/log/puma_error.log', true
threads 4,8
bind 'unix:///home/deployer/apps/qeystate/shared/tmp/sockets/puma.sock'
workers 4
preload_app!
prune_bundler

on_worker_boot do
  ActiveSupport.on_load(:active_record) do
    ActiveRecord::Base.establish_connection
  end
end

before_fork do
  ActiveRecord::Base.connection_pool.disconnect!
end

Ответ 1

Я бы предложил пару вещей - два возможных решения, один подход к тестированию/воспроизведению и одно предложение для более глубоких показателей.

1) Возможное быстрое решение: отделите эту 1-минутную работу, чтобы она не блокировалась. Посмотрите, устранена ли проблема. Попробуйте использовать Redis + Sidekiq, который довольно просто встать и запустить (или что-то подобное).

2) Второе возможное решение: найдите все полные блокировки таблиц или эксклюзивные блокировки строк в Postgres - посмотрите, действительно ли вы делаете полные блокировки таблиц, и если да, найдите выражение о нарушении и устраните его.

3) Тестирование/репликация: для тестирования см., можно ли воспроизвести эту проблему за пределами производства. Я бы рекомендовал jmeter как очень полезный инструмент для симуляции множества запросов и запросов разных типов и посмотреть, можете ли вы воспроизвести его в контролируемом/промежуточном контексте. Согласованная репликация является ключом к решению таких проблем. Обратитесь к журналам вашего рабочего сервера примерно в то время, когда возникла проблема, чтобы генерировать ваши тестовые запросы jmeter, которые, мы надеемся, помогут воспроизвести проблему.

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

4) Аналитика. Установите NewRelic или аналогичную аналитику gem, чтобы получить более глубокое представление о том, что происходит, когда приходит этот запрос. Вы действительно хотите получить четкое представление о том, действительно ли запрос действительно заблокирован в Postgres (эксклюзивным блокировка строки/таблицы, которая блокирует ваш запрос), или же вы выполняете резервное копирование медленным запросом в очереди выполнения Puma или где-то внутри Ruby существует какое-то неудачное состояние ожидания.

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

Моя общая стратегия для этого типа проблемы (в этом порядке):

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