Migrate a NodeBB forum with MongoDB to Discourse

As you properly know, NodeBB supports two DB backends, Redis and MongoDB. Discourse importer script supports them both. In this tutorial, we will learn how to migrate NodeBB with MongoDB as DB backend. We will be using NodeBB Importer with mongo adapter. If your NodeBB forum uses Redis as backend then please follow this tutorial which demonstrate the redis adapter.

The plan

  • Preparing the development environment.

  • Export database from production environment.

  • Import the production DB to a Discourse instance.

  • Run the importer script.

What can be migrated

  • Groups
  • Attachments
  • Categories
    • Root Category => Root Category
    • Sub Category & Sub-Sub-Category => Sub-Category
  • Topics & Posts
    • pinned topic => pinned topic
    • locked topic => closed topic
    • topic views
    • upvoted_by
    • styles, mentions, emoji, and attachments.
  • Users (with the following attributes)
    • profile background
    • avatars
    • banned status
    • username
    • name
    • email
    • admin
    • bio
    • group
    • website
    • location
    • joining at

Preparing Local Development Environment

As mentioned in our plan we first need to prepare our development environment. Follow one of these guides to install Discourse itself:

:bulb: Please, consult this guide if you have any problems setting up Discourse server.

Then install MongoDB database server.

Ubuntu-18-04:

$ wget -qO - https://www.mongodb.org/static/pgp/server-4.0.asc | sudo apt-key add -
$ echo "deb [ arch=amd64 ] https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/4.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.0.list
$ sudo apt-get update
$ sudo apt-get install -y mongodb-org
$ sudo service mongod status

For more details, refer to the official guide

Mac OS:

$ brew tap mongodb/brew
$ brew install mongodb-community@4.0
$ brew services start mongodb-community@4.0
$ brew services status mongodb-community@4.0

For more details, refer to the official guide

Windows 10:

Download the installer and install MongoDB as a Windows service:

https://fastdl.mongodb.org/win32/mongodb-win32-x86_64-2008plus-ssl-4.0.12-signed.msi

Run the cmd with Administrative privileges to check if the mongo server is working properly:

"C:\Program Files\MongoDB\Server\4.0\bin\mongo.exe"

For more details, refer to the official guide.

This environment will be our Discourse server.

Exporting Production Database Dump:

Shut down your NodeBB forum (production server).

$ cd /path_to_nodebb
$ ./nodebb stop

Shut down your database:

$ sudo service mongodb stop

Backup your DB:

$ mongodump --out ~/my_backup_path/

The output of the previous command will be something like this:

2019-08-16T15:17:09.845+0300 done dumping admin.system.users (1 document)
2019-08-16T15:17:09.846+0300 done dumping admin.system.version (2 documents)
2019-08-16T15:17:09.849+0300 done dumping nodebb.sessions (10 documents)
2019-08-16T15:17:09.850+0300 done dumping nodebb.socket.io (215 documents)
2019-08-16T15:17:09.854+0300 done dumping nodebb.objects (1488 documents)

Note that the backup is actually a directory, not just a file.

:bulb: You can check the size of your DB by running use myDatabase then db.stats().dataSize; inside mongo CLI. The returned value will be in bytes.

Backup your forum assets:

$ cd /path_to_nodebb_root_directory/
$ tar -czf ./uploads.tar.gz ./public/uploads

After you have your DB and assets forum you should copy them to Discourse server.

Importing The Database

Now that we have our database we can import it in our local MongoDB instance:

$ mongorestore ~/path_of_my_backup_directory/

For more options, refer to official guide.

Next, you need to extract the uploads.tar.gz so the importer can import the assets:

$ tar xvzf uploads.tar.gz

Running The Importer Script

Now that our database and assets in place we are ready to run our importer script. Before that, we need to edit the NodeBB importer script to fit our needs.

This is the path of your NodeBB uploads folder:

ATTACHMENT_DIR = '/absolute_path/uploads'

Next, we need to tell the importer to use the mongo adapter instead of redis adapter:

adapter = NodeBB::Mongo
@client = adapter.new('mongodb://127.0.0.1:27017/nodebb')

# adapter = NodeBB::Redis
# @client = adapter.new(
# host: "localhost",
# port: "6379",
# db: 14
# )

Run the importer with clean Discourse and mongo gem support:

$ cd ~/discourse
$ echo "gem 'mongo'" >> Gemfile
$ bundle install
$ bundle exec rake db:drop db:create db:migrate
$ bundle exec ruby script/import_scripts/nodebb/nodebb.rb

The importer will connect to the MongoDB instance and migrates everything to Discourse.

After the importer finishes, start Discourse:

