vB3からDiscourseへの移行完了(旧vB3の「いいね」含む)

ほぼ完了しました(まだテスト環境にありますが、「難しい部分」は基本的に終了し、移行も正常に動作しています)。2000 年発の vb2 フォーラム(信じられないかもしれませんが)が元となった、15 年前の vb3 フォーラムの移行を完了させました。

このフォーラムは約 20 年間存続しており(想像を絶するカスタム PHP コード、テーブル、プラグインなどがあります)、私の見解では Discourse だけが「移行プロジェクト」に値するフォーラムソフトウェアです。私は約 1 週間前に着手し、実現可能性の検討から完了(ほぼ)に至りました。

第一に、このような傑作ともいえる現代的なフォーラムを提供してくださったDiscourse チーム全体に感謝申し上げます。Discourse は、私の見解では(そして多くの他の方々もそうお感じだと確信しています)、本当に素晴らしいものです。

第二に、元々の vbulletin.rb 移行スクリプトをコーディングしたチームに感謝します。このスクリプトは約 95% 問題ありませんでした。私はそのスクリプトを修正して vb3 で正しく動作するようにしました。例えば、vb3 は vb4 のような添付ファイルに関連する filedata テーブルを持っていません。そのため、vb3 で動作するように vbulletin.rb を修正しました。また、子 vb3 フォーラムをカテゴリとしてインポートする際に問題がありましたが、すべてのフォーラムをトップレベルのカテゴリとしてインポートするようにコードを修正し、その後 postgres psql を実行して新しい親子カテゴリ関係を構築しました(または手動で設定しました)。他にもいくつかの「落とし穴」がありましたが(これはまた別の日の話題に)、それらの小悪魔たちが楽しさと挑戦をもたらしましたが、乗り越えられないものではありませんでした。

第三に、私の vb3 のお礼(レガシープラグインからのもので、vB の標準機能には含まれていません)を Discourse のいいね に変換する際、Sam のコード lithium.rb にある import_likes ルーチンを基盤として使用したことにSam に感謝します。私は import_likes ルーチンをわずかに変更し、数時間のデバッグの末、動作するようにしました。

以下は、今日移行された 2018 年の投稿のスクリーンショットです。

ユーザーにとって、vb の「お礼」が Discourse の「いいね」に移行することは重要だと考えました。そのため、他の「失われた vb3 フォーラムの魂」の「いいね」を移行するために使用したコードを以下に示します。

