오류: 정수가 범위를 벗어났습니다

Sidekiq(재시도 및 Dead 목록)에서 이 오류를 자주 확인하고 있습니다:

Jobs::HandledExceptionWrapper: Wrapped ActiveRecord::RangeError: PG::NumericValueOutOfRange: ERROR: integer out of range 

이 동일한 오류를 확인한 작업들은 다음과 같습니다:
Jobs::PostAlert
Jobs::ProcessPost
Jobs::NotifyCategoryChange

이전에 여기에서 이 문제에 대해 일부 논의가 있었습니다: Feedback on the new Review Queue (2019) - #250 by markersocial

2개의 좋아요

아래 명령어를 실행해 주시겠어요?

cd /var/discourse/
./launcher enter app
su postgres
psql
\x
\connect discourse 
SELECT id FROM notifications ORDER BY 1 DESC LIMIT 1;
\q
exit
4개의 좋아요

@Falco 감사합니다. 방금 실행해 보았습니다. 결과는 다음과 같습니다:

-[ RECORD 1 ]--
id | 2147483496
4개의 좋아요

자, 유명한 정수 최대값 문제네요. 이 문제를 해결하려면 bigint로 변경해야 합니다. 제가 확인해 보겠습니다.

6개의 좋아요

현재 단계에서 임시 해결책은 다음을 실행하는 것입니다:

cd /var/discourse/
./launcher enter app
su postgres
psql
\x
\connect discourse 
ALTER TABLE notifications ALTER COLUMN id SET DATA TYPE bigint
\q
exit

이것은 새 설치 환경의 기본 설정이지만, 기존 설치 환경에서는 데이터 타입이 잘못 설정되어 있을 수 있습니다.

임시 해결책을 실행하면 테이블이 차단되므로 어려울 수 있으며, 먼저 웹 트래픽을 줄여야 할 수 있습니다.

2개의 좋아요

@Falco 님과 @sam 님 감사합니다 :slight_smile:

우회 조치에 대해 질문이 있는데, 이 방법은 비교적 안전할까요? 다운타임은 걱정하지 않고, 무언가를 깨뜨리는 것만 걱정됩니다.

표준 app.yml 단일 컨테이너를 사용하고 있습니다. 웹 부하를 줄이기 위해, 우회 조치를 실행하기 전에 읽기 전용 모드를 사용하고 ./launcher stop app, ./launcher start app 명령을 실행하는 것으로 충분할까요?

아무것도 망가지지 않을 것입니다. 최악의 경우에도 아주 오랫동안 “멈춰 있는” 상태가 될 뿐입니다.

2개의 좋아요

샘, 감사합니다. 우회 방법을 적용했습니다. 다만, 아래 명령을 입력했을 때 피드백이 전혀 없었습니다:

ALTER TABLE notifications ALTER COLUMN id SET DATA TYPE bigint

아직 처리 중인지 모르겠습니다. 현재 동일한 오류가 계속 발생하고 있으며, /sidekiq의 dead list에서 마지막 재시도가 '방금 전’인 작업들에서 다음과 같은 오류를 확인했습니다:

Jobs::PostAlert
Jobs::ProcessPost
Jobs::NotifyCategoryChange

post_id 컬럼도 함께 실행해야 할 것 같습니다

1개의 좋아요

감사합니다! :slight_smile:

확인차, 이렇게 작성한 것이 올바른가요?

ALTER TABLE notifications ALTER COLUMN post_id SET DATA TYPE bigint

네, 안전할 것입니다. 공식 마이그레이션을 추가하면 이를 허용할 수 있습니다.

1개의 좋아요

완벽합니다, 감사합니다 :slight_smile:

해당 작업을 실행해 보았지만(이전과 마찬가지로 확인이나 피드백을 받지 못했습니다) 포럼의 속도가 느려지지 않아서 실제로 적용되었는지 확실하지 않습니다. 현재 동일한 오류가 계속 발생하고 있습니다.

아마 공식 마이그레이션을 기다리는 것이 가장 좋을 것 같습니다.

1개의 좋아요

네, 이 데이터가 post_alerts 테이블에 있는 것 같네요. 여러 테이블을 확인해야 할 것 같습니다.

1개의 좋아요

현재 이 문제에 대해 어떤 상태인지 궁금합니다. 오류가 더 이상 발생하지 않나요?

처음에는 공식적인 마이그레이션을 진행하는 것을 고려했지만, 위험성이 이점을 훨씬 초과한다고 판단했습니다. 게시물이 2,147,483,647개를 초과하는 데이터베이스를 발견하는 경우는 매우 드뭅니다. 21억은 정말로 큰 숫자입니다.

모든 곳에서 크기를 늘리는 것의 단점은 저장 공간 요구량이 증가한다는 점입니다.

현재 우리는 Discourse의 테이블에 20억 개의 행(또는 20억 개의 행이 생성/삭제되는 변동이 있었던 경우)이 포함된 예외적인 사례를 위해 "공간을 확보"하는 rake 작업을 추가하는 것을 검토하고 있습니다.

1개의 좋아요

@sam 님, 후속 답변 감사합니다.

2.8.0.beta6로 업그레이드했지만 여전히 정수 범위 초과(integer out of range) 오류가 발생하고 있습니다.

게시물 수보다 알림 수가 훨씬 빠르게 한계에 도달할 수 있어, 알림이 방대한 숫자에 도달한 것이 원인이라고 생각합니다. 여러 사용자의 수많은 답변, 좋아요 등이 포함된 대규모 토픽이 많으면 알림이 상당히 많이 생성될 수 있습니다.

rake 작업을 사용한다는 소식은 정말 좋습니다. :slight_smile:

이 오래된 스레드인 걸 알지만 –

저희 환경(devforum.roblox.com)에서도 이 문제를 겪었습니다! 현재 v2.8.9를 사용 중이며 곧 3.0.1로 업데이트할 예정입니다.

사용자들이 게시글 좋아요/좋아요 취소 시 403/500 오류를 보고 있다는 걸 알게 되면서 뭔가 문제가 있다는 사실을 인지했습니다.

그런 다음 이 스레드를 우연히 발견하여 notifications 테이블을 확인해 보았습니다:

=> SELECT id FROM notifications ORDER BY 1 DESC LIMIT 1;
     id     
------------
 2147483647
(1 row)

@sam 2021년 9월 이후 rake task에 대해 더 많은 고려가 이루어졌나요? 아니면 위의 우회 방법이 여전히 최선의 제안인가요?

추가 정보 –

notifications.id 컬럼을 변경한 후, \tJobs::PostAlert 작업에서 별도의 문제가 발생하고 있습니다.

Job exception: 2147498514 is out of range for ActiveModel::Type::Integer with limit 4 bytes

놓친 다른 테이블/컬럼이 있을까요? 아니면 ruby 코드 어딘가에서 여전히 integer 데이터 타입을 기대하고 있는 건가요?

backtrace
activemodel-6.1.6.1/lib/active_model/type/integer.rb:49:in `ensure_in_range'

activemodel-6.1.6.1/lib/active_model/type/integer.rb:28:in `serialize'

activemodel-6.1.6.1/lib/active_model/attribute.rb:56:in `value_for_database'

activemodel-6.1.6.1/lib/active_model/attribute.rb:68:in `forgetting_assignment'

activemodel-6.1.6.1/lib/active_model/attribute_set.rb:90:in `transform_values'

activemodel-6.1.6.1/lib/active_model/attribute_set.rb:90:in `map'

activemodel-6.1.6.1/lib/active_model/dirty.rb:262:in `forget_attribute_assignments'

activemodel-6.1.6.1/lib/active_model/dirty.rb:154:in `changes_applied'

activerecord-6.1.6.1/lib/active_record/attribute_methods/dirty.rb:202:in `_create_record'

activerecord-6.1.6.1/lib/active_record/callbacks.rb:461:in `block in _create_record'

activesupport-6.1.6.1/lib/active_support/callbacks.rb:106:in `run_callbacks'

activesupport-6.1.6.1/lib/active_support/callbacks.rb:824:in `_run_create_callbacks'

activerecord-6.1.6.1/lib/active_record/callbacks.rb:461:in `_create_record'

activerecord-6.1.6.1/lib/active_record/timestamp.rb:108:in `_create_record'

activerecord-6.1.6.1/lib/active_record/persistence.rb:900:in `create_or_update'

activerecord-6.1.6.1/lib/active_record/callbacks.rb:457:in `block in create_or_update'