$ bundle exec rails server

Start Sidekiq to process the migrated data:

$ bundle exec sidekiq

You can monitor the progress at http://localhost:3000/sidekiq/queues.

Perform a Discourse backup and upload it to your Discourse production server by following this tutorial.

:tada:

If you have any questions about the process I am happy to help.

Happy migration :grinning:

10개의 좋아요

Great work really. While I was trying, I am getting below error. Any idea what I might be doing wrong here?

importing groups
   10 / 10 (100.0%)  [474765 items/min]    
importing top level categories...
    8 / 8 (100.0%)  [437896 items/min]    
importing child categories...
   68 / 68 (100.0%)  [774048 items/min]  
importing users
 5534 / 5534 (100.0%)  [355520 items/min]    
adding users to groups...

importing topics...
Traceback (most recent call last):
	12: from script/import_scripts/nodebb/nodebb.rb:532:in `<main>'
	11: from /home/fsuzer/discourse/script/import_scripts/base.rb:47:in `perform'
	10: from script/import_scripts/nodebb/nodebb.rb:52:in `execute'
	 9: from script/import_scripts/nodebb/nodebb.rb:336:in `import_topics'
	 8: from /home/fsuzer/discourse/script/import_scripts/base.rb:877:in `batches'
	 7: from /home/fsuzer/discourse/script/import_scripts/base.rb:877:in `loop'
	 6: from /home/fsuzer/discourse/script/import_scripts/base.rb:878:in `block in batches'
	 5: from script/import_scripts/nodebb/nodebb.rb:337:in `block in import_topics'
	 4: from /home/fsuzer/discourse/script/import_scripts/nodebb/mongo.rb:71:in `topics'
	 3: from /home/fsuzer/discourse/script/import_scripts/nodebb/mongo.rb:71:in `map'
	 2: from /home/fsuzer/discourse/script/import_scripts/nodebb/mongo.rb:71:in `block in topics'
	 1: from /home/fsuzer/discourse/script/import_scripts/nodebb/mongo.rb:79:in `topic'
/home/fsuzer/discourse/script/import_scripts/nodebb/mongo.rb:100:in `post': undefined method `[]' for nil:NilClass (NoMethodError)

토픽의 일부 필드가 비어 있습니다. 또는 토픽 쿼리가 아무것도 반환하지 않았을 수도 있습니다.

1개의 좋아요

누군가 도와줄 수 있을까요?

오류: Gemfile에 나열된 gem 소스에서 ‘mongo’ gem을 찾을 수 없습니다.

$ bundle install 실행 후

이 단계를 건너뛰면 이러한 문제가 발생합니다:

Gemfile에 다음 줄이 있는지 확인하세요:

gem 'mongo'

그런 다음 다음을 실행하세요:

$ bundle install
1개의 좋아요

도와주셔서 감사합니다. 하지만 이 줄은 이미 제 파일에 존재합니다. 또한 이전에 안내받은 단계들도 이미 수행했습니다.

도움이 될지는 모르겠지만, gem list mongo를 실행해도 목록에 표시되지 않습니다.

Gemfile 내용이 좀 이상하게 느껴지네요. 전체 내용을 공유해 주시겠어요?

Gemfile가 잘못되었습니다. 아래 링크와 같은 내용으로 수정해야 합니다:

또한 다음을 추가해야 합니다:

gem 'mongo'

안녕하세요,

가이드 감사합니다! 마이그레이션 스크립트를 실행하면, 스크립트가 사용자 가져오기 지점에 도달했을 때 다음과 같은 오류가 발생합니다:

Traceback (most recent call last):
        21: from script/import_scripts/nodebb/nodebb.rb:532:in `<main>'
        20: from /home/odyslam/forum/discourse/script/import_scripts/base.rb:47:in `perform'
        19: from script/import_scripts/nodebb/nodebb.rb:50:in `execute'
        18: from script/import_scripts/nodebb/nodebb.rb:130:in `import_users'
        17: from /home/odyslam/forum/discourse/script/import_scripts/base.rb:262:in `create_users'
        16: from /home/odyslam/forum/discourse/script/import_scripts/base.rb:262:in `each'
        15: from /home/odyslam/forum/discourse/script/import_scripts/base.rb:274:in `block in create_users'
        14: from /home/odyslam/forum/discourse/script/import_scripts/base.rb:391:in `create_user'
        13: from /home/odyslam/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.0.3.3/lib/active_support/core_ext/object/try.rb:15:in `try'
        12: from /home/odyslam/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.0.3.3/lib/active_support/core_ext/object/try.rb:15:in `public_send'
        11: from script/import_scripts/nodebb/nodebb.rb:164:in `block (2 levels) in import_users'
        10: from script/import_scripts/nodebb/nodebb.rb:211:in `import_profile_picture'
         9: from /home/odyslam/forum/discourse/lib/upload_creator.rb:45:in `create_for'
         8: from /home/odyslam/forum/discourse/lib/distributed_mutex.rb:14:in `synchronize'
         7: from /home/odyslam/forum/discourse/lib/distributed_mutex.rb:29:in `synchronize'
         6: from /home/odyslam/forum/discourse/lib/distributed_mutex.rb:29:in `synchronize'
         5: from /home/odyslam/forum/discourse/lib/distributed_mutex.rb:33:in `block in synchronize'
         4: from /home/odyslam/forum/discourse/lib/upload_creator.rb:66:in `block in create_for'
         3: from /home/odyslam/forum/discourse/lib/upload_creator.rb:381:in `optimize!'
         2: from /home/odyslam/forum/discourse/app/models/optimized_image.rb:178:in `ensure_safe_paths!'
         1: from /home/odyslam/forum/discourse/app/models/optimized_image.rb:178:in `each'
/home/odyslam/forum/discourse/app/models/optimized_image.rb:179:in `block in ensure_safe_paths!': Discourse::InvalidAccess (Discourse::InvalidAccess)

프로필 사진 업로드 부분을 주석 처리해서 우회했는데, 그건 문제가 되지 않습니다.

이제 토픽을 가져올 때 nil 오류가 발생합니다. :frowning:

Traceback (most recent call last):
        12: from script/import_scripts/nodebb/nodebb.rb:532:in `<main>'
        11: from /home/odyslam/forum/discourse/script/import_scripts/base.rb:47:in `perform'
        10: from script/import_scripts/nodebb/nodebb.rb:52:in `execute'
         9: from script/import_scripts/nodebb/nodebb.rb:336:in `import_topics'
         8: from /home/odyslam/forum/discourse/script/import_scripts/base.rb:862:in `batches'
         7: from /home/odyslam/forum/discourse/script/import_scripts/base.rb:862:in `loop'
         6: from /home/odyslam/forum/discourse/script/import_scripts/base.rb:863:in `block in batches'
         5: from script/import_scripts/nodebb/nodebb.rb:337:in `block in import_topics'
         4: from /home/odyslam/forum/discourse/script/import_scripts/nodebb/mongo.rb:71:in `topics'
         3: from /home/odyslam/forum/discourse/script/import_scripts/nodebb/mongo.rb:71:in `map'
         2: from /home/odyslam/forum/discourse/script/import_scripts/nodebb/mongo.rb:71:in `block in topics'
         1: from /home/odyslam/forum/discourse/script/import_scripts/nodebb/mongo.rb:79:in `topic'
/home/odyslam/forum/discourse/script/import_scripts/nodebb/mongo.rb:100:in `post': undefined method `[]' for nil:NilClass (NoMethodError)

혹시 미래에 관심이 있는 분들을 위해, 저는 마이그레이션을 성공적으로 완료했습니다. 제 경험을 바탕으로 몇 가지 코멘트를 남기려 합니다. 이 과정에서 주의해야 할 함정이 몇 가지 있습니다.

https://odyslam.com/blog/Migrating-from-Nodebb-to-Discourse/

2개의 좋아요
21: from script/import_scripts/nodebb/nodebb.rb:532:in `<main>'
        20: from /home/nodebb/discourse/script/import_scripts/base.rb:47:in `perform'
        19: from script/import_scripts/nodebb/nodebb.rb:50:in `execute'
        18: from script/import_scripts/nodebb/nodebb.rb:130:in `import_users'
        17: from /home/nodebb/discourse/script/import_scripts/base.rb:264:in `create_users'
        16: from /home/nodebb/discourse/script/import_scripts/base.rb:264:in `each'
        15: from /home/nodebb/discourse/script/import_scripts/base.rb:276:in `block in create_users'
        14: from /home/nodebb/discourse/script/import_scripts/base.rb:393:in `create_user'
        13: from /home/nodebb/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.4.1/lib/active_support/core_ext/object/try.rb:15:in `try'
        12: from /home/nodebb/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.4.1/lib/active_support/core_ext/object/try.rb:15:in `public_send'
        11: from script/import_scripts/nodebb/nodebb.rb:164:in `block (2 levels) in import_users'
        10: from script/import_scripts/nodebb/nodebb.rb:211:in `import_profile_picture'
         9: from /home/nodebb/discourse/lib/upload_creator.rb:64:in `create_for'
         8: from /home/nodebb/discourse/lib/distributed_mutex.rb:14:in `synchronize'
         7: from /home/nodebb/discourse/lib/distributed_mutex.rb:29:in `synchronize'
         6: from /home/nodebb/discourse/lib/distributed_mutex.rb:29:in `synchronize'
         5: from /home/nodebb/discourse/lib/distributed_mutex.rb:33:in `block in synchronize'
         4: from /home/nodebb/discourse/lib/upload_creator.rb:83:in `block in create_for'
         3: from /home/nodebb/discourse/lib/upload_creator.rb:501:in `optimize!'
         2: from /home/nodebb/discourse/app/models/optimized_image.rb:181:in `ensure_safe_paths!'
         1: from /home/nodebb/discourse/app/models/optimized_image.rb:181:in `each'
/home/nodebb/discourse/app/models/optimized_image.rb:182:in `block in ensure_safe_paths!': Discourse::InvalidAccess (Discourse::InvalidAccess)