まず、vb3 mysql テーブルuser_actions という名前で作成し、レガシー vb の post_thanks テーブルから PHP スクリプトを使用してデータを埋めました。以下がそのコードです(洗練されてはいませんが、動作します)。

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 like action == 2
$target_user_id = "";
while ($action = $vbulletin->db->fetch_array($allthanks)) {

    #code to deal with how discourse identifies the first post in a topic in the post_custom_fields table
    $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 テーブルは移行ダンプに含まれています。

以下は、Discourse 側で移行を完了させるために使用した修正済みの import_likes ルーチンです。

def import_likes
    puts "\nimporting likes..."

    # created mysql user_actions table in vb3 using PHP script and included that table with migration dump
    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"

    #manually created the temp like_data table so I could check the table after session ends
    #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

    # reverse action
    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 の「お礼」プラグインを使用してきた多くの人々が、vB3 から Discourse へ移行する際に役立つことを願っています。

また、私は Discourse データベース や多数の Discourse Ruby スクリプト の内部で多くの時間を費やしました。そのため、この移行は(心臓の弱い方には確かに)難易度が高いものでしたが、決して不可能ではありませんでした。

実際、私はそれを楽しむことができました。これは私にとって Ruby での初めての経験でした。Ruby を学び始めるのは楽しかったですし、長年の MySQL 担当者 として、Postgres 部分 は主にルーチンワークでした。

まもなく公開予定です… Discourse チームに感謝します!!!

お疲れ様でした!共有いただき、ありがとうございます!!

@codinghorror ありがとうございます。

小さな問題(グリーmlin)を発見しましたので、これから対応します。import_likes ルーチンでは、トピックの最初の投稿(トピックの開始者による投稿)の「いいね」が移行されていません(返信のみが移行されています)。

袖をまくり上げてこの問題を解決し、更新された import_likes ルーチンを投稿します。

勝利を宣言する前に、もう一つ小さな問題(ゴブリン)を倒さなければならないことが判明しました。少し早すぎましたね。

更新:

vB3 の PHP スクリプトに以下のコードを追加し、vB3 と Discourse がトピック/スレッド内の最初の投稿を扱う方法の違いに対処しました。

    <?php
    #vB3 code to deal with how discourse identifies the first post in a topic in the post_custom_fields table
    $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 タスクを使用しましたか?そして何が問題でしょうか?すべての数値が大きくずれているのか、それとも一部のユーザーに限られているのか、それとも…?

こんにちは。

まず、(当時)私にとって理にかなっていた唯一の rake タスクを試しました(問題は user_actionspost_actions に関連しているため)。しかし、結果は出ませんでした(実際、振り返ってみれば、その rake が何を exactamente 行っているかを確認すべきでした、LOL):

rake user_actions:rebuild    

正直なところ、import_likes マイグレーションスクリプトで何が間違っているのか、まだ特定できていません。何がどこで間違っているかが分かれば、修正できたはずです(問題を見つけるのに数時間から数日費やしても、問題が分かれば修正は 1 分で済むことがよくあります…これが コーディングの本質 です、LOL)。スナップショットから何度か復元し、import_likes ルーチンをいくつか異なる方法で再実行しました(追加の puts 文などを使用し、テーブルを確認しました)。しかし、結果は決定的ではありませんでした。これは、rubydiscourse 全体における私の 未熟さ がさらに複雑にしています。バックエンドにまだ流暢ではありません。(ただし、begin... rescue ... end の使い方は上達してきました…ハハ、これは rubynube としての新しいデバッグツールの一つになっています)。

私が確認したところ、一時的な postgres like_data テーブル(discourse 側)は正常で、正しい形式で全てのデータを含んでおり、エントリは mysql マイグレーションテーブルと vB サイトの両方と一致しています。

プロセスは、user_actions およびおそらく post_actions の作成段階で破綻しているようです。私のテストでは、これらの重要なテーブルへデータが正しく転送されていないことが示されています。

ご参考までに、vB3 のコアマイグレーションは正常です。この問題は、Discourse の「いいね」に変換しているレガシーな vB3 の「ありがとう」プラグインに関連しています。次のステップとして、時間ができたら今日中に、正常な like_data テーブルから user_actions および post_actions への転送プロセスがどこで破綻しているのかを詳しく調べたいと考えています。

昨日のテストに基づいた私の予備的な結論は、ruby raking は役立たないということです。なぜなら、破綻は user_actions または post_actions テーブルの更新プロセスにあるからです。したがって、どこでなぜ破綻しているのかを正確に特定する必要があります。単純な DB タイプの不一致や、コード内の他の些細な問題(グリムリン)かもしれません。

私たちの Discourse サイトはまだ公開されていませんので、緊急事態 ではありません。ここや他の場所の多くの人々同様、移民手続きや銀行業務などのタスクを処理することは、言うまでもなく かなり面倒 になっており、今日も Discourse 以外の用事で市街地へ出かける必要があります。できれば Discourse を探索し、コードのグリムリン を探したいところです。

マイグレーションスクリプトを再度実行しました(スクリプトの変更は行わず)、カスタムの vb3 の thanks から Discourse の likes への移行(import_likes)スクリプトを含めましたが、現在は問題ないようです。

最新の vB データベースダンプから大きな「D」を同期して、どうなるか試してみます!

Discourse に非常に満足しています。この「芸術作品」のようなモダンなフォーラムを提供していただき、改めてありがとうございます。まだテスト中ですが、まもなく公開予定です。

5.6k 件のいいねが寄せられました…それはたくさんの愛ですね :heart:

すべての vb thanksdiscourse likes に移行したところ、2000 年(20 年前)のトピック投稿の移行例を以下に示します。トピック内で移行された「いいね」が表示されています:

…そして、かっこいいプラグインの作成を学ぶのが楽しみになってきました… :slight_smile:

Discourse とそのチームには非常に満足しています… 特に Ruby の begin .... rescue ...puts "look at me".... end は本当に助かりました!

本当です… old dogs も骨を投げてやれば新しい芸を覚えることができます :slight_smile:

TEAM Discourse の皆様、ありがとうございます! 20 年前、vB2 を使って Unix や Linux ユーザー向けのフォーラムを立ち上げ、約 15 年前に vB3 へ「アップグレード」しました。今日は世界中の Unix や Linux ユーザーにとって大きな変化の日です。私たちにとっても、Discourse への移行が正式に開始された記念すべき日です。

このソフトウェアを開発し、オープンソースプロジェクトとして誰もが利用できるようにしてくださり、心から感謝申し上げます。皆様のご厚意に深く感謝しています。私の見解では(そして多くの皆様も同感だと思いますが)、2020 年現在、Discourse は地球上で最も優れたフォーラムソフトウェアです。

一方、vB3 のレガシーユーザーで Discourse への移行を検討されている方々へ申し上げますと、私の説明ほど移行は「簡単」ではありません。プログラミングに精通し、コマンドラインからデータベースと直接連携することに慣れていない限り、meta.discourse.org にいる才能あふれるプロフェッショナルな方々のサービスを利用されることをお勧めします。

改めて、TEAM Discourse の皆様に感謝申し上げます!

備考:新しいサイトにて、今回の移行で得た教訓をいくつか追加予定です。