저는 포럼 관리자입니다. 한 사용자가 기존 계정을 새 계정으로 병합해 달라고 요청했습니다.
아래는 해당 사용자의 활동 내역입니다:
계정 병합을 시도했을 때, 사이트가 ‘계정 병합 중…’ 화면에서 멈춰 있었습니다. 10분 동안 기다렸지만 아무런 변화가 없었습니다.
이 상황에서 어떻게 해야 하나요?
우연히 병합은 되었지만 UI가 새로고장되지 않은 것 같습니다. 삭제되었어야 할 사용자의 관리자 페이지를 다른 탭에서 열어보세요.
계정을 병합할 때 가끔 이런 일이 발생하는 걸로 기억합니다. 새로고침을 하면 해당 계정이 병합된 것이 표시되는데, 페이지가 그 사실을 반영하도록 리디렉션되지 않는 경우였죠.
안타깝게도 두 계정 모두 여전히 활성 상태입니다.
'게시물이 있는 사용자는 삭제할 수 없다’는 고지 문구를 확인했는데… 이 사용자는 4,000개 이상의 게시물을 작성했습니다.
어떤 조언을 주실 수 있을까요?
병합을 하고 있으므로 문제가 없어야 하는데… 브라우저 콘솔이나 /logs에 관련 에러가 있나요?
이런 문제를 겪게 되어, 다른 분들도 같은 문제를 경험할 경우 도움이 될 수 있도록 여기서 제 조사 결과를 공유하고자 합니다.
제 경우, 계정 병합 작업이 “병합 진행” 다이얼로그가 "게시물 소유권 변경 중…"과 “사용자 이름 업데이트 중…” 메시지 사이를 반복하며 영원히 멈춰 있었습니다.
스태프 작업 로그(/admin/logs/staff_action_logs)를 살펴보니, 병합을 트리거한 직후 많은 게시물의 소유권이 즉시 이전되었지만 추가적인 작업은 없었습니다. 이는 작업이 단순히 모든 게시물을 처리하는 데 매우 오랜 시간이 걸리는 것이 아니라 실제로 멈춰 있음을 증명했습니다.
에러 로그(/logs/)를 확인해 보니, 백트레이스를 동반한 “Job exception: topic” 에러가 주기적으로 발생하고 있었습니다:
/var/www/discourse/app/services/post_owner_changer.rb:12:in `block in
initialize'
/var/www/discourse/app/services/post_owner_changer.rb:11:in `each'
/var/www/discourse/app/services/post_owner_changer.rb:11:in `initialize'
/var/www/discourse/app/services/user_merger.rb:84:in `new'
/var/www/discourse/app/services/user_merger.rb:84:in `change_post_owner'
/var/www/discourse/app/services/user_merger.rb:72:in `block in move_posts'
/var/www/discourse/app/services/user_merger.rb:70:in `each'
/var/www/discourse/app/services/user_merger.rb:70:in `move_posts'
/var/www/discourse/app/services/user_merger.rb:14:in `merge!'
/var/www/discourse/app/jobs/regular/merge_user.rb:15:in `execute'
/var/www/discourse/app/jobs/base.rb:316:in `block (2 levels) in perform'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/rails_multisite-6.1.0/lib/rails_multisite/connection_management/null_instance.rb:49:in
`with_connection'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/rails_multisite-6.1.0/lib/rails_multisite/connection_management.rb:21:in
`with_connection'
/var/www/discourse/app/jobs/base.rb:303:in `block in perform'
/var/www/discourse/app/jobs/base.rb:299:in `each'
/var/www/discourse/app/jobs/base.rb:299:in `perform'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:220:in
`execute_job'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:185:in
`block (4 levels) in process'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/middleware/chain.rb:180:in
`traverse'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/middleware/chain.rb:183:in
`block in traverse'
/var/www/discourse/lib/sidekiq/discourse_event.rb:6:in `call'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/middleware/chain.rb:182:in
`traverse'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/middleware/chain.rb:183:in
`block in traverse'
/var/www/discourse/lib/sidekiq/pausable.rb:131:in `call'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/middleware/chain.rb:182:in
`traverse'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/middleware/chain.rb:183:in
`block in traverse'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/job/interrupt_handler.rb:9:in
`call'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/middleware/chain.rb:182:in
`traverse'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/middleware/chain.rb:183:in
`block in traverse'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/metrics/tracking.rb:26:in
`track'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/metrics/tracking.rb:134:in
`call'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/middleware/chain.rb:182:in
`traverse'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/middleware/chain.rb:173:in
`invoke'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:184:in
`block (3 levels) in process'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:145:in
`block (6 levels) in dispatch'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/job_retry.rb:118:in
`local'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:144:in
`block (5 levels) in dispatch'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/config.rb:39:in
`block in <class:Config>'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:139:in
`block (4 levels) in dispatch'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:281:in
`stats'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:134:in
`block (3 levels) in dispatch'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/job_logger.rb:15:in
`call'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:133:in
`block (2 levels) in dispatch'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/job_retry.rb:85:in
`global'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:132:in
`block in dispatch'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/job_logger.rb:40:in
`prepare'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:131:in
`dispatch'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:183:in
`block (2 levels) in process'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:182:in
`handle_interrupt'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:182:in
`block in process'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:181:in
`handle_interrupt'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:181:in
`process'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:86:in
`process_one'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:76:in
`run'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/component.rb:10:in
`watchdog'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/component.rb:19:in
`block in safe_thread'
그리고 환경은 다음과 같았습니다:
hostname [...]
process_id 701
application_version 3146142c2ed2c880d53b2f8c136af9f46df99b59
current_db default
current_hostname forum.arduino.cc
job Jobs::MergeUser
problem_db default
time 8:15 am
opts
user_id 42
target_user_id 123
current_user_id 456
current_site_id default
결국, 멈춤 현상은 “from” 사용자 계정에 “고아(orphaned)” 게시물이 있었기 때문임을 확인했습니다. 여기서 "고아"라 함은 게시물의 데이터베이스 항목은 존재하지만, 해당 게시물이 연관된 토픽의 항목은 없다는 의미입니다.
특정 사용자 계정에 "고아 게시물"이 존재하는지 확인하려면 이 Data Explorer 쿼리를 실행할 수 있습니다:
-- [params]
-- integer :user_id
SELECT
posts.id,
posts.topic_id
FROM
posts
WHERE
posts.user_id = :user_id
AND posts.topic_id NOT IN (
SELECT
topics.id
FROM
topics
)
고아 게시물이 데이터베이스에서 제거된 후(Discourse 팀의 도움으로, 우리는 호스팅 플랜을 사용하고 있기 때문입니다), 계정 병합 작업을 성공적으로 완료할 수 있었습니다.
@per1234 님, 문제 해결 단계를 공유해 주셔서 감사합니다!
@Nick_Torres 님, 문제를 해결하셨나요?
향후 분류(triaging)를 돕기 위해 merge-user 태그를 추가했습니다. 병합이 유용한 오류 메시지 없이 중단되는 몇 가지 예외적인 경우가 있는 것 같습니다.