Vous n'êtes pas identifié(e).

#1 05/06/2012 09:03:07

mortimer.pw
Membre

Requête mise en évidence par PgFouine

Bonjour à tous,

Je travaille sur des bases en 8.4 et 9.0 sous CentOs 5.4.
Les machines : DL360 - 64bits, 4Go RAM
Les paramètres Postgresql.conf
     Shared_buffers : 1024MB
     Work_mem : 10MB
     Maintenance_work_mem : 512MB
     Effective_cache_size : 2730MB

Je "parse" des fichiers de log avec PgFouine et une requête sort fréquemment.

     Queries that took up the most time
          Total duration : 49.8s
          Times executed : 112
          Query : select Igeo_pack.ActiviteCourante('')as id_activite;
     Slowest queries
          Duration : 0.98
          Query : select Igeo_pack.ActiviteCourante('I8246')as id_activite;
     Most frequent queries
          Times executed : 112
          Total duration : 49.8s
          Av duration : 0.44
          Query : select Igeo_pack.ActiviteCourante('')as id_activite;

Ci-dessous la description de la Fonction :
    CREATE OR REPLACE FUNCTION IGEO_PACK.ActiviteCourante(VARCHAR) RETURNS VARCHAR AS '
    DECLARE
        cActiviteCourante VARCHAR(10000);
        RPointage RECORD;
    BEGIN
        cActiviteCourante:=''NULL'';
        SELECT INTO RPointage P.*,A.*,OO.libelle
        FROM GEO.Pointage P
            LEFT OUTER JOIN GEO.O_f OO ON (P.of_sec=OO.of_sec)
            LEFT OUTER JOIN GEO.Vue_activite_geo A ON (P.id_ligne=A.id_ligne AND P.id_plan=A.id_plan AND P.id_fonction=A.id_fonction AND P.id_operation=A.id_operation)
        WHERE P.jour=CURRENT_DATE
        AND P.id_perso=$1
        ORDER BY P.heure DESC LIMIT 1;
        IF NOT FOUND THEN
            cActiviteCourante:=IGEO_PACK.DerniereActivite($1);
        ELSE
            cActiviteCourante:=RPointage.id_ligne||'';''||RPointage.lib_ligne||'';''||RPointage.id_plan||'';''||RPointage.lib_plan||'';''||
            RPointage.id_fonction||'';''||RPointage.lib_fonction||'';''||RPointage.id_operation||'';''||RPointage.lib_operation||'';'';
            IF RPointage.of_sec IS NOT NULL THEN
                IF RPointage.libelle IS NOT NULL THEN
                    cActiviteCourante:=cActiviteCourante||RPointage.of_sec||'';''||RPointage.libelle;
                ELSE
                    cActiviteCourante:=cActiviteCourante||'';'';
                END IF;
            ELSE
                cActiviteCourante:=cActiviteCourante||'';'';
            END IF;
            cActiviteCourante:=cActiviteCourante||'';''||RPointage.qte_igeo||'';''||TO_CHAR(RPointage.jour,''YYYYMMDD'')||'';''||
            RPointage.heure;
        END IF;
        RETURN cActiviteCourante;
    END;
    ' LANGUAGE 'plpgsql';
    ALTER FUNCTION IGEO_PACK.ActiviteCourante(VARCHAR) OWNER TO postgres;

