네, 사용자 가져오기를 최소한 완료할 수 있다면 그렇게 할 예정입니다. 현재는 이메일 작업을 시도할 때 오류가 발생하고 있습니다.
script/bulk_import/vbulletin5.rb 스크립트의 첫 번째 줄인
# frozen_string_literal: true를 삭제하면 됩니다.
자, 첫 세 가지 함수만 실행하면 다음과 같습니다:
def execute
# 필요에 따라 활성화:
#SiteSetting.automatic_backups_enabled = false
#SiteSetting.disable_emails = "non-staff"
#SiteSetting.authorized_extensions = '*'
#SiteSetting.max_image_size_kb = 102400
#SiteSetting.max_attachment_size_kb = 102400
#SiteSetting.clean_up_uploads = false
#SiteSetting.clean_orphan_uploads_grace_period_hours = 43200
#SiteSetting.max_category_nesting = 3
import_groups
import_users
import_group_users
#import_user_emails
#import_user_stats
#import_user_profiles
#import_user_account_id
#import_categories
#import_topics
#import_topic_first_posts
#import_replies
#import_likes
#import_private_topics
#import_topic_allowed_users
#import_private_first_posts
#import_private_replies
#create_oauth_records
#create_permalinks
#import_attachments
end
결과:
일관성 확인에 관한 그 메시지는 전체 가져오기가 완료된 후에 실행하는 것을 의미하는 것 같습니다. 아니면 제가 실행하는 각 "단계"마다 실행한 후 호스트에서 discourse 디렉토리의 사본을 만들어 백업으로 사용해야 하는 건가요?
우선 이 글로 인해 불쾌감을 느끼실 수 있는 분들에게 사과부터 드리고 싶습니다. 솔직히 월요일부터 이 문제들과 씨름하고 있는데, 지금은 더 이상 Discourse 코드에 대한 디버깅/핫픽스를 하는 것이 지칩니다.
n번째 시도(7번째 이후로는 세는 것을 포기했습니다)를 거친 후, 이제 포기해야 할 것 같습니다. Discourse가 마이그레이션 지원에 많은 시간을 투자하지 않은 것 같기 때문입니다.
가장 큰 문제는 이 거대한 데이터베이스에서 사용되는 문자 인코딩이 utf8mb4인데, 스크립트가 이를 지원하지 않는 것 같다는 점입니다.(?)
utf8(기본값)를 사용하면 오류가 대량으로 발생하며 보고되지만, 스크립트가 그대로 진행되기 때문에 정확히 무슨 일이 일어나고 있는지 명확하지 않습니다. DB의 항목이 건너뛰어지는 건가요? 아니면 지원되지 않는 문자(전형적인 사각형 박스)와 함께 복사되는 건가요?
게다가 동일한 명령어 세트를 정확히 따르고 실행한 최근 세 번의 실행(벌크 임포터 사용) 모두 서로 다른 결과를 낳았습니다. 마지막 실행에서는 토픽 가져오기에 진입하자마자 즉시 오류를 보고했지만 진행을 계속했습니다.(???):
Loading application...
Starting...
Preloading I18n...
Fixing highest post numbers...
Loading imported group ids...
Loading imported user ids...
Loading imported category ids...
Loading imported topic ids...
Loading imported post ids...
Loading groups indexes...
Loading users indexes...
Loading categories indexes...
Loading topics indexes...
Loading posts indexes...
Loading post actions indexes...
Importing categories...
Importing parent categories...
5 - 1104/sec
Importing children categories...
500 - 1539/secERROR: duplicate key value violates unique constraint "unique_index_categories_on_name"
DETAIL: Key (COALESCE(parent_category_id, '-1'::integer), name)=(-1, Armata Brancaleone) already exists.
CONTEXT: COPY categories, line 69
/var/www/discourse/vendor/bundle/ruby/2.7.0/gems/pg-1.4.5/lib/pg/connection.rb:204:in `get_last_result'
/var/www/discourse/vendor/bundle/ruby/2.7.0/gems/pg-1.4.5/lib/pg/connection.rb:204:in `copy_data'
/var/www/discourse/script/bulk_import/base.rb:720:in `create_records'
/var/www/discourse/script/bulk_import/base.rb:361:in `create_categories'
script/bulk_import/vbulletin5.rb:291:in `import_categories'
script/bulk_import/vbulletin5.rb:69:in `execute'
/var/www/discourse/script/bulk_import/base.rb:98:in `run'
script/bulk_import/vbulletin5.rb:779:in `<main>'
Importing topics...
600 - 4073/sec
ERROR: undefined method `[]' for nil:NilClass
/var/www/discourse/script/bulk_import/base.rb:513:in `process_topic'
/var/www/discourse/script/bulk_import/base.rb:724:in `block (2 levels) in create_records'
/var/www/discourse/vendor/bundle/ruby/2.7.0/gems/rack-mini-profiler-3.0.0/lib/patches/db/mysql2/alias_method.rb:8:in `each'
/var/www/discourse/vendor/bundle/ruby/2.7.0/gems/rack-mini-profiler-3.0.0/lib/patches/db/mysql2/alias_method.rb:8:in `each'
/var/www/discourse/script/bulk_import/base.rb:721:in `block in create_records'
/var/www/discourse/vendor/bundle/ruby/2.7.0/gems/pg-1.4.5/lib/pg/connection.rb:196:in `copy_data'
/var/www/discourse/script/bulk_import/base.rb:720:in `create_records'
/var/www/discourse/script/bulk_import/base.rb:364:in `create_topics'
script/bulk_import/vbulletin5.rb:321:in `import_topics'
script/bulk_import/vbulletin5.rb:70:in `execute'
/var/www/discourse/script/bulk_import/base.rb:98:in `run'
script/bulk_import/vbulletin5.rb:779:in `<main>'
결국 다음 오류에서 크래시 occurred했습니다:
script/bulk_import/vbulletin5.rb:779:in `<main>'
572329 - 531/sec
Importing replies...
client_loop: send disconnect: Connection reset
그러나 그 전에 다음과 같은 두 가지 오류를 끊임없이 좌우로 스팸하듯 출력했습니다:
ERROR: undefined method `gsub!' for nil:NilClass
script/bulk_import/vbulletin5.rb:727:in `preprocess_raw'
script/bulk_import/vbulletin5.rb:369:in `block in import_topic_first_posts'
/var/www/discourse/script/bulk_import/base.rb:723:in `block (2 levels) in create_records'
/var/www/discourse/vendor/bundle/ruby/2.7.0/gems/rack-mini-profiler-3.0.0/lib/patches/db/mysql2/alias_method.rb:8:in `each'
/var/www/discourse/vendor/bundle/ruby/2.7.0/gems/rack-mini-profiler-3.0.0/lib/patches/db/mysql2/alias_method.rb:8:in `each'
/var/www/discourse/script/bulk_import/base.rb:721:in `block in create_records'
/var/www/discourse/vendor/bundle/ruby/2.7.0/gems/pg-1.4.5/lib/pg/connection.rb:196:in `copy_data'
/var/www/discourse/script/bulk_import/base.rb:720:in `create_records'
/var/www/discourse/script/bulk_import/base.rb:367:in `create_posts'
script/bulk_import/vbulletin5.rb:361:in `import_topic_first_posts'
script/bulk_import/vbulletin5.rb:71:in `execute'
/var/www/discourse/script/bulk_import/base.rb:98:in `run'
script/bulk_import/vbulletin5.rb:779:in `<main>'
그리고
ERROR: invalid byte sequence in UTF-8
script/bulk_import/vbulletin5.rb:727:in `gsub!'
script/bulk_import/vbulletin5.rb:727:in `preprocess_raw'
script/bulk_import/vbulletin5.rb:369:in `block in import_topic_first_posts'
/var/www/discourse/script/bulk_import/base.rb:723:in `block (2 levels) in create_records'
/var/www/discourse/vendor/bundle/ruby/2.7.0/gems/rack-mini-profiler-3.0.0/lib/patches/db/mysql2/alias_method.rb:8:in `each'
/var/www/discourse/vendor/bundle/ruby/2.7.0/gems/rack-mini-profiler-3.0.0/lib/patches/db/mysql2/alias_method.rb:8:in `each'
/var/www/discourse/script/bulk_import/base.rb:721:in `block in create_records'
/var/www/discourse/vendor/bundle/ruby/2.7.0/gems/pg-1.4.5/lib/pg/connection.rb:196:in `copy_data'
/var/www/discourse/script/bulk_import/base.rb:720:in `create_records'
/var/www/discourse/script/bulk_import/base.rb:367:in `create_posts'
script/bulk_import/vbulletin5.rb:361:in `import_topic_first_posts'
script/bulk_import/vbulletin5.rb:71:in `execute'
/var/www/discourse/script/bulk_import/base.rb:98:in `run'
script/bulk_import/vbulletin5.rb:779:in `<main>'
참고로, 저는 실행할 함수를 주석 처리하는 방식으로 단계별로 진행했고, 계속하기 전에 rake import:ensure_consistency를 실행한 후 방금 실행된 것들을 주석 처리하는 식으로 반복했습니다. 왜냐하면 전체 스크립트를 그냥 다시 실행하면 이전에 실행된 단계에서 중복된 ID를 발견하여 단순히 크래시 발생하기 때문입니다.
"무료 소프트웨어에 대해 불평할 수 없다"는 통상적인 논쟁이 나오기 전에, 저는 다른 오픈 소스 프로젝트에도 기여하고 있으며 무료로 소프트웨어를 개발하고 있지만, 제가 무언가를 릴리스한다면 그것이 작동하고 잘 문서화되어 있어야 한다는 점이 저에게는 매우 중요합니다(그렇지 않으면 '이것이 어떻게 작동하는지’라고 정당하게 질문하는 수천 개의 메시지를 피할 수 없으니까요). 그렇지 않으면 발생하는 모든 버그를 수정할 준비가 되어 있어야 합니다.
Discourse는 훌륭한开箱(experience) 경험을 제공하는 것 같지만, 2022년이라는 점을 잘 인식해야 합니다. 커뮤니티는 이 제품보다 훨씬 전에 존재했습니다. "도입(Adoption)"에는 강력한 마이그레이션 지원이 필요하지만, Discourse의 현재 상태로는 그렇지 않은 것 같습니다.
20GB 데이터베이스는 엣지 케이스임을 인정하지만, 여기서 문제는 크기가 아니라 문자 인코딩이거나 무엇이든 마찬가지입니다. 일관된 오류가 없기 때문이죠. 과거에 같은 고난을 겪은 사람들이 남긴 스레드와 게시물을 찾아다니는 것 외에 문서가 없으며, 우회 방법이 발견되었고 그 이후로 소스 코드가 크게 변경되지 않았기를 바랄 뿐입니다.
이 시점에서, vbulletin에서 넘어오는 모든 사용자에게 마이그레이션 스크립트의 전면적인 개편(진행 중인 것으로 보이지만?)이 완료될 때까지 마이그레이션을 보류할 것을 강력히 권장합니다.
그런 고충을 충분히 이해합니다(마이그레이션은 정말 까다로운 주제이니까요). 하지만 Discourse의 마이그레이션 전문가로서 사실을 바로잡겠습니다.
우리는 다양한 플랫폼을 위해 60개 이상의 스크립트를 갖춘 성숙한 마이그레이션 프레임워크, 5개의 스크립트를 포함한 별도의 대량 처리 프레임워크, 그리고 성능, 코드 조직, 테스트 가능성, 검증 가능성, 문서화 등 모든 측면에서 크게 개선된 새로운 프레임워크를 개발 중입니다.
우리는 광범위한 코어 개발자 지원을 받는 별도의 마이그레이션 팀을 보유하고 있으며, 완료하는 각 마이그레이션마다 범용적인 개선 사항을 코드로 되돌려 반영하고 있습니다. 우리는 사소한 것부터 믿을 수 없을 정도로 복잡한 것까지, 다양한 고객사를 위해 지속적으로 마이그레이션을 수행하고 있습니다.
최종 목표는 호스팅 고객과 커뮤니티 모두에게 마이그레이션을 최대한 원활하게 만드는 것입니다. 하지만 마이그레이션 과정에서 범위에 포함되는 코드 양이 너무 방대하고, 시스템 수준의 소프트웨어 구성, 제3자 소프트웨어 변경 사항, 입력 데이터의 다양성 등이 문제를 더욱 복잡하게 만듭니다.
다시 한번 말씀드리지만, 이 모든 과정이 덜 고통스러웠으면 좋겠습니다. 하지만 이를 위해 필요한 개발 및 유지보수 인력 시간이 엄청나며, 가용한 인력은 한정되어 있습니다.
포기하지 마세요! ![]()
범위가 얼마나 방대한지 충분히 이해하고 있습니다. 하지만 예외를 하나 해결하면 또 다른 예외가 반복적으로 발생하는 상황이 정말 짜증스럽습니다. 게다가 이 프로젝트가 Ruby로 작성되어 있다는 사실도 도움이 되지 않습니다. 기본적으로 도움을 구할 수 있는 곳은 여기뿐인데, 말씀하신 것처럼 일부는 매우 특수한 사례라 실제 데이터에 직접 접근하지 않는 한 도움을 줄 수조차 없는 경우가 많기 때문입니다.
또한 상당 부분 책임을 vBulletin의 구조에 돌리고 싶습니다. 그 구조는 정말 엉망이니까요.
오늘 아침에 확인해 보니, 테이블 크기에 대한 요약은 다음과 같습니다.
문맥을 설명하자면, “text” 테이블에는 실제 콘텐츠가 저장됩니다.
node 테이블은 계층 구조를 유지하고 closure는… 여기서 인용구를 빌리겠습니다. 제가 직접 설명하기가 너무 어렵거든요:
Closure 테이블은 모든 노드 간의 부모-자식 관계를 구축합니다. 데이터베이스의 대부분은 첨부 파일로 구성되어 있으며, 이러한 파일은 애초에 데이터베이스에 저장되어서는 안 됩니다.
결국 약 8GB의 콘텐츠를 가진 포럼에서 28GB의 오버헤드가 발생하고 있습니다. 정말 훌륭한 성과네요, vBulletin을 축하합니다.
제가 ‘지루하다’라고 말할 때 이런 상황을 뜻합니다.
다시 말하지만, 동일한 작업 세트(제 스스로 시행착오를 거쳐 작성한 런북을 따르는 것)를 새로운 discourse 설치 환경에서 실행했습니다.
결과:

import_user_account_id는 어디에 있나요? ![]()
하지만 가장 중요한 것은? 토픽 가져오기에서 실패했던 이전 실행에서는 어떻게 에러를 일으키지 않았나요? ![]()
그 함수 호출을 주석 처리하고(어쨌든 중요한 것처럼 보이는데) 다시 실행합니다:
중복된 키 에러… 스크립트가 이미 해당 ID를 처리했다는 것을 알고 넘어가야 하지 않나요?
모든 가져오기(import) 작업은 다릅니다. your-previously-favorite-forum의 한 인스턴스에 작동하던 스크립트가 다른 곳에서도 당연히 작동할 것이라 생각하기 쉽지만, 실제로는 그렇지 않습니다. 그리고 규모가 큰 포럼의 경우, 그 작업은 정말로 어렵습니다. 단순히 지원하기 쉬운 일이 아닙니다. 또한 대량 가져오기 도구는 Rails가 처리되는 동안 자동으로 문제를 확인하도록 하는 대신 데이터베이스에 직접 접근합니다.
그다지 드물지 않은 문제이며, 스크립트의 결함은 아닙니다. 기존 데이터베이스를 utf8로 변환하는 방법을 찾아야 합니다.
강력한 마이그레이션 지원은 존재합니다. 다만 무료 마이그레이션 지원은 없습니다. 저는 약 100건의 마이그레이션을 수행했고, 지원되지 않거나 맞춤형 시스템에 대한 여러 가져오기 스크립트를 작성한 경험이 있습니다. 만약 데이터베이스를 가져오도록 의뢰하신다면, 저는 대략 3,000~5,000달러를 청구할 것입니다. 이는 제안을 위한 것이 아니라, 이 작업을 여러 번 해본 사람으로서 얼마나 많은 작업이 필요한지 감을 잡게 해드리기 위한 것입니다. 비즈니스 호스팅을 1년 치로 지불하신다면, CDCK가 무료로 해줄 가능성이 높으며, 이는 제가 청구할 금액보다 적을 수 있습니다. (하지만 해당 데이터베이스 크기로는 비즈니스 호스팅 자격이 되지 않을 수도 있습니다.)
여기서 탐구를 계속합니다.
- 스크립트가 존재하지 않는 함수
import_user_account_id를 참조하고 있습니다. (Discourse 개발진) 해당 문제를 수정해 주시면 좋겠습니다. - 토픽 제목을 확인하는 로직이 어떤 이유에서인지 제목이 빈 문자열인 일부 토픽에서 오류를 일으키고 있습니다. 이런 일이 일어나서는 안 되지만, 이를 평가하는 체크는 이를 잡아내고
nil을 반환해야 합니다만, 실제로는 임포트에 작성된 후속 로직을 깨뜨리는 것으로 보입니다(상세 내용은 여기를 참고하세요).
최근에 제가 진행한 어떤 가져오기(import) 작업에서 이 문제를 겪었습니다. 더 나은 방법은 "토픽 XXX에 제목이 없습니다"와 같은 메시지를 반환하거나, 게시물의 첫 줄 텍스트를 가져오는 것입니다. 하지만 이 컨텍스트에서는 그렇게 하는 것이 어렵습니다. 제가 시도해 볼 것 같다면, 데이터베이스를 직접 수정(munging)하여 문제를 해결하고, 제목이 없는 부분에 대해서는 다른 도구를 사용해 제목을 생성하는 방법을 사용할 것입니다.
네, 이런 문제들을 발견할 때 기본적으로 DB를 "정화(sanitizing)"하고 있긴 한데, 스크립트를 디버깅한 뒤 매번 어떤 원인이 문제를 일으키는지 추측해야 해서 꽤 어렵습니다 ![]()
여전히 누락된 함수 import_user_account_id에 대해서는 전혀 감이 안 잡힙니다 ![]()
특히 휴가가 곧 다가오는 시점이라, 해당 스크립트를 직접 사용 중인 사람이 아닌 한 그 문제를 수정해 줄 사람은 없을 가능성이 큽니다. (보통 이런 경우 리처드가 나타나 상황을 해결해 줍니다.)
하하, 오늘 여러분을 실망시켜 드릴 것 같습니다. 노력은 해 봤지만, 이 임포트기의 커밋이 불완전해서 base.rb의 일부 변경 사항이 포함되지 않은 것 같다고 추정됩니다. @justin 님이 이 부분을 작업하셨으니, 아마 알고 계실 겁니다. 이건 고객에 특화된 문제라서, 추가적인 결과 없이 주석 처리해도 될 것 같다고 생각합니다.
저도 벌크 임포터(bulk importer)를 직접 사용해 본 적이 없습니다.
네, 임포트 스크립트는 복잡하고 데이터베이스의 특정 사항에 의존할 수 있지만, 일부 스크립트는 단순히 작동하지 않는 상태입니다. 이 스크립트도 그렇고, # frozen_string_literal: true가 포함된 다른 몇 가지 스크립트도 바로 실행해서 작동하지 않습니다.
하!
그것이 (적어도)
내가 만든 변경 사항에 대해 PR을 제출하는 것이 그렇게 어려운 이유입니다. 작업을 마칠 때쯤이면 거기에는 사례별로 특화된 내용이 너무 많아져서, 내가 제출하는 것이 어딘가에서 깨질까 봐 두렵습니다.
네. 어떤 과정이 모든 파일에 frozen_string_literal을 추가한 것 같습니다. 테스트가 있는 대부분의 파일은 수정되었지만, 임포트 스크립트에는 테스트가 없습니다.
자, 명확히 하자면, 지금 바로 누군가 이걸 고쳐주길 기대하는 건 아니에요 (카렌처럼). 코드베이스 자체에 분명 문제가 있는 몇 가지 부분을 지적하는 것뿐이고, 아마도 그냥 "아, 이 변경사항을 커밋에 추가하는 걸 잊었네!
"인 경우일 거예요.
솔직히 말해서, 이 마이그레이션이 1월 이전에는 진행되지 않을 거라는 점은 이미 받아들였어요.
다들 연말연시를 잘 즐기세요 ![]()
연휴 후에 이 문제를 다시 언급하거나 새로운 스레드를 열겠지만, 그때는 이 마이그레이션에 할애할 시간이 확실히 줄어들 거예요 ![]()
네, 정말 그렇습니다 - import의 경우, 보통 서로 다른 두 클라이언트에서 두 번 import를 수행한 후에야 PR을 제출합니다.
단순히 이 내용을 최신 상태로 유지하고 있습니다.
커뮤니티의 다른 엔지니어들과 논의한 후 base.rb에 몇 가지 변경 사항을 적용했습니다. 상당수의 오류는 gsub! 메서드에서 발생했는데, Apparently 일부 토픽의 제목이 ''(빈 문자열)이었기 때문이었습니다.
우리는 normalize_text 함수를 모방하는 함수를 추가했는데, 이 함수는 콘텐츠가 없는 경우 스레드의 imported_id를 문자열로 변환하여 반환합니다.
def normalize_text_thread(text, imported_id)
return imported_id.to_s unless text.present?
@html_entities.decode(normalize_charset(text.presence || "").scrub)
end
그 다음 vbulletin5.rb에서 create_topic의 해당 행을 다음과 같이 변경했습니다:
create_topics(topics) do |row|
created_at = Time.zone.at(row[5])
title = normalize_text_thread(row[1], row[0])
이로 인해 문제가 해결되었습니다. 기본적으로 gsub! 메서드는 입력값이 nil인 경우를 잘 처리하지 못합니다.
하지만 이 조치로 스크립트가 계속 실행되긴 했으나, import_private_topics 단계에서 멈춰버렸습니다. 우리 데이터베이스에는 253,427개의 개인 토픽(PM)이 있는데, 이는 답변 수보다 훨씬 적은 양입니다. 9시간 동안 실행한 후 실제로 무슨 일이 일어나고 있는지 확인하기 위해 스크립트를 중단했습니다.
인터페이스를 실행해 보니 몇 가지 사항을 발견했습니다.
- 제 계정이 가져와지지 않았습니다. 생성된 관리자 사용자와 제 이메일 주소가 동일하기 때문인 것 같습니다. 당연한 일이지만 어딘가에 명시해 두는 것이 좋을지도 모르겠습니다.
- 카테고리(vBulletin 서브포럼) 중 일부만 가져와졌습니다.
- 토픽과 첫 번째 답변만 가져와졌습니다(정말 모든 것이 그런지는 확실하지 않습니다). 또한 모든 토픽이 올바른 카테고리에 배치되지 않고 가져와졌으며, 카테고리가 생성되었어야 하는 토픽도 마찬가지였습니다. 모든 것이 "카테고리 없음"으로 가져와졌습니다.
- "답변 카운터"에
-1이 표시되는데, 아마도 답변이 실제로 전혀 가져와지지 않았기 때문일 것입니다.
추가로, 이 일괄 가져오기 과정에서 발생하는 수많은 문제는 페이지네이션(pagination) 방식을 구현하면 대부분 사라질 것입니다. 7GB의 데이터를 한꺼번에 처리하려 했기 때문에 답변이 유실된 것으로 보입니다. 솔직히 일괄 가져오기 도구가 페이지네이션 방식을 사용하지 않는다는 점이 이해가 되지 않습니다. 단순히 1,000개씩 레코드를 가져와서 작성하고, 마지막으로 작성된 레코드 ID를 저장한 후 반복하는 것만으로도 큰 데이터베이스 관련 문제를 해결할 수 있을 것입니다.
참고로, 저는 이 주제에 큰 관심을 가지고 지켜보고 있으며, 업데이트에 매우 감사하고 있습니다.
아직 마이그레이션에 대해 많이 알지는 못하지만, 이 글들이 매우 유용하다고 느낍니다.







