Import schlägt fehl mit „could not create unique index"

TL;DR: Wir haben ein Upgrade vermasselt und suchen Hilfe


Mit Home Assistant nutzen wir Discourse für unsere Community. Wir betreiben es mit der Methode discourse_docker auf einer EC2-Instanz bei AWS.

Als Open-Source-Projekt geriet die Wartung des Forums ins Hintertreffen, und wir landeten bei einer alten Version, die zuletzt Anfang 2019 aktualisiert wurde.

Um es noch schlimmer zu machen, hatten wir bei einem früheren Upgrade PostgreSQL auf Version 9.5 festgelegt, weil uns der benötigte Festplattenspeicher für ein Upgrade auf PostgreSQL 10 fehlte. Dieses Problem haben wir nie gelöst.

Zudem hatten wir einmal eine Änderung an der Cloudflare-Vorlage vorgenommen und diese ins Repository eingecheckt. Dadurch konnte sich der discourse_docker-Branch nicht automatisch auf die neueste Version aktualisieren.

Gestern haben wir uns entschlossen, das Upgrade durchzuführen …

Beim Migrieren der Datenbank stießen wir auf ein Problem: Es wurde eine Syntax verwendet, die mit 9.5 nicht kompatibel war:

== 20200429095034 AddTopicThumbnailInformation: migrating =====================
-- execute("ALTER TABLE posts\nADD COLUMN IF NOT EXISTS image_upload_id bigint\n")

Das Problem mit der Festlegung auf Version 9.5 wurde uns schnell bewusst. Wir beschlossen daher, auf PostgreSQL 10 zu migrieren. Das funktionierte jedoch nicht, und wir erhielten folgenden Fehler:

