Verwirrung beim Datenbank kaputt machen

Ich spiele derzeit damit herum, Probleme in einer DB zu erkennen.

Der hochgeschätzte Kollege @laurenz hatte mal in einem Vortrag die Idee vorgestellt, den Datentyp einer Tabelle im Katalog zu ändern. (“Katalogtabellen ändern ist immer ein guter Weg, die Datenbank zu zerstören.”)

Also los:

-- Ausgangssituation
\d test2
                               Tabelle »public.test2«
 Spalte |   Typ   | Sortierfolge | NULL erlaubt? |            Vorgabewert
--------+---------+--------------+---------------+-----------------------------------
 id     | integer |              | not null      | nextval('test2_id_seq'::regclass)
 wert   | text    |              |               |
Indexe:
    "test2_pkey" PRIMARY KEY, btree (id)
Fremdschlüsselverweise von:
    TABLE "test2_fk" CONSTRAINT "test2_fk_test2_fkey" FOREIGN KEY (test2) REFERENCES test2(id)

select min(id), max(id) from test2;
 min |   max
 -----+---------
  101 | 1000000
  (1 Zeile)


-- Bad things happen
UPDATE  pg_attribute
SET atttypid = 'smallint'::regtype  
WHERE attrelid = 'test2'::regclass AND attname = 'id';

\d test2
                                Tabelle »public.test2«
 Spalte |   Typ    | Sortierfolge | NULL erlaubt? |            Vorgabewert
--------+----------+--------------+---------------+-----------------------------------
 id     | smallint |              | not null      | nextval('test2_id_seq'::regclass)
 wert   | text     |              |               |
Indexe:   
    "test2_pkey" PRIMARY KEY, btree (id int4_ops)
Fremdschlüsselverweise von:
    TABLE "test2_fk" CONSTRAINT "test2_fk_test2_fkey" FOREIGN KEY (test2) REFERENCES test2(id)

andreas=# select min(id), max(id) from test2;
  min   |  max
--------+-------
 -32768 | 32767
(1 Zeile)

andreas=# select min(test2), max(test2) from test2_fk;
 min |   max
-----+---------
 101 | 1000017
(1 Zeile)

select id, count(*) from test2 group by 1 order by 2 desc limit 10;
  id   | count
-------+-------
 15805 |    16
  8869 |    16
  1552 |    16
  1373 |    16
   161 |    16
  1595 |    16
  9038 |    16
  5697 |    16
 10792 |    16
  8660 |    16
(10 Zeilen)

SELECT bt_index_check('test2_pkey'::regclass, true, true);
 bt_index_check
----------------

(1 Zeile) 

select version();
                                                             version
----------------------------------------------------------------------------------------------------------------------------------
 PostgreSQL 17.7 on x86_64-pc-linux-gnu, compiled by x86_64-pc-linux-gnu-gcc (Gentoo 15.2.1_p20251122 p3) 15.2.1 20251122, 64-bit
(1 Zeile)

Ich sehe derzeit keine Möglichkeit soetwas zu entdecken. Die armen Waisen in test2_fk werden nie gefunden

  • REINDEX läuft durch
  • pg_amcheck läuft durch (s. auch oben) – hier hatte ich ja auf checkunique gesetzt
  • pg_dump läuft durch
  • pg_restore fällt hin, beim Anlegen des PK auf test2

„Herr Doktor, wenn ich mir mit dem Messer in den Bauch steche, dann blutet es und tut weh!”

:grinning_face_with_smiling_eyes:
Schmerzen und Blut sind ja das, was ich erwarten würde. Aber genau das, passiert ja nicht.

CREATE TABLE test2 (id integer PRIMARY KEY, wert text);

INSERT INTO test2 VALUES (1000000, 'zzzzzz');

UPDATE pg_attribute
SET atttypid = 'smallint'::regtype  
WHERE attrelid = 'test2'::regclass AND attname = 'id';

INSERT INTO test2 VALUES (1000, 'zzzzzz');

TABLE test2;
ERROR:  compressed pglz data is corrupt

Also, ich würde schon sagen, daß das weh tut.

Das ist ein Thema, das ich immer wieder beobachte: Leute sind überrascht, weil Datenkorruption nicht notwendigerweise sofort auffällt. Ich begegne regelmäßig Menschen, die Befremden darüber äußern, daß der Server ohne lautes Schreien startet, wenn die Datenbank korrupt ist — wobei sich dieselben Leute wahrscheinlich auch laut beschweren würden, wenn der Start einer 10TB-Datenbank mehrere Stunden dauert, weil alle Blöcke überprüft werden.

Im konkreten Fall werden die bestehenden Zeilen wohl deshalb ohne Fehler gelesen, weil pg_attribute.attlen immer noch 4 ist.

Sicher könnte man in PostgreSQL noch mehr Tests auf Konsistenz machen, etwa ob der Datentyp und seine Größe in pg_attribute zusammenpassen. Aber solche Tests kosten Zeit.
Wenn man PostgreSQL mit --enable-cassert konfiguriert und kompiliert, werden tatsächlich wesentlich mehr Invarianten überprüft (“sanity checks”). Allerdings läuft die Datenbank dann merklich langsamer, und für Produktivsysteme ist das nicht ratsam.

Es ist schwierig, die gute Balance zwischen Paranoia und Performance zu finden.

1 „Gefällt mir“

Sowas erwarte ich doch gar nicht. Was ich aber schon erstaunlich fand: pg_amcheck (mit isunique) findet kein Problem und eine Reindex auf den kaputten PK (Duplikate ohne Ende) sieht keine Schwierigkeiten.
Das pg_dump durch läuft ist ja durchaus verständlich.
Mir geht es ja darum, was ich tun kann, um sowas zu finden.

Was sagt verify_heapam()?

Das ist hilfreich:

select verify_heapam('kaputt'::regclass,false,true,'none');
                                          verify_heapam
--------------------------------------------------------------------------------------------------
 (0,2,1,"attribute with length 4294967295 ends at offset 513711802 beyond total tuple length 33")
(1 Zeile)
``

Aber das verstehe ich gar nicht:

postgres@[local]:andreas()# CREATE TABLE kaputt (id integer PRIMARY KEY, wert text);
CREATE TABLE

postgres@[local]:andreas()# INSERT INTO kaputt VALUES (1000000, 'zzzzzz');
INSERT 0 1

postgres@[local]:andreas()# UPDATE pg_attribute
SET atttypid = 'smallint'::regtype
WHERE attrelid = 'kaputt'::regclass AND attname = 'id';
UPDATE 1

postgres@[local]:andreas()# INSERT INTO kaputt VALUES (1000, 'zzzzzz');
INSERT 0 1

Wieso bekommst Du da einen Fehler und ich nicht? (Ich fühle mich gemobbt)

Keine Panik. Der Fehler kommt erst dann, wenn man die Daten abfragt:

TABLE kaputt;
1 „Gefällt mir“

Das braucht aber auch einen INSERT, damit es liefert. (So wie Dein TABLE). Und dann wird auch pg-amcheck ohne weitere Parameter fündig.

Ich finde das geziemend. Sobald man in der Tabelle ein INSERT oder UPDATE ausführt, knallt es. Und verify_heapam() findet das Problem auch. Besser geht’s kaum.
Diese konkrete Datenkorruption is auch sehr künstlich.

1 „Gefällt mir“

Dieses Thema wurde automatisch 60 Tage nach der letzten Antwort geschlossen. Es sind keine neuen Antworten mehr erlaubt.