Développons une nouvelle fonctionnalité pour PostgreSQL (9/13)

Désolé pour les dix jours qui se sont passés entre le précédent billet et celui-ci... j'avais un peu peur de m'y remettre, peur que ce soit trop complexe, voire tout simplement impossible.

Voyons la réponse de Tom à mes interrogations :

Why? If what you are doing is changing the database's default tablespace (which IMHO is what such a command ought to do) then all you have to do is bulk-copy the per-DB subdirectory from the old default tablespace to the new one. There's no reason to think about it at the individual-relation level, and there won't be any change to the contents of any catalog in the DB either (only its pg_database row will change).

En gros, pourquoi se connecter à la base qu'on souhaite déplacer. Il suffit de trouver le répertoire et de le déplacer en masse vers l'emplacement du tablespace de destination. En gros, si je comprend bien, on récupère le nom du répertoire source et celui du répertoire destination, et on déplace chaque fichier un par un. La seule modification à faire en base concerne l'identifiant du tablespace par défaut de la base de données.

Alors on se retrousse les manches :)

Première chose, notre précédent patch vérifiait que nous étions sur la même base et qu'il ne devait y avoir qu'une seule personne connectée, nous. Il faut changer cela pour que personne ne soit connecté à la base. On se retrouve donc avec l'ancien test :

 if (CountOtherDBBackends(db_id, &notherbackends, &npreparedxacts))
   ereport(ERROR,
     (errcode(ERRCODE_OBJECT_IN_USE),
       errmsg("source database \"%s\" is being accessed by other users",
       dbname),
       errdetail_busy_db(notherbackends, npreparedxacts)));

Ensuite, nous allons déplacer les fichiers. Plutôt que de les déplacer, nous allons les copier vers le nouveau tablespace (fonction copydir du fichier src/port/copydir.c), puis supprimer les anciens fichiers (fonction rmtree du fichier src/port/dirmod.c). La fonction copydir attend trois arguments : le nom du répertoire source, le nom du répertoire destination et un booléen indiquant si la fonction doit être récursive. Pour trouver le nom des deux répertoires, nous allons utiliser la fonction GetDatabasePath. Elle attend comme arguments l'OID de la base de données et l'OID du tablespace. Bref, nous arrivons à ce code :

 src_dbpath = GetDatabasePath(db_id, src_tblspcoid);
 dst_dbpath = GetDatabasePath(db_id, dst_tblspcoid);
 copydir(src_dbpath, dst_dbpath, false);

Ensuite, il faut supprimer l'ancien répertoire. Nous allons faire appel à la fonction rmtree qui prend deux arguments : le nom du répertoire à supprimer, et un booléen indiquant s'il faut aussi supprimer le répertoire de base. Pratiquement trop facile :

 rmtree(src_dbpath, true);

Bon. Reste plus qu'à tester. Après l'étape habituelle « make && make install », voici mon script de test :

 guillaume@laptop$ psql postgres
 psql (8.4devel)
 Type "help" for help.
 
 postgres=# create database db1;
 CREATE DATABASE
 postgres=# \c db1
 psql (8.4devel)
 You are now connected to database "db1".
 db1=# create tablespace ts1
 db1-# location '/home/guillaume/postgresql_tblspc';
 CREATE TABLESPACE
 db1=# create table t1(id int4);
 CREATE TABLE
 db1=# insert into t1 values (1);
 INSERT 0 1
 db1=# \c postgres
 psql (8.4devel)
 You are now connected to database "postgres".
 postgres=# alter database db1 tablespace ts1;
 NOTICE:  alter tablespace db1 set tablespace ts1!
 NOTICE:  move base/16384 to pg_tblspc/16385/16384
 NOTICE:  remove base/16384
 ALTER DATABASE
 postgres=# \c db1
 psql (8.4devel)
 You are now connected to database "db1".
 db1=# \d
          List of relations
  Schema | Name | Type  |   Owner
 - - - - +- - - +- - - -+- - - - - -
  public | t1   | table | guillaume
 (1 row)
 db1=# select datname, dattablespace from pg_database
 db1-# where datname='db1';
  datname | dattablespace
 - - - - -+- - - - - - - -
  db1     |         16385
 (1 row)
 db1=# select relname, relfilenode, reltablespace from pg_class
 db1-# where relname='t1';
  relname | relfilenode | reltablespace
 - - - - -+- - - - - - -+- - - - - - - -
  t1      |       16386 |             0
 (1 row)

Whouou, ça fonctionne :)

Vous croyez qu'on en a fini ? Je crois qu'il reste encore un petit soucis. Oui, encore un. En fait, le mouvement des fichiers d'un tablespace vers l'autre n'est pas tracé dans les journaux de transactions. Je pense que c'est un soucis. Quand on regarde la fonction createdb de src/backend/commands/dbcommands.c, on s'aperçoit que les fichiers du template sont copiés de la même façon que nous copions les fichiers d'un tablespace à un autre (logique, en fait, c'est moi qui ai copié sur cette fonction :) ). Tout de suite après la copie, on voit que la fonction insère un enregistrement dans les journaux de transactions pour indiquer la création de la nouvelle base de données. La question est donc : doit-on réellement créer ce nouvel enregistrement ? comment fait-on cela ? qu'est-ce que cela réclame si PostgreSQL tombe dans un mode de restauration ? J'avoue que je ne sais pas trop actuellement, j'ai rapidement posé la question sur pgsql-hackers, mais je vais continuer à chercher en attendant une éventuelle réponse.

Ajouter un commentaire

Les commentaires peuvent être formatés en utilisant une syntaxe wiki simplifiée.

Fil des commentaires de ce billet