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

Да, я собираюсь это сделать, если мне удастся хотя бы завершить импорт пользователей. Сейчас процесс падает при попытке работать с электронными письмами

Просто удалите первую строку из скрипта 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 с хоста?

Повторный запуск с активными следующими 4 функциями возвращает ошибку для уже существующих ID

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

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

Самое раздражающее — это то, что всё случилось словно из ниоткуда.
Теперь повторный запуск выдаёт эту ошибку.

Сейчас слишком устал, чтобы разбираться, что именно имеется в виду. Тем более что duplicate key value вообще не должно возникать, если я просто перезапустил скрипт массовой импорта, верно??

Сначала я хочу извиниться перед всеми, кто может почувствовать себя атакованным этим постом, потому что, честно говоря, я борюсь с этими проблемами с понедельника, и к этому моменту я устал от отладки и горячих исправлений кода Discourse.

После N-й попытки (перестал считать после седьмой) я думаю, что сдамся, потому что, похоже, миграция — это то, на что Discourse не потратил много времени для поддержки.

Я считаю, что главная проблема в том, что кодировка, используемая в этой огромной базе данных, — utf8mb4, которая, возможно, не поддерживается скриптом.

Использование utf8 (по умолчанию) просто генерирует множество ошибок, о которых сообщается, но неясно, что происходит, так как скрипт продолжает работу. Запись в БД пропускается? Копируется с некоторыми неподдерживаемыми символами (классические квадратики)?

Кроме того, три последних запуска (с использованием пакетных импортеров) с точно таким же набором инструкций дали разные результаты. Этот последний запуск дошел до импорта тем, сразу начал сообщать об ошибках, но продолжил работу (???):

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>'

Наконец, он упал на этом:

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, кажется, отличный опыт «из коробки», должно быть совершенно ясно, что сейчас 2022 год, и сообщества существовали задолго до этого продукта. «Принятие» потребовало бы мощной поддержки миграции, и, похоже, это не текущее состояние для Discourse.

Я признаю, что база данных объемом 20 ГБ — это крайний случай, но у нас здесь нет проблем с размером, скорее с кодировкой или кто знает чем еще, так как нет даже постоянной ошибки, и в большинстве случаев: нет никакой документации, кроме как искать темы и сообщения, оставленные теми, кто прошел через те же испытания в прошлом, надеясь, что был найден обходной путь и что исходный код с тех пор сильно не изменился.

На данном этапе я настоятельно рекомендую всем, кто переходит с vBulletin, воздержаться от любой миграции до тех пор, пока не будет завершена, по-видимому, уже начавшаяся полная переработка скриптов миграции.

Хотя я понимаю ваши чувства (миграция — сложная тема), как специалист по миграциям в Discourse, хочу прояснить ситуацию.

У нас есть зрелая система миграций с более чем 60 скриптами для различных платформ, отдельный фреймворк для массовой загрузки с 5 скриптами, а также новая система, которая находится в разработке и значительно улучшает все аспекты: производительность, организацию кода, тестируемость, проверяемость, документацию и многое другое.

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

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

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

Не сдавайтесь! :slight_smile:

Я ценю и понимаю, что масштаб задачи огромен. Просто раздражает постоянно натыкаться на одно исключение за другим, а тот факт, что проект написан на Ruby, не облегчает поиск помощи — кроме как приходить сюда, что, разумеется, не может удовлетворить все запросы, так как, как вы сами заметили, некоторые случаи слишком узкоспециализированы и просто невозможно помочь без доступа к самим данным.

Также в значительной степени я возлагаю вину на абсолютный хаос в структуре vBulletin.

Я только что проверил утром, и вот сводка по размерам таблиц:

Для контекста: таблица “text” содержит фактический контент. Таблица node хранит иерархию, а closure… позвольте процитировать, потому что я даже не могу сформулировать:

Таблица замкнутости (Closure table) строит связи «родитель-потомок» между всеми узлами. Большая часть вашей базы данных состоит из вложенных файлов, которые вообще не должны храниться в базе данных.

Таким образом, для форума с ~8 ГБ контента накладные расходы составляют 28 ГБ. Отличная работа, поздравляю, vBulletin.

Вот что я имею в виду, когда говорю, что это раздражает.

Опять тот же набор действий (следуя руководству, которое я сам написал на основе всех проб и ошибок), выполняемый на новой установке Discourse.

Результат:

Tired Tv Land GIF by TV Land Classic

Где твой import_user_account_id? :expressionless:
Но самое главное? Как тебе удалось не вызвать ошибку в предыдущем запуске, когда он потерпел неудачу на импорте тем? :confounded:

Заккомментирую вызов этой функции (который, похоже, был важен в любом случае) и запущу снова:

Эти ошибки дублирования ключей… разве скрипт не должен знать, что он уже обработал эти идентификаторы, и переходить дальше?

Каждый импорт уникален. Можно подумать, что скрипт, который работает для одного экземпляра вашего-ранее-любимого-форума, будет работать и для других, но это не так. А для огромного форума это действительно сложно. Это просто не то, что легко поддерживать. К тому же, инструменты массового импорта обращаются к базе данных напрямую, а не полагаются на Rails, чтобы автоматически проверять всё по ходу процесса.

Это довольно распространённая проблема, и она не является виной скрипта. Вам нужно будет разобраться, как перенести вашу старую базу данных в кодировку utf8.

Поддержка миграции есть. Просто бесплатной поддержки миграции нет. Я провёл около 100 миграций и написал несколько скриптов импорта для неподдерживаемых или уникальных систем. Скорее всего, я бы взял 3000–5000 долларов за импорт вашей базы данных. Это не предложение, а просто пример того, сколько это работы для человека, который делал это много раз. Я подозреваю, что если бы вы оплатили год бизнес-хостинга, CDCK сделали бы это бесплатно, что может быть дешевле, чем я бы взял. (О, но вы можете не соответствовать требованиям бизнес-хостинга с базой данных такого размера).

Продолжаю здесь исследование.

  • В скрипте есть ссылка на несуществующую функцию: import_user_account_id. Возможно, вам (разработчикам Discourse) стоит это исправить.
  • Логика проверки заголовков тем каким-то образом сбоит на некоторых темах, которые по какой-то причине имеют пустую строку в качестве заголовка. Хотя такого происходить не должно, проверка, которая должна это отловить и вернуть nil должна сработать, но, по-видимому, это ломает последующую логику, написанную в скрипте импорта (см. здесь).

У меня была такая проблема с одним импортом, который я делал в недавнем прошлом. Было бы лучше, если бы он возвращал что-то вроде «тема XXX не имеет заголовка» или извлекал первую строку текста из сообщения, но в данном контексте это сложно сделать. Думаю, я бы склонился к исправлению этого путём изменения вашей базы данных и использовал бы что-то другое для генерации заголовков там, где они отсутствуют.

Да, я по сути «очищаю» БД, когда натыкаюсь на такие проблемы, но это сложно, так как мне приходится отлаживать скрипт и каждый раз гадать, что именно вызывает ошибку :sweat_smile:

По-прежнему не понимаю, чего не хватает функции import_user_account_id :expressionless:

Особенно с учётом того, что праздники уже скоро, маловероятно, что кто-то исправит это, если только они сами не используют этот скрипт. (Обычно, когда я так говорю, Ричард появляется и спасает положение.)

LOL, ну, похоже, сегодня я вас разочарую. Я попытался, но подозреваю, что коммит этого импортера был неполным и не включал некоторые изменения в base.rb. Над этим работал @justin, возможно, он знает. Я также подозреваю, что это могло быть специфичным для конкретного клиента, и это можно закомментировать без дальнейших последствий.

Я сам тоже никогда не использовал пакетные импортеры.

Да, скрипты импорта могут быть сложными и зависеть от специфики базы данных, но некоторые скрипты просто не находятся в рабочем состоянии. Это касается и этого, и есть ещё несколько скриптов, например, с # frozen_string_literal: true, которые просто не работают из коробки.

Ха!

Это (по крайней мере)

часть причины, по которой мне так сложно отправлять PR с внесёнными мной изменениями. К тому моменту, как я заканчиваю, там оказывается столько специфичных для случая вещей, что я боюсь, будто бы всё, что я отправлю, каким-то образом окажется сломанным.

Да. Я думаю, что что-то прошло и добавило frozen_string_literal в каждый файл. Большинство файлов было исправлено, потому что у них были тесты, но для скриптов импорта тестов нет.

Привет, просто уточню: я не жду, что кто-то исправит это прямо сейчас (в стиле Кэрен). Я просто указываю на некоторые вещи, которые явно имеют проблемы в самой кодовой базе и, вероятно, это просто «ой, я забыл добавить это изменение в коммит! :sweat_smile:».

Я уже принял, что эта миграция не произойдёт как минимум до января.

Пусть все просто наслаждаются праздниками :slightly_smiling_face:
Я подниму этот вопрос или открою новую тему после праздников, даже если у меня будет определённо меньше времени, чтобы посвятить этой миграции :disappointed_face:

Да, абсолютно верно — для импортов я обычно не отправляю PR, пока не сделаю два импорта из разных клиентов.

Просто держу это в актуальном состоянии.

После обсуждения с другими инженерами нашего сообщества я внес несколько изменений в base.rb. Многие ошибки возникали из-за сбоя gsub!, так как, как выяснилось, у нас были темы с заголовком ''.

Мы добавили функцию, имитирующую 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 часов я остановил скрипт, чтобы понять, что происходит.

Запустив интерфейс, я заметил несколько вещей:

  1. Мой аккаунт не был импортирован, вероятно, потому что созданный пользователь-администратор использовал тот же адрес электронной почты. Очевидно, но, возможно, об этом стоит где-то написать?
  2. Были импортированы только некоторые категории (подфорумы vbulletin).
  3. Были импортированы только темы и их первые ответы (не уверен, что все), причем все они были импортированы без привязки к правильным категориям, даже те, у которых должна была быть создана категория. Всё импортировалось «без категории».
  4. «Счётчик ответов» показывает -1, вероятно, потому что ответы на самом деле не были импортированы.

В целом, БОЛЬШОЕ количество проблем с этой массовой импортировкой исчезло бы, если бы она реализовывала подход с пагинацией. Думаю, ответы пропали, потому что скрипт пытался обработать их все сразу, а с 7 ГБ данных это было невозможно. Честно говоря, меня удивляет, что инструмент массовой импортировки не использует подход с пагинацией. Даже простое чтение по 1000 записей за раз, запись их и сохранение ID последней записанной записи с последующим циклом решило бы любые проблемы с большими базами данных.

Кстати, я слежу за этим с интересом и очень ценю обновления. :pray: Пока я не очень разбираюсь в миграциях, но нахожу эту информацию очень полезной.