Maintenant un plan d'exécution :
Limit  (cost=3365.54..3365.55 rows=1 width=684) (actual time=0.070..0.070 rows=0 loops=1)
   ->  Sort  (cost=3365.54..3365.55 rows=1 width=684) (actual time=0.066..0.066 rows=0 loops=1)
         Sort Key: p.heure
         Sort Method:  quicksort  Memory: 25kB
         ->  Nested Loop Left Join  (cost=24.62..3365.53 rows=1 width=684) (actual time=0.023..0.023 rows=0 loops=1)
               ->  Nested Loop Left Join  (cost=24.62..3361.25 rows=1 width=655) (actual time=0.023..0.023 rows=0 loops=1)
                     Join Filter: (((p.id_ligne)::text = (a.id_ligne)::text) AND ((p.id_plan)::text = (a.id_plan)::text) AND ((p.id_fonction)::text = (a.id
_fonction)::text) AND ((p.id_operation)::text = (a.id_operation)::text))
                     ->  Seq Scan on pointage p  (cost=0.00..1.50 rows=1 width=498) (actual time=0.022..0.022 rows=0 loops=1)
                           Filter: (((id_perso)::text = '80387'::text) AND (jour = ('now'::text)::date))
                     ->  Hash Join  (cost=24.62..3117.87 rows=12094 width=157) (never executed)
                           Hash Cond: ((a.id_operation)::text = (o.id_operation)::text)
                           ->  Hash Join  (cost=12.48..2939.44 rows=12094 width=139) (never executed)
                                 Hash Cond: ((a.id_plan)::text = (p.id_plan)::text)
                                 ->  Hash Join  (cost=4.50..2765.16 rows=12094 width=128) (never executed)
                                       Hash Cond: ((a.id_ligne)::text = (l.id_ligne)::text)
                                       ->  Hash Join  (cost=2.12..2596.50 rows=12094 width=103) (never executed)
                                             Hash Cond: ((a.id_fonction)::text = (f.id_fonction)::text)
                                             ->  Seq Scan on activite a  (cost=0.00..2428.08 rows=12094 width=83) (never executed)
                                                   Filter: (((id_type_ptg_sst)::text = 'G'::text) AND (valide = 1::numeric) AND (to_char((('now'::text)::da
te)::timestamp with time zone, 'YYYYMMDD'::text) <= to_char((((date_fin_valide)::text)::date)::timestamp with time zone, 'YYYYMMDD'::text)))
                                             ->  Hash  (cost=1.50..1.50 rows=50 width=24) (never executed)
                                                   ->  Seq Scan on fonction f  (cost=0.00..1.50 rows=50 width=24) (never executed)
                                       ->  Hash  (cost=1.61..1.61 rows=61 width=29) (never executed)
                                             ->  Seq Scan on ligne l  (cost=0.00..1.61 rows=61 width=29) (never executed)
                                 ->  Hash  (cost=4.66..4.66 rows=266 width=15) (never executed)
                                       ->  Seq Scan on plan p  (cost=0.00..4.66 rows=266 width=15) (never executed)
                           ->  Hash  (cost=7.06..7.06 rows=406 width=22) (never executed)
                                 ->  Seq Scan on operation o  (cost=0.00..7.06 rows=406 width=22) (never executed)
               ->  Index Scan using pk_of on o_f oo  (cost=0.00..4.27 rows=1 width=37) (never executed)
                     Index Cond: ((p.of_sec)::text = (oo.of_sec)::text)
Total runtime: 0.370 ms
(30 rows)

Cette Fonction est-elle mal écrite ? optimisable ? Les temps sont-ils acceptables ? Avez-vous besoin d'autres infos ?

Merci de me lire.

Hors ligne

#2 05/06/2012 09:32:32

gleu
Administrateur

Re : Requête mise en évidence par PgFouine

Je suppose que le EXPLAIN vient de la requête SELECT avec les deux jointures LEFT OUTER JOIN. Difficile de dire quoi que ce soit sur ce plan d'exécution vu qu'il s'exécute en 0,07 ms, ce qui est excellent smile C'est d'ailleurs excellent parce que tout n'est pas exécuté, le parcours séquentiel sur la table pointage ne ramenant aucune ligne.

Concernant la fonction, elle a l'air très simple. Je ne vois que deux possibilités pour un problème de performances : la requête SELECT (mais avec des arguments qui demandent une exécution complète du plan), et la fonction IGEO_PACK.DerniereActivite.

Avez-vous activé track_functions pour connaître la durée d'exécution des fonctions ? Cet article (http://blog.guillaume.lelarge.info/inde … -functions) pourrait vous intéresser.


Guillaume.

Hors ligne

#3 05/06/2012 09:58:27

mortimer.pw
Membre

Re : Requête mise en évidence par PgFouine

Bonjour Guillaume,

Désolé, oui le EXPLAIN vient bien de la requête SELECT avec les 2 jointures.

J'ai lancé le EXPLAIN sur ma machine de test, encore désolé.
Voilà la même chose sur une machine de prod :
Limit  (cost=178.25..178.26 rows=1 width=371) (actual time=39.090..39.093 rows=1 loops=1)
   ->  Sort  (cost=178.25..178.26 rows=1 width=371) (actual time=39.087..39.087 rows=1 loops=1)
         Sort Key: p.heure
         Sort Method:  top-N heapsort  Memory: 25kB
         ->  Nested Loop Left Join  (cost=26.12..178.24 rows=1 width=371) (actual time=6.126..39.018 rows=3 loops=1)
               ->  Nested Loop Left Join  (cost=26.12..173.96 rows=1 width=341) (actual time=6.080..38.954 rows=3 loops=1)
                     Join Filter: (((p.id_ligne)::text = (a.id_ligne)::text) AND ((p.id_plan)::text = (a.id_plan)::text) AND ((p.id_fonction)::text = (a.i
d_fonction)::text) AND ((p.id_operation)::text = (a.id_operation)::text))
                     ->  Index Scan using pointage_chaine on pointage p  (cost=0.01..4.28 rows=1 width=185) (actual time=0.059..0.075 rows=3 loops=1)
                           Index Cond: ((jour = ('now'::text)::date) AND ((id_perso)::text = '80396'::text))
                     ->  Hash Join  (cost=26.11..162.12 rows=378 width=156) (actual time=0.271..12.536 rows=1133 loops=3)
                           Hash Cond: ((a.id_ligne)::text = (l.id_ligne)::text)
                           ->  Hash Join  (cost=23.74..154.55 rows=378 width=131) (actual time=0.239..11.567 rows=1133 loops=3)
                                 Hash Cond: ((a.id_plan)::text = (p.id_plan)::text)
                                 ->  Hash Join  (cost=15.57..141.19 rows=378 width=120) (actual time=0.160..10.626 rows=1133 loops=3)
                                       Hash Cond: ((a.id_operation)::text = (o.id_operation)::text)
                                       ->  Hash Join  (cost=2.17..122.59 rows=378 width=102) (actual time=0.045..9.653 rows=1133 loops=3)
                                             Hash Cond: ((a.id_fonction)::text = (f.id_fonction)::text)
                                             ->  Seq Scan on activite a  (cost=0.00..115.22 rows=378 width=83) (actual time=0.024..8.580 rows=1133 loops=3
)
                                                   Filter: (((id_type_ptg_sst)::text = 'G'::text) AND (valide = 1::numeric) AND (to_char((('now'::text)::d
ate)::timestamp with time zone, 'YYYYMMDD'::text) <= to_char((((date_fin_valide)::text)::date)::timestamp with time zone, 'YYYYMMDD'::text)))
                                             ->  Hash  (cost=1.52..1.52 rows=52 width=23) (actual time=0.047..0.047 rows=52 loops=1)
                                                   ->  Seq Scan on fonction f  (cost=0.00..1.52 rows=52 width=23) (actual time=0.009..0.021 rows=52 loops=
1)
                                       ->  Hash  (cost=8.18..8.18 rows=418 width=22) (actual time=0.330..0.330 rows=418 loops=1)
                                             ->  Seq Scan on operation o  (cost=0.00..8.18 rows=418 width=22) (actual time=0.012..0.143 rows=418 loops=1)
                                 ->  Hash  (cost=4.74..4.74 rows=274 width=15) (actual time=0.223..0.223 rows=274 loops=1)
                                       ->  Seq Scan on plan p  (cost=0.00..4.74 rows=274 width=15) (actual time=0.014..0.107 rows=274 loops=1)
                           ->  Hash  (cost=1.61..1.61 rows=61 width=29) (actual time=0.064..0.064 rows=61 loops=1)
                                 ->  Seq Scan on ligne l  (cost=0.00..1.61 rows=61 width=29) (actual time=0.016..0.031 rows=61 loops=1)
               ->  Index Scan using pk_of on o_f oo  (cost=0.00..4.27 rows=1 width=38) (actual time=0.017..0.018 rows=1 loops=3)
                     Index Cond: ((p.of_sec)::text = (oo.of_sec)::text)
Total runtime: 39.414 ms
(30 rows)

Oui, le track_functions est activé.

Je vais lire votre article de suite.

Hors ligne

#4 05/06/2012 10:44:09

flo
Membre

Re : Requête mise en évidence par PgFouine

C'est normal toutes ces conversions en texte dans la requête?

Sinon, c'est bien cette requête-là?

SELECT INTO RPointage P.*,A.*,OO.libelle
        FROM GEO.Pointage P
            LEFT OUTER JOIN GEO.O_f OO ON (P.of_sec=OO.of_sec)
            LEFT OUTER JOIN GEO.Vue_activite_geo A ON (P.id_ligne=A.id_ligne AND P.id_plan=A.id_plan AND P.id_fonction=A.id_fonction AND P.id_operation=A.id_operation)
        WHERE P.jour=CURRENT_DATE
        AND P.id_perso=$1
        ORDER BY P.heure DESC LIMIT 1;

Parce que je ne comprends pas pourquoi, dans le plan d'exécution fourni, il y a la table ligne, la table fonction...
cela non plus :

Filter: (((id_type_ptg_sst)::text = 'G'::text) AND (valide = 1::numeric) AND (to_char((('now'::text)::d
ate)::timestamp with time zone, 'YYYYMMDD'::text) <= to_char((((date_fin_valide)::text)::date)::timestamp with time zone, 'YYYYMMDD'::text)))

?

Hors ligne

#5 05/06/2012 10:50:58

mortimer.pw
Membre

Re : Requête mise en évidence par PgFouine

Bonjour Flo,

La requête utililse une Vue (GEO.Vue_activite_geo) décrite ci-dessous :

CREATE OR REPLACE VIEW geo.vue_activite_geo AS
    SELECT a.id_ligne,l.libelle AS lib_ligne,a.id_plan,p.libelle AS lib_plan,a.id_fonction,f.libelle AS lib_fonction,a.id_operation,
    o.libelle AS lib_operation,a.id_type_ptg,a.id_type_act,a.tps_gam,a.tps_gam_test,a.id_type_coq,a.id_type_qte_act,a.qte_igeo,a.id_type_cum_doc,
    a.dbl_ptg,a.code_compta,a.date_fin_valide,a.id_site
    FROM geo.activite a,geo.ligne l,geo.plan p,geo.fonction f,geo.operation o
    WHERE a.id_type_ptg_sst='G'
    AND a.valide=1
    AND to_char('now'::text::date::timestamp with time zone,'YYYYMMDD'::text)<=to_char(a.date_fin_valide::text::date,'YYYYMMDD'::text)
    AND a.id_ligne=l.id_ligne
    AND a.id_plan=p.id_plan
    AND a.id_fonction=f.id_fonction
    AND a.id_operation=o.id_operation;
ALTER TABLE geo.vue_activite_geo OWNER TO postgres;

Les conversions en texte ne sont pas nécessaires ?

Merci pour votre participation.

Hors ligne

#6 05/06/2012 10:56:21

rjuju
Administrateur

Re : Requête mise en évidence par PgFouine

La conversion n'est pas nécessaire, mais en plus empêche l'utilisation d'un index sur la colonne s'il est présent, et prend aussi du temps cpu supplémentaire.

Comme le montrait flo, cela résulte à un seqscan sur la table activite (cela dépend aussi si vous avez des index sur les colonnes id_type_ptg_ss, valide et selon le nombre de lignes correspondant à ces critères).



>  Seq Scan on activite a  (cost=0.00..115.22 rows=378 width=83) (actual time=0.024..8.580 rows=1133 loops=3
)
                                                   Filter: (((id_type_ptg_sst)::text = 'G'::text) AND (valide = 1::numeric) AND (to_char((('now'::text)::d
ate)::timestamp with time zone, 'YYYYMMDD'::text) <= to_char((((date_fin_valide)::text)::date)::timestamp with time zone, 'YYYYMMDD'::text)))



Vous pouvez également essayer un analyze sur la table activite, les statistiques ayant l'air erronées.

Dernière modification par rjuju (05/06/2012 10:58:01)

Hors ligne

#7 05/06/2012 11:06:35

gleu
Administrateur

Re : Requête mise en évidence par PgFouine

Comme l'ont indiqué Florence et Julien, le gros problème vient du parcours séquentiel sur la table activite. C'est très flagrant sur http://explain.depesz.com/s/clE. Trois parcours séquentiels à 9ms chacune, donne 24 ms, soit un peu plus que la moitié de la requête. Commencez par regarder la table activite pour vérifier si des index sont présents sur les colonnes filtrées. Et ne pas faire de conversion de texte, une comparaison de date est beaucoup plus simple et peut utiliser les index.


Guillaume.

Hors ligne

#8 05/06/2012 11:07:51

gleu
Administrateur

Re : Requête mise en évidence par PgFouine

Cela étant dit, on est loin des secondes dont parle pgfouine...


Guillaume.

Hors ligne

#9 05/06/2012 11:10:37

mortimer.pw
Membre

Re : Requête mise en évidence par PgFouine

Bonjour Rjuju,

J'ai remplacé :
     to_char('now'::text::date::timestamp with time zone,'YYYYMMDD'::text)<=to_char(a.date_fin_valide::text::date,'YYYYMMDD'::text)
Par :
     now()<=a.date_fin_valide
Dans la Vue, le temps d'exécution ne semble pas bouger.

Il n'y a pas d'index sur les colonnes date_fin_valide, id_type_ptg_sst et valide.

La table activite contient 1864 lignes, la vue retourne 1133 lignes.

Les statistiques de la table :

geo=# select * from pg_stat_user_tables where relname='activite';
relid | schemaname | relname  | seq_scan | seq_tup_read | idx_scan | idx_tup_fetch | n_tup_ins | n_tup_upd | n_tup_del | n_tup_hot_upd | n_live_tup | n_dea
d_tup |          last_vacuum          |       last_autovacuum        |         last_analyze          |       last_autoanalyze
-------+------------+----------+----------+--------------+----------+---------------+-----------+-----------+-----------+---------------+------------+------
------+-------------------------------+------------------------------+-------------------------------+------------------------------
16484 | geo        | activite |  1314165 |   1967106299 |  9028205 |     105013628 |      2380 |       356 |       120 |            19 |       1864 |
    0 | 2012-06-03 08:01:10.716333+02 | 2012-05-29 14:21:05.13258+02 | 2012-06-03 08:01:10.819685+02 | 2012-05-29 14:21:05.13258+02


Comment voit-on que les stats sont erronées ?

Hors ligne

#10 05/06/2012 11:15:20

rjuju
Administrateur

Re : Requête mise en évidence par PgFouine

Sur le explain analyze, l'analyseur s'attend à trouver 378 ligne et en trouve au final 1133


Seq Scan on activite a  (cost=0.00..115.22 rows=378 width=83) (actual time=0.024..8.580 rows=1133 loops=3


edit: pgFouine pointe cette requête comme prenant 49.8 secondes sur 112 exécutions, soit un temps moyen de 444 ms., avec un pic à 980 ms. Votre explain durant 39ms, le problème vient peut-être d'accès concurrent dans la journée, d'un cache pas assez grand ou de temps différent selon le paramètre en entrée.

Dernière modification par rjuju (05/06/2012 11:24:03)

Hors ligne

#11 05/06/2012 11:26:06

mortimer.pw
Membre

Re : Requête mise en évidence par PgFouine

Je ne comprend pas pourquoi l'analyseur s'attend à trouver 378 lignes ?

Un vacuum full analyze (vacuumdb -a -f -z) est fait le dimanche et il n'y a pas eu de changement dans la table depuis.

Hors ligne

#12 05/06/2012 11:39:14

rjuju
Administrateur

Re : Requête mise en évidence par PgFouine

Il faudrait regarder sur chacune des colonnes le n_distinct (pg_stats) et comparer à ce que vous avez réellement. S'il y a une grosse différence vous pouvez augmenter le nombre de lignes utilisées pour les statistiques (ALTER TABLE <table> ALTER COLUMN <column> SET STATISTICS <number>; et relancer un analyze.


Dans tous les cas, la table étant parcourue par un scan séquentiel, il serait intéressant d'ajouter un index sur une ou plusieurs des colonnes utilisées.

Dernière modification par rjuju (05/06/2012 11:40:18)

Hors ligne

#13 05/06/2012 12:54:41

mortimer.pw
Membre

Re : Requête mise en évidence par PgFouine

Rjuju,

La colonne id_type_ptg_sst ne comporte actuellement qu'une seule valeur et à terme peut être 4 ou 5.
La colonne valide vaut 1 ou 0
La colonne date_fin_valide vaut par défaut 01/01/3000 et change de date lorsque l'activité n'est plus valide.

Est-il intéressant de créer un index ? sera t'il utilisé vu le faible nombre de lignes dans la tables et le faible nombre de valeurs différentes ?

Hors ligne

#14 05/06/2012 13:00:17

rjuju
Administrateur

Re : Requête mise en évidence par PgFouine

Votre vue récupère 1133 lignes sur les 1864, ce qui fait 60% de la table, ça peut valoir le coup d'essayer.
Votre colonne valide ne fait-elle pas double emploi avec la colonne date_fin_valide ? Surtout si vous interrogez la base à une autre date que la date du jour.


Vous devriez essayer de mettre un index sur la date et voir si le plan d'exécution change pour gagner un peu en IO.

Hors ligne

Pied de page des forums