복원 실패 - 고유 인덱스를 생성할 수 없음

안녕하세요, 백업 복원 시 다음 오류가 발생합니다:

CREATE INDEX
ERROR:  could not create unique index "index_incoming_referers_on_path_and_incoming_domain_id"
DETAIL:  Key (path, incoming_domain_id)=(/m/search, 25) is duplicated.
EXCEPTION: psql failed: DETAIL:  Key (path, incoming_domain_id)=(/m/search, 25) is duplicated.

비슷한 주제를 찾았지만, 어떤 조치를 취해야 하는지 파악하지 못했습니다: 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로 업그레이드하는 방향으로 진행했습니다. 성공적으로 완료되어 당분간 여유가 생겼습니다.
도움 주셔서 감사합니다.

이 줄을 대체하세요

인덱스가 작동한다고 가정하지 않도록 퍼지 검색을 수행해야 합니다. 앞부분에 하나만 사용해도 충분할 것 같습니다.

추가된 레코드를 삭제하면 됩니다. 다만 올바르게 처리하려면 이 테이블을 참조하는 다른 테이블도 업데이트해야 합니다. 서로 연결된 테이블이 여러 개 있어서 매번 찾아봐야 합니다.

이 문제는 서드파티 확장으로 인한 것이라고 하는데, 그건 별로 말이 안 됩니다. 포스트그레SQL의 문제인 것 같지만, 확신은 없습니다. 한 달에 두세 번 정도 이 문제를 마주치는 것 같습니다(여러 사이트에서 (score) 관련 문제인 것으로 보입니다).

저도 중복 키 문제를 겪고 있습니다. 문서화된 해결 방법이 있을까요?

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

해당 중복 항목 2개를 삭제하는 작업은 성공적이었으나, 이후 인덱스를 다시 구축하는 과정에서 새로운 오류가 발생했습니다. 이것이 큰 문제인가요? 검색 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
You are now connected to database "discourse" as user "postgres".
discourse=# REINDEX SCHEMA CONCURRENTLY public;
WARNING:  cannot reindex invalid index "public.incoming_referers_pkey_ccnew" concurrently, skipping
WARNING:  cannot reindex invalid index "public.index_incoming_referers_on_path_and_incoming_domain_id_ccnew" concurrently, skipping
WARNING:  cannot reindex invalid index "pg_toast.pg_toast_2782645_index_ccnew" concurrently, skipping
ERROR:  could not create unique index "index_incoming_referers_on_path_and_incoming_domain_id_ccnew1"
DETAIL:  Key (path, incoming_domain_id)=(/search/, 3433) is duplicated.
CONTEXT:  parallel worker

여기는 생성을 처리하는 코드입니다… 이렇게 하면 적절히 처리되어야 하지만, 필요할 경우 ON CONFLICT insert로 업데이트할 수 있을까요?

그 4개 인덱스를 수동으로 다시 생성(REINDEX)해 보았습니다. 2개는 성공했고, 2개는 실패했습니다. 중복된 그 2개 행을 삭제해야 할까요?

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 pg에서 인덱스가 손상되는 문제는 pg의 버그이지 discourse의 버그가 아닙니다. 해당 insert의 성능은 분명히 개선할 수 있지만, pg의 버그는 pg에서 수정해야 하는 사항입니다.

제 추측으로는 db 엔진의 비정상 종료, 예를 들어 전원이 차단된 경우와 관련이 있을 것 같습니다.

합리적인 설명입니다. ./launcher shutdown app(또는 재구축)이 postgres를 어떻게든 깔끔하게 종료하나요? 아, 근데 무인 업그레이드는 docker 컨테이너를 깔끔하게 종료하는 방법을 모를 것 같은데, 그렇지 않나요?