恢复失败 - 无法创建唯一索引

您好,备份恢复失败,错误如下:

CREATE INDEX
错误:无法创建唯一索引 "index_incoming_referers_on_path_and_incoming_domain_id"
详情:键 (path, incoming_domain_id)=(/m/search, 25) 重复。
异常:psql 执行失败:详情:键 (path, incoming_domain_id)=(/m/search, 25) 重复。

我找到了一个类似的主题,但无法弄清楚需要做什么:https://meta.discourse.org/t/getting-this-error-during-restore-could-not-create-unique-index/

感谢提供任何适合新手的逐步修复帮助。

最近 incoming_referers 表的问题出现了几次。我不确定为什么这个特定的表会引发问题,但看起来这些问题可能是相关的。也许 Discourse 团队的其他成员对是什么导致了重复记录的创建会有些想法。

您是否仍然可以访问创建备份文件的那个站点?如果可以,修复方法是先从数据库中删除重复记录,然后创建一个新的备份文件。为此,您需要通过 SSH 登录到旧服务器,并 cd 进入 /var/discourse 目录:

cd /var/discourse

然后运行

./launcher enter app

接着通过以下命令进入 Rails 控制台:

rails c

此时您应该会看到一个类似于以下的提示符:

[1] pry(main)>

请在 Rails 控制台中尝试运行以下命令,并告知我们它返回了什么:

IncomingReferer.where(path: "/m/search")

它应该返回一个包含两条或更多记录的数组。

谢谢。
我将在早上运行并反馈结果。

这是来自旧安装的内容——看起来只有一条记录?

[1] pry(main)> IncomingReferer.where(path: "/m/search")
=> [#<IncomingReferer:0x00005638d834b130
  id: 5153,
  path: "/m/search",
  incoming_domain_id: 25>]
[2] pry(main)>

编辑:在新服务器上也试过了。显示结果如下:

[1] pry(main)> IncomingReferer.where(path: "/m/search")
=> []
[2] pry(main)>

感谢您的核实!您得到的结果实际上与我今天早些时候在其他网站上看到的情况一致。这是一个可以解决的问题,但我将尝试让我们的工程师之一查看具体原因。

我迁移服务器的主要原因是因为当时使用的是即将停止支持的 Debian 8。由于遇到恢复问题,我选择了在同一台服务器上升级到 Debian 9。升级已成功,暂时松了一口气。感谢您的支持。

替换这一行

你需要执行模糊搜索,这样它就不会假设索引有效。如果百分号在开头,我认为一个就足够了。

你可以直接删除多余的记录。不过要彻底解决,你还需要更新链接到该表的其他表。我每次都得查一下,因为有好几个不同的表会涉及这种情况。

这个问题被归咎于第三方扩展,这听起来不太合理。看起来似乎是 PostgreSQL 的错,但我不确定。我每个月似乎都会遇到几次这个问题(在多个网站上都有类似情况)。

我也遇到了重复键的问题,有文档记录的修复方法吗?

discourse=# REINDEX SCHEMA CONCURRENTLY public;
    ERROR:  could not create unique index "index_incoming_referers_on_path_and_incoming_domain_id_ccnew"
DETAIL:  Key (path, incoming_domain_id)=(/search/, 1905) is duplicated.

[1] pry(main)> IncomingReferer.where(path: "/m/search")
=> [#<IncomingReferer:0x0000557176d3f210 id: 44231, path: "/m/search", incoming_domain_id: 4>,
 #<IncomingReferer:0x0000557176d925c8 id: 42228, path: "/m/search", incoming_domain_id: 26>]

虽然我刚刚原地升级了服务器,因此不再需要恢复到新服务器,但我出于好奇尝试了一下,但在模糊搜索中未找到任何记录:

[1] pry(main)> IncomingReferer.where(path: "%/m/search%")
=> []
[2] pry(main)> IncomingReferer.where(path: "%/m/search")
=> []
[3] pry(main)> IncomingReferer.where(path: "/m/search%")
=> []

你需要使用 LIKE 才能使通配符生效:

IncomingReferer.where("path LIKE '%/m/search%'")

这又引出了不少重复的键。

[1] pry(main)> IncomingReferer.where("path LIKE '%/m/search%'")
=> [#<IncomingReferer:0x0000557eaa7ed488 id: 408, path: "/m/search", incoming_domain_id: 26>,
 #<IncomingReferer:0x0000557eaabd80c0 id: 1508, path: "/m/search", incoming_domain_id: 45>,
 #<IncomingReferer:0x0000557eaabe3268 id: 2216, path: "/m/search", incoming_domain_id: 420>,
 #<IncomingReferer:0x0000557eaabe2f20 id: 3081, path: "/m/search", incoming_domain_id: 230>,
 #<IncomingReferer:0x0000557eaabe2c00 id: 33210, path: "/m/search", incoming_domain_id: 4>,
 #<IncomingReferer:0x0000557eaabe2908 id: 44231, path: "/m/search", incoming_domain_id: 4>,
 #<IncomingReferer:0x0000557eaabe27c8 id: 42228, path: "/m/search", incoming_domain_id: 26>]

我直接删除所有重复的行……这条信息没什么价值。

很乐意帮忙,你能提供正确的命令吗?我对 PostgreSQL 不太熟悉,但我懂 SQL。

听到这个太好了。我一直在费力地更新链接到这些数据的另一张表。这非常痛苦,因为我总是记不住它是什么,所以只能一遍又一遍地从头开始。

IncomingReferer.find(44231).destroy
IncomingReferer.find(42228).destroy

删除那两个重复项已成功,但随后重建索引时又出现了新错误。这是一个严重问题吗?我们该如何修复,删除那条搜索 3433 的记录?

[1] pry(main)> IncomingReferer.find(44231).destroy
=> #<IncomingReferer:0x000055734c65d8e8 id: 44231, path: "/m/search", incoming_domain_id: 4>
[2] pry(main)> IncomingReferer.find(42228).destroy
=> #<IncomingReferer:0x000055734cd81a70 id: 42228, path: "/m/search", incoming_domain_id: 26>
postgres=# \connect discourse
您现在已作为用户 "postgres" 连接到数据库 "discourse"。
discourse=# REINDEX SCHEMA CONCURRENTLY public;
警告:无法并发重新索引无效的索引 "public.incoming_referers_pkey_ccnew",已跳过
警告:无法并发重新索引无效的索引 "public.index_incoming_referers_on_path_and_incoming_domain_id_ccnew",已跳过
警告:无法并发重新索引无效的索引 "pg_toast.pg_toast_2782645_index_ccnew",已跳过
错误:无法创建唯一索引 "index_incoming_referers_on_path_and_incoming_domain_id_ccnew1"
详情:键 (path, incoming_domain_id)=(/search/, 3433) 重复。
上下文:并行工作进程

这是处理创建过程的代码……这应该能正确处理,但如果有需要,我们是否可以将其更新为 ON CONFLICT 插入?

我尝试手动重建这 4 个索引。其中两个成功,两个失败。我应该删除那两个重复的行吗?

discourse=# REINDEX INDEX CONCURRENTLY "public"."incoming_referers_pkey_ccnew";
REINDEX
discourse=# REINDEX INDEX CONCURRENTLY "public"."index_incoming_referers_on_path_and_incoming_domain_id_ccnew";
ERROR:  could not create unique index "index_incoming_referers_on_path_and_incoming_domain_id_cc_ccnew"
DETAIL:  Key (path, incoming_domain_id)=(/search/, 1861) is duplicated.
discourse=# REINDEX INDEX CONCURRENTLY "pg_toast"."pg_toast_2782645_index_ccnew";
REINDEX
discourse=# REINDEX INDEX CONCURRENTLY "index_incoming_referers_on_path_and_incoming_domain_id_ccnew1";
ERROR:  could not create unique index "index_incoming_referers_on_path_and_incoming_domain_id_c_ccnew1"
DETAIL:  Key (path, incoming_domain_id)=(/search/, 1905) is duplicated.

好的,请删除重复的行。

@riking PostgreSQL 索引损坏是 PostgreSQL 本身的 bug,而非 Discourse 的问题。我们当然可以优化该插入操作的性能,但 PostgreSQL 的这个 bug 需要在 PostgreSQL 端进行修复。

我猜测这可能与数据库引擎的某种非正常关闭有关,例如断电导致的情况。

这是一个合理的解释。那么 ./launcher shutdown app(或重建)是否会对 Postgres 执行正常关闭呢?不过,我敢打赌,无人值守升级应该不知道如何正常关闭 Docker 容器,对吧?