activesupport-6.1.6.1/lib/active_support/callbacks.rb:106:in `run_callbacks'

activesupport-6.1.6.1/lib/active_support/callbacks.rb:824:in `_run_save_callbacks'

activerecord-6.1.6.1/lib/active_record/callbacks.rb:457:in `create_or_update'

activerecord-6.1.6.1/lib/active_record/timestamp.rb:126:in `create_or_update'

activerecord-6.1.6.1/lib/active_record/persistence.rb:507:in `save!'

activerecord-6.1.6.1/lib/active_record/validations.rb:53:in `save!'

activerecord-6.1.6.1/lib/active_record/transactions.rb:302:in `block in save!'

activerecord-6.1.6.1/lib/active_record/transactions.rb:354:in `block in with_transaction_returning_status'

activerecord-6.1.6.1/lib/active_record/connection_adapters/abstract/database_statements.rb:320:in `block in transaction'

activerecord-6.1.6.1/lib/active_record/connection_adapters/abstract/transaction.rb:319:in `block in within_new_transaction'

activesupport-6.1.6.1/lib/active_support/concurrency/load_interlock_aware_monitor.rb:26:in `block (2 levels) in synchronize'

activesupport-6.1.6.1/lib/active_support/concurrency/load_interlock_aware_monitor.rb:25:in `handle_interrupt'

activesupport-6.1.6.1/lib/active_support/concurrency/load_interlock_aware_monitor.rb:25:in `block in synchronize'

activesupport-6.1.6.1/lib/active_support/concurrency/load_interlock_aware_monitor.rb:21:in `handle_interrupt'

activesupport-6.1.6.1/lib/active_support/concurrency/load_interlock_aware_monitor.rb:21:in `synchronize'

activerecord-6.1.6.1/lib/active_record/connection_adapters/abstract/transaction.rb:317:in `within_new_transaction'

activerecord-6.1.6.1/lib/active_record/connection_adapters/abstract/database_statements.rb:320:in `transaction'

activerecord-6.1.6.1/lib/active_record/transactions.rb:350:in `with_transaction_returning_status'

activerecord-6.1.6.1/lib/active_record/transactions.rb:302:in `save!'

activerecord-6.1.6.1/lib/active_record/suppressor.rb:48:in `save!'

/app/app/models/notification.rb:40:in `tap'

/app/app/models/notification.rb:40:in `consolidate_or_create!'

activerecord-6.1.6.1/lib/active_record/relation/delegation.rb:67:in `block in consolidate_or_create!'

activerecord-6.1.6.1/lib/active_record/relation.rb:406:in `block in scoping'

activerecord-6.1.6.1/lib/active_record/relation.rb:804:in `_scoping'

activerecord-6.1.6.1/lib/active_record/relation.rb:406:in `scoping'

activerecord-6.1.6.1/lib/active_record/associations/collection_proxy.rb:1109:in `scoping'

activerecord-6.1.6.1/lib/active_record/relation/delegation.rb:67:in `consolidate_or_create!'

/app/app/services/post_alerter.rb:496:in `create_notification'

/app/app/services/post_alerter.rb:825:in `block in notify_post_users'

/app/app/services/post_alerter.rb:838:in `block (2 levels) in each_user_in_batches'

activerecord-6.1.6.1/lib/active_record/relation/delegation.rb:88:in `each'

activerecord-6.1.6.1/lib/active_record/relation/delegation.rb:88:in `each'

/app/app/services/post_alerter.rb:838:in `block in each_user_in_batches'

/app/app/services/post_alerter.rb:837:in `each'

/app/app/services/post_alerter.rb:837:in `each_slice'

/app/app/services/post_alerter.rb:837:in `each_user_in_batches'

/app/app/services/post_alerter.rb:821:in `notify_post_users'

/app/app/services/post_alerter.rb:162:in `after_save_post'

/app/app/jobs/regular/post_alert.rb:11:in `execute'

/app/app/jobs/base.rb:232:in `block (2 levels) in perform'

rails_multisite-4.0.0/lib/rails_multisite/connection_management.rb:80:in `with_connection'

/app/app/jobs/base.rb:221:in `block in perform'

/app/app/jobs/base.rb:217:in `each'

/app/app/jobs/base.rb:217:in `perform'

sidekiq-6.3.1/lib/sidekiq/processor.rb:196:in `execute_job'