I, [2020-06-12T00:30:55.448351 #1]  INFO -- : Upgrading PostgreSQL from version 9.5 to 10
WARNING: Upgrading PostgresSQL would require an addtional 89M of disk space
Please free up some space, or expand your disk, before continuing.

Wir hatten 47 GB verfügbar, was seltsam war. Dann stellten wir fest, dass discourse_docker veraltet war, und aktualisierten es auf die neueste Version. Überraschung: PostgreSQL 12 war gerade erschienen.

Nachdem wir den Neuaufbau erneut ausgeführt hatten, erhielten wir diesmal diesen Fehler:

I, [2020-06-12T00:41:17.378129 #1]  INFO -- : Upgrading PostgreSQL from version 9.5 to 12
WARNING: Upgrading PostgresSQL would require an addtional 92G of disk space
Please free up some space, or expand your disk, before continuing.

Das ist etwas mehr Speicherplatz, aber sicher. Lassen Sie uns unseren Festplattenspeicher einfach auf 300 GB erhöhen und es erneut ausführen.

Dieses Mal brach pg_upgrade während der Migration ab:

Restoring database schemas in the new cluster
  template1
  discourse

*failure* Consult the last few lines of "pg_upgrade_dump_16384.log" for the probable cause of the failure. Failure, exiting

Als wir die Datei pg_upgrade_dump_16384.log prüften, sahen wir folgenden Fehler:

pg_restore: creating VIEW "postgres_exporter.pg_stat_activity"
pg_restore: [archiver (db)] Error while PROCESSING TOC:
pg_restore: [archiver (db)] Error from TOC entry 721; 1259 678554 VIEW pg_stat_activity postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  column pg_stat_activity.waiting does not exist
LINE 27:     "pg_stat_activity"."waiting",
             ^
    Command was:
-- For binary upgrade, must preserve pg_type oid
SELECT pg_catalog.binary_upgrade_set_next_pg_type_oid('678556'::pg_catalog.oid);


-- For binary upgrade, must preserve pg_type array oid
SELECT pg_catalog.binary_upgrade_set_next_array_pg_type_oid('678555'::pg_catalog.oid);


-- For binary upgrade, must preserve pg_class oids
SELECT pg_catalog.binary_upgrade_set_next_heap_pg_class_oid('678554'::pg_catalog.oid);

CREATE VIEW "postgres_exporter"."pg_stat_activity" AS
 SELECT "pg_stat_activity"."datid",
    "pg_stat_activity"."datname",
    "pg_stat_activity"."pid",
    "pg_stat_activity"."usesysid",
    "pg_stat_activity"."usename",
    "pg_stat_activity"."application_name",
    "pg_stat_activity"."client_addr",
    "pg_stat_activity"."client_hostname",
    "pg_stat_activity"."client_port",
    "pg_stat_activity"."backend_start",
    "pg_stat_activity"."xact_start",
    "pg_stat_activity"."query_start",
    "pg_stat_activity"."state_change",
    "pg_stat_activity"."waiting",
    "pg_stat_activity"."state",
    "pg_stat_activity"."backend_xid",
    "pg_stat_activity"."backend_xmin",
    "pg_stat_activity"."query"
   FROM "pg_stat_activity";

Oh je.

An diesem Punkt beschlossen wir, einige Schritte zurückzugehen. Könnten wir das Forum einfach wieder hochfahren und im Nur-Lese-Modus betreiben, während wir uns um die Backup-Problematik kümmern? Wir schafften dies, indem wir einige Berechtigungsprobleme für sowohl postgres als auch redis behoben. Das Forum war dann wieder auf der alten Version online. Nicht alles funktioniert; beispielsweise führt der Aufruf von Admin → Benutzer → Gruppen zu diesem Fehler:

NoMethodError (undefined method `automatic_membership_retroactive' for #<Group:0x00007fcaca3045e8>)
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/activemodel-6.0.1/lib/active_model/attribute_methods.rb:431:in `method_missing'

Der Rest scheint jedoch zu funktionieren.

An diesem Punkt entschieden wir, dass wir, da wir uns durch chown wieder zu einer funktionierenden Instanz durchkämpfen mussten, einfach eine neue Instanz starten und unser Backup importieren sollten.

Also starteten wir eine neue EC2-Instanz, führten die Einführungsanweisungen für discourse_docker aus und starteten unseren Import. Dann stießen wir auf ein seltsames Problem: Es konnte kein Index erstellt werden, da die Daten nicht den Eindeutigkeitsanforderungen des Index entsprachen:

ERROR:  could not create unique index "index_incoming_domains_on_name_and_https_and_port"
DETAIL:  Key (name, https, port)=(homeassistant.home, f, 8123) is duplicated.
EXCEPTION: psql failed: DETAIL:  Key (name, https, port)=(homeassistant.home, f, 8123) is duplicated.
/var/www/discourse/lib/backup_restore/database_restorer.rb:95:in `restore_dump'

Als wir jedoch in die Rails-Konsole unserer laufenden Instanz sprangen, war es keine Duplikation:

[7] pry(main)> IncomingDomain.where(name: "homeassistant.home")
=> [#<IncomingDomain:0x000055e5cabc3760 id: 8648, name: "homeassistant.home", https: false, port: 8123>]

Dort stehen wir also gerade. Und wir sind ziemlich ratlos.

  • Wir haben eine laufende Instanz mit einer schlechten Datenbank im Vergleich zum Ruby-Code, die nicht auf neuere PostgreSQL-Versionen migrieren kann.
  • Wir haben ein Backup, das nicht in einer neuen Instanz importiert werden kann.

Wir haben untersucht, ob wir zu einem kostenpflichtigen gehosteten Discourse wechseln können. Da wir jedoch 3 Millionen Seitenaufrufe und eine Million Beiträge haben, ist das Enterprise-Pricing für uns zu große Verpflichtung.

Wir müssen also einen Ausweg finden. Vorzugsweise könnten wir unser Backup importieren, aber auch die Migration unserer alten Instanz wäre eine Lösung.

Hat jemand Ideen? Wir haben nichts dagegen, jemanden zu bezahlen, der uns hilft.

Ich glaube, der einfachste Weg für dich ist es, ein funktionierendes Backup zu erstellen und wie beim letzten Mal in eine neue Instanz zu importieren.

Lass uns versuchen, die doppelten Daten zu beheben:

# SSH-Verbindung zur Maschine herstellen
cd /var/discourse
./launcher enter app
su postgres
psql
\connect discourse
SELECT * FROM incoming_domains WHERE name LIKE '%homeassistant.home%';

# Dies sollte mehrere Zeilen ausgeben
# Verwende SQL-DELETE-Anweisungen, um das Problem zu beheben
# und beende mit \q

Kannst du das versuchen und dich bei Problemen um weitere Hilfe bitten?

Müsste ich auch IncomingLink und IncomingReferrer bereinigen, da Referrer auf IncomingDomain verweist und IncomingLink auf IncomingReferrer?

Ich führe die Abfrage jetzt aus und werde versuchen, ein weiteres Backup zu importieren. Die Abfrage über PostgreSQL liefert tatsächlich andere Ergebnisse als über Rails. Aber ich vermute, das liegt vielleicht an einem Standard-Scope.

Wir haben versucht, ein weiteres Backup zu importieren, was aufgrund eines weiteren beschädigten Index fehlgeschlagen ist. Wir haben alle eindeutigen Indizes auf der ursprünglichen Instanz neu indiziert und erleben nun bei einer Handvoll Benutzer dieses Problem.

Wir halten Sie auf dem Laufenden.

Alles klar, wir haben es geschafft und sind wieder online. Danke für die Hinweise @Falco.

Um anderen Leuten bei ihren Problemen zu helfen, hier eine Zusammenfassung dessen, was wir getan haben.

Wir hatten einige beschädigte Indizes, die den Import scheitern ließen. Wir konnten das beheben, indem wir die Duplikate manuell löschten. Außerdem gab es 8 Benutzer mit einem doppelten username_lower (zu viele Mike’s und Marco’s). Wir haben diese umbenannt, indem wir sowohl username als auch username_lower aktualisiert haben. Aus den Benutzerdaten haben wir festgestellt, dass die erste Beschädigung im Dezember 2019 aufgetreten ist.

Anstatt den Zyklus „Backup erstellen

Ein letzter Tipp für alle, die Probleme mit Datenkorruption debuggen. Als unser Import aufgrund von Duplikaten fehlgeschlug, habe ich zunächst in die Rails-Konsole gesprungen und nach den Daten gesucht, die dazu führten, dass der Index nicht erstellt werden konnte.

Da ich jedoch über die indizierten Felder abgefragt habe, verwendete Postgres den defekten Index, um die Ergebnisse zu generieren! Meine ursprüngliche Abfrage zeigte also ein Ergebnis, und als ich später diesen Eintrag löschte, zeigte sie null Ergebnisse.

Abfragen, die einen vollständigen Tabellenscan durchführen, gewinnen :slight_smile: