Un Electron Libre...

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

mardi 16 septembre 2008

Optimisation apache (mod_deflate, mod_expires, ETag)

Pour un projet interne JCDecaux, je me suis penché sur les optimisations du serveur afin d'améliorer ses performances.

Ci-après les résultats fournis par YSlow :

Pour le chargement de la page d'accueil, en version non optimisée :

  • Sans cache (Empty cache) :
    • Taille 223.7K
    • Requêtes HTTP : 48
  • Avec cache navigateur (Primed cache) :
    • Taille : 53.5K
    • Requêtes HTTP : 48

Pour le chargement de la page d'accueil, en version optimisée :

  • Sans cache (Empty cache) :
    • Taille 160K
    • Requêtes HTTP : 48
  • Avec cache navigateur (Primed cache) :
    • Taille : 7K
    • Requêtes HTTP : 8

Sur une RHEL 5.1, dotée de Apache 2.2, PHP 5.2.6 + APC, MySQL 5.0.x, cela donne les éléments suivants :

/etc/httpd/conf.d/mod_deflate.conf :

#
## Mod Deflate - http://httpd.apache.org/docs/2.2/mod/mod_deflate.html
#

LoadModule headers_module modules/mod_headers.so
LoadModule deflate_module modules/mod_deflate.so

<IfModule mod_deflate.c>
        <Location />
                # Insert filter
                SetOutputFilter DEFLATE

                # Netscape 4.x has some problems...
                BrowserMatch ^Mozilla/4 gzip-only-text/html

                # Netscape 4.06-4.08 have some more problems
                BrowserMatch ^Mozilla/4\.0[678] no-gzip

                # MSIE masquerades as Netscape, but it is fine
                BrowserMatch \bMSIE !no-gzip !gzip-only-text/html

                # Don't compress images
                SetEnvIfNoCase Request_URI \
                \.(?:gif|jpe?g|png)$ no-gzip dont-vary

                # Make sure proxies don't deliver the wrong content
                Header append Vary User-Agent env=!dont-vary
        </Location>
</IfModule>

/etc/httpd/conf.d/disable_etag.conf :

#
## Disable ETags as caching & co is handled by mod_expires & mod_deflate
## /!\ Gzipped items will not have the same etags (even if the content did not change).
## So they're useless in our configuration
# 

# Remote ETag from headers - http://www.askapache.com/htaccess/apache-speed-etags.html
Header unset ETag
# Disable ETag for files
FileETag None

/etc/httpd/conf.d/mod_expires.conf :

#
## Mod Expires - http://httpd.apache.org/docs/2.2/mod/mod_expires.html
#
LoadModule expires_module modules/mod_expires.so

