vBulletin 5 데이터베이스 마이그레이션 - 가져오기 스크립트 오류

자, 간단히 요약해 보겠습니다.

현재 vBulletin 3을 사용하고 있는 포럼의 마이그레이션을 돕는 자원봉사를 하고 있습니다.
스테이징 환경에서 데이터베이스 덤프(20GB, 맞습니다)를 기반으로 시작했습니다.

vBulletin 5로 업그레이드했습니다. 5~6시간이 걸렸지만 순조롭게 진행되었습니다. 버전은 vBulletin 5.4입니다.
discourse에서 허용되는 형식으로 사용자 이름을 정리하는 작업도 수행했습니다.

이제 docker discourse를 설치하고, 준비 작업을 위해 이 가이드를 대략적으로 따랐습니다. '대략적으로’라는 말은 가이드의 대부분이 중복되거나 구식이었지만, 무엇을 해야 하는지에 대한 방향을 잡는 데 도움이 되었다는 뜻입니다.

Ruby 코딩 경험이 거의 없어서 눈앞이 캄캄해지는 단계에 도달했습니다.
즉, 설치 완료 후 ./launcher enter app 명령으로 컨테이너에 진입한 후 다음과 같은 작업을 수행했습니다:

  • freetds-dev와 libmariadb-dev 추가
  • Gemfile을 편집하여 php_serialize gem 추가.
  • 셸에서 export IMPORT=1 실행하여 가져오기(import) 환경을 설정
  • discourse 사용자로 bundle install --no-deployment --without test --without development --path vendor/bundle 실행

다음과 같은 오류가 발생했습니다:

You are trying to install in deployment mode after changing
your Gemfile. Run `bundle install` elsewhere and add the
updated Gemfile.lock to version control.

If this is a development machine, remove the /var/www/discourse/Gemfile freeze
by running `bundle config unset deployment`.

The list of sources changed
The dependencies in your gemfile changed

You have added to the Gemfile:
* mysql2
* redcarpet
* php_serialize
* sqlite3 (~> 1.3, >= 1.3.13)
* ruby-bbcode-to-md
* reverse_markdown
* tiny_tds
* csv
* parallel

따라서 다음을 계속 진행했습니다.

  • bundle config unset deployment 실행 후 이전 명령 재실행
  • mysql2와 php_serialize가 모두 존재하는지 확인 (존재했습니다)
  • 기존 포럼의 아바타(가져오기할 첨부 파일 없음)를 추가하고 /home/discourse 내 해당 디렉터리의 소유권을 discourse 사용자에게 할당
  • script/import_scripts/vbulletin5.rb를 편집하여 DB 연결 참조 변경
  • discourse 사용자로 bundle exec ruby script/import_scripts/vbulletin5.rb 실행

이로 인해 tzinfo Integer values not supported라는 오류가 발생했는데, 이 문제를 이 discourse의 여기에서 언급된 것을 확인했습니다.

Loading existing groups...
Loading existing users...
Loading existing categories...
Loading existing posts...
Loading existing topics...

importing groups...
       41 / 41 (100.0%)  [2294 items/min]  ]
importing users
Traceback (most recent call last):
        15: from script/import_scripts/vbulletin5.rb:726:in `<main>'
        14: from /var/www/discourse/script/import_scripts/base.rb:47:in `perform'
        13: from script/import_scripts/vbulletin5.rb:46:in `execute'
        12: from script/import_scripts/vbulletin5.rb:79:in `import_users'
        11: from /var/www/discourse/script/import_scripts/base.rb:916:in `batches'
        10: from /var/www/discourse/script/import_scripts/base.rb:916:in `loop'
         9: from /var/www/discourse/script/import_scripts/base.rb:917:in `block in batches'
         8: from script/import_scripts/vbulletin5.rb:98:in `block in import_users'
         7: from /var/www/discourse/script/import_scripts/base.rb:264:in `create_users'
         6: from /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'
         5: from /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'
         4: from /var/www/discourse/script/import_scripts/base.rb:265:in `block in create_users'
         3: from script/import_scripts/vbulletin5.rb:110:in `block (2 levels) in import_users'
         2: from script/import_scripts/vbulletin5.rb:718:in `parse_timestamp'
         1: from /var/www/discourse/vendor/bundle/ruby/2.7.0/gems/tzinfo-2.0.5/lib/tzinfo/timezone.rb:575:in `utc_to_local'
/var/www/discourse/vendor/bundle/ruby/2.7.0/gems/tzinfo-2.0.5/lib/tzinfo/timestamp.rb:138:in `for': Integer values are not supported (ArgumentError)

@Haddoq의 제안은 Time.zone.at(@tz.utc_to_local(timestamp)) 라인을 Time.zone.at(timestamp)으로 변경하라는 것이었습니다.

또한 lastvisit를 사용자 쿼리에 추가해야 한다고 제안했는데, 그렇지 않으면 다른 오류가 발생하기 때문이라고 하였습니다. 그래서 이것도 수행했습니다.

그러나 이제 bundle exec ruby script/import_scripts/vbulletin5.rb로 마이그레이션을 실행하면 다음과 같은 결과가 나옵니다:

Loading existing groups...
Loading existing users...
Loading existing categories...
Loading existing posts...
Loading existing topics...

importing groups...
       41 / 41 (100.0%)  [120217 items/min]
importing users
Traceback (most recent call last):
        13: from script/import_scripts/vbulletin5.rb:727:in `<main>'
        12: from /var/www/discourse/script/import_scripts/base.rb:47:in `perform'
        11: from script/import_scripts/vbulletin5.rb:46:in `execute'
        10: from script/import_scripts/vbulletin5.rb:79:in `import_users'
         9: from /var/www/discourse/script/import_scripts/base.rb:916:in `batches'
         8: from /var/www/discourse/script/import_scripts/base.rb:916:in `loop'
         7: from /var/www/discourse/script/import_scripts/base.rb:917:in `block in batches'
         6: from script/import_scripts/vbulletin5.rb:80:in `block in import_users'
         5: from script/import_scripts/vbulletin5.rb:723:in `mysql_query'
         4: from /var/www/discourse/vendor/bundle/ruby/2.7.0/gems/rack-mini-profiler-3.0.0/lib/patches/db/mysql2/alias_method.rb:22:in `query'
         3: from /var/www/discourse/vendor/bundle/ruby/2.7.0/gems/mysql2-0.5.4/lib/mysql2/client.rb:147:in `query'
         2: from /var/www/discourse/vendor/bundle/ruby/2.7.0/gems/mysql2-0.5.4/lib/mysql2/client.rb:147:in `handle_interrupt'
         1: from /var/www/discourse/vendor/bundle/ruby/2.7.0/gems/mysql2-0.5.4/lib/mysql2/client.rb:148:in `block in query'
/var/www/discourse/vendor/bundle/ruby/2.7.0/gems/mysql2-0.5.4/lib/mysql2/client.rb:148:in `_query': You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'CASE WHEN u.scheme='blowfish:10' THEN token (Mysql2::Error)
                 WHEN u.scheme='lega' at line 2

이 시점에서 조금 혼란스럽습니다. 누군가 도와줄 수 있을까요?

"Migration Vietnam"에 참여했던 @Canapin님을 호출합니다. 더 많은 정보를 알고 계실 수 있으니요 :heart:

8시간 이상 연속으로 작업하는 것은 좋지 않다는 것을 깨달았습니다.

SELECT u.userid, u.username, u.homepage, u.usertitle, u.usergroupid, u.joindate, u.email, 뒤에 u.lastvisit을 추가할 때, 뒤에 ,를 추가하는 것을 잊었습니다.

Canapin에게 핑을 보낸 점 죄송합니다 :frowning:

자, 후속 문제입니다. 제가 따르던 가이드에는 등록 과정이 느려지면 다시 시작할 수 있다고 되어 있었습니다.

하지만 다시 시작하면 postgres 데이터베이스에 이미 사용자가 존재한다는 오류가 발생합니다.

아무 생각 없이 데이터베이스에서 id > 1인 모든 사용자를 삭제하고 다시 시작했더니(discobot, system, admin만 남긴 채로) 가져오기는 계속 진행되지만, 이전에 생성된 모든 사용자의 이메일이 어딘가에 "이미 사용됨"으로 표시되어 있습니다.

이 문제를 정리하려면 어떻게 해야 하며, 이미 일치하는 사용자 이름이 있는 경우 삽입을 건너뛰도록 하는 것이 더 좋지 않을까요?

수정: 자, users, email_tokens, user_emails를 비워야 한다는 것을 알아냈습니다.

불행히도 이전 가져오기 작업에서 무엇을 조정했는지 기록을 남겨두지 않아, 제가 아는 정보가 거의 없습니다. 이제 저는 이런 내용을 작성할 수 있는 개인 Discourse 인스턴스를 가지고 있습니다… 그 전에 그렇게 했어야 했는데 말이죠!
실제로 가져오기 작업을 수행할 때만 관련 사항을 더 잘 다룰 수 있습니다.

사용자 삭제에 관해서는, rails 콘솔을 통해 UserDestroyer를 사용하려고 했을 수도 있습니다:

좋은 정보네요. “실제” 마이그레이션할 때 기억해 두겠습니다. 지금은 런북을 작성하면서 전체 프로세스를 테스트 실행하고 있을 뿐입니다. :slight_smile:

왜 그렇게 하셨나요? 스크립트에서 해당 항목들이 다르게 가져오도록 변경하지 않았다면, 그냥 재시작하면 이미 가져오지 않은 항목들이 가져와질 것입니다.

처음부터 다시 시작해야 한다면, 백업을 복원하거나 데이터베이스를 삭제하고 새로 만드는 것이 훨씬 쉽습니다.

가져오기 ID는 UserCustomFields 테이블에서 찾아야 합니다. 그 오류가 어떻게 발생하는지 정확히 알 수 없습니다.

변경 사항은 제가 나열한 것입니다. 저도 당신만큼 놀랐지만, 스크립트가 진행되지 않고 단순히 오류가 발생하여 중단되었습니다.

수입 속도를 높이는 방법이 있을까요?

이 커뮤니티에는 90,000명 이상의 사용자가 있고, 이유를 모르겠지만 시간이 지날수록 수입 속도가 떨어집니다.

하루 종일 실행했는데, 사용자만 봐도 25123 / 95635 ( 26.3%) [분당 42개] 수준입니다.

게시글 수는 이보다 몇 배나 많습니다. 마이그레이션이 완료되는 데 얼마나 걸릴 것으로 예상해야 할까요? 며칠? 몇 주?

RAM 용량이 얼마나 되나요? 그게 문제일 가능성이 큽니다. 중지했다가 다시 시작해 보세요.

저는 몇 주가 걸린 적도 있습니다. 그래서 일괄 가져오기 도구가 있는 겁니다.

RAM이 겨우 2GB밖에 없어요. 테스트용 머신이라서요. VM 대신 로컬에서 실행할 수도 있겠죠 (RAM 16GB가 훨씬 나을까요?), 그리고 나중에 모든 것을 패키징해서 업로드하면 될 것 같아요.

대량 가져오기(bulk importers)에 대해 좀 더 자세히 설명해 주실 수 있을까요? 처음 들어보는 내용인데, 구글에서 "vbulletin to discourse migrate"를 검색했을 때 분명히 이런 정보가 나왔어야 했는데 말이죠 :angry:

Frustrated Jason Segel GIF by NETFLIX

다른 가져오기 도구가 이미 일부 사용자를 처리한 경우에도 이걸 실행할 수 있나요, 아니면 정리해야 하나요?

일단 시도해 봤고, 최악의 경우 안 되는 거겠죠. 그런데 계속 이런 에러가 뜨네요.

ERROR: no implicit conversion of nil into String
/var/www/discourse/script/bulk_import/base.rb:861:in `encode'
/var/www/discourse/script/bulk_import/base.rb:861:in `normalize_charset'
/var/www/discourse/script/bulk_import/base.rb:856:in `normalize_text'
script/bulk_import/vbulletin5.rb:123:in `block in import_users'
/var/www/discourse/script/bulk_import/base.rb:725: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:723: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:722:in `create_records'
/var/www/discourse/script/bulk_import/base.rb:340:in `create_users'
script/bulk_import/vbulletin5.rb:120:in `import_users'
script/bulk_import/vbulletin5.rb:63:in `execute'
/var/www/discourse/script/bulk_import/base.rb:100:in `run'
script/bulk_import/vbulletin5.rb:781:in `<main>'

다른 가져오기(importer)가 생성하거나 생성을 시작한 유일한 항목인 사용자(user)와 그룹(group)을 정리해야 할 것 같습니다.

다시 데이터베이스를 건드리기 전에, 이를 깔끔하게 처리해 줄 수 있는 루비(ruby) 명령어가 있을까요?

이번엔 그냥 Discourse 설치를 삭제하고 다시 설치할 겁니다. Docker에 감사드립니다.

아니요, 클린 설치 후에도 스크립트는 여전히 이 일반적인 오류로 실패합니다.

ERROR: no implicit conversion of nil into String
/var/www/discourse/script/bulk_import/base.rb:861:in `encode'
/var/www/discourse/script/bulk_import/base.rb:861:in `normalize_charset'
/var/www/discourse/script/bulk_import/base.rb:856:in `normalize_text'
script/bulk_import/vbulletin5.rb:123:in `block in import_users'
/var/www/discourse/script/bulk_import/base.rb:725: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:723: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:722:in `create_records'
/var/www/discourse/script/bulk_import/base.rb:340:in `create_users'
script/bulk_import/vbulletin5.rb:120:in `import_users'
script/bulk_import/vbulletin5.rb:63:in `execute'
/var/www/discourse/script/bulk_import/base.rb:100:in `run'
script/bulk_import/vbulletin5.rb:781:in `<main>'

어떤 값이 nil이며 다른 값으로 기대되는지 이해할 수 있도록 디버깅하는 방법이 있을까요?

