Завершена миграция с vB3 на Discourse, включая устаревшую версию vB3. Спасибо за лайки

Только что завершил, по большей части (ещё в тестовой среде, но «самые сложные части» в основном сделаны, и миграция работает исправно), миграцию 15-летнего форума vb3, который изначально (поверьте или нет) был форумом vb2 из 2000 года.

Этот форум существует уже около 20 лет (с невообразимым количеством кастомного PHP-кода, таблиц, плагинов и т. д.), и, на мой взгляд, только Discourse заслуживает названия «достойного» проекта миграции, который я начал примерно неделю назад, пройдя путь от оценки可行性 до завершения (в основном).

Прежде всего, я хочу поблагодарить всю команду Discourse за такой замечательный «произведение искусства» — современный форум. Discourse поистине удивителен, на мой взгляд (и я уверен, что многие другие согласятся).

Во-вторых, я хотел бы поблагодарить команду, написавшую оригинальный скрипт миграции vbulletin.rb. Этот скрипт был примерно на 95% рабочим. Я модифицировал его, чтобы он корректно работал с vb3. Например, в vb3 нет таблицы filedata, как в vb4 (относящейся к вложениям); поэтому я изменил vbulletin.rb, чтобы он работал. Также были проблемы с импортом дочерних форумов vb3 как категорий, но я изменил код так, чтобы все форумы импортировались как категории верхнего уровня, а затем написал некоторые запросы postgres psql для создания новых связей родитель-дочерняя категория (или настроил их вручную). Были и другие «подводные камни» (история для другого дня), но эти гоблины сделали процесс увлекательным и сложным, но не непреодолимым.

В-третьих, я хотел бы поблагодарить Сэма и его код lithium.rb, где я использовал его процедуру import_likes как основу для моей миграции vb3 thank you (из устаревшего плагина, не входящего в стандартную поставку vB) в discourse likes. Я внес незначительные изменения в процедуру import_likes и после нескольких часов отладки добился её работы.

Как видно из этого поста 2018 года, мигрированного сегодня:

Я считал важным, чтобы пользователи сохранили свои vb thanks в виде discourse likes, поэтому вот код, который я использовал для миграции likes для других потерянных душ форумов vb3:

Сначала я создал таблицу vb3 mysql под названием user_actions и написал PHP-скрипт для её заполнения из устаревшей таблицы vb post_thanks, вот так (не отполировано, но работает):

ubuntu:/var/www/includes/cron# cat thanks_to_discourse.php
 <?php

/**************************************************
+-----------+------------+-----------+-----------+
| thanker   | unixtime   | id        | thanked   |
+-----------+------------+-----------+-----------+
|         1 | 1584149592 | 303045211 | 302093876 |
| 302153369 | 1584136706 | 303045214 | 302116191 |
| 302108573 | 1584128526 | 303045211 | 302093876 |
| 302153369 | 1584126659 | 303042175 | 302116191 |
| 302153369 | 1584126400 | 303045174 | 302116191 |
| 302153369 | 1584117711 | 303045184 |         1 |
|     37898 | 1584108187 | 303045175 |         1 |
| 302181242 | 1584106664 | 303045201 | 302122047 |
| 302181242 | 1584104642 | 303045074 | 302052697 |
| 302025710 | 1584103722 | 303045184 |         1 |
+-----------+------------+-----------+-----------+
 **************************************************/

$query = 'SELECT p.threadid AS threadid,t.userid AS thanker,t.date AS unixtime,t.postid AS postid,p.userid AS thanked from post_thanks as t LEFT JOIN post p ON p.postid = t.postid';

$allthanks = $vbulletin->db->query_read($query);

/****************************************
mysql> describe user_actions;
+-----------------+------------------+------+-----+---------+----------------+
| Field           | Type             | Null | Key | Default | Extra          |
+-----------------+------------------+------+-----+---------+----------------+
| id              | int(11)          | NO   | PRI | NULL    | auto_increment |
| action_type     | int(11) unsigned | NO   |     | NULL    |                |
| user_id         | int(11) unsigned | NO   |     | NULL    |                |
| target_topic_id | int(11) unsigned | YES  |     | NULL    |                |
| target_post_id  | varchar(16)      | YES  |     | NULL    |                |
| target_user_id  | int(11) unsigned | YES  |     | NULL    |                |
| acting_user_id  | int(11) unsigned | YES  |     | NULL    |                |
| created_at      | timestamp        | YES  |     | NULL    |                |
| updated_at      | timestamp        | YES  |     | NULL    |                |
+-----------------+------------------+------+-----+---------+----------------+
9 rows in set (0.00 sec)
INSERT INTO table_name (column1, column2, column3, ...)
VALUES (value1, value2, value3, ...);
 *************************************/

