Я уже несколько лет использую довольно небольшую установку Discourse. Трафик там невысокий, поэтому я заметил проблему с отправкой писем (уведомлений, дайджестов) только несколько месяцев назад. Расследование указывает на обновление до версии 2.8.0.beta7 примерно 22 октября 2022 года; до этого мы использовали версию 2.8.0.beta4. По крайней мере, я не получал от этой установки никаких писем о публикациях или сообщениях.
Письма накапливаются в Sidekiq с сообщением об ошибке, которое мне не удаётся соотнести с какой-либо известной проблемой, и при поиске по нему ничего подходящего не находится. Хотя были сообщения об ошибках типа undefined method, ни одно из описанных условий не совпадает с моей ситуацией. (Это не проблема TLS, не тайм-аут подключения к почтовому серверу, не плагин событий, и исправление для безопасной медиа-отправки уже должно быть применено — к тому же, точные сообщения об ошибках были другими.)
Ошибка в Sidekiq:
Wrapped NoMethodError: undefined method `value' for #<Array:0x00007f7fd5277d68> Did you mean? values_at
Часть после #<Array: различается для каждого задержанного письма. Вчера я переустановил Discourse на новую виртуальную машину и восстановил данные из свежей резервной копии, но, похоже, проблема с отправкой писем тоже была восстановлена вместе с данными
Поскольку частота ошибок начала расти в конце октября, я почти уверен, что проблема была введена в версии 2.8.0.beta7:
## Плагины размещаются здесь
## подробности см. по адресу https://meta.discourse.org/t/19157
hooks:
after_code:
- exec:
cd: $home/plugins
cmd:
- git clone https://github.com/discourse/docker_manager.git
## Любые пользовательские команды для выполнения после сборки
Это стандартная установка? Если у вас отдельный контейнер с данными, был ли он пересобран? Актуальна ли версия PostgreSQL? (Хотя, кажется, в коде всё ещё есть проверка этого).
Да, это стандартная установка; насколько я вижу, запущен только один контейнер. (Запустите новую виртуальную машину, перенастройте DNS, выполните apt update, apt dist-upgrade, перезагрузитесь, выполните git pull, ./discourse-setup, настройку через веб-интерфейс, загрузите резервную копию, восстановите из неё, снова включите отправку почты — и ошибки проявятся снова.) Обратите внимание, что старая установка могла отправлять ссылку на резервную копию, а отправка тестовых писем в новой установке всё ещё работает — похоже, что проблемы возникают только с почтой, связанной с публикациями.
root@discourse:~# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ac408a70305d local_discourse/app "/sbin/boot" 12 hours ago Up 12 hours 0.0.0.0:80->80/tcp, :::80->80/tcp, 0.0.0.0:443->443/tcp, :::443->443/tcp app
Редактирование:
root@discourse:~# apt update
Hit:1 https://download.docker.com/linux/debian buster InRelease
Hit:2 http://deb.debian.org/debian buster InRelease
Get:3 http://deb.debian.org/debian buster-updates InRelease [51,9 kB]
Get:4 http://security.debian.org/debian-security buster/updates InRelease [65,4 kB]
Fetched 117 kB in 2s (60,5 kB/s)
Reading package lists... Done
Building dependency tree
Reading state information... Done
All packages are up to date.
Однако первые попытки восстановления не увенчались успехом. Файл database_restorer.rb завершился ошибкой в функции restore_dump из-за дублирующихся ссылок в post_id 3841. В итоге я заменил эти ссылки в сообщении 2017 года на скриншот с их отображением из старой установки и создал новую резервную копию; только после этого мне удалось восстановить резервную копию на новой установке. Как вы упомянули, проблема с PostgreSQL возникла во время выполнения CREATE INDEX с ошибкой: ERROR: could not create unique index "unique_post_links". Дополнительная информация: EXCEPTION: psql failed: DETAIL: Key (topic_id, post_id, url)=(1300, 3841, [redacted]) is duplicated.
Хотя я не думаю, что это напрямую связано, я всё же решил упомянуть об этом.
Итак, если никто не знает простого способа исправить это, давайте отладим это правильно. Но мне нужна ваша помощь, так как Discourse — это довольно сложное приложение, использующее множество технологий, с которыми я не знаком. Итак: как письма «отправляются» в Sidekiq?
Какой компонент Discourse передаёт Sidekiq метод «value»?
Из-за того, что уведомления по электронной почте перестали работать после обновления, Discourse теперь, к сожалению, практически бесполезен. Вместо немедленного внимания темы теперь требуют дней, чтобы получить его, если вообще получают, так как в 2022 году люди не занимаются активным опросом. Нет уведомления ⇒ ничего не произошло ⇒ нет необходимости проверять сайт
Похоже на повреждённый индекс. Я не припоминаю, чтобы сталкивался с этим в PostgreSQL 13. Звучит так, будто вы исправили это на старом сайте, а затем обновили его, сделав резервную копию и восстановив её на новом сайте?
Похоже, проблема связана с чем-то в коде, отправляющем уведомления, но на самом деле это проблема с Sidekiq.
Как я уже говорил, старый сайт работает. Я сделал резервную копию и попытался восстановить её на чистой установке, но восстановление не удалось. Я изменил сообщение, указанное как причина проблемы, после чего восстановление на чистой установке прошло успешно. Однако проблема с Sidekiq всё ещё сохраняется.
Старый сайт также работает на PostgreSQL 13 (но существует уже несколько лет, так что скорее всего изначально он не запускался с этой версией )
Попробовал с новым пользователем: письмо с регистрацией пришло корректно. Но уведомления на ответы к его постам не приходят — Sidekiq выдаёт ошибку.
Значит, по-моему, Discourse передаёт Sidekiq некорректную информацию, когда instructs его отправлять уведомления (в отличие от регистрационных писем). Как продолжить отладку?
Хорошо, если индексы исправлены, это говорит мне о том, что где-то вызывается функция с массивом вместо модели, у которой есть метод value. Проблема не в самом Sidekiq, а в функции, которую Sidekiq вызывает.
Похоже, что вызывается что-то, что возвращает массив вместо одного элемента, но я не могу точно сказать, что именно. Думаю, вам нужно посмотреть в /var/discourse/shared/standalone/logs/rails/production.log (или в файл с очень похожим путём, если моя память или пальцы меня подводят). Затем вы можете найти эту ошибку в логах (или вызвать её снова, чтобы она появилась в конце файла). Там вы получите больше информации о том, что именно не работает.
Started POST "/sidekiq/retries" for 185.39.142.187 at 2022-04-11 16:31:35 +0000
start
Started GET "/sidekiq/retries" for 185.39.142.187 at 2022-04-11 16:31:35 +0000
Rendered email/notification.html.erb (Duration: 42.8ms | Allocations: 4323)
Rendered layouts/email_template.html.erb (Duration: 0.3ms | Allocations: 29)
Job exception: undefined method `value' for #<Array:0x00007ff393af6c78>
Did you mean? values_at
fail
— в коде Discourse — именно здесь Sidekiq прерывает выполнение?
Это вызывается из …
228 MessageBuilder.custom_headers(SiteSetting.email_custom_headers).each do |key, _|
*229 value = header_value(key)
230
231 # Remove Auto-Submitted header for group private message emails, it does
232 # not make sense there and may hurt deliverability.
Значит, проблема может быть в пользовательском заголовке?
У меня действительно есть такая запись:
Я сбросил это значение по умолчанию, и … на самом деле кажется, что уведомления по электронной почте снова отправляются, что подтвердили получатели.
Спасибо!
Однако остаётся один вопрос: почему »Auto-Submitted: auto-generated|Precedence: bulk« вызывает эту ошибку? В инструкции сказано, что пользовательские заголовки должны разделяться символом »|«.
Согласно RFC, многие поля могут встречаться более одного раза; мы вернем строку со значением, если заголовок только один, или, если совпадающих заголовков больше одного, вернем массив значений в порядке их появления в заголовке сверху вниз.
Discourse автоматически устанавливает заголовок Precedence, поэтому, поскольку вы также добавляете его через настройку email_custom_headers, теперь есть два заголовка Precedence, и @message.header["Precedence"] возвращает массив вместо строки.
Мне кажется, эта ошибка будет возникать каждый раз, когда в email_custom_headers содержится заголовок, который уже есть в объекте сообщения.
Мне кажется, что происходит именно это (я предположил, что что-то является массивом, а не отдельным элементом, но не мог представить, как это может быть правдой), и это баг.
Сегодня я объединил исправление для этой проблемы: если мы обнаруживаем дублирующийся заголовок, мы используем тот, который определён в ядре Discourse, а не пользовательский из настроек сайта: