Le blog de Jean David TECHER, un Réunionnais à Saint-Priest/Lyon

Aller au contenu | Aller au menu | Aller à la recherche


1 2 >

lundi 28 avril 2008

Afficher le contenu du fichier de configuration de PostgreSQL sans les commentaires

La commande suivante suffit
grep -v '^#' $(psql -At -p 5433 -h localhost -c "show config_file")|sed -e "s:#.*$::g"|grep '^[a-z]'
qui renverra par exemple
port = 5433
max_connections = 100
shared_buffers = 24MB
max_fsm_pages = 153600
datestyle = 'iso, dmy'
lc_messages = 'fr_FR.UTF-8'
lc_monetary = 'fr_FR.UTF-8'
lc_numeric = 'fr_FR.UTF-8'
lc_time = 'fr_FR.UTF-8'
default_text_search_config = 'pg_catalog.french'

mercredi 16 avril 2008

Test de PostgreSQL 8.3 sur fonction en C pour extraire les 'e' d'une chaîne de caractères

Les fonctionnalités en C ont quelques peu changé depuis le temps! Faisons donc un petit test sur une fonctionnalité extract_e() qui enlève tous les 'e' contenues dans une chaîne de caractère de type text. Commençons donc par créer une petite table pour nos tests. Je profite ici d'utiliser la structure de requête INSERT INTO ... RETURNING ... apparu depuis la 8.2

BEGIN TRANSACTION;
-- Déclaration de la fonction extract_e en tant que module C
SET search_path = public;
CREATE OR REPLACE FUNCTION extract_e(text) RETURNS text
     AS 'MODULE_PATHNAME', 'extract_e'
     LANGUAGE 'C' IMMUTABLE STRICT;
-- table de test
DROP TABLE IF EXISTS test;
CREATE TABLE test(id serial,data text)WITH OIDS;
INSERT INTO test(data) VALUES 
('david'),
('maman je t''aime'),
('Isabelle tu es la plus belle de toutes'),
('La vie vaut parfois le coup d''être vécue!'),
(NULL),
('Isidore dort dans la chambre')
RETURNING id,data,length(data),extract_e(data),length(extract_e(data));
END TRANSACTION;
qui me renverra
 id |                   data                    | length |               extract_e               | length 
----+-------------------------------------------+--------+---------------------------------------+--------
  1 | david                                     |      5 | david                                 |      5
  2 | maman je t'aime                           |     15 | maman j t'aim                         |     13
  3 | Isabelle tu es la plus belle de toutes    |     38 | Isabll tu s la plus bll d touts       |     31
  4 | La vie vaut aussi le coup d'être vécue! |     41 | La vi vaut aussi l coup d'êtr vécu! |     37
  5 |                                           |        |                                       |       
  6 | Isidore dort dans la chambre              |     28 | Isidor dort dans la chambr            |     26
(6 rows)

Le progamme extract_e.c

#include "postgres.h" 
#include "fmgr.h" 

#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif

#ifndef SET_VARSIZE
#define SET_VARSIZE(n,s) VARATT_SIZEP(n) = s; 
#endif 

#define VARDATALENGTH(s) VARSIZE(s)-VARHDRSZ;

Datum extract_e( PG_FUNCTION_ARGS );
int GetNb_e(text *);
text* Chaine_WithOut_E(text *,int);


// chaine sans les 'e'
text* Chaine_WithOut_E(text *chaine_In,int Nb_e){
  int i=0,j,t=0,len=VARDATALENGTH(chaine_In);
  text* chaine_Out=(text*)palloc(len-Nb_e+VARHDRSZ);
  SET_VARSIZE(chaine_Out,len-Nb_e+VARHDRSZ);
  while(i<len){j=(!(*(VARDATA(chaine_In)+i)=='e'))?(*(VARDATA(chaine_Out)+t++)=*(VARDATA(chaine_In)+i++)):i++;}
  return chaine_Out;
}

// Retourne le nombre de lettres 'e' de chaine
int GetNb_e(text *chaine){
  int pos,nb,len=VARDATALENGTH(chaine);
   nb=pos=0;
  while(pos<len){nb+=(*(VARDATA(chaine)+pos++)=='e');}
  return nb;
}

PG_FUNCTION_INFO_V1( extract_e );
Datum extract_e( PG_FUNCTION_ARGS )
{
   text *chaine_In;
   int Nb_e;
   chaine_In = PG_GETARG_TEXT_P(0);
   PG_RETURN_TEXT_P((!(Nb_e=GetNb_e(chaine_In)))?chaine_In:Chaine_WithOut_E(chaine_In,Nb_e)); 
}

Le Makefile

MODULES = extract_e
#PG_CPPFLAGS = -ggdb
DATA_built = extract_e.sql
DATA = uninstall_extract_e.sql
PGXS := $(shell pg_config --pgxs)
include $(PGXS)

Le fichier extract_e.sql.in


BEGIN TRANSACTION;
-- Déclaration de la fonction extract_e en tant que module C
SET search_path = public;
CREATE OR REPLACE FUNCTION extract_e(text) RETURNS text
     AS 'MODULE_PATHNAME', 'extract_e'
     LANGUAGE 'C' IMMUTABLE STRICT;
-- table de test
DROP TABLE IF EXISTS test;
CREATE TABLE test(id serial,data text)WITH OIDS;
INSERT INTO test(data) VALUES 
('david'),
('maman je t''aime'),
('Isabelle tu es la plus belle de toutes'),
('La vie vaut parfois le coup d''être vécue!'),
('Isidore dort dans la chambre')
RETURNING id,data,length(data),extract_e(data);
END TRANSACTION;;

Le fichier uninstall_extract_e.sql.in

SET search_path = public;
DROP FUNCTION extract_e(text);

dimanche 13 avril 2008

PostGIS 1.3.3 is out et refonte du site de GEOS sous Track

C'est en me rendant sur le site de PostGIS ce matin que j'ai vu que cette nouvelle version était sortie hier. Quand j'aurais le temps j'en profiterais pour tester les nouvelles fonctionnalités. Les news sur cette version mineure sont disponibles ici

Entre temps, j'ai aussi noté la refonte du site de geos sous Tracks...

A l'heure où j'écris ce billet, vous pouvez retrouver la doc au format PDF de la 1.3.3 ici généré grâce à db2latex

Exemple pour ST_SimplifyPreserveTopology()

En considérant le multipolygon suivant,
testgis=# select ST_AsText(the_geom) from testorig;
-[ RECORD 1 ]---------------------------------------------------------------------------------------------------------------------------------------------------
st_astext | MULTIPOLYGON(((283.681871106464 262.407705415416,333.681871106464 327.407705415416,406.681871106464 

273.407705415416,398.681871106464 238.407705415416,434.681871106464 224.407705415416,409.681871106464 215.407705415416,375.681871106464 

216.407705415416,391.681871106464 223.407705415416,361.681871106464 220.407705415416,387.681871106464 233.407705415416,375.681871106464 

246.407705415416,358.681871106464 226.407705415416,355.681871106464 254.407705415416,318.681871106464 229.407705415416,333.681871106464 

255.407705415416,319.681871106464 259.407705415416,312.681871106464 246.407705415416,310.681871106464 260.407705415416,302.681871106464 

244.407705415416,300.681871106464 260.407705415416,287.681871106464 246.407705415416,263.681871106464 237.407705415416,262.681871106464 

260.407705415416,286.681871106464 283.407705415416,283.681871106464 262.407705415416)))
On aura par exemple
testgis=# select ST_AsText(st_simplifypreservetopology(the_geom,10)) from testorig;
 POLYGON((283.681871106464 262.407705415416,333.681871106464 327.407705415416,406.681871106464 273.407705415416,398.681871106464 

238.407705415416,434.681871106464 224.407705415416,361.681871106464 220.407705415416,387.681871106464 233.407705415416,375.681871106464 

246.407705415416,358.681871106464 226.407705415416,355.681871106464 254.407705415416,318.681871106464 229.407705415416,333.681871106464 

255.407705415416,312.681871106464 246.407705415416,310.681871106464 260.407705415416,302.681871106464 244.407705415416,300.681871106464 

260.407705415416,263.681871106464 237.407705415416,262.681871106464 260.407705415416,286.681871106464 283.407705415416,283.681871106464 

262.407705415416))
correspondant au cas de l'image suivante

ST_SimplifyPreserveTopology sur un MULTIPOLYGON

samedi 12 avril 2008

Intérêt du VACUUM FULL lors d'UPDATE fréquents - Exemple concret

Souvent on me pose la question à quoi sert l'option FULL du VACUUM? Je ne vais pas décortiquer ici tout ce que fais le FULL mais juste montrer un exemple pratique d'un UPDATE sur toute une table.

Pour se faire, je vais partir d'un exemple pris du bouquin de référence (que j'avais déjà introduis dans un précédent billet)...

CHARGEMENT DES DONNEES

On va commencer par télécharger les sources en faisant
wget http://www.conjectrix.com/pgbook/source2/bookdata.tar.gz
On les décompresse ensuite
tar xvzf bookdata.tar.gz
Pour rester en conformité avec les données, on va se créer une base performance et un utilisateur korry
postgres@bremko:~$ createdb performance
CREATE DATABASE
postgres@bremko:~$ createuser -s korry
CREATE USER
On se rend dans le sous-répertoire chapter04 qui contient un fichier sql recalls.sql contenant la structure de la table recalls qui va nous servir. On en profite par la même occasion de charger le fichier. Au passage, on notera que le fichier fait 236Mo
postgres@bremko:~/chapter04$ du -hs recalls.sql
236M    recalls.sql
postgres@bremko:~/chapter04$ psql -d performance -f recalls.sql
You are now connected to database "performance" as user "korry".
CREATE TABLE
CREATE INDEX
CREATE INDEX
postgres@bremko:~/chapter04$
Au passage, on se connecte à la base performance pour la suite, on obtient le descriptif suivant
performance=# \d recalls
              Table "public.recalls"
   Column    |          Type           | Modifiers
-------------+-------------------------+-----------
 record_id   | numeric(9,0)            |
 campno      | character(9)            |
 maketxt     | character(25)           |
 modeltxt    | character(25)           |
 yeartxt     | character(4)            |
 mfgcampno   | character(10)           |
 compdesc    | character(75)           |
 mgftxt      | character(30)           |
 bgman       | character(8)            |
 endman      | character(8)            |
 vet         | character(1)            |
 potaff      | numeric(9,0)            |
 ndate       | character(8)            |
 odate       | character(8)            |
 influenced  | character(4)            |
 mfgname     | character(30)           |
 rcdate      | character(8)            |
 datea       | character(8)            |
 rpno        | character(3)            |
 fmvss       | character(3)            |
 desc_defect | character varying(2000) |
 con_defect  | character varying(2000) |
 cor_action  | character varying(2000) |
Indexes:
    "recall_potaff" btree (potaff)
    "recall_record_id" btree (record_id)

CAS D'UN SEUL UPDATE ET ETUDE

Examinons maintenant la taille de la table
performance=# SELECT relname,reltuples,relpages,pg_relation_size(relname)  FROM pg_class WHERE relname='recalls';
 relname | reltuples | relpages | pg_relation_size
---------+-----------+----------+------------------
 recalls |     39241 |     4400 |         36044800
(1 row)
Une page pour PostgreSQL est de 8192 bytes. Ce que nous confirme la commande suivante
performance=# show block_size ;
 block_size
------------
 8192
(1 row)
La table a 39241 tuples et 4400 pages. Ce qui donne donc 4400x8192=36044800.Ce que confirme la fonctionnalité pg_relation_size(). Ici bien sûr, je n'ai pas tenu compte de la place qu'occupait les index. On peut aussi le vérifier sur le disque mais pour celà, il faut remonter l'OID associé à la table recalls. Ce qui s'obtient en faisant
performance=# SELECT relname,relfilenode  FROM pg_class WHERE relname='recalls';
 relname | relfilenode
---------+-------------
 recalls |      188438
(1 row)
Par la suite, j'ai
postgres@bremko:~/chapter04$ ls -al /mnt/pgdata/base/188437/188438
-rw------- 1 postgres postgres 36044800 2008-04-12 10:50 /mnt/pgdata/base/188437/188438
Maintenant si l'option stats_row_level est active depuis le démarrage du serveur (à ne pas faire dans un environnement de prod...), on a aussi
performance=# \x
Expanded display is on.
performance=# Select * from pg_stat_user_tables ;
-[ RECORD 1 ]----+--------
relid            | 188438
schemaname       | public
relname          | recalls
seq_scan         | 2
seq_tup_read     | 78482
idx_scan         | 0
idx_tup_fetch    | 0
n_tup_ins        | 39241
n_tup_upd        | 0
n_tup_del        | 0
last_vacuum      |
last_autovacuum  |
last_analyze     |
last_autoanalyze |
Le champs n_tup_ins indique le nombre de tuples inséré depuis le démarrage du serveur soit 39241 tuples. On va maintenant modifier la table en faisant un simple UPDATE sur le champs potaff
performance=# START TRANSACTION ;
START TRANSACTION
performance=# UPDATE recalls SET potaff = potaff +1;
UPDATE 39241
performance=# END TRANSACTION ;
COMMIT
Revenons à une de nos précédentes requêtes
performance=# SELECT relname,reltuples,relpages,pg_relation_size(relname)  FROM pg_class WHERE relname='recalls';
 relname | reltuples | relpages | pg_relation_size
---------+-----------+----------+------------------
 recalls |     39241 |     4400 |         71868416
(1 row)
On s'apercoit qu'un simple UPDATE sur la table entière à suffit pour doubler la taille de la table! Ce que confirme aussi la commande suivante
postgres@bremko:~/chapter04$ ls -al /mnt/pgdata/base/188437/188438
-rw------- 1 postgres postgres 71868416 2008-04-12 10:50 /mnt/pgdata/base/188437/188438
En revanche le nombre de pages n'est pas à jour. Un simple ANALYZE sur la table suffira pour le mettre à jour
performance=# ANALYZE recalls ;
ANALYZE
performance=# SELECT relname,reltuples,relpages,pg_relation_size(relname)  FROM pg_class WHERE relname='recalls';
 relname | reltuples | relpages | pg_relation_size
---------+-----------+----------+------------------
 recalls |     39493 |     8773 |         71868416
(1 row)
Le nombre de pages a aussi quasiment doublé! C'est maintenant qu'intervient le FULL! Bien sûr, on supposera aussi qu'aucune autre activité n'a lieu pendant ce temps sur la base. PostgreSQL en fait garde une trace des lignes qui sont sujet à modification et le rend non accessibles. Tout celà repose en grande partie sur le principe du MVCC et isolation de transaction qu'à choisi PostgreSQL qui par défaut est en REAL COMMITTED. D'ailleurs PostgreSQL ne supporte que deux niveaux d'isolation
performance=# show transaction_isolation ;
 transaction_isolation
-----------------------
 read committed
(1 row)
So let's go now for notre FULL de la mort qui tue le diable
performance=# VACUUM FULL recalls ;
VACUUM
performance=# SELECT relname,reltuples,relpages,pg_relation_size(relname)  FROM pg_class WHERE relname='recalls';
 relname | reltuples | relpages | pg_relation_size
---------+-----------+----------+------------------
 recalls |     39241 |     4248 |         34799616
(1 row)
Or mon remarquera que par rapport à la taille initiale on a 34799616 < 36044800 ce qui est tout à fait normal car le FULL essait de "compresser/réagencer au mieux" l'espace disque sur les blocks de la table (déplace etc...), un peu comme le principe de la défragmentation sous Windaube. Dès fois le gain n'est pas important mais parfois il peut le devenir en fonction de ce qui a dans la base.

Donc des updates fréquents sur une table font grossir la table . Voilà une des raisons pour laquelle un FULL s'avère efficace pour récupérer l'espace disque lorsque ce dernier arrive à moins de 10 à 20 % de sa capacité.

Actuellement j'ai donc en utilisant pg_size_pretty () et pg_total_relation_size()
performance=# SELECT relname,reltuples,relpages,pg_size_pretty(pg_total_relation_size(relname))  FROM pg_class WHERE relname='recalls';
 relname | reltuples | relpages | pg_size_pretty
---------+-----------+----------+----------------
 recalls |     39241 |     4248 | 39 MB
(1 row)
Ma table occupe donc en y incluant (les index etc...) 39 MB sur le disque. Imaginons maintenant 10 UPDATES successifs comme suit

...ET POUR 10 UPDATES IDENTIQUES???

START TRANSACTION;
UPDATE recalls SET potaff=potaff+1;
UPDATE recalls SET potaff=potaff+1;
UPDATE recalls SET potaff=potaff+1;
UPDATE recalls SET potaff=potaff+1;
UPDATE recalls SET potaff=potaff+1;
UPDATE recalls SET potaff=potaff+1;
UPDATE recalls SET potaff=potaff+1;
UPDATE recalls SET potaff=potaff+1;
UPDATE recalls SET potaff=potaff+1;
UPDATE recalls SET potaff=potaff+1;
END TRANSACTION;
Dans un environnement de production, si le serveur tient la charge, on peut imaginer que celà correspond à 10 connexions au serveur de manière séquentiels (ce qui est encore l'un des meilleurs cas par rapport à ce accès concurrents faisant monté en charge le CPU) répétant la même requête...Essayez d'imaginer la taille de votre table par la suite! Sans faire de FULL au bout de 6 UPDATES, on est passé de 39 M à plus de 260M. :(. Et encore on en se plaindra pas car il s'agit ici d'un table de taille très raisonnable.

Faisons donc les tests! On en profitera pour créer une table qui stockera au fur et à mesure l'avancée suite à un requête de mise jour

START TRANSACTION;
CREATE TABLE stats_size(id serial,dtsize numeric);
INSERT INTO stats_size(dtsize) VALUES ((SELECT trim(both ' MB' from pg_size_pretty(pg_total_relation_size('recalls')))::numeric));
UPDATE recalls SET potaff=potaff+1;
INSERT INTO stats_size(dtsize) VALUES ((SELECT trim(both ' MB' from pg_size_pretty(pg_total_relation_size('recalls')))::numeric));
UPDATE recalls SET potaff=potaff+1;
INSERT INTO stats_size(dtsize) VALUES ((SELECT trim(both ' MB' from pg_size_pretty(pg_total_relation_size('recalls')))::numeric));
UPDATE recalls SET potaff=potaff+1;
INSERT INTO stats_size(dtsize) VALUES ((SELECT trim(both ' MB' from pg_size_pretty(pg_total_relation_size('recalls')))::numeric));
UPDATE recalls SET potaff=potaff+1;
INSERT INTO stats_size(dtsize) VALUES ((SELECT trim(both ' MB' from pg_size_pretty(pg_total_relation_size('recalls')))::numeric));
UPDATE recalls SET potaff=potaff+1;
INSERT INTO stats_size(dtsize) VALUES ((SELECT trim(both ' MB' from pg_size_pretty(pg_total_relation_size('recalls')))::numeric));
UPDATE recalls SET potaff=potaff+1;
INSERT INTO stats_size(dtsize) VALUES ((SELECT trim(both ' MB' from pg_size_pretty(pg_total_relation_size('recalls')))::numeric));
UPDATE recalls SET potaff=potaff+1;
INSERT INTO stats_size(dtsize) VALUES ((SELECT trim(both ' MB' from pg_size_pretty(pg_total_relation_size('recalls')))::numeric));
UPDATE recalls SET potaff=potaff+1;
INSERT INTO stats_size(dtsize) VALUES ((SELECT trim(both ' MB' from pg_size_pretty(pg_total_relation_size('recalls')))::numeric));
UPDATE recalls SET potaff=potaff+1;
INSERT INTO stats_size(dtsize) VALUES ((SELECT trim(both ' MB' from pg_size_pretty(pg_total_relation_size('recalls')))::numeric));
UPDATE recalls SET potaff=potaff+1;
INSERT INTO stats_size(dtsize) VALUES ((SELECT trim(both ' MB' from pg_size_pretty(pg_total_relation_size('recalls')))::numeric));
END TRANSACTION;
Ce qui donne donc
performance=# select * from stats_size;
 id | dtsize
----+--------
  1 |     36
  2 |     74
  3 |    111
  4 |    147
  5 |    185
  6 |    222
  7 |    258
  8 |    295
  9 |    333
 10 |    369
 11 |    407
(11 rows)
En effectuant 10 fois de suite la même requête on est passé de 36 à 407 soit un facteur de 10 pour la totalité de la table qui augmente de manière quasi linéraire comme le montre le graphique suivant

Taille de la table suite à 10 UPDATE successifs
Faisons maintenant un VACUUM FULL ANALYZE et insérons la nouvelle valeur dans la table stats_size en faisant;
performance=# VACUUM FULL ANALYZE recalls ;
VACUUM
performance=# INSERT INTO stats_size(dtsize) VALUES ((SELECT trim(both ' MB' from pg_size_pretty(pg_total_relation_size('recalls')))::numeric));
INSERT 0 1
Et on obtient

Taille de la table suite à 10 UPDATE successifs + VACUUM FULL ANALYZE
Et hop! On ne s'affolera pas ici mais ce simple exemple montre au combien il est nécessaire de penser à faire un FULL sur sa base lorsque des mises à jours sont fréquents sur une ou plusieurs tables de la base en production. On voit donc que lorsqu'on a des mises à jour fréquentes sur sa/ses tables(s), il est nécessaire de planifier un FULL. En revanche, il ne faut pas non plus oublier qu'un FULL est bien plus long qu'un simple VACUUM, que dans l'usage on ne le fait pas souvent non plus....Pour de plus amples informations, on pourra toujours se reporter à la documentation de PostgreSQLFR sur le VACUUM