음, 오류가 인코딩과 관련이 있는 것 같습니다.
bulk_import/vbulletin5.rb에서는 인코딩을 지정할 수 있지만(우리의 경우 UTF8mb4), base.rb 파일에서는 문자셋 맵에 아무것도 매핑되지 않는 것으로 보입니다.

안녕하세요!

일반적인 팁 몇 가지:

  1. 스크립트를 수정하는 경우, 처음부터 시작하거나 최소한 알려진 정상 상태의 지점부터 시작하는 것이 일반적으로 권장됩니다. @pfaffman 님이 말씀하신 것처럼, 마이그레이션을 실행하기 직전에 만든 백업을 복원하는 것이 가장 쉬운 방법입니다.
  2. 일괄 가져오기 도구는 일반적으로 훨씬 적은 시간이 소요되지만, 이 특정 도구는 메모리에 데이터를 캐시하기 때문에 막대한 양의 RAM을 소모할 수 있습니다. 압축이 풀린 2GB 크기의 SQL 파일에서 vBulletin 일괄 마이그레이션을 수행한 특정 사례에서, 해당 프로세스에는 22GB의 RAM이 필요했습니다 (다시 확인해 보았으며, 오타가 아닙니다).
  3. 스크립트를 수정하는 경우, 각 테이블별로 100개 또는 1000개의 레코드를 가진 입력 데이터의 테스트 버전을 만드는 것을 권장합니다 (참조 무결성에 주의하세요 - 즉, 다른 테이블에서 참조하는 테이블을 truncate하지 마세요). 8시간 이상 걸리는 프로세스로 변경 사항을 테스트하면 정신력이 매우 빠르게 소모될 것입니다.

스택 트레이스에 대한 더 구체적인 팁: 실행한 특정 파일에 대한 언급이 있는 줄을 찾아보세요. 이 경우 인코딩 문제이기는 하지만, 더 중요한 사실은 이것이 사용자 이름에 관한 것이라는 점입니다:

사용자 이름을 정리(sanitize)해야 한다고 하셨으므로, 스크립트가 기대하는 대로 인코딩했는지 두 번 확인해 보시는 것이 좋습니다.

Discourse를 다시 만드는 대신 데이터베이스를 삭제, 생성, 마이그레이션하는 것만 수행할 수 있습니다. 다만, 다음을 실행해야 하므로 다소 까다로울 수 있습니다.

  sv stop unicorn

그리고 다음을 실행합니다.

  rake db:drop db:create db:migrate

rake 작업 앞에 설정해야 하는 ENV 변수를 설정하라고 경고 메시지와 함께 알려줍니다.

또는 백업을 복원하는 것도 더 편리할 수 있습니다.

참고로, 저는 대량 마이그레이션 스크립트를 사용해 본 적이 없다고 생각합니다.

사용자 이름이 모두 Discourse 가이드라인을 따르도록 정제(sanitize)되어, 기본적으로 모두 문자, 숫자 또는 _로 변경된 상태라 더 이상합니다.

어쨌든 기본값이었던 utf8으로 문자 인코딩을 utf8mb4에서 변경하니 처리는 진행되었지만, 이번에는 잘못된 이메일 관련 오류가 발생하고 있습니다.

ERROR: can't modify frozen String: "24ef401b30f5161e5a0bb27ec49ed921@email.invalid"
/var/www/discourse/script/bulk_import/base.rb:457:in `downcase!'
/var/www/discourse/script/bulk_import/base.rb:457:in `process_user_email'
/var/www/discourse/script/bulk_import/base.rb:726: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:723: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:722:in `create_records'
/var/www/discourse/script/bulk_import/base.rb:351:in `create_user_emails'
script/bulk_import/vbulletin5.rb:151:in `import_user_emails'
script/bulk_import/vbulletin5.rb:66:in `execute'
/var/www/discourse/script/bulk_import/base.rb:100:in `run'
script/bulk_import/vbulletin5.rb:781:in `<main>'

지금 이 문제가 무엇인지 알아볼 예정인데, “비벌크”(단건) 가져오기에서는 일부 잘못된 형식의 이메일을 감지하고 자동으로 대체했기 때문입니다.

그 작업을 완료한 후, 비어 있는 깨끗한 Discourse 설치에 대한 백업을 생성하세요.
언제든 그 백업을 빠르게 복원하여 처음부터 다시 시작할 수 있습니다.

그리고 만약 게시물 가져오기 같은 작업에서 문제가 발생한다면, 사용자 가져오기가 정상적으로 완료된 시점에서 스크립트를 종료하고 새로운 백업을 만들 수도 있습니다. 그런 다음 스크립트를 다시 시작하면 이미 사용자를 가져온 지점부터 계속 진행할 수 있습니다.