Восстановление не удалось: не удалось создать уникальный индекс

Привет, восстановление из резервной копии завершается ошибкой:

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 к старому серверу и перейдите в директорию /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 на том же сервере. Это прошло успешно, так что пока можно выдохнуть.
Спасибо за вашу поддержку.

Замените эту строку

Вам нужно выполнить нечёткий поиск, чтобы система не предполагала, что индекс работает. Одного знака процента, вероятно, достаточно, если он стоит в начале, на мой взгляд.

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

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

У меня тоже проблема с дубликатом ключа. Есть ли задокументированное решение?

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
Вы подключены к базе данных "discourse" как пользователь "postgres".
discourse=# REINDEX SCHEMA CONCURRENTLY public;
WARNING:  невозможно одновременно перестроить некорректный индекс "public.incoming_referers_pkey_ccnew", пропускаем
WARNING:  невозможно одновременно перестроить некорректный индекс "public.index_incoming_referers_on_path_and_incoming_domain_id_ccnew", пропускаем
WARNING:  невозможно одновременно перестроить некорректный индекс "pg_toast.pg_toast_2782645_index_ccnew", пропускаем
ERROR:  не удалось создать уникальный индекс "index_incoming_referers_on_path_and_incoming_domain_id_ccnew1"
DETAIL:  Ключ (path, incoming_domain_id)=(/search/, 3433) дублируется.
CONTEXT:  параллельный рабочий процесс

Вот код, отвечающий за создание… он должен обрабатывать это правильно, но, возможно, стоит обновить его до вставки с 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, а не Discourse. Мы, безусловно, можем улучшить производительность этой вставки, но ошибка в PostgreSQL требует исправления в самой базе данных.

Мне кажется, это связано с некорректным завершением работы движка базы данных, возможно, из-за отключения питания.

Это разумное объяснение. Выполняет ли команда ./launcher shutdown app (или rebuild) корректное завершение работы Postgres? Хотя, наверное, автоматическое обновление не умеет корректно завершать работу контейнеров Docker, верно?