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 목록은 다음과 같습니다. 아래 스크린샷 외에도 API를 호출하는 머신의 IP를 화이트리스트에 추가했습니다. (시스템 수준의 API 키를 사용하여 기존 포럼 소프트웨어의 오래된 주제/메시지를 대량으로 가져오는 작업을 수행하고 있습니다.)

참고로, API에 차단된 IP 목록을 요청해 보기도 했는데… 결과는 동일합니다. (https://mydiscourse.com/admin/logs/screened_ip_addresses.json)

무엇을 더 확인해야 할지 모르겠습니다. :man_shrugging:t2:

아시는 분 계신가요:

1. 이 오류의 원인은 무엇이며,
2. 현재 이를 어떻게 수정하고, 향후 재발을 방지할 수 있을까요?

도와주세요 :slight_smile:

감사합니다!

아니면 다른 SQL이 해당 테이블에 삽입을 시도하는 문제일까요?

인증 정보를 제거한 상태에서 API 요청을 보내는 데 사용하고 있는 코드를 공유해 주실 수 있으신가요? API 요청을 어떻게 보내고 있는지 확인해 보고 싶습니다.

Hey @blake, 답변해 주셔서 감사합니다. 제 코드에 문제가 없는지 확실히 확인하기 위해 Insomnia (Postman과 비슷하지만 개인적으로 더 단순하고 사용하기 쉽다고 생각합니다)에 기본적인 API 호출을 구성해 보았습니다. 불행히도 결과는 동일했지만, 적어도 이제 상황이 매우 명확해졌습니다:

새로운 게시글을 생성하기 위해 준비한 호출입니다 (이미 “게시글 최소 단어 수” 설정을 1로 낮춰 두었습니다):

그리고 이것이 결과입니다

그리고 이 테스트 호출들에서 로그에 기록된 두 가지 오류 메시지입니다:

오류 1:

Message (15 copies reported)

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'

Backtrace

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:

Message (15 copies reported)

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_...
                                                             ^


Backtrace

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'

방금 이 사용자를 생성했기 때문에, 이것이 신뢰 수준(trust levels)과 관련된 버그인지, 아니면 새 사용자에게 완전히 권한이 부여되지 않은 다른 문제인지는 모르겠습니다. 이 부분을 조금 실험해 보고 게시글이 API를 통과하게 하는 방법을 찾아보겠습니다.

어쨌든, 어떤 종류의 버그를 발견한 것 같습니다… ?!

그래서 버그를 우회하는 방법을 찾았어요… 실험을 시작했죠:

  1. API 호출의 user를 'system’으로 변경했는데, 이게 잘 작동했어요.
  2. 그래서, 음, 이전에 시도하지 않은 3번째 user로 변경해봤습니다. 이 역시 잘 작동했어요.
  3. 그다음 user를 원래 사용자 이름으로 다시 변경했는데, 이는 이전에 500 에러를 발생시켰던 API 호출을 정확히 반복하는 것이었습니다. 그런데 이번에는 잘 작동했어요. :man_shrugging:t2:

다시 발생하면 어떻게 하면 재현할 수 있는지 알아볼게요. 그 사이에, 저보다 Discourse 코드를 더 잘 아는 분이 :wink: 그 부분을 한번 살펴봐 주셔서 명백한 버그가 없는지 확인해 주시면 좋겠습니다.

음, 여기 분명 뭔가 이상한 일이 일어나고 있는 것 같아요. 스크립트를 다시 실행해 보니, 대량으로 토픽을 생성하고 메시지를 게시하는 중이네요… 그리고 또 다시, 이번에는 다른 사용자로 테스트했는데 500 에러가 발생했어요.

그 사용자명을 Insomnia로 가져와서 API를 테스트해 보니, 역시 500 에러가 뜨네요. 'system’으로 바꾸니 정상적으로 통과하더니… 원래 사용자명으로 다시 돌아가니 또 500 에러가 나요!

500 에러를 일으키는 사용자(그리고 'system’을 제외한 모든 내 사용자)는 TL1이고, TL1이 게시물을 올릴 수 있도록 제한을 변경했는데도 문제가 지속되고 있어요.

이건 분명某种 방식으로 레이트 리미팅(rate limiting)과 관련이 있는 것 같은 느낌이 드네요. nginx나 다른 업스트림 구성 요소가 500 에러의 원인일 수도 있겠다고 의심했지만, nginx에서 모든 레이트 리미팅을 제거했는데도 에러 로그에 표시되는 500 에러는 여전히 그 SQL 버그와 동일하게 나타나고 있어요.

SQL 에러가 IP 주소를 참조하는 컬럼 주변에서 발생하고 있는 것 같다는 걸 알아차렸어요. 레이트 리미팅 작동 방식에 뭔가 이상한 점이 있어서 이 문제를 일으킬 수 있을까요? IP를 변경하기 위해 VPN으로 로그인해 보았지만 여전히 500 에러가 나요.

그동안, 문제가 rack-mini-profiler에 있는 것 같다는 걸 알게 되었어요. 이건 필수적인 것이 아니니 꺼보려고 해요. 이게 문제를 해결해 줄지 볼게요… 아니요. 이제 관리자 계정에서 미니 프로파일러는 더 이상 보이지 않지만, 에러 로그에 동일한 에러와 함께 동일한 HTTP 500 에러가 계속 나고 있어요 :frowning:

업데이트: 사용자를 TL3으로 변경하면 문제가 사라지는 것 같아요. 다시 TL1로 전환하면 문제가 다시 발생해요. 이 가설을 테스트해 보고 있어요… 아니요. 때로는 효과가 있지만, 다른 경우에는 사용자가 수동으로 TL을 편집해도 사용자의 상태가 "고정(stuck)"된 것처럼 보이기도 해요.

@blake 또는 @staff 중 다른 누군가… 도와주세요! :wink: 이제 뭘 더 시도해 볼 수 있을까요? (에러를 일으키는 코드와 관련된) 완전히 초기화하고 공장 출하 상태로 리셋할 수 있는 것이 있을까요?

스택 트레이스를 보면 때때로 사용자 IP 주소가 표시되지 않는 경우가 있어서, 네트워크 문제일 가능성이 높아 보입니다. 아니면 관리자, 로그, 차단된 IP 주소에 잘못된 데이터가 있는 건가요?

동의합니다. <<=는 "좌변이 우변에 포함"을 의미하므로, 잘못된/빈 IP 주소가 앱으로 전달되고 있는 것으로 보입니다.

(앱에 IP 주소가 전혀 없는 상황이 어떻게 발생하는지 궁금합니다. 제 추측으로는 요청이 전달된 IP 정보 헤더가 없는 유닉스 소켓을 통해 들어오는 것 같습니다.)

동의합니다. 하지만 왜 때로는 IP가 있고 때로는 없는지 이해가 안 됩니다. 제 임포트 스크립트에서는 최소 5~7번의 성공적인 API 호출 후에 500 에러가 발생합니다. 그래서 데이터베이스에 어딘가 손상된 데이터가 있는 것 같다는 생각을 하고 있습니다.

네, 이상하긴 합니다. 하지만 동시에 “잘못된” 사용자 이름으로 호출하면 실패하고, "system"이나 다른 사용자 이름으로 호출하면 작동합니다. 그래서 아마도 제 데이터베이스에 손상된 데이터가 있는 것 같습니다.

손상되었을 수 있는 데이터베이스 테이블을 초기화/리셋할 수 있는 간단한 Ruby 커맨드라인 명령어를 알려줄 수 있는 분이 있을까요?

오류가 스크리닝된 IP 주소 테이블에 빈 IP 주소가 있는지 확인하는 것이지, 데이터베이스에 빈 IP 주소가 있는지 확인하는 것이 아니므로 데이터베이스에 문제가 있다고 생각하지 않습니다.

그러나 흥미로운 점은, 500 에러를 발생시키는 해당 SQL 요청을 수행하기 전에 이 코드가 ip_address의 존재 여부를 확인하고 있다는 것입니다.

Discourse 인스턴스가 어떻게 설정되어 있는지 설명해 주시겠습니까? 가져오기(import)를 통해 설치한 것인가요? 최신 버전을 사용 중인가요? RAM 용량은 얼마나 되나요? 이 가이드를 따르셨나요?

이 사이트 설정을 확인해 보실 수 있을까요: max new accounts per registration ip? 이 경우 중요한지 확실하지 않지만, 문제가 원인이 될 수도 있습니다.

벌크 스크립트에서 요청 사이에 1초의 대기 시간을 두고 속도를 늦춰 보시면 차이가 나는지 확인해 볼 수 있을까요?

이 API 호출의 목적은 무엇인가요? 사용자와 게시물을 가져오기 위해 한 번만 실행하는 스크립트라면, API 호출을 하지 않고 실제 서버에서 실행되는 가져오기 스크립트를 사용하는 것이 더 나을 수 있습니다.

많은 질문을 드려 죄송합니다. 이전에 이 문제를 겪어 본 적이 없고 screened_ip_addresses 주변의 코드는 오랫동안 업데이트되지 않았기 때문입니다. 코드베이스 어딘가에 버그가 없다고 말하는 것은 아니지만, 간단히 살펴본 결과 현재까지는 눈에 띄는 부분이 없습니다.

물론이죠. Digital Ocean에서 완전히 Docker로 구동되고 있습니다. 그 훌륭한 가이드를 그대로 따랐습니다.

확인했습니다. 해당 설정을 기본값인 3에서 99999로 변경해 봤습니다. 달라진 점은 없고, 여전히 500 에러가 발생합니다.

그렇게 해봤지만 달라진 점이 없습니다. Insomnia를 통해 ‘문제 있는’ 계정 하나만 사용해도 500 에러가 발생한다는 점을 참고해 주세요. 즉, 지금은 그 하나의 계정이 '독성’을 띠고 있는 것처럼 보입니다. 그 계정으로 ‘create message’ API 호출을 하나만 수행해도(그 전이나 후의 호출이 없어도) 여전히 500 에러가 발생합니다. 하지만 네, 제 임포트 스크립트도 500 에러를 받고 있긴 합니다 :wink:

네, 저는 숙련된 개발자이지만 RoR/Ruby는 전혀 모르기 때문에 여러분이 제공하는 표준 옵션을 실제로 사용할 수 없습니다. 물론 기존 포럼을 수동으로 돌아다니며 API를 통해 사용자를 생성하는 것보다 훨씬 우수할 것이라는 점은 인정합니다. 그래서 마켓플레이스 게시글을 올린 것입니다… 제 손으로 이 모든 것을 작동시키는 것을 원하지만, 엄격한 마감 기한도 있습니다 :wink:

전혀 이해합니다. 그리고 이 문제에 관심을 가져주셔서 감사합니다.

그럼 도움이 될 수 있는 것을 하나 제안하겠습니다. 표준 설치 환경이고 커스터마이징을 거의 하지 않았으며, 그리고 제 코드 없이도 버그를 쉽게 재현할 수 있고(Insomnia만 사용하면 됩니다) 또한 포럼을 정식 오픈하지 않았기 때문에, Digital Ocean 인스턴스의 루트 로그인 정보, API 키 등을 전달해 드릴 수 있습니다. 그리고 거기에 자유롭게 들어가서 이것저것 만져보셔도 괜찮습니다. 제 Discourse 포럼은 현재 비어 있는 카테고리들과 우리가 설정해 둔 몇 개의 특별 소개 메시기가 있을 뿐, 기본적으로 비어 있고 실제 사용자가 아직 없습니다(관리자만 있음). 따라서 테스트를 원하시면 주제와 메시지를 생성하거나 삭제하는 등 자유롭게 사용하셔도 됩니다.

이것이 버그를 직접 눈으로 확인하는 가장 빠른 방법일 것입니다. 그리고 루트 권한으로 들어가게 되면, 이것이 왜 발생하는지 알아내기 위해 원하시는 저수준의 Discourse 설정들을 자유롭게 조정할 수도 있습니다.

E

SSO를 비활성화하면 어떻게 되나요?

차이가 없습니다. 여전히 500 오류가 발생합니다.

또 다른 아이디어: 해당 계정이 작업된 모든 계정이 관리자(admin)인 것은 아닌가요? 애플리케이션 레벨의 속도 제한(ratelimit)이 관리자에 대해서는 우회되는 경우가 몇 가지 있다는 것을 알고 있습니다.

하지만 일반적으로는 그냥 가져오기(import) 스크립트를 작성하는 것이 훨씬 쉽습니다. 이 전체 주제가 바로 그 "훨씬"이 어떤 수준인지를 보여주는 것이죠 :wink:

음, 거기에 뭔가 있는 것 같았어요… 제 테스트용 ‘독이 든’ 문제 사용자 george21은 TL0 상태였습니다. 그래서 TL1로 변경했는데 그제서야 작동했어요. 좋아요! 아마 그게 원인인 것 같습니다! 그래서 george21을 다시 TL0로 되돌렸더니… 이제 더 이상 ‘독이 든’ 상태가 아니에요. TL0 상태에서도 API 호출을 할 수 있습니다.

이제 다시 가져오기(import) 스크립트를 실행해 보겠습니다. 아하. 이제 george21이 가져오기 스크립트에서 500 오류를 던지고 있습니다. Insomnia에서 시도해 보았는데 실패합니다. 그래서 이제 george21을 다시 TL1로 변경해 보면… 네, HTTP 호출을 실행할 수 있습니다.

즉, 제가 재현할 수 있는 현상은 다음과 같습니다:

  1. 일련의 API 호출이 이루어지면(?) 어떤 식으로든 TL0 사용자의 후속 API 호출이 실패하게 됩니다.
  2. TL0 사용자를 TL1로 변경하면 API 호출이 통과합니다.
  3. 그리고 기이하게도, 그 같은 사용자를 다시 TL0로 변경해도 여전히 API 호출이 통과합니다.
  4. 스크립트를 다시 실행하면 또 다른 TL0 사용자에 대해 실패할 때까지 정상적으로 작동합니다.

참고로:

  1. 제 지식 범위 내에서 TL0에 대한 모든 최소 요건 등이 상향 조정되었습니다(즉, TL0 사용자가 게시물을 올리는 것을 막을 수 있는 모든 블로킹 요소를 제거하려고 시도했습니다) 그리고,
  2. 이것이 TL0 사용자를 위한某种 내부 속도 제한 문제일지라도, API가 500 오류를 던지고 오류 로그에 SQL 오류를 기록하는 것은 아닙니다. 따라서 이 시점에서 어딘가에 분명히 버그가 있다고 말할 수 있습니다.

네, 음, 알고 있습니다. 그리고 (제시된 예시들을 바탕으로) 왜 제 가져오기 스크립트를 직접 작성하지 않는지에 대한 이유를 이미 네 번쯤 설명드렸죠. :wink: 그래서 제 접근 방식 변경.

그리고 그 사이, 저는 이 버그를 찾아 수정하는 데 도움을 주기 위해 여기에서 계속 기여하고 있습니다. 오늘은 제 가져오기 스크립트에 영향을 미치고 있습니다. 내일은 여러분 사이트에서 API가 필요한 중요한 스크립트에 영향을 미칠 수도 있습니다…