$action_code = 2; // действие «лайк» в Discourse == 2
$target_user_id = "";
while ($action = $vbulletin->db->fetch_array($allthanks)) {

    # код для обработки того, как Discourse идентифицирует первый пост в теме в таблице post_custom_fields
    $query = 'SELECT firstpostid FROM thread WHERE threadid =' . $action['threadid'] . ' LIMIT 1';
    $threadinfo = $vbulletin->db->query_first($query);
    if ($threadinfo['firstpostid'] == $action['postid']) {
        $action['postid'] = 'thread-' . $action['threadid'];
    }

    $update = 'INSERT INTO user_actions (action_type, user_id, target_topic_id, target_post_id, target_user_id,acting_user_id, created_at, updated_at)' .
        ' VALUES (' . $action_code . ',' .
        '"' . $action['thanked'] . '",' .
        '"' . $action['threadid'] . '",' .
        '"' . $action['postid'] . '",' .
        '"' . $target_user_id . '",' .
        '"' . $action['thanker'] . '",' .
        'FROM_UNIXTIME(' . $action['unixtime'] . '),' .
        'FROM_UNIXTIME(' . $action['unixtime'] . '))';
    $doit = $vbulletin->db->query_write($update);
}

Таким образом, эта таблица mysql user_actions включена в дамп миграции.

А вот модифицированная процедура import_likes, которую я использовал для завершения миграции на стороне Discourse:

def import_likes
    puts "\nimporting likes..."

    # создана таблица mysql user_actions в vb3 с помощью PHP-скрипта и включена в дамп миграции
    sql = "select acting_user_id as user_id, target_post_id as post_id, created_at from user_actions"
    results = mysql_query(sql)
    puts "length() method form : #{results.count}\n\n"

    puts "skip loading unique id map"
    existing_map = {}
    PostCustomField.where(name: 'import_id').pluck(:post_id, :value).each do |post_id, import_id|
      existing_map[import_id] = post_id
      #puts "postcustomfield existing_map post_id: #{post_id} import_id #{import_id}\n"
    end

    puts "loading data into temp table"

    # вручную создана временная таблица like_data, чтобы можно было проверить таблицу после окончания сессии
    #DB.exec("create temp table like_data(user_id integer, post_id integer, created_at timestamp without time zone)")
    
    puts "like_data temp table created"
    PostAction.transaction do
      results.each do |result|

        result["user_id"] = user_id_from_imported_user_id(result["user_id"].to_s)
        result["post_id"] = existing_map[result["post_id"].to_s]

        next unless result["user_id"] && result["post_id"]

        puts "insert like table user_id: #{result["user_id"]} post_id #{result["post_id"]}\n"

        DB.exec("INSERT INTO like_data VALUES (:user_id,:post_id,:created_at)",
          user_id: result["user_id"],
          post_id: result["post_id"],
          created_at: result["created_at"]
        )

      end
    end


    puts "creating missing post actions"
    DB.exec <<~SQL

    INSERT INTO post_actions (post_id, user_id, post_action_type_id, created_at, updated_at)
             SELECT l.post_id, l.user_id, 2, l.created_at, l.created_at FROM like_data l
             LEFT JOIN post_actions a ON a.post_id = l.post_id AND l.user_id = a.user_id AND a.post_action_type_id = 2
             WHERE a.id IS NULL
    SQL

    puts "creating missing user actions"
    DB.exec <<~SQL
    INSERT INTO user_actions (user_id, action_type, target_topic_id, target_post_id, acting_user_id, created_at, updated_at)
             SELECT pa.user_id, 1, p.topic_id, p.id, pa.user_id, pa.created_at, pa.created_at
             FROM post_actions pa
             JOIN posts p ON p.id = pa.post_id
             LEFT JOIN user_actions ua ON action_type = 1 AND ua.target_post_id = pa.post_id AND ua.user_id = pa.user_id

             WHERE ua.id IS NULL AND pa.post_action_type_id = 2
    SQL

    # обратное действие
    DB.exec <<~SQL
    INSERT INTO user_actions (user_id, action_type, target_topic_id, target_post_id, acting_user_id, created_at, updated_at)
             SELECT p.user_id, 2, p.topic_id, p.id, pa.user_id, pa.created_at, pa.created_at
             FROM post_actions pa
             JOIN posts p ON p.id = pa.post_id
             LEFT JOIN user_actions ua ON action_type = 2 AND ua.target_post_id = pa.post_id AND
                ua.acting_user_id = pa.user_id AND ua.user_id = p.user_id

             WHERE ua.id IS NULL AND pa.post_action_type_id = 2
    SQL
    puts "updating like counts on posts"

    DB.exec <<~SQL
        UPDATE posts SET like_count = coalesce(cnt,0)
                  FROM (
        SELECT post_id, count(*) cnt
        FROM post_actions
        WHERE post_action_type_id = 2 AND deleted_at IS NULL
        GROUP BY post_id
    ) x
    WHERE posts.like_count <> x.cnt AND posts.id = x.post_id

    SQL

    puts "updating like counts on topics"

    DB.exec <<-SQL
      UPDATE topics SET like_count = coalesce(cnt,0)
      FROM (
        SELECT topic_id, sum(like_count) cnt
        FROM posts
        WHERE deleted_at IS NULL
        GROUP BY topic_id
      ) x
      WHERE topics.like_count <> x.cnt AND topics.id = x.topic_id

    SQL
    end
end

Надеюсь, этот пост поможет другим, кто может мигрировать с vB3 на Discourse, поскольку многие люди годами использовали устаревший плагин «спасибо» vb3.

Также я потратил много времени в базе данных discourse и внутри множества скриптов ruby для discourse, поэтому эта миграция была непростой (определённо не для слабого сердца), но вполне выполнимой.

На самом деле, мне это понравилось. Это был мой первый опыт работы с ruby, поэтому было весело начать изучать ruby, а как давний специалист по mysql, часть с postgres была в основном рутинной.

Мы скоро выйдем в онлайн… Спасибо команде Discourse!!!

Молодец и спасибо, что поделился!!

Спасибо, @codinghorror

Только что обнаружил маленького гремлина, с которым разберусь. Рутинная процедура import_likes не переносит лайки с первого поста (темы-стартера), только с ответов.

Мне придётся засучить рукава, разобраться в этом и опубликовать обновлённую версию рутинной процедуры import_likes.

Похоже, я был немного преждевременен, опубликовав это, так как перед тем как объявить о победе, нужно победить ещё одного маленького гоблина.

Обновление:

Добавил этот код в PHP-скрипт на vB3, чтобы компенсировать разницу в том, как vB3 и Discourse обрабатывают первый пост в теме/трэде:

    <?php
    # Код vB3 для обработки того, как Discourse идентифицирует первый пост в теме в таблице post_custom_fields
    $query = 'SELECT firstpostid FROM thread WHERE threadid =' . $action['threadid'] . ' LIMIT 1';
    $threadinfo = $vbulletin->db->query_first($query);
    if ($threadinfo['firstpostid'] == $action['postid']) {
        $action['postid'] = 'thread-' . $action['threadid'];
    }

Теперь всё работает как задумано, и «благодарности», данные в vB3, мигрируют в Discourse, включая первый пост в теме; путём корректировки таблицы миграции из vB3 для соответствия таблице Discourse post_custom_fields.

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

Также я обновил код в своём исходном посте, добавив приведённый выше фрагмент PHP.

Я читал, что вы не мигрируете пароли, потому что хеши различаются. Возможно, стоит посмотреть на мой плагин миграции паролей (здесь). Он, вероятно, уже поддерживает хеши VB3, а если нет — я открыт для их добавления.

Спасибо, Майкл,

Мы не планируем мигрировать пароли и предпочитаем, чтобы пользователи сами сменили пароли (на более надёжные и длинные).

В любом случае спасибо за предложение помощи! Очень ценим.

У нас есть одна небольшая проблема, возможно, вы сможете нам помочь.

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

Не знаете ли вы хорошего способа вручную обновить эти счётчики «лайков» в профилях?

Какую задачу rake вы использовали? И в чём проблема? Все числа неверны, или только у некоторых пользователей, или…?

Привет.

Для начала я попробовал единственный rake-запуск, который на тот момент казался мне логичным (так как проблема связана с user_actions и post_actions), но он не дал никаких результатов (на самом деле, оглядываясь назад, мне следовало сначала прочитать, что именно делает этот rake, ха-ха):

rake user_actions:rebuild    

Честно говоря, пока я так и не определил, где именно в скрипте миграции import_likes происходит сбой. Если бы я точно знал, что и где пошло не так, я бы уже исправил это (я часто трачу часы или дни на поиск проблемы, которая после её нахождения исправляется за минуту… вот в чём суть программирования, ха-ха). Я несколько раз восстанавливался из снимков и пробовал запускать процедуру import_likes разными способами (добавлял лишние операторы puts и т.д.), проверял таблицы. Результаты были неубедительными. Это усугубляется моей неопытностью в ruby и discourse в целом, так как я ещё не владею бэкендом в достаточной степени. (Однако я постепенно осваиваю конструкции begin... rescue ... end… ха-ха, которые стали одним из моих новых инструментов отладки как ruby-новичка).

Я заметил, что временная таблица postgres like_data (на стороне discourse) в порядке: она содержит все данные в правильном формате, и записи согласуются как с таблицей миграции mysql, так и с сайтом vB.

Похоже, процесс ломается при создании таблиц user_actions и, возможно, даже post_actions, потому что мои тесты показали, что данные некорректно передаются в эти ключевые таблицы.

Обратите внимание: основная миграция vB3 прошла успешно. Эта проблема связана с устаревшим плагином vB3 «спасибо», который я конвертирую в лайки discourse. Мой следующий шаг, когда у меня сегодня будет время, — сесть и попытаться точно определить, где именно происходит сбой при передаче данных из таблицы like_data (которая в порядке) в таблицы user_actions и post_actions.

Мой предварительный вывод, основанный на вчерашнем тестировании, таков: ruby raking не поможет, так как сбой происходит в процессе обновления таблиц user_actions или post_actions; поэтому мне нужно точно определить, где и почему происходит сбой. Это может быть так же просто, как несоответствие типов в базе данных или какой-то другой мелкий «глюк» в коде.

Наш сайт discourse ещё не запущен, так что это не аварийная ситуация. И, как и многие здесь и в других местах, решение задач вроде оформления документов в миграционной службе или посещения банков стало более чем немного проблематичным, мягко говоря, и мне сегодня снова нужно ехать в город по делам, не связанным с discourse. Я бы предпочел изучать discourse и искать глюки в коде.

Запустил скрипт миграции снова (без каких-либо изменений в скрипте), включая пользовательский скрипт преобразования thanks из vb3 в likes в Discourse (import_likes), и теперь всё, кажется, в порядке.

Попробую синхронизировать большую “D” с последним дампом базы данных vB и посмотрю, что получится!

Очень доволен Discourse… Спасибо ещё раз за этот современный форум, настоящий «произведение искусства». Всё ещё тестирую, но запустим в работу очень скоро.

5,6 тыс. лайков… Это очень много любви :heart:

После того как все vb thanks были перенесены в discourse likes, вот пример перенесённого сообщения темы от 2000 года, двадцать лет назад, с отображёнными перенесёнными лайками в теме:

… и я начинаю с нетерпением ждать изучения написания крутых плагинов… :slight_smile:

Очень доволен Discourse и командой… и особенно конструктом begin .... rescue ...puts "look at me".... end в Ruby… просто спасение!

Это правда… старые собаки могут выучить новые трюки, если подбросить им кость :slight_smile:

Спасибо, команда Discourse! 20 лет назад мы создали форум для пользователей Unix и Linux на vB2, а примерно 15 лет назад «обновились» до vB3. Сегодня знаменует собой крупное изменение для пользователей Unix и Linux по всему миру и для нас, поскольку миграция на Discourse теперь запущена.

Огромное спасибо за создание этого программного обеспечения и предоставление его всем в качестве проекта с открытым исходным кодом. Ваша щедрость высоко ценится. По моему мнению (и я уверен, что многие разделяют эту точку зрения), Discourse на сегодняшний день является лучшим форумным программным обеспечением на планете в 2020 году.

Что касается других пользователей vB3, которые хотят мигрировать на Discourse, то эта миграция не так «легка», как может показаться. Если вы не хорошо разбираетесь в программировании и не комфортно чувствуете себя при прямой интеграции с базами данных через командную строку, я рекомендую воспользоваться услугами некоторых талантливых и активных специалистов на meta.discourse.org.

Ещё раз спасибо, команда Discourse!

Примечание: Я добавлю несколько полезных выводов в этот пост на новом сайте: