Миграция базы данных vBulletin 5: ошибки скрипта импорта

Хорошо, давайте быстро подытожим.

Я занимаюсь добровольной миграцией форума, который сейчас работает на vBulletin 3.
Начинаем с дампа базы данных (20 ГБ, вы не ослышались) в тестовой среде.

Выполнил обновление до vBulletin 5. Заняло 5–6 часов, но процесс завершился успешно. Версия — vBulletin 5.4.
Произвёл чистку имён пользователей, чтобы они соответствовали требованиям Discourse.

Затем установил Discourse в Docker и в общих чертах последовал этому руководству для подготовки. Под «в общих чертах» имеется в виду, что большая часть информации была избыточной или устаревшей, но это помогло сориентироваться в дальнейших действиях.

Сейчас я на этапе, где буквально перестаю видеть от усталости, так как у меня почти нет опыта программирования на Ruby.
Итак, после завершения установки я зашёл в контейнер командой ./launcher enter app, затем:

  • Установил пакеты freetds-dev и libmariadb-dev.
  • Отредактировал файл Gemfile, добавив gem php_serialize.
  • Из командной строки выполнил export IMPORT=1, чтобы настроить среду для импорта.
  • От имени пользователя 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 присутствуют (они были).
  • Добавил аватары со старого форума (вложений импортировать не нужно) и назначил владельца директорий пользователю discourse в его домашней папке /home/discourse.
  • Отредактировал файл script/import_scripts/vbulletin5.rb, изменив параметры подключения к базе данных.
  • От имени пользователя discourse выполнил bundle exec ruby script/import_scripts/vbulletin5.rb.

Это вернуло ошибку о том, что tzinfo не поддерживает целочисленные значения, о которой упоминается здесь на этом же форуме 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

На данный момент я немного растерян. Кто-нибудь может помочь?

Пингую @Canapin, так как он участвовал в «Миграция Вьетнам» и, возможно, знает больше :heart:

Оказалось, что работать над чем-то более 8 часов подряд — это плохо.

Когда я добавил u.lastvisit в конец SELECT u.userid, u.username, u.homepage, u.usertitle, u.usergroupid, u.joindate, u.email,, я забыл поставить запятую после него.

Извини, Canapin, что побеспокоил :frowning:

Хорошо, это продолжение проблемы. В руководстве, которому я следовал, говорилось, что процесс регистрации можно перезапустить, если он замедлится.

Но при перезапуске я получаю ошибки о том, что пользователи уже существуют в базе данных PostgreSQL.

Я, глупый, удалил всех пользователей с id > 1 в базе данных (оставив discobot, system и admin в основном) и перезапустил процесс. Это позволило импорту продолжиться, но email всех ранее созданных пользователей где-то помечен как «уже использованный».

Что я могу сделать, чтобы это исправить, и не должен ли этот процесс вместо этого пропускать вставку, если уже существует совпадающее имя пользователя?

Редактирование: Хорошо, выяснил, что мне нужно очистить users, email_tokens и user_emails.

К сожалению, я не вел учет того, что настраивал при моих предыдущих импортах, поэтому у меня мало информации. Сейчас у меня есть личный экземпляр Discourse, где я пишу подобные вещи… Я должен был сделать это раньше!
Я более компетентен в вопросах импорта, когда непосредственно работаю над ним.

Что касается удаления пользователей, возможно, вы хотели использовать UserDestroyer через консоль Rails:

Отлично, я буду иметь это в виду для «настоящей» миграции. Пока я просто провожу тестовый запуск всего процесса, параллельно составляя инструкцию :slight_smile:

Зачем ты это сделал? Если ты не менял ничего в скрипте, который импортирует их иначе, то тебе просто нужно перезапустить процесс, и он импортирует тех, кто ещё не был импортирован.

Если тебе действительно нужно начать заново, гораздо проще восстановить резервную копию или удалить и создать новую базу данных.

Он должен найти идентификаторы импорта в таблице UserCustomFields. Не совсем понятно, как у тебя возникла эта ошибка.

Изменения те, которые я перечислил. Я так же удивлён, как и вы, но скрипт не мог продолжить работу, выдавал ошибку и останавливался.

Есть ли способ ускорить импорт?

В этом сообществе более 90 тысяч пользователей, и по какой-то причине скорость импорта со временем падает, и я не могу понять почему.

Процесс работает всю ночь, и только для пользователей мы достигли отметки 25123 / 95635 ( 26.3%) [42 элемента/мин].

Постов на несколько порядков больше. Сколько времени следует ожидать для завершения миграции? Дни? Недели?

Сколько оперативной памяти? Скорее всего, проблема в этом. Можно остановить и перезапустить.

У меня они занимали недели. Именно поэтому существуют инструменты для массовой импорта.

У него всего 2 ГБ оперативной памяти. Это тестовая машина. Я мог бы запустить его локально вместо виртуальной машины (16 ГБ оперативной памяти было бы намного лучше?), а затем собрать всё и загрузить, я полагаю.

Можете ли вы подробнее рассказать о инструментах массовой импорта? Впервые слышу об этом, и это точно должно было появиться, когда я искал в Google «миграция vBulletin в Discourse» :angry:

Разочарованный Джейсон Сигел, гифка от 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>'

Понимаю, что нужно почистить пользователей и группы — это единственное, что создал или начал создавать другой импортер.

Прежде чем лезть в базу данных снова, есть ли какая-то команда 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. Инструменты массовой импорта обычно работают значительно быстрее, но этот конкретный может потреблять ОГРОМНОЕ количество оперативной памяти, поскольку кэширует данные в памяти. В одном случае, когда я делал массовую миграцию из vBulletin с помощью нераспакованного SQL-файла объемом 2 ГБ, процессу требовалось 22 ГБ ОЗУ (проверено дважды, это не опечатка).
  3. Если вы вносите изменения в скрипт, рекомендую создать тестовую версию входных данных с примерно 100 или 1000 записями для каждой таблицы (но будьте осторожны с целостностью ссылок — то есть не удаляйте таблицы, на которые ссылаются другие таблицы). Тестирование изменений с процессом длительностью более 8 часов очень быстро подорвет ваше рассудок.

Более конкретный совет насчет трассировок стека: ищите строки, упоминающие конкретный файл, который вы запустили. В данном случае да, это проблема кодировки, но более важно то, что она касается имен пользователей:

Вы упомянули, что вам пришлось очищать имена пользователей, поэтому я бы дважды проверил, что вы закодировали их так, как ожидает скрипт.

Вы можете просто удалить, создать и выполнить миграцию базы данных, вместо того чтобы переустанавливать Discourse. Это немного сложно, так как вам нужно

  sv stop unicorn

а затем

  rake db:drop db:create db:migrate

Система выдаст предупреждение и сообщит, что нужно установить переменную окружения, которую следует добавить в строку перед запуском задачи rake.

Также можно восстановить резервную копию, что может быть удобнее.

Кстати, я не помню, чтобы когда-либо использовал скрипт для массовой миграции.

Это ещё страннее, так как имена пользователей были полностью очищены в соответствии с правилами Discourse: по сути, все они были изменены так, чтобы содержать только буквы, цифры или символ _, и ничего больше.

В любом случае, изменение набора символов с utf8mb4 на utf8, как было по умолчанию, позволило процессу пройти, но теперь возникают ошибки из-за некорректных адресов электронной почты:

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>'
```\n
Теперь я пытаюсь разобраться, в чём дело, поскольку при «непакетном» импорте некоторые некорректные адреса электронной почты обнаруживались и автоматически заменялись.

После этого создайте резервную копию вашей чистой установки Discourse.
Вы всегда сможете быстро восстановить её и начать заново.

В дополнение к этому, если у вас возникнут проблемы, например, с импортом сообщений, вы можете также завершить выполнение скрипта после успешного завершения импорта пользователей и создать новую резервную копию. Затем вы можете перезапустить скрипт и продолжить с того момента, где пользователи уже были импортированы.