Ошибка SQL с `screened_ip_addresses` (API возвращает 500)

Привет, друзья,

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

ActiveRecord::StatementInvalid (PG::InvalidTextRepresentation: ERROR:  invalid input syntax for type inet: ""
LINE 1: ..._addresses".* FROM "screened_ip_addresses" WHERE ('' <<= ip_...
                                                             ^
)
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/rack-mini-profiler-2.0.1/lib/patches/db/pg.rb:69:in `exec_params'

Failed to handle exception in exception app middleware : PG::InvalidTextRepresentation: ERROR:  invalid input syntax for type inet: ""
LINE 1: ..._addresses".* FROM "screened_ip_addresses" WHERE ('' <<= ip_...
                                                             ^

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

Просто ради интереса, я также запросил через API список заблокированных IP-адресов… результаты те же. (https://mydiscourse.com/admin/logs/screened_ip_addresses.json)

Не уверен, что ещё можно проверить. :man_shrugging:t2:

Кто-нибудь знает:

1. Что вызывает эту ошибку, и
2. Как исправить это сейчас и предотвратить повторение в будущем?

Помогите :slight_smile:

Спасибо!

Или речь идет о другом SQL-запросе, пытающемся вставить данные в эту таблицу?

Можете ли вы опубликовать код, который вы используете для выполнения запроса к API, удалив учетные данные, чтобы мы могли увидеть, как вы выполняете запрос к API?

Привет, @blake, спасибо за ответ. Чтобы быть полностью уверенным, что проблема не в моём коде, я подготовил базовый вызов API в Insomnia (похож на Postman, но, на мой взгляд, проще и удобнее). К сожалению, результат тот же, но теперь всё предельно ясно:

Вот вызов, который я подготовил для создания нового поста (я уже снизил настройку «минимальное количество слов в постах» до 1):

А вот результаты

И два сообщения об ошибках в логах от этих тестовых вызовов:

Ошибка 1:

Сообщение (15 дубликатов)

ActiveRecord::StatementInvalid (PG::InvalidTextRepresentation: ОШИБКА: недопустимый синтаксис ввода для типа inet: ""
СТРОКА 1: ..._addresses".* FROM "screened_ip_addresses" WHERE ('' <<= ip_...
                                                             ^
)
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/rack-mini-profiler-2.0.1/lib/patches/db/pg.rb:69:in `exec_params'

Стек вызовов

rack-mini-profiler-2.0.1/lib/patches/db/pg.rb:69:in `exec_params'
rack-mini-profiler-2.0.1/lib/patches/db/pg.rb:69:in `exec_params'
activerecord-6.0.1/lib/active_record/connection_adapters/postgresql_adapter.rb:672:in `block (2 levels) in exec_no_cache'
activesupport-6.0.1/lib/active_support/dependencies/interlock.rb:48:in `block in permit_concurrent_loads'
activesupport-6.0.1/lib/active_support/concurrency/share_lock.rb:187:in `yield_shares'
activesupport-6.0.1/lib/active_support/dependencies/interlock.rb:47:in `permit_concurrent_loads'
activerecord-6.0.1/lib/active_record/connection_adapters/postgresql_adapter.rb:671:in `block in exec_no_cache'
activerecord-6.0.1/lib/active_record/connection_adapters/abstract_adapter.rb:718:in `block (2 levels) in log'
/usr/local/lib/ruby/2.6.0/monitor.rb:235:in `mon_synchronize'
activerecord-6.0.1/lib/active_record/connection_adapters/abstract_adapter.rb:717:in `block in log'

Ошибка 2:

Сообщение (15 дубликатов)

Не удалось обработать исключение в промежуточном ПО обработки исключений: PG::InvalidTextRepresentation: ОШИБКА: недопустимый синтаксис ввода для типа inet: ""
СТРОКА 1: ..._addresses".* FROM "screened_ip_addresses" WHERE ('' <<= ip_...
                                                             ^


Стек вызовов

rack-mini-profiler-2.0.1/lib/patches/db/pg.rb:69:in `exec_params'
rack-mini-profiler-2.0.1/lib/patches/db/pg.rb:69:in `exec_params'
activerecord-6.0.1/lib/active_record/connection_adapters/postgresql_adapter.rb:672:in `block (2 levels) in exec_no_cache'
activesupport-6.0.1/lib/active_support/dependencies/interlock.rb:48:in `block in permit_concurrent_loads'
activesupport-6.0.1/lib/active_support/concurrency/share_lock.rb:187:in `yield_shares'
activesupport-6.0.1/lib/active_support/dependencies/interlock.rb:47:in `permit_concurrent_loads'
activerecord-6.0.1/lib/active_record/connection_adapters/postgresql_adapter.rb:671:in `block in exec_no_cache'
activerecord-6.0.1/lib/active_record/connection_adapters/abstract_adapter.rb:718:in `block (2 levels) in log'
/usr/local/lib/ruby/2.6.0/monitor.rb:235:in `mon_synchronize'
activerecord-6.0.1/lib/active_record/connection_adapters/abstract_adapter.rb:717:in `block in log'

Я создал этого пользователя только что, поэтому задаюсь вопросом: не связано ли это с багом, связанным с уровнями доверия, или, возможно, с чем-то другим, что ещё не полностью разрешено для нового пользователя. Я немного поэкспериментирую с этим и посмотрю, что позволит посту пройти через API.

Но в любом случае, похоже, я выявил какой-то баг… ?!

Итак, вот как я обошел эту ошибку… Я начал экспериментировать:

  1. Я изменил пользователя в моем API-вызове на ‘system’, и это сработало.
  2. Тогда я подумал: «Хм», и сменил пользователя на третьего, которого я еще не пробовал. Это тоже сработало.
  3. Затем я вернул имя пользователя обратно к исходному, то есть сделал тот же самый API-вызов, который ранее вызывал ошибку 500. Но теперь он сработал. :man_shrugging:t2:

Если ошибка повторится, я постараюсь выяснить, как воспроизвести её надёжно. А пока, возможно, кто-то, кто знает код Discourse лучше меня :wink: , сможет взглянуть на этот участок и убедиться, что там нет явных ошибок.

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

Я ввожу это имя пользователя в Insomnia и тестирую API… так и есть, ошибка 500. Я переключаюсь на «system», и всё проходит… затем возвращаюсь к исходному имени пользователя, и снова возникает ошибка 500!

Пользователь, вызывающий ошибку 500 (как и все мои импортированные пользователи, кроме «system»), имеет уровень TL1, и я изменил ограничения так, чтобы TL1 мог публиковать сообщения.

Это определённо похоже на проблему, связанную с ограничением частоты запросов, верно? Я подозревал, что nginx или что-то ещё выше по потоку может вызывать ошибку 500, но я полностью отключил ограничение частоты запросов в nginx, а ошибка 500 очень чётко указывает на ту самую ошибку SQL в логах ошибок.

Я заметил, что ошибка SQL возникает вокруг столбца, ссылающегося на IP-адрес… не может ли быть что-то странное в работе ограничения частоты запросов, что вызывает эту проблему? Я попытался войти через VPN (чтобы изменить свой IP), но всё равно получаю ошибку 500.

Тем временем я вижу, что проблема заключается в rack-mini-profiler, который не является необходимым. Давайте посмотрим, смогу ли я его отключить и исправит ли это проблему… Нет. Теперь я больше не вижу мини-профайлер в своей учётной записи администратора, но всё ещё получаю ту же ошибку HTTP 500 с теми же ошибками в логе ошибок :frowning:

Обновление: при изменении уровня пользователя на TL3 проблема, кажется, исчезает. Возврат к TL1 снова вызывает её. Я сейчас проверяю эту гипотезу… нет. Иногда это работает, но в других случаях, даже если я вручную редактирую уровень пользователя, пользователь как бы «зависает».

@blake или кто-то ещё из @staff… помогите! :wink: Что ещё можно попробовать? Есть ли что-то, что я могу полностью очистить здесь (связанное с кодом, вызывающим ошибку), и сбросить настройки до заводских?

Мне кажется, это проблема с сетью: иногда IP-адрес пользователя почему-то не отображается в стеке вызовов? Или у вас некорректные данные в базе данных (администрирование, логи, отфильтрованные IP-адреса)?

Согласен: <<= означает «LHS содержится в RHS», поэтому, похоже, в приложение попадает некорректный или пустой IP-адрес.

(Мне интересно, как у вас в приложении вообще нет доступного IP-адреса; единственное предположение — запрос приходит через unix-сокет без заголовка с информацией о пересланным IP-адресом)

Согласен, но не понимаю, почему IP-адрес иногда есть, а иногда нет. В моём скрипте импорта после 5–7 успешных вызовов API возникает ошибка 500. Поэтому я думаю, что в базе данных где-то повреждены данные.

Да, это странно. Но в то же время, если я делаю запрос с «некорректным» именем пользователя, он завершается ошибкой; если же я использую «system» или другое имя пользователя, всё работает. Поэтому, думаю, проблема именно в повреждённых данных в моей базе данных.

Может, кто-то подскажет простые команды Ruby для командной строки, которые помогут очистить или сбросить любые таблицы базы данных, которые могли повредиться?

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

Однако интересно то, что этот код проверяет наличие ip_address перед выполнением SQL-запроса, который вызывает ошибку 500.

Можете ли вы описать, как настроен ваш экземпляр Discourse? Это импорт? Вы используете последнюю версию? Сколько у него оперативной памяти? Вы следовали этому руководству?

Можете ли вы проверить эту настройку сайта: max new accounts per registration ip? Не уверен, что это имеет значение в данном случае, но, возможно, это вызывает проблему.

В вашем скрипте массовой обработки могли бы вы замедлить его и добавить паузу в 1 секунду между запросами, чтобы посмотреть, изменится ли что-то?

В чём цель этих вызовов API? Если это разовый скрипт для создания импортированных пользователей и сообщений, то, возможно, лучше использовать скрипт импорта, который запускается непосредственно на сервере и не делает вызовов API?

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

Конечно — всё полностью на Docker, размещено на Digital Ocean. Я следовал этому отличному руководству буквально до буквы.

Конечно, я только что изменил эту настройку со значения по умолчанию 3 на 99999. Разницы нет, всё ещё получаю ошибку 500.

Попробовал, разницы нет. Обратите внимание: я всё ещё получаю ошибку 500 с ОДНОЙ «плохой» учётной записью через Insomnia. Так что на данном этапе похоже, что эта одна учётная запись «отравлена», и даже если я сделаю только один вызов API «создать сообщение» с ней (никаких запросов до или после), я всё равно получу ошибку 500. Но да, мой скрипт импорта тоже получает ошибку 500 :wink:

Да, я опытный программист, но совершенно не знаю RoR/Ruby, поэтому не могу использовать готовые решения, которые вы предоставляете, хотя понимаю, что они, скорее всего, превосходят мой ручной обход существующих форумов и создание пользователей и т. д. через API на лету. Поэтому мой пост на маркетплейсе… Я бы очень хотел, чтобы всё это заработало у меня самостоятельно, но у меня также жёсткий дедлайн :wink:

Полностью понимаю, и я ценю ваше внимание к этому вопросу.

Вот что я могу предложить, что, возможно, очень поможет: поскольку это установка из готового пакета, и я почти ничего не настраивал кастомно, КРОМЕ того, что баг легко воспроизводится без моего кода (просто используйте Insomnia), И я ещё не запустил форумы, я могу передать вам root-доступ к экземпляру Digital Ocean, свой API-ключ и т. д., и у меня нет никаких возражений, если вы захотите покопаться там. Мои форумы Discourse сейчас представляют собой множество пустых категорий и несколько специальных вводных сообщений, которые мы настроили, но в целом они пусты, и реальных пользователей там пока нет (только администраторы). Так что если вы захотите протестировать что-то, создавать/удалять темы и сообщения и т. д., это будет нормально.

Это определённо самый быстрый способ увидеть баг собственными глазами. И поскольку вы будете там с правами root, вы также сможете покопаться в любых низкоуровневых вещах Discourse, чтобы выяснить, почему это происходит.

E

Что произойдет, если вы отключите SSO?

Разницы нет, по-прежнему возникает ошибка 500.

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

В целом, однако, гораздо проще просто написать скрипт для импорта. Вся эта тема — это как раз то, что выглядит как «много» :wink:

Хм, мне казалось, что я нашёл решение… мой «отравленный» тестовый пользователь george21 был на уровне TL0. Поэтому я повысил его до TL1, и тогда всё заработало. Отлично! Возможно, именно в этом дело! Тогда я вернул george21 обратно на TL0… и теперь он больше не «отравлен» — он может выполнять вызов API даже на TL0.

Теперь я снова запускаю свой скрипт импорта, и ага. Теперь george21 выдаёт ошибку 500 в скрипте импорта. И когда я пытаюсь сделать то же самое в Insomnia, это тоже не работает. Так что я снова возвращаю george21 на TL1 и… да, теперь он может выполнить HTTP-запрос.

Итак, вот что, похоже, удаётся воспроизвести:

  1. Если выполняется серия вызовов API (?), это каким-то образом приводит к сбою последующего вызова API у пользователя TL0.
  2. Повышение пользователя TL0 до TL1 позволяет вызову API пройти.
  3. И странно, но даже после возврата того же самого пользователя обратно на TL0 вызов API всё ещё проходит.
  4. Повторный запуск скрипта работает нормально, пока он снова не терпит неудачу с другим пользователем TL0.

Обратите внимание на следующее:

  1. Насколько мне известно, все минимальные требования и т.п. для TL0 были повышены (то есть я попытался убрать все блоки, которые могли бы помешать пользователю TL0 публиковать сообщения), и
  2. Даже если это проблема с каким-то внутренним ограничением скорости для пользователей TL0, API не должен выдавать ошибку 500 и записывать ошибку SQL в журнал ошибок. Поэтому на данном этапе можно с уверенностью сказать, что где-то точно есть ошибка.

Да, да, я знаю, и я уже четыре раза объяснял, почему не пишу свой собственный скрипт импорта (на основе приведённых примеров). :wink: Поэтому я изменил подход.

А пока я продолжаю участвовать здесь, чтобы помочь найти и исправить эту ошибку. Сегодня она влияет на мой скрипт импорта. Завтра она может нарушить работу важного скрипта на вашем сайте, которому нужен API…