@enigmaty 저는 @OdysLam @FreeWorLD 님과 동일한 오류를 겪고 있습니다.

안녕하세요!
NodeBB 4.9.x 최신 버전에서 현재 Discourse 개발 버전으로 마이그레이션을 시도해 본 분 계신가요?

/home/dev/discourse/script/import_scripts/nodebb/mongo.rb:100:in 'NodeBB::Mongo#post': undefined method '[]' for nil (NoMethodError)

      post["timestamp"] = timestamp_to_date(post["timestamp"])
                                                ^^^^^^^^^^^^^
        from /home/dev/discourse/script/import_scripts/nodebb/mongo.rb:95:in 'block in NodeBB::Mongo#posts'
        from /home/dev/discourse/script/import_scripts/nodebb/mongo.rb:95:in 'Array#map'
        from /home/dev/discourse/script/import_scripts/nodebb/mongo.rb:95:in 'NodeBB::Mongo#posts'
        from script/import_scripts/nodebb/nodebb.rb:413:in 'block in ImportScripts::NodeBB#import_posts'
        from /home/dev/discourse/script/import_scripts/base.rb:943:in 'block in ImportScripts::Base#batches'
        from <internal:kernel>:168:in 'Kernel#loop'
        from /home/dev/discourse/script/import_scripts/base.rb:942:in 'ImportScripts::Base#batches'
        from script/import_scripts/nodebb/nodebb.rb:412:in 'ImportScripts::NodeBB#import_posts'
        from script/import_scripts/nodebb/nodebb.rb:52:in 'ImportScripts::NodeBB#execute'
        from /home/dev/discourse/script/import_scripts/base.rb:47:in 'ImportScripts::Base#perform'
        from script/import_scripts/nodebb/nodebb.rb:568:in '<main>'

개발 환경에서 마이그레이션을 진행하던 중 이 에러가 발생했습니다.
혹시 의견 주실 분 계신가요?

2026년에도 커뮤니티에서 마이그레이션 스크립트를 여전히 지원하고 있나요?

timestamp 필드는 존재하며 유효한 데이터를 포함하고 있습니다.

감사합니다.

문제는 post가 nil이라는 것입니다. 즉, 설정이 올바르게 되어 있지 않습니다. 사용자가 가져와졌나요?

시도해 볼 수는 있지만, 기능이 상당히 제한적입니다. 포럼에서 쉽게 지원할 수 있는 범위를 벗어나는 많은 부분의 작동 방식을 이해해야 할 가능성이 높습니다.

3개의 좋아요
importing users
      182 / 182 (100.0%)  [437953 items/min]
adding users to groups...

importing topics...
      132 / 132 (100.0%)  [3567036 items/min]
importing posts...
/home/dev/discourse/script/import_scripts/nodebb/mongo.rb:100:in 'NodeBB::Mongo#post': undefined method '[]' for nil (NoMethodError)

      post["timestamp"] = timestamp_to_date(post["timestamp"])
                                                ^^^^^^^^^^^^^
        from /home/dev/discourse/script/import_scripts/nodebb/mongo.rb:95:in 'block in NodeBB::Mongo#posts'
        from /home/dev/discourse/script/import_scripts/nodebb/mongo.rb:95:in 'Array#map'
        from /home/dev/discourse/script/import_scripts/nodebb/mongo.rb:95:in 'NodeBB::Mongo#posts'
        from script/import_scripts/nodebb/nodebb.rb:413:in 'block in ImportScripts::NodeBB#import_posts'
        from /home/dev/discourse/script/import_scripts/base.rb:943:in 'block in ImportScripts::Base#batches'
        from <internal:kernel>:168:in 'Kernel#loop'
        from /home/dev/discourse/script/import_scripts/base.rb:942:in 'ImportScripts::Base#batches'
        from script/import_scripts/nodebb/nodebb.rb:412:in 'ImportScripts::NodeBB#import_posts'
        from script/import_scripts/nodebb/nodebb.rb:52:in 'ImportScripts::NodeBB#execute'
        from /home/dev/discourse/script/import_scripts/base.rb:47:in 'ImportScripts::Base#perform'
        from script/import_scripts/nodebb/nodebb.rb:568:in '<main>'


위 스크립트 출력에 따르면 사용자는 올바르게 가져온 것으로 보입니다.

죄송합니다, 당신은 실제 사람이신가요, 아니면 그저 똑똑한 AI 어시스턴트일 뿐인가요?

그러면 문제가 게시물 데이터를 읽지 못하는 것과 관련이 있을 것입니다.

제 추측은 여기에서, 어떤 이유로든 게시물을 찾지 못하고 있다는 것입니다.

    def posts(offset = 0, page_size = 2000)
      post_keys = mongo.find(_key: "posts:pid").skip(offset).limit(page_size).pluck(:value)

      post_keys.map { |post_key| post(post_key) }
    end

저는 10년 동안 Discourse를 지원하며 생계를 유지해 온 살아있는 사람이며, 다양한 다른 플랫폼에서 100개 이상의 포럼을 가져온 여러 개의 가져오기(importer) 도구를 작성한 바 있습니다.

AI 어시스턴트의 도움이 필요하시다면 https://ask.discourse.com/ 을 참조하세요.

6개의 좋아요

어떻게든 스크립트 함수를 정상적으로 작동하게 만들었습니다.

아래는 제 변경 사항입니다 (Claude Code의 작은 도움 덕분에 :slight_smile:)

 >>mongo.rb
   
   def posts(offset = 0, page_size = 1000)
      post_keys = mongo.find(_key: "posts:pid").skip(offset).limit(page_size).pluck(:value)
      post_keys
          .map { |pid| post(pid) }
          .compact  # <-- nil 결과(고아 pid) 제거
      post_keys.map { |post_key| post(post_key) }
    end

    def post(id)
    post = mongo.find(_key: "post:#{id}").first
    return nil if post.nil? # <-- null 체크
    post["timestamp"] = timestamp_to_date(post["timestamp"])
    if post["upvoted_by"] = mongo.find(_key: "pid:#{id}:upvote").first
        post["upvoted_by"] = mongo.find(_key: "pid:#{id}:upvote").first[:members]
      else
        post["upvoted_by"] = []
      end

      post["pid"] = post["pid"].to_s
      post["deleted"] = post["deleted"].to_s

      post
    end
	
>>nodebb.rb

 create_posts(posts, total: post_count, offset: offset) do |post|
        # post가 null이면 건너뜀
		# merged_post인 경우 건너뜀
        next if post.nil?
        next if @merged_posts_map[post["pid"]]

        # 삭제된 경우 건너뜀
        next if post["deleted"] == "1"

        raw = post["content"]
        post_id = "p#{post["pid"]}"

        next if raw.blank?
        topic = topic_lookup_from_imported_post_id("t#{post["tid"]}")

        unless topic
          puts "Topic with id #{post["tid"]} not found, skipping"
          next
        end	

이제 제대로 작동하는 것 같습니다.

Discourse의 내부 아키텍처 관점에서 이것이 얼마나 정확한지는 알 수 없지만, 첫눈에 보기에는 작동하는 것 같습니다.

개선 및 최적화에 대한 제안은 언제든지 환영합니다.

1개의 좋아요

팁: 완전히 조용히 실패하지 않도록 하세요. 이 코드를 아래처럼 수정하세요.

if post.nil?
  puts "!!! Could not find post #{id}"
  return nil
end

그렇지 않으면 게시물의 절반이 사라진 이유를 궁금해하다가, 몇 시간의 고생 끝에 결국 이 코드 때문이라는 사실을 알게 될 것입니다.

아래 두 곳도 마찬가지입니다.

        next if post.nil?
        next if post["deleted"] == "1"
5개의 좋아요

mongo.rb 파일에서 posts 메서드를 다시 작성했습니다

def posts(offset = 0, page_size = 1000)
post_keys = mongo.find(_key: “posts:pid”).sort(score: 1).skip(offset).limit(page_size).pluck(:value)
post_keys.map { |pid| post(pid)}.compact
end

이 접근 방식은 주제 내의 게시물이 올바른 시간 순서로 정렬되도록 보장합니다. 오름차순입니다.
sort(score: 1) 호출에 주목하세요.