Corruption d’un bloc de
données
Vérifier que l’instance utilise bien les checksums. Au besoin les
ajouter avec pg_checksums
.
data_checksums
----------------
on
Si la réponse est off
, on peut (à partir de la v12)
mettre les checksums en place :
$ /usr/pgsql-17/bin/pg_checksums -D /var/lib/pgsql/15/data.BACKUP/ --enable --progress
58/58 MB ( 100% ) computed
Checksum operation completed
Files scanned: 964
Blocks scanned: 7524
pg_checksums: syncing data directory
pg_checksums: updating control file
Checksums enabled in cluster
Créer une base pgbench et la remplir avec l’outil de
même, avec un facteur d’échelle 10 et avec les clés étrangères
entre tables ainsi :
/usr/pgsql-17/bin/pgbench -i -s 10 -d pgbench --foreign-keys
$ dropdb --if-exists pgbench ;
$ createdb pgbench ;
$ /usr/pgsql-17/bin/pgbench -i -s 10 -d pgbench --foreign-keys
...
creating tables...
generating data...
100000 of 1000000 tuples ( 10% ) done ( elapsed 0.15 s, remaining 1.31 s)
200000 of 1000000 tuples ( 20% ) done ( elapsed 0.35 s, remaining 1.39 s)
..
1000000 of 1000000 tuples ( 100% ) done ( elapsed 2.16 s, remaining 0.00 s)
vacuuming...
creating primary keys...
creating foreign keys...
done.
Voir la taille de pgbench_accounts
, les valeurs que
prend sa clé primaire.
La table fait 128 Mo selon un \d+
.
La clé aid
va de 1 à 100000 :
# SELECT min (aid), max (aid) FROM pgbench_accounts ;
min | max
-----+---------
1 | 1000000
Un SELECT
montre que les valeurs sont triées mais c’est
dû à l’initialisation.
Retrouver le fichier associé à la table pgbench_accounts
(par exemple avec pg_file_relationpath
).
SELECT pg_relation_filepath('pgbench_accounts' ) ;
pg_relation_filepath
----------------------
base/16454/16489
Arrêter PostgreSQL.
# systemctl stop postgresql-15
Cela permet d’être sûr qu’il ne va pas écraser nos modifications lors
d’un checkpoint.
Avec un outil hexedit
(à installer au besoin, l’aide
s’obtient par F1 ), modifier une ligne dans le PREMIER
bloc de la table.
postgres$ hexedit /var/lib/pgsql/15/data/base/16454/16489
Aller par exemple sur la 2è ligne, modifier 80 9F
en
FF FF
. Sortir avec Ctrl-X, confirmer la sauvegarde.
Redémarrer PostgreSQL et lire le contenu de
pgbench_accounts
.
# systemctl start postgresql-15
# SELECT * FROM pgbench_accounts ;
WARNING: page verification failed, calculated checksum 62947 but expected 57715
ERROR: invalid page in block 0 of relation base/16454/16489
Tenter un pg_dumpall > /dev/null
.
$ pg_dumpall > /dev/null
pg_dump: WARNING: page verification failed, calculated checksum 62947 but expected 57715
pg_dump: error: Dumping the contents of table "pgbench_accounts" failed: PQgetResult( ) failed.
pg_dump: error: Error message from server:
ERROR: invalid page in block 0 of relation base/16454/16489
pg_dump: error: The command was:
COPY public.pgbench_accounts ( aid, bid, abalance, filler) TO stdout;
pg_dumpall: error: pg_dump failed on database "pgbench" , exiting
Arrêter PostgreSQL.
Voir ce que donne pg_checksums
(pg_verify_checksums
en v11).
# systemctl stop postgresql-15
$ /usr/pgsql-17/bin/pg_checksums -D /var/lib/pgsql/15/data/ --check --progress
pg_checksums: error: checksum verification failed in file
"/var/lib/pgsql/15/data//base/16454/16489" , block 0:
calculated checksum F5E3 but block contains E173
216/216 MB ( 100% ) computed
Checksum operation completed
Files scanned: 1280
Blocks scanned: 27699
Bad checksums: 1
Data checksum version: 1
Faire une copie de travail à froid du PGDATA.
Protéger en écriture le PGDATA original.
Dans la copie, supprimer la possibilité d’accès
depuis l’extérieur.
Dans l’idéal, la copie devrait se faire vers un autre support, une
corruption rend celui-ci suspect. Dans le cadre du TP, ceci
suffira :
$ cp -upR /var/lib/pgsql/15/data/ /var/lib/pgsql/15/data.BACKUP/
$ chmod -R -w /var/lib/pgsql/15/data/
Dans /var/lib/pgsql/15/data.BACKUP/pg_hba.conf
ne doit
plus subsister que :
Avant de redémarrer PostgreSQL, supprimer les sommes de contrôle dans
la copie (en désespoir de cause).
$ /usr/pgsql-17/bin/pg_checksums -D /var/lib/pgsql/15/data.BACKUP/ --disable
pg_checksums: syncing data directory
pg_checksums: updating control file
Checksums disabled in cluster
Démarrer le cluster sur la copie avec pg_ctl
.
/usr/pgsql-17/bin/pg_ctl -D /var/lib/pgsql/15/data.BACKUP/ start
Que renvoie ceci ?
SELECT * FROM pgbench_accounts LIMIT 100 ;
# SELECT * FROM pgbench_accounts LIMIT 10 ;
ERROR: out of memory
DÉTAIL : Failed on request of size 536888061 in memory context "printtup".
Ce ne sera pas forcément cette erreur, plus rien n’est sûr en cas de
corruption. L’avantage des sommes de contrôle est justement d’avoir une
erreur moins grave et plus ciblée.
Un pg_dumpall
renverra le même message.
Tenter une récupération avec SET zero_damaged_pages
.
Quelles données ont pu être perdues ?
pgbench= # SET zero_damaged_pages TO on ;
SET
pgbench= # VACUUM FULL pgbench_accounts ;
VACUUM
pgbench= # SELECT * FROM pgbench_accounts LIMIT 100 ;
aid | bid | abalance | filler
-----+-----+----------+--------------------------------------------------------------------------------------
2 | 1 | 0 |
3 | 1 | 0 |
4 | 1 | 0 |
[...]
pgbench= # SELECT min (aid), max (aid), count (aid) FROM pgbench_accounts ;
min | max | count
-----+---------+--------
2 | 1000000 | 999999
Apparemment une ligne a disparu, celle portant la valeur 1 pour la
clé. Il est rare que la perte soit aussi évidente !
Corruption
d’un bloc de données et incohérences
Consulter le format et le contenu de la table
pgbench_branches
.
Cette petite table ne contient que 10 valeurs :
# SELECT * FROM pgbench_branches ;
bid | bbalance | filler
-----+----------+--------
255 | 0 |
2 | 0 |
3 | 0 |
4 | 0 |
5 | 0 |
6 | 0 |
7 | 0 |
8 | 0 |
9 | 0 |
10 | 0 |
(10 lignes)
Retrouver les fichiers des tables pgbench_branches
(par
exemple avec pg_file_relationpath
).
# SELECT pg_relation_filepath('pgbench_branches' ) ;
pg_relation_filepath
----------------------
base/16454/16490
Pour corrompre la table :
Arrêter PostgreSQL.
Avec hexedit, dans le premier bloc en tête de fichier, remplacer les
derniers caractères non nuls (C0 9E 40
) par
FF FF FF
.
En toute fin de fichier, remplacer le dernier 01
par un
FF
.
Redémarrer PostgreSQL.
$ /usr/pgsql-17/bin/pg_ctl -D /var/lib/pgsql/15/data.BACKUP/ stop
$ hexedit /var/lib/pgsql/15/data.BACKUP/base/16454/16490
$ /usr/pgsql-17/bin/pg_ctl -D /var/lib/pgsql/15/data.BACKUP/ start
Compter le nombre de lignes dans
pgbench_branches
.
Recompter après
SET enable_seqscan TO off ;
.
Quelle est la bonne réponse ? Vérifier le contenu
de la table.
Les deux décomptes sont contradictoires :
pgbench= # SELECT count (* ) FROM pgbench_branches ;
count
-------
9
pgbench= # SET enable_seqscan TO off ;
SET
pgbench= # SELECT count (* ) FROM pgbench_branches ;
count
-------
10
En effet, le premier lit la (petite) table directement, le second
passe par l’index, comme un EXPLAIN le montrerait. Les deux objets
diffèrent.
Et le contenu de la table est devenu :
# SELECT * FROM pgbench_branches ;
bid | bbalance | filler
-----+----------+--------
255 | 0 |
2 | 0 |
3 | 0 |
4 | 0 |
5 | 0 |
6 | 0 |
7 | 0 |
8 | 0 |
9 | 0 |
(9 lignes)
Le 1 est devenu 255 (c’est notre première modification) mais la ligne
10 a disparu !
Les requêtes peuvent renvoyer un résultat incohérent avec leur
critère :
pgbench= # SET enable_seqscan TO off ;
SET
pgbench= # SELECT * FROM pgbench_branches
WHERE bid = 1 ;
bid | bbalance | filler
-----+----------+--------
255 | 0 |
Qu’affiche pageinspect
pour cette table ?
pgbench= # CREATE EXTENSION pageinspect ;
pgbench= # SELECT t_ctid, lp_off, lp_len, t_xmin, t_xmax, t_data
FROM heap_page_items(get_raw_page('pgbench_branches' ,0 ));
t_ctid | lp_off | lp_len | t_xmin | t_xmax | t_data
--------+--------+--------+--------+--------+--------------------
(0,1) | 8160 | 32 | 63726 | 0 | \xff00000000000000
(0,2) | 8128 | 32 | 63726 | 0 | \x0200000000000000
(0,3) | 8096 | 32 | 63726 | 0 | \x0300000000000000
(0,4) | 8064 | 32 | 63726 | 0 | \x0400000000000000
(0,5) | 8032 | 32 | 63726 | 0 | \x0500000000000000
(0,6) | 8000 | 32 | 63726 | 0 | \x0600000000000000
(0,7) | 7968 | 32 | 63726 | 0 | \x0700000000000000
(0,8) | 7936 | 32 | 63726 | 0 | \x0800000000000000
(0,9) | 7904 | 32 | 63726 | 0 | \x0900000000000000
| 32767 | 127 | | |
(10 lignes)
La première ligne indique bien que le 1 est devenu un 255.
La dernière ligne porte sur la première modification, qui a détruit
les informations sur le ctid
. Celle-ci est à présent
inaccessible.
Avec l’extension amcheck
, essayer de voir si le problème
peut être détecté. Si non, pourquoi ?
La documentation est sur https://docs.postgresql.fr/current/amcheck.html .
Une vérification complète se fait ainsi :
pgbench= # CREATE EXTENSTION amcheck ;
pgbench= # SELECT bt_index_check (index => 'pgbench_branches_pkey' ,
heapallindexed => true );
bt_index_check
----------------
(1 ligne)
pgbench= # SELECT bt_index_parent_check (index => 'pgbench_branches_pkey' ,
heapallindexed => true , rootdescend => true );
ERROR: heap tuple (0 ,1 ) from table "pgbench_branches"
lacks matching index tuple within index "pgbench_branches_pkey"
Un seul des problèmes a été repéré.
Un REINDEX
serait ici une mauvaise idée : c’est la table
qui est corrompue ! Les sommes de contrôle, là encore, auraient permis
de cibler le problème très tôt.
Pour voir ce que donnerait une restauration :
Exporter pgbench_accounts
, définition des index
comprise.
Supprimer la table (il faudra supprimer pgbench_history
aussi).
Tenter de la réimporter.
$ pg_dump -d pgbench -t pgbench_accounts -f /tmp/pgbench_accounts.dmp
$ psql pgbench -c 'DROP TABLE pgbench_accounts CASCADE'
NOTICE: drop cascades to constraint pgbench_history_aid_fkey on table pgbench_history
DROP TABLE
$ psql pgbench < /tmp/pgbench_accounts.dmp
SET
SET
SET
SET
SET
set_config
------------
( 1 ligne)
SET
SET
SET
SET
SET
SET
CREATE TABLE
ALTER TABLE
COPY 999999
ALTER TABLE
CREATE INDEX
ERROR: insert or update on table "pgbench_accounts"
violates foreign key constraint "pgbench_accounts_bid_fkey"
DÉTAIL : Key ( bid ) = ( 1 ) is not present in table "pgbench_branches" .
La contrainte de clé étrangère entre les deux tables ne peut être
respectée : bid
est à 1 sur de nombreuses lignes de
pgbench_accounts
mais n’existe plus dans la table
pgbench_branches
! Ce genre d’incohérence doit être
recherchée très tôt pour ne pas surgir bien plus tard, quand on doit
restaurer pour d’autres raisons.