是的,如果我至少能完成用户导入,我就会这样做。目前它在尝试处理电子邮件时崩溃了。
只需删除 script/bulk_import/vbulletin5.rb 脚本的第一行
# frozen_string_literal: true
好的,只运行前三个函数:
def execute
# enable as per requirement:
#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 目录以进行备份?
再次重试。进程运行了一段时间,然后突然出现这种情况。
令人沮丧的是,这似乎是毫无征兆的。
现在再次启动它会导致此错误。
我现在太累了,无法检查它指的是什么。特别是如果我只是重新启动批量导入脚本,duplicate key value 根本就不应该发生,对吧??
我想首先向任何可能因这篇帖子而感到受攻击的人道歉,因为说实话,我从周一开始就在处理这些问题,现在我厌倦了为 Discourse 代码进行调试/热修复。
在尝试了无数次(数到第七次就放弃计数了)之后,我认为我将放弃,因为看起来 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 目前的状态。
我承认一个 20GB 的数据库是一个边缘情况,但我们在这里没有遇到大小问题,而是字符集或其他未知问题,因为甚至没有一个持续的错误,而且大多数情况下:没有文档,只能通过搜索过去经历过同样痛苦的人留下的帖子和线索来寻找,希望找到一个解决方法,并且源代码自那时以来没有太大变化。
此时,我强烈建议任何从 vbulletin 迁移过来的用户,在迁移脚本(似乎正在进行中?)的全面检修完成之前,暂停任何迁移。
虽然我能体会你的痛苦(迁移是个棘手的问题),但作为 Discourse 的迁移专家,让我来澄清一下。
我们有一个成熟的迁移框架,拥有针对不同平台的 60 多个脚本,一个独立的批量处理框架,包含 5 个脚本,并且正在开发一个较新的框架,该框架将在各个方面进行大规模改进——性能、代码组织、可测试性、可验证性、文档等等。
我们有一个独立的迁移团队,拥有广泛的核心开发人员支持,并且在每次完成迁移时都会将通用的改进贡献回代码中。我们不断为客户进行迁移,其复杂程度从微不足道到难以置信。
我们的最终目标是让托管客户和社区的迁移尽可能地顺畅,但迁移过程中涉及的代码量太庞大,而系统级软件配置、第三方软件更改和输入数据变异只会加剧问题。
再说一遍,我希望所有这些事情都能更轻松一些,但要做到这一点需要无数的工作时间来创建和维护,而这些资源是有限的。
不要放弃! ![]()
我理解并且明白范围非常巨大。但不断遇到异常中的异常,而且项目是用 Ruby 写的,这使得除了来这里之外很难找到帮助,正如你所说,有些情况非常特殊,除非能实际接触到数据,否则根本无法提供帮助。
我也很大程度上将责任归咎于 vBulletin 那一团糟的结构。
我今早刚查看了一下,这是表大小的摘要。
为了提供背景信息,“text”表是实际内容所在的地方。
node表保存着层级结构,而closure……让我引用一下,因为我甚至无法理解:
Closure 表构建了所有节点之间的父子关系。你的数据库大部分是由附件组成,而附件本就不应该存储在数据库中。
所以总的来说,对于一个大约有 8GB 内容的论坛,却有 28GB 的开销。太棒了,恭喜 vBulletin。
这就是我所说的令人沮丧的原因。
同样的操作(遵循我自己编写的、包含所有试错过程的 runbook),在一个新的 discourse 安装上运行。
结果:

import_user_account_id 在哪里? ![]()
但最重要的是?你上次运行失败主题导入时是如何做到没有出错的? ![]()
注释掉那个函数调用(它似乎很重要),然后再次启动:
那些重复键错误……脚本难道不知道它已经处理过那些 ID 了,然后继续进行吗?
每次导入都不同。你可能会认为一个适用于 你以前最喜欢的论坛 实例的脚本会“开箱即用”,但事实并非如此。对于一个大型论坛来说,这真的很难。这根本不是一个容易支持的事情。批量导入器直接访问数据库,而不是依赖 Rails 能够自动检查正在进行的事情。
这是一个不常见的问,不是脚本的错。你需要弄清楚如何将旧数据库迁移到 utf8。
有强大的迁移支持。只是没有免费的迁移支持。我大约进行了 100 次迁移,并为不支持的或定制的系统编写了几个导入脚本。我可能会收取 3000-5000 美元来导入你的数据库。这不是一个报价,只是为了让你了解对于一个经常做这件事的人来说,这是多少工作量。我怀疑如果你支付一年的 Business 托管费用,CDCK 会免费为你做,这可能比我收费要少。(哦,但你可能没有资格获得具有如此大数据库的 Business 托管)。
继续在此处进行探索。
- 脚本引用了一个不存在的函数:
import_user_account_id。你们(Discourse 开发人员)可能想修复它。 - 检查主题标题的逻辑似乎对某些标题为空字符串的主题感到不满。尽管这不应该发生,但评估该 应该捕获它并返回
nil的检查,但显然这会破坏导入中后续的逻辑(参见 此处)。
我最近进行的一些导入也遇到了这个问题。更好的做法是返回类似“主题 XXX 缺少标题”的内容,或者从帖子中提取第一行文本,但这在此上下文中很难做到。我认为我会通过修改数据库来解决这个问题,并使用其他方法来生成缺失的标题。
是的,我基本上是在“清理”数据库时发现了这些问题,但这很难,因为我必须调试脚本,然后每次都要猜测是什么导致了问题 ![]()
关于缺失的函数 import_user_account_id 仍然没有头绪 ![]()
尤其考虑到假期很快就要到了,除非有人自己在使用这个脚本,否则不太可能有人来修复它。(通常我说这话的时候,Richard 就会出现并拯救大家。)
哈哈,好吧,我今天恐怕要让你失望了。我试过了,但我怀疑这个导入器的提交不完整,没有包含对 base.rb 的一些更改。@justin 处理过这个问题,也许他知道。我确实怀疑这可能是客户特定的问题,可以注释掉而不会有进一步的后果。
我自己也从未使用过批量导入器。
是的,导入脚本可能很复杂,并且依赖于数据库的特定细节,但有些脚本根本就无法正常工作。这个脚本也是如此,还有一些脚本,例如带有 # frozen_string_literal: true 的脚本,根本就无法开箱即用。
哈!
这(至少)是
这就是为什么我觉得提交我所做的更改的 PR 如此困难。到我完成时,里面有太多特定于案例的东西,我担心我提交的任何东西都会以某种方式损坏。
是的。我认为有些东西通过了,并将 frozen_string_literal 添加到了每个文件中。大多数文件都得到了修复,因为它们有测试,但导入脚本没有测试。
嘿,只是想澄清一下,我并不是期望现在有人来修复这个问题(卡伦式)。我只是指出了代码库本身存在一些明显问题的地方,可能只是“哎呀,我忘了把这个更改添加到提交中了!
”。
我此刻已经接受了这个迁移至少要到一月份才能完成。
大家还是好好享受假期吧 ![]()
假期后我会再提起这件事或开个新帖子,即使我肯定会花更少的时间来处理这个迁移 ![]()
是的,完全正确——对于导入,我通常在从不同客户那里进行了两次导入之后才会提交 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 小时后,我停止了脚本以查看实际情况。
启动界面后,我注意到了一些问题。
- 我的帐户未导入,因为创建的管理用户使用了相同的电子邮件,我猜是这样。这很明显,但也许应该写在某处?
- 只有部分类别(vbulletin 子论坛)被导入。
- 只有主题及其第一个回复被导入(不确定是否真的全部导入),并且它们都没有被导入到正确的类别中,即使是那些有类别但本应被创建的。所有内容都“没有类别”地导入了。
- “回复计数器”显示为
-1,可能是因为回复实际上根本没有被导入。
我将添加总体看法,如果此批量导入器实现了分页方法,那么大量的导入问题将会消失。我认为回复丢失是因为脚本试图一次性处理所有回复,而 7GB 的数据量是不可能的。说实话,批量导入器不采用分页方法进行导入,这让我感到困惑。即使一次只获取 1000 条记录,写入它们,存储最后写入的记录 ID 并循环,也能解决大型数据库的任何问题。
FWIW,我一直对此很感兴趣,并且非常感谢这些更新。
我对迁移了解不多,但我发现这非常有信息量。