<IfModule mod_expires.c>
        # enable expirations
        ExpiresActive On

        # Default rule
        ExpiresDefault "access plus 1 week"

        # expire images after a month in the client's cache
        ExpiresByType image/* "access plus 1 month"

        # expire video after a month in the client's cache
        ExpiresByType video/* "access plus 1 month"

        # expire audio after a month in the client's cache
        ExpiresByType audio/* "access plus 1 month"

        # expire application after a month in the client's cache
        ExpiresByType application/* "access plus 1 month"

        # HTML documents are good for a week from the time they were changed
        ExpiresByType text/html "modified plus 1 week"
        ExpiresByType text/css  "modified plus 1 week"
        ExpiresByType text/javascript "modified plus 1 week"
</IfModule>

L'arbitrage entre access et modified est à voir selon vos besoins et le choix présenté ci-dessus est largement discutable. Il doit également être possible d'aller plus loin dans les optimisations mais dans mon cas d'espèce, le mieux étant l'ennemi du bien, je préfère m'arrêter là pour le moment.

Sauf erreur, la notation de votre site dans YSlow devrait significativement s'améliorer (dans mon cas d'exemple, passage de F à C et ce sans toucher à l'applicatif).

D'autres astuces à partager ?

mercredi 10 septembre 2008

Saison des pommes

Je profite de l'arrivée des pommes toutes fraîches pour m'en offrir une :-)

Switching.png

lundi 8 septembre 2008

En 2008, ça se fait encore...

Besoin : afficher les 8 dernières actualités et mettre en place un système de pagination pour accéder aux actualités

Réponse obtenue et livrée d'un développeur d'une SSII :

  • Récupération de toutes les news (via l'équivalent d'un joli SELECT *) dans un tableau en PHP
  • Parcours du tableau pour conter le nombre d'éléments du tableau obtenu précédemment et gérer ainsi la pagination
  • Récupération en base des 8 dernières news en vue de leur affichage (ce serait trop bête d'utiliser le tableau obtenu précédemment)
  • Pas d'utilisation du mécanisme de cache fourni par le CMS (eZ Publish) - donc à chaque rechargement de page, on recommence...

Quand il y a une 20aine d'actualités, coté temps d'affichage ça va encore - lorsqu'il y en a >1000, ça le fait tout de suite moins.

Réponse attendue d'un développeur sensible aux bonnes pratiques du web et conscient des problématiques de charges :

  • Utilisation de COUNT (ou plutôt de son équivalent dans le langage du CMS utilisé)
  • Récupération en base des 8 dernières actualités en vue de leur affichage
  • Mise en cache du résultat obtenu

C'est dans ces cas aussi que l'on souhaite une professionnalisation des métiers du web. Certains diront que la qualité se vend mal. C'est sur que si les clients ont déchanté face aux promesses de qualité faites par les SSII/Editeurs, ils vont avoir du mal à acheter une telle qualité annoncée. Le problème tient au fait pour les SSII de prouver à leurs clients que la qualité annoncée sera au rendez-vous et de former le cas échéant ces collaborateurs. Pour le bien de tous (développeur, SSII, client), il est évident que cette professionnalisation se fasse mais faut-il encore le vouloir et le financer... Dans ce cadre, on ne peut pas demander à un collaborateur de se former sur son temps libre ou chez un client...

vendredi 5 septembre 2008

Django, Lighttpd et l'admin de Django sont dans un bateau...

Comme toute personne faisant tourner Django avec lighttpd, j'ai la configuration suivante :

$HTTP["host"] =~ "(^|www\.)steinmetz\.fr$" {
        server.document-root = "/home/nicolas/django/lessteinmetz"
        fastcgi.server = (
                "/django.fcgi" => (
                        "main" => (
                                "host" => "127.0.0.1",
                                "port" => 8080,
                                "check-local" => "disable",
                        )
                ),
        )
        alias.url = (
                "/media/" => "/home/nicolas/django/django-svn/django/contrib/admin/media/",
        )
        url.rewrite-once = (
                "^(/media.*)$" => "$1",
                "^/favicon\.ico$" => "/media/favicon.ico",
                "^(/.*)$" => "/django.fcgi$1",
        )
}

Là où le bas blesse avec l'arrivée de Django 1.0, c'est que si vous souhaitez accéder à l'interface d'administration, vous vous retrouvez avec une jolie erreur puisque Django, pour l'url en /admin/ vous renvoie un /django.fcgi/admin/ que votre serveur web réécrit en django.fcgi/django.fcgi/admin, ce qui ne peut pas fonctionner.

Cela est du à l'arrivée de FORCE_SCRIPT_NAME. La solution consiste donc à rajouter dans votre fichier settings.py la déclaration suivante :

FORCE_SCRIPT_NAME=""

Relancez votre instance Django et le tour est joué.

jeudi 4 septembre 2008

Django 1.0 disponible...

Après la sortie hier d'une discrète version "RC", voici que la version finale de Django 1.0 sort ce jour.

Je vous invite à lire les Release Notes, que David a traduit dans son billet "Sortie de Django 1.0, une année de nouveautés". Pour ceux en version 0.96, on notera l'existence d'un guide de migration vers la version 1.0.

David ayant tout dit et la doc anglaise étant aussi très chouette, je ne vais pas en dire plus à ce sujet... Il va maintenant vraiment falloir que je mette à jour mes projets en Django 1.0 (tout comme ne plus utiliser la version svn sur ce serveur...)

lundi 1 septembre 2008

Conseils pour achat d'un appareil photo demandés...

Hello,

Mon anniversaire approchant (mais pas encore le changement de décennie...) et le Canon Powershot S60 offert par mon beau-père suite à mon mariage étant presque mort, je suis à la recherche d'un nouvel appareil.

Le but de cet appareil est de pouvoir faire des photos de famille, vacances & co - pour le coup, mon Canon laissait à désirer dans les endroits sombres (église & co) et le zoom était parfois un peu juste (le zoom logiciel laissant à désirer, je ne l'utilisais jamais au vu des résultats).

Pour le moment :

  • ma préférence va plutôt vers un compact mais un bridge peut s'envisager - ça me rappelera l'argentique de mon père qui dort au fond d'un sac...
  • aucune idée de budget

Des choses à me (dé)conseiller ?

mercredi 27 août 2008

Intégration : Recette interne

La recette arrivant en bout de course d'un projet, elle est souvent sacrifiée pour compenser les retards de développements pour des bonnes et/ou mauvaises raisons. Il arrive donc souvent que la version livrée en recette a peu été testée (voir pas du tout ou pas de façon suffisamment significative).

Cela conduit à différentes choses :

  • Les jours payés par le client ne sont pas utilisés à cet effet,
  • L'effort de recette coté client explose puisqu'il doit assurer la recette en lieu et place du prestataire
  • L'effort de recette coté presta va également exploser coté prestataire et non être réduit comme escompté car il aura (souvent) tendance à :
    • multiplier les livraisons correctives partielles
    • générer de nombreux aller/retour pour cause de non-correction voir régression
    • exploser le cout de livraison (packaging, documentation, etc)
  • Désorganise tant le prestataire que le client du fait de cette charge à absorber dans des plannings qui ne le permettent pas forcément
  • Au final, une tension croissante va pourrir les relations entre le client et le prestataire, sans compter la démotivation croissante de part et d'autres pour faire avancer le projet. Sans compter que cela ternit l'image de marque du prestataire et peut mettre fin à tout travail potentiel entre le prestataire et le client.

Au final, tout le monde est clairement perdant.

Quelques pistes d'amélioration :

  • Mieux estimer les périodes de recette (structurellement sous estimée) et qu'elle fasse partie du budget du projet. A croire qu'annoncer une période de recette est une honte en matière de programmation... Attention, il faut quand même que cette durée reste cohérente avec la taille/complexité de l'application.
  • Etre transparent/honnête avec son client en disant qu'il y aura un retard de livraison le temps de faire la recette. Après tout, c'est logique, le temps perdu au niveau du développement ne se rattrappe jamais par magie...
  • Mieux résister à la pression interne de livrer "à tout prix et/ou au plus vite" au client
  • Prendre le temps de faire cette recette
  • Coté presta, prévoir des zones tampons si des projets doivent se succéder afin qu'un retard sur un projet n'en condamne pas un autre. Cela suppose aussi que le chef de projet remonte les infos à temps à sa hiérarchie.
  • Dans le cas de plusieurs itérations, faire en sorte d'en avoir le moins possible pour ne pas se démotiver dans les phases de recette,
  • S'il y a des applications auxquelles se connecte le projet, soit vous êtes en mesure de mettre en place réellement cette plateforme chez le prestataire, soit il faut venir faire des tests d'intégration chez le client
  • Faire venir le prestataire sur site pour pouvoir travailler de concert et éviter les parties de ping-pong par mail ou téléphone,
  • Se dire que ce retard sera compensé par le fait que tout ce qui a été dit dans le paragraphe précédent ne se produira pas... ou dans une moindre mesure

Enfin, si le jour de la livraison il y a un retard de dernière minute ou une opération plus longue que prévue, prévenir le plus tôt possible le client et le tenir informé des éventuels reports de livraison. Ou alors se donner une vraie marge pour tenir les horaires. Cela évite en outre que le client excédé vous appelle pour savoir où vous en êtes et à quelle heure vous allez livrer.

Tout cela suppose également que le chef de projet ait une visibilité suffisamment bonne sur le travail de ces développeurs et qu'il connaisse suffisamment bien le produit sur lequel il travaille pour que ces estimations soient les plus justes possibles. Faute de quoi, il risque de se trouver dans une fort mauvaise posture (vécu inside).

A ce jour, une telle méthode m'aurait épargné un gros nombre des 100 bugs trouvés juste en cliquant à droite et à gauche (sans prendre en compte les specs fonctionnelles d'un projet) et les 5 livraisons que j'ai réduit à 3 intégrations. Je tairais le nom de la SSII, vu que ce n'est pas propre à cette SSII et que j'espère que d'autres équipes projets de cette SSII travaillent de façon plus satisfaisante. Pour avoir de l'autre coté, j'en garde également des mauvais souvenirs et des conditions de travail déplorables, se retrouvant entre le marteau (le client) et l'enclume (sa hierarchie).

mardi 26 août 2008

Résolutions de rentrée

  • Faire avancer le projet Atome, surtout que la contrib "comments", nouvelle formule vient d'attérir dans django 1.0 beta2.
  • Me (re)mettre à faire de l'acquisition et du montage vidéo - vu les cassettes DV qui commencent à s'empiler; ce serait dommage de pas le faire, ni de les visionner. Enfin, je veux arriver à faire mieux que ma soeur sous Windows Movie Maker...
  • S'occuper de ces *** de dents de sagesse et analyses sanguines

Plus de résolution, ce serait du suicide... :-P

jeudi 21 août 2008

Intégration : Cahier de recette (cet ami qui vous veut du bien)

Je vais lancer une série "Intégration" dans laquelle je vais parler de points vue dans mon quotidien ou dans mon passé en SSII.

Cela faisait un moment que je voulais écrire ce billet, convaincu de plus en plus que le cahier de recette devient un élément de plus en plus crucial dans un projet informatique.

Idéalement, je vois une première version réalisée à l'issue de la phase des spécifications. Sans aller parler de "Test Driven Development" (Développement piloté par les tests, qui l'on peut résumer synthétiquement par le fait d'écrire les tests avant de coder quoi que ce soit), l'idée de rédiger le cahier de tests à l'issue de la phase de spécifications a tout son sens selon moi. Rédiger le cahier de recette (technique et/ou fonctionnel) à cette étape apporte les avantages suivants :

  • Cela permet de consigner par écrit ce qui semble parfois "tellement évident" à certains membres du projet et qui peut ne pas l'être pour d'autres ou bien pas si évident que cela (périmètre de tests, éléments à tester, conditions de tests, etc).
  • On peut s'apercevoir qu'il y a des manques dans les spécifications que l'on peut alors enrichir (règles de gestion, cas non imaginés de premier abord, etc) : imaginons par ex un intranet groupe avec une authentification intégrée, par défaut, on imagine très bien le cas du français se connectant sur l'application depuis son poste france, mais que se passe-t-il s'il se déplace dans une filliale pour laquelle l'authentification intégrée n'est pas en place ? L'utilisateur pourra-t-il se connecter malgré tout à l'application ?).
  • Dans le cas d'une relation avec un prestataire, cela évite les mauvaises surprises en phase de recette (le cas de recette 12, pourtant tellement évident coté client et absolument pas (pré)vu par le prestataire)
  • A l'issue de la phase de développement, vous pouvez demander au prestataire de vous indiquer le résultat de sa recette vis à vis de ce cahier de recette (qui aura pu être complété/détaillé entre temps).
  • Lors de la phase de recette, vous avez un référentiel sur lequel peut s'appuyer votre démarche de recette.

En tout état de cause, il est important de faire vivre votre cahier de recette : de test très macro (état initial > étapes > résultat) réalisés dans un premier temps, vous pourrez le compléter (nouveaux cas) ou bien le détailler/préciser ou encore définir des "sous-cas" de tests.

Il est également important de communiquer ce cahier de recette aux parties impliquées pour avoir leur réaction et échanger. Cela peut le cas échéant donner lieu à des avenants/aménagements des phases de développement (cas d'évolution). Le fait que ce soit dans votre cahier de recette ne veut pas forcément dire qu'il doit être accepté systématiquement par votre équipe de développement / prestataire ; en effet, si cela n'est pas dans le périmètre de départ ou a un impact trop significatif sur les développements, il vous faudra en discuter (c'est sur qu'en cas d'engagement au forfait...)

Pour être honnête, moi même en SSII, j'ai très peu appliqué cette méthode - seulement lors de mes dernières missions en AMOA lorsque j'étais en SSII ou pour un projet interne actuel chez mon actuel employeur. Dernièrement, lors d'un POC (Proof Of Concept / Prototype), j'avoue avoir été étonné que personne ne s'en est pré-occupé avant que j'en fournisse un - permettant d'ailleurs de découvrir le cas du français en déplacement dans une autre filliale par ex. Tout le monde savait qu'il fallait tester deux fonctions "authentification" et "indexation" et il a bien été démontré que cette soit-disant évidence des tests n'allait pas de soit sur de nombreux sujets ou tout bonnement sur le périmètre même du POC.

La seule explication au fait que cela ne soit pas plus utilisé à présent dans les méthodes de développement est un temps de spécification mal estimé conduisant à ce qu'il soit négligé / repoussé à la phase de tests. Vous en voyez d'autres (bonnes|mauvaises) raisons ?

Apache : authentification intégrée sauf pour certains clients dont on ne connait pas la provenance réseau

Cas d'étude : une application auxquels les collaborateurs d'une entreprise accèdent via une authentification intégrée. Pour les filliales ne disposant pas du SSO ou pour permettre à des utilisateurs extérieurs de se connecter à l'application, une connexion anonyme doit être mise en place.

Solution retenue :

Principe : par défaut, le serveur va chercher à authentifier l'utilisateur par authentification intégrée.

Exception : pour l'indexation de l'application, il faut autoriser l'accès anonyme au serveur

Problématique : seuls les plages IP des pays utilisant l'authentification intégrée est connue. Il faut donc permettre l'accès en anonyme à tout le monde sauf ces IP là.

L'importance ici tient dans la directive Order, Allow , Deny, Satisfy et SetEnvIf.

LoadModule auth_ntlm_winbind_module modules/mod_auth_ntlm_winbind.so
LoadModule setenvif_module modules/mod_setenvif.so
 
<Location />
    Order Deny,Allow
    Deny From All
 
    <IfModule mod_auth_ntlm_winbind.c>
        AuthName "NTLM Authentification"
        NTLMAuth on
        NTLMAuthHelper "/usr/bin/ntlm_auth --helper-protocol=squid-2.5-ntlmssp"
        NTLMBasicAuthoritative on
        AuthType NTLM
    </IfModule>
 
    Require valid-user
	<fModule mod_setenvif.c>
		# Declaration des clients utilisant l'authentification intégrée
		SetEnvIf Remote_Addr "10\.(10|13|14|19|20|44|45|49)\.[0-9]{1,3}\.[0-9]{1,3}" pays # Pays 1
		SetEnvIf Remote_Addr "10\.(70|18|48)\[0-9]{1,3}\.[0-9]{1,3}" pays # Pays 2
 
		# Declaration de l'IP du robot indexant le site
		SetEnvIf Remote_Addr "10\.135\.255\.(1|44|35|244)" indexation
	
		# Declaration de la machine pour generation du cache statique
		SetEnvIf Remote_Addr "(127\.0\.0\.1|10\.135\.255\.45)" local
		
		# Sont autorises en anonyme "tout le monde" sauf les pays avec authentification intégrée.
		Allow from env=local env=indexation !env=pays
	</IfModule>
	Satisfy any
</Location>

- page 2 de 54 -