sidekiq-6.3.1/lib/sidekiq/processor.rb:164:in `block (2 levels) in process'

sidekiq-6.3.1/lib/sidekiq/middleware/chain.rb:138:in `block in invoke'

/app/lib/sidekiq/pausable.rb:138:in `call'

sidekiq-6.3.1/lib/sidekiq/middleware/chain.rb:140:in `block in invoke'

sidekiq-6.3.1/lib/sidekiq/middleware/chain.rb:143:in `invoke'

sidekiq-6.3.1/lib/sidekiq/processor.rb:163:in `block in process'

sidekiq-6.3.1/lib/sidekiq/processor.rb:136:in `block (6 levels) in dispatch'

sidekiq-6.3.1/lib/sidekiq/job_retry.rb:112:in `local'

sidekiq-6.3.1/lib/sidekiq/processor.rb:135:in `block (5 levels) in dispatch'

sidekiq-6.3.1/lib/sidekiq/rails.rb:14:in `block in call'

activesupport-6.1.6.1/lib/active_support/execution_wrapper.rb:91:in `wrap'

activesupport-6.1.6.1/lib/active_support/reloader.rb:72:in `block in wrap'

activesupport-6.1.6.1/lib/active_support/execution_wrapper.rb:91:in `wrap'

activesupport-6.1.6.1/lib/active_support/reloader.rb:71:in `wrap'

sidekiq-6.3.1/lib/sidekiq/rails.rb:13:in `call'

sidekiq-6.3.1/lib/sidekiq/processor.rb:131:in `block (4 levels) in dispatch'

sidekiq-6.3.1/lib/sidekiq/processor.rb:257:in `stats'

sidekiq-6.3.1/lib/sidekiq/processor.rb:126:in `block (3 levels) in dispatch'

sidekiq-6.3.1/lib/sidekiq/job_logger.rb:13:in `call'

sidekiq-6.3.1/lib/sidekiq/processor.rb:125:in `block (2 levels) in dispatch'

sidekiq-6.3.1/lib/sidekiq/job_retry.rb:79:in `global'

sidekiq-6.3.1/lib/sidekiq/processor.rb:124:in `block in dispatch'

sidekiq-6.3.1/lib/sidekiq/logger.rb:11:in `with'

sidekiq-6.3.1/lib/sidekiq/job_logger.rb:33:in `prepare'

sidekiq-6.3.1/lib/sidekiq/processor.rb:123:in `dispatch'

sidekiq-6.3.1/lib/sidekiq/processor.rb:162:in `process'

sidekiq-6.3.1/lib/sidekiq/processor.rb:78:in `process_one'

sidekiq-6.3.1/lib/sidekiq/processor.rb:68:in `run'

sidekiq-6.3.1/lib/sidekiq/util.rb:43:in `watchdog'

sidekiq-6.3.1/lib/sidekiq/util.rb:52:in `block in safe_thread'

네, 여기서는 이것이 유일한 우회 방법입니다. 코어에서 이를 변경하는 것에 대해 우려가 있지만, 이 문제를 수정하지 않으면 거대한 포럼에서 이런 일이 계속 발생할 것 같습니다.

단점은 저장 공간이 증가한다는 점입니다.

1개의 좋아요

post_actions 서비스(또는 알림 처리 과정 어딘가)에서 ALTER 실행 후에도 여전히 정수(int)를 기대하는 사용처가 있는지 알고 계신가요?

/post_actions의 좋아요/좋아요 취소 호출에서 다음과 같은 응답과 함께 5xx 에러를 확인하고 있습니다.

{"errors":["The requested URL or resource could not be found."],"error_type":"not_found"}

또한, 알림 관련 작업 실패(Jobs::BookmarkReminderNotifications, Jobs::GrantAnniversaryBadges, Jobs::PostAlert)도 몇 건 발생하고 있습니다.

PostAlert의 백트레이스는 이전 메시지에 추가해 두었습니다. notification.rb의 consolidate_or_create에서 정수 제한 관련 문제가 발생하고 있는 것으로 보입니다.

저희 사용 사례에서는 기능이 복구될 수만 있다면 저장 공간 증가는 큰 문제가 되지 않습니다 :crossed_fingers:

컨테이너를 재시작해 보세요. 메모리에 캐시된 데이터가 남아 있을 수 있습니다.