В Sidekiq много ошибок и задач в очереди

В консоли администратора появилось предупреждение, касающееся наших очередей задач Sidekiq. У нас почти 200 000 задач в очереди, и мы не знаем, как решить эту проблему. Мы используем версию 3.2.0.

Все задачи находятся в очереди ultra_low и, судя по всему, относятся к задаче Jobs::ProcessPost. Они все выглядят примерно так:

	[{"bypass_bump"=>true, "cooking_options"=>nil, "new_post"=>false, "post_id"=>729508, "skip_pull_hotli... 
{"bypass_bump"=>true, "cooking_options"=>nil, "new_post"=>false, "post_id"=>729508, "skip_pull_hotlinked_images"=>false, "current_site_id"=>"default"}

Как нам заставить эти задачи выполниться и как сделать это так, чтобы они не оказали значительного влияния на производительность (или это неизбежно)?

Два дня назад было около 195 тыс., а сейчас выросло до 211 тыс. Очень нужна ваша помощь: на что обратить внимание и как решить эту проблему.

Мы заметили 5 неудачных заданий, которые выглядят так (IP-адреса скрыты):

Jobs::HandledExceptionWrapper: Wrapped Errno::ENETUNREACH: Failed to open TCP connection to x.x.x.x:443 (Network is unreachable - connect(2) for "x.x.x.x" port 443)

Этот адрес принадлежит серверу в нашей инфраструктуре, но он не используется форумом (это сервер репозитория исходного кода). Я нигде в конфигурации не вижу упоминания этого хоста, поэтому не понимаю, почему система считает, что нужно подключаться к нему. Но, возможно, это связано с растущим количеством заданий в очереди ultra_low?

За последние ~23 часа в очередь добавлено ещё 7000 задач.

Это также сдерживает внедрение нескольких изменений: автоматическое закрытие тем и добавление плагина с решениями (просто не хочется вносить изменения, которые могут или не могут быть затронуты этим, или когда это может усугубиться из-за внесения столь значительных изменений в настройку системы).

Добавленные 7 тысяч задач не соответствуют количеству новых сообщений за последний день, поэтому я не понимаю, что вообще является причиной этого.

Какая ещё информация поможет решить эту проблему?

Я не могу помочь, кроме как предложить, что больше ответов и лайков к сообщениям в этой теме могут привлечь к ней внимание, которого она, кажется, заслуживает!

Редакция: Я также упомянул эту проблему в теме о порядке сортировки по умолчанию «по популярности», что, на мой взгляд, очень эффективно и крайне нецелесообразно скрывает эту тему.

Можете предоставить полный стек вызовов? Я предполагаю, что это связано с нашим кодом onebox, но хочу убедиться в этом.

Я предполагаю, что это нужно сделать через консоль — мне придется попросить кого-то с соответствующим доступом сделать это. Можете подсказать, откуда это можно получить, чтобы я мог передать эту информацию человеку с доступом?

Спасибо!

Нет, нет, трассировка стека отображается на вкладке на странице /logs, когда вы выбираете ошибку.

Ах, спасибо за подсказку. Вот что показывается (IP-адрес замазан):

Сообщение (10 копий в отчёте)

Исключение в задаче: Не удалось установить TCP-соединение с x.x.x.x:443 (Сеть недоступна — connect(2) для "x.x.x.x" порт 443)


Стек вызовов

/usr/lib64/ruby/gems/3.2.0/gems/net-http-0.4.1/lib/net/http.rb:1603:in `initialize'
/usr/lib64/ruby/gems/3.2.0/gems/net-http-0.4.1/lib/net/http.rb:1603:in `open'
/usr/lib64/ruby/gems/3.2.0/gems/net-http-0.4.1/lib/net/http.rb:1603:in `block in connect'
/usr/lib64/ruby/gems/3.2.0/gems/timeout-0.4.1/lib/timeout.rb:186:in `block in timeout'
/usr/lib64/ruby/gems/3.2.0/gems/timeout-0.4.1/lib/timeout.rb:193:in `timeout'
/usr/lib64/ruby/gems/3.2.0/gems/net-http-0.4.1/lib/net/http.rb:1601:in `connect'
/srv/www/vhosts/discourse/lib/final_destination/http.rb:27:in `block in connect'
/srv/www/vhosts/discourse/lib/final_destination/http.rb:17:in `each'
/srv/www/vhosts/discourse/lib/final_destination/http.rb:17:in `each_with_index'
/srv/www/vhosts/discourse/lib/final_destination/http.rb:17:in `connect'

Я заметил, что это отличается от вывода во вкладке «backtrace», но тот вывод не похож на стек вызовов, тогда как то, что здесь скопировано (с помощью кнопки копирования), — это именно он. Дайте знать, если для этого случая потребуется дополнительная информация.

Также я попробую получить больше информации о 220+ тысячах задач в очереди таким же образом — я не знал о наличии конечной точки /logs.

Нет лишних строк в нижней части трассировки?

Вот что успел скопировать кнопка «Копировать» — здесь полный вывод из вкладки трассировки:

net-http-0.4.1/lib/net/http.rb:1603:in `initialize' 
net-http-0.4.1/lib/net/http.rb:1603:in `open' 
net-http-0.4.1/lib/net/http.rb:1603:in `block in connect' 
timeout-0.4.1/lib/timeout.rb:186:in `block in timeout' 
timeout-0.4.1/lib/timeout.rb:193:in `timeout' 
net-http-0.4.1/lib/net/http.rb:1601:in `connect' 
/srv/www/vhosts/discourse/lib/final_destination/http.rb:27:in `block in connect' 
/srv/www/vhosts/discourse/lib/final_destination/http.rb:17:in `each' 
/srv/www/vhosts/discourse/lib/final_destination/http.rb:17:in `each_with_index' 
/srv/www/vhosts/discourse/lib/final_destination/http.rb:17:in `connect' 
net-http-0.4.1/lib/net/http.rb:1580:in `do_start' 
net-http-0.4.1/lib/net/http.rb:1569:in `start' 
net-http-0.4.1/lib/net/http.rb:1029:in `start' 
/srv/www/vhosts/discourse/lib/final_destination.rb:551:in `safe_session' 
/srv/www/vhosts/discourse/lib/final_destination.rb:486:in `safe_get' 
/srv/www/vhosts/discourse/lib/final_destination.rb:170:in `get' 
/srv/www/vhosts/discourse/lib/retrieve_title.rb:90:in `fetch_title' 
/srv/www/vhosts/discourse/lib/retrieve_title.rb:12:in `crawl' 
/srv/www/vhosts/discourse/lib/inline_oneboxer.rb:76:in `lookup' 
/srv/www/vhosts/discourse/lib/cooked_processor_mixin.rb:310:in `process_inline_onebox' 
/srv/www/vhosts/discourse/lib/cooked_processor_mixin.rb:39:in `block in post_process_oneboxes' 
/srv/www/vhosts/discourse/lib/oneboxer.rb:214:in `block in apply' 
/srv/www/vhosts/discourse/lib/oneboxer.rb:162:in `block in each_onebox_link' 
nokogiri-1.16.0/lib/nokogiri/xml/node_set.rb:235:in `block in each' 
nokogiri-1.16.0/lib/nokogiri/xml/node_set.rb:234:in `upto' 
nokogiri-1.16.0/lib/nokogiri/xml/node_set.rb:234:in `each' 
/srv/www/vhosts/discourse/lib/oneboxer.rb:162:in `each_onebox_link' 
/srv/www/vhosts/discourse/lib/oneboxer.rb:213:in `apply' 
/srv/www/vhosts/discourse/lib/cooked_processor_mixin.rb:9:in `post_process_oneboxes' 
/srv/www/vhosts/discourse/lib/cooked_post_processor.rb:42:in `block in post_process' 
/srv/www/vhosts/discourse/lib/distributed_mutex.rb:53:in `block in synchronize' 
/srv/www/vhosts/discourse/lib/distributed_mutex.rb:49:in `synchronize' 
/srv/www/vhosts/discourse/lib/distributed_mutex.rb:49:in `synchronize' 
/srv/www/vhosts/discourse/lib/distributed_mutex.rb:34:in `synchronize' 
/srv/www/vhosts/discourse/lib/cooked_post_processor.rb:38:in `post_process' 
/srv/www/vhosts/discourse/app/jobs/regular/process_post.rb:28:in `block in execute' 
/srv/www/vhosts/discourse/lib/distributed_mutex.rb:53:in `block in synchronize' 
/srv/www/vhosts/discourse/lib/distributed_mutex.rb:49:in `synchronize' 
/srv/www/vhosts/discourse/lib/distributed_mutex.rb:49:in `synchronize' 
/srv/www/vhosts/discourse/lib/distributed_mutex.rb:34:in `synchronize' 
/srv/www/vhosts/discourse/app/jobs/regular/process_post.rb:8:in `execute' 
/srv/www/vhosts/discourse/app/jobs/base.rb:297:in `block (2 levels) in perform' 
rails_multisite-5.0.0/lib/rails_multisite/connection_management.rb:82:in `with_connection'
/srv/www/vhosts/discourse/app/jobs/base.rb:284:in `block in perform' 
/srv/www/vhosts/discourse/app/jobs/base.rb:280:in `each' 
/srv/www/vhosts/discourse/app/jobs/base.rb:280:in `perform' 
sidekiq-6.5.12/lib/sidekiq/processor.rb:202:in `execute_job' 
sidekiq-6.5.12/lib/sidekiq/processor.rb:170:in `block (2 levels) in process' 
sidekiq-6.5.12/lib/sidekiq/middleware/chain.rb:177:in `block in invoke' 
/srv/www/vhosts/discourse/lib/sidekiq/pausable.rb:132:in `call' 
sidekiq-6.5.12/lib/sidekiq/middleware/chain.rb:179:in `block in invoke' 
sidekiq-6.5.12/lib/sidekiq/middleware/chain.rb:182:in `invoke' 
sidekiq-6.5.12/lib/sidekiq/processor.rb:169:in `block in process' 
sidekiq-6.5.12/lib/sidekiq/processor.rb:136:in `block (6 levels) in dispatch' 
sidekiq-6.5.12/lib/sidekiq/job_retry.rb:113:in `local' 
sidekiq-6.5.12/lib/sidekiq/processor.rb:135:in `block (5 levels) in dispatch' 
sidekiq-6.5.12/lib/sidekiq/rails.rb:14:in `block in call' 
activesupport-7.0.8/lib/active_support/execution_wrapper.rb:92:in `wrap' 
activesupport-7.0.8/lib/active_support/reloader.rb:72:in `block in wrap' 
activesupport-7.0.8/lib/active_support/execution_wrapper.rb:92:in `wrap' 
activesupport-7.0.8/lib/active_support/reloader.rb:71:in `wrap' 
sidekiq-6.5.12/lib/sidekiq/rails.rb:13:in `call' 
sidekiq-6.5.12/lib/sidekiq/processor.rb:131:in `block (4 levels) in dispatch' 
sidekiq-6.5.12/lib/sidekiq/processor.rb:263:in `stats' 
sidekiq-6.5.12/lib/sidekiq/processor.rb:126:in `block (3 levels) in dispatch' 
sidekiq-6.5.12/lib/sidekiq/job_logger.rb:13:in `call' 
sidekiq-6.5.12/lib/sidekiq/processor.rb:125:in `block (2 levels) in dispatch' 
sidekiq-6.5.12/lib/sidekiq/job_retry.rb:80:in `global' 
sidekiq-6.5.12/lib/sidekiq/processor.rb:124:in `block in dispatch' 
sidekiq-6.5.12/lib/sidekiq/job_logger.rb:39:in `prepare' 
sidekiq-6.5.12/lib/sidekiq/processor.rb:123:in `dispatch' 
sidekiq-6.5.12/lib/sidekiq/processor.rb:168:in `process' 
sidekiq-6.5.12/lib/sidekiq/processor.rb:78:in `process_one' 
sidekiq-6.5.12/lib/sidekiq/processor.rb:68:in `run' 
sidekiq-6.5.12/lib/sidekiq/component.rb:8:in `watchdog' 
sidekiq-6.5.12/lib/sidekiq/component.rb:17:in `block in safe_thread' 

Извините, что не скопировал весь вывод. Мне показалось странным, что кнопка «Копировать» не захватила больше, но я ошибочно предположил, что она скопировала всё необходимое.

О, теперь гораздо лучше!

Это означает, что есть одна или несколько встроенных ссылок с URL-адресом, который указывает на IP-адрес вашего сервера. Если этот сервер недоступен, мы записываем ошибку в лог, но процесс обработки Markdown должен продолжаться без выполнения «улучшения ссылок», которое обычно запускает встроенный onebox.

Ещё один момент: как именно вы установили Discourse? Похоже, что установка не соответствовала нашему официальному руководству по установке.

Я на самом деле не выполнял установку — полагаю, наша команда инфраструктуры развернула её через конвейер CI/CD, но я не знаю деталей.

Похоже, что сообщения об ошибках не являются проблемой — большая очередь задач в очереди ultra_low выглядит как более серьёзная проблема. В логах ничего не найдено (что, впрочем, логично, поскольку задача ещё не была выполнена).

Что произойдёт, если вы вручную запустите несколько задач?

Я не вижу опции, чтобы принудительно запускать их в веб-интерфейсе — у меня есть возможность «показать все» аргументы или удалять задачи по отдельности.

Хм. На форуме, который я помогаю поддерживать, нет ни одной задачи в очереди, так что извините, что я предложил кнопку, которой там нет.

Ссылка на страницу повторных попыток, так как я думал, что она находится на странице очереди

Не переживайте — да, они отображаются на странице «В очереди» (по адресу /sidekiq/queues/ultra_low).

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

Погрузившись чуть глубже в то, что я вижу в задачах Jobs::ProcessPost, обнаружил, что значения post_id идут в обратном порядке. Я извлек ID постов из первой и последней задач в очереди: даты соответствуют посту 2012 года (до миграции на Discourse, но с меткой времени ‘updated_at’ из 2022 года, что, возможно, совпадает с датой нашей миграции — это нужно будет проверить) и самому последнему посту от января 2023 года.

Я также иногда вижу задачи для генерации миниатюр тем, но они встречаются довольно редко. При более чем 8 800 страницах очереди я не могу проверить всё, но похоже, что в очередь добавляются в основном задачи типа Jobs::ProcessPost, которые обрабатывают ответы и исходные посты тем, двигаясь назад во времени (самый ранний пост был в середине обсуждения, а самый поздний — корневым постом темы).

Я сделал резервную копию из производственной системы и загрузил её в тестовую среду, настроенную локально. Очередь, похоже, вообще не резервируется — более того, я не вижу, чтобы Job::ProcessPost появлялся в этой очереди, пока наблюдаю за ней (я настрою запись экрана очереди и оставлю систему работать самостоятельно, чтобы проверить, не упускаю ли я момент её появления).

Это приводит меня к двум вопросам:

  1. Что вызывает запуск задания ProcessPost?
  2. Что может помешать обработке очереди ultra_low?

Я не эксперт в технологиях, используемых в Discourse (redis, sidekiq или rails), но мне комфортно учиться и пробовать различные варианты в изолированной тестовой среде, чтобы понять, на что именно нужно обратить внимание в производственной среде.

Хорошая новость в том, что эта проблема, похоже, пока не вызывает никаких затруднений у наших пользователей. Третий вопрос: поможет ли просто очистка этой очереди, или же запрет на выполнение этих заданий может как-то навредить системе.

Дополнительно: я только что увидел, как в тестовой среде прошла партия заданий, и они там обрабатываются. Значит, проблема, скорее всего, в том, что на производственном сервере очередь по какой-то причине не обрабатывается.

Похоже, мы пришли к решению. Используемая нами установка основана на пакете, созданном для openSUSE (речь идёт об экземпляре, о котором мы говорили, — это экземпляр для форумов openSUSE), поэтому по сути используется процесс установки «сборка из исходного кода».

У нас есть среды разработки и производства, и очередь ultra_low была ошибочно исключена из конфигурации sidekiq для производственной среды. Это исправлено, и похоже, что очередь опустошается, а задержка снизилась с одного года до четырёх недель.

Я считаю, что теперь вопрос можно считать решённым. Благодарю всех, кто предоставил свои замечания — это помогло мне понять суть проблемы.