Flex HTTPService performance avec MySQL et PHP : XML : 1 - AMF : 0
English version - Traduire/Translate (by Google) : Espagnol - Deutsch
Cet article présente différentes méthodes pour améliorer les performances de lecture du XML à travers un HTTPService.
Voir les sources - View the sources
Mise à jour / Updated : 4 mai 2010 voir modifications
PerfReadXML.zip - 6 081 Ko (Flex 3 Export Project with Zend Framework 1.10.4 minimal)
PerfReadXMLnozend.zip - 500 Ko (Flex 3 Export Project without Zend Framework)
Cet article compare 9 méthodes pour lire des données constituées de 1 000 à 10 000 enregistrements pour alimenter un datagrid. Un enregistrement est un customer constitué d'un id, d'un prénom, d'un nom, de son sexe, d'une adresse et d'une ville.
Les tests utilisent une base de données MySQL (pour stocker les données) et le langage PHP pour écrire les Web-Services sur un serveur Apache.
Les optimisations concernent la partie Flex et le flux entre le serveur et le navigateur : l'accès aux données en MySql est le même pour tous les exemples.
Tous les tests sont réalisés sur un serveur mutualisé (un 240plan chez OVH - phpinfo) Apache qui permet la manipulation du fichier .htaccess. Si vous avez un serveur dédié, il est généralement conseillé d'effectuer les modifications dans le fichier de configuration httpd.conf
La méthode XML "simple" avec des noeuds : e4x_node
Pour représenter les données, la méthode simple permet de décrire les données sous forme de noeuds XML.
Un customer est représenté de la manière suivante :
<customer>
<id>1</id>
<lastname>Deschamps</lastname>
<firstname>Amandine</firstname>
<sex>1</sex>
<address>23, rue de la Lancette</address>
<city>Yerres</city>
</customer>
Pour Flex, le HTTPService lit les données, les transforme en objets Flex et les affiche dans un Datagrid à travers un dataProvider (ArrayCollection).
Inconvénient : Pour une donnée (un nom de client), on utilise des noms de noeuds qui sont (parfois) plus longs que la donnée elle-même. Par exemple : on utilise <lastname>Deschamps</lastname>
(30 caractères) pour une donnée Deschamps
(9 caractères).
La méthode XML avec attributs : e4x_attribute
Dans cette méthode, on utilise des attributs (au lieu de noeuds) pour les données.
Un client est représenté de la manière suivante :
<customer id="1" lastname="Deschamps" firstname="Amandine" sex="1" address="23, rue de la Lancette" city="Yerres"/>
Inconvénient : Pour une donnée (un nom de client), on utilise des noms d'attributs qui sont plus longs que la donnée elle-même. Par exemple : on utilise lastname="Deschamps"
(20 caractères) pour une donnée Deschamps
(9 caractères).
La méthode XML avec des attributs courts : e4x_short_attribute
Dans cette méthode, on utilise des attributs avec des noms courts : 1 à 2 lettres.
Un client est représenté de la manière suivante :
<c id="1" ln="Deschamps" fn="Amandine" s="1" ad="23, rue de la Lancette" ci="Yerres"/>
Activer la compression du fichier XML
Lorsque l'on a 1000 à 10000 enregistrements, l'idée est de compresser le fichier XML entre le serveur PHP et le navigateur. C'est une première optimisation proposée sur les serveurs (par exemple par pageSpeed de Google)
Pour ceci, on va utiliser la compression dynamique d'Apache. Comme on est sur un serveur mutualisé, on définit un fichier .htaccess au niveau du répertoire des web-services pour activer le mode Deflate.
Dans notre cas, on active la compression pour les types text/xml et application/xml et on désactive la compression pour les images :
<IfModule mod_deflate.c>
SetOutputFilter DEFLATE
# Insert filter on selected content types only
AddOutputFilterByType DEFLATE text/xml
AddOutputFilterByType DEFLATE application/xml
# 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
</IfModule>
On va utiliser les 3 méthodes précédentes pour voir l'impact sur les performances :
e4x_node_gzip
e4x_attribute_gzip
e4x_short_attribute_gzip
Comment vérifier si la compression est active ou non ?
Pour ceci, on utilise Firefox et l'extension HttpFox qui est analyseur HTTP d'entête et de réponse.
Il suffit de lancer manuellement le web-service et de vérifier la réponse avec cette extension :
http://www.inwayvideo.com/xmlperf/ws_gzip/gzipcustomerAttribute.php
Vous devez voir gzip dans Content-Encoding dans Response Header
Content type application/xml, text/xml et cache du navigateur : e4x_cache_attribute_gzip
Si vos données évoluent peu (toutes les 4 heures ou tous les jours ou semaines ou mois), l'idée est d'utiliser le cache du navigateur pour stocker les résultats d'une requête.
Un HTTPService en mode POST force un rafraichissement de la requête/page : les résultats ne seront pas gardés dans le cache du navigateur.
L'idée est donc d'utiliser le mode GET. Mais, il y a une subtilité en Flex. Si vous utilisez le contentType (ou MIME type) application/xml, Flex transforme le GET en POST (et donc empêche le navigateur de stocker le résultat dans le cache). Voici la méthode send de mx.rpc.http.HTTPService (dans Flex 3 et SDK 3.3) ou sendBody de mx.rpc.http.AbstractOperation (dans Flash Builder 4 et le SDK 3.5.0)
if (contentType == CONTENT_TYPE_XML && message.method == HTTPRequestMessage.GET_METHOD)
message.method = HTTPRequestMessage.POST_METHOD;
Pour activer le cache du navigateur, il faut donc utiliser la méthode GET avec le contentType text/xml.
Si vous êtes sur un serveur mutualisé, votre hébergeur peut avoir activé la méthode gzip selon le MIME type.
Chez OVH, la compression gzip est activée pour application/xml mais n'est pas activée pour text/xml.
Pour activer la compression pour les types text/xml et application/xml et avoir un cache de 30 minutes, on définit le fichier .htaccess du répertoire des web-services de la façon suivante :
<IfModule mod_deflate.c>
SetOutputFilter DEFLATE
# Insert filter on selected content types only
AddOutputFilterByType DEFLATE text/xml
AddOutputFilterByType DEFLATE application/xml
</IfModule>
<IfModule mod_expires.c>
#CACHE ExpiresActive on
#xml
ExpiresByType text/xml "access plus 30 minutes"
#tout le reste
#ExpiresDefault "modification plus 2 years"
ExpiresDefault "access plus 2 days"
</IfModule>
C'est la méthode la plus rapide. Il faut cliquer deux fois sur le bouton. Lors du second clique, la lecture s'effectue dans le cache.
Méthode Amfphp : amfphp
Amfphp a été une des premières solutions open-source utilisant le protocole AMF (Action Message Format) sur PHP.
Malheureusement, ce projet n'est plus maintenu. Amfphp est de nouveau maintenu. On utilise la version officielle. L'intérêt de cette méthode est d'utiliser un class mapping entre PHP et Flex (pour éviter l'encodage et le décodage entre les différents langages).
Dans notre exemple, on utilise la version amfphp 1.9 du 2010-02-02.
Méthode Zend-AMF : zend_amf
Une autre méthode est d'utiliser Zend-AMF. Comme dans Amfphp, on utilisera le class mapping entre PHP et Flex.
Dans notre exemple, on utilise le Zend Frameword 1.9.7 1.10.4
Malheureusement, c'est une "fausse" bonne idée. Les performances ne sont pas au rendez-vous.
Pour essayer d'améliorer les performances, j'ai repris une autre méthode (zend_flex4) qui s'inspire du code généré dans Flex 4 et qui donne de meilleures performances. En effet, dans le cas de Zend-AMF, il suffit de caster en PHP les variables avec le bon type pour améliorer les performances (si vous utilisez la méthode du cast dans amfphp, cela n'a pas d'influence sur les performances contrairement à Zend-AMF).
Pour information, la version 1.10.4 de Zend contient un patch pour améliorer les performances de Zend AMF :
http://framework.zend.com/changelog/1.10.4
Les performances sont effectivement améliorées mais sont en dessous de AMFPHP et de XML.
Le Zend Framework est très intéressant lorsque l'on réalise des applications en PHP mais sur cet exemple précis les performances ne sont pas au rendez-vous sur un serveur mutualisé.
Les problèmes de performances du Zend Framework sont connus :
http://framework.zend.com/issues/browse/ZF-7493
Si vous êtes un expert Zend ou AMF, je suis preneur de vos conseils pour améliorer les performances.
Résultats
Vous pouvez tester directement l'application sur notre serveur et vous verrez que la méthode la plus rapide est la méthode e4x_short_attribute_gzip.
En effet, le Flash Player possède un lecteur très efficace de XML en utilisant la méthode e4x.
L'utilisation du cache du navigateur améliore encore les performances avec la méthode e4x_cache_attribute_gzip (mais c'est normal : on utilise le cache du navigateur).
Comment utiliser ceci dans une application RIA ?
En fait, l'idée est de lire de nombreux enregistrements lors du démarrage de l'application et d'utiliser de façon "intelligente" la méthode "filter" sur les ArrayCollection pour visualiser les informations.
De plus, lors de la conception, nous créons des tables supplémentaires dans la base de données pour lire plus efficacement les données.
Installation sur votre serveur
Vous pouvez tester les différentes méthodes sur votre serveur.
Pour ceci :
- installer le répertoire xmlperf sur votre serveur PHP (PHP5 est requis).
- configurer le fichier xmlperf/xmlperfDefine.php
- pour les accès à MySQL (DATABASE_SERVER, DATABASE_USERNAME, DATABASE_PASSWORD, DATABASE_NAME)
- le nombre d'enregistrements maximum à générer : MAXIMUM_RECORDS (10000 par défaut).
- le répertoire du Zend Framework : ZEND_LIBRARY (path relatif au répertoire ws_zend)
Par défaut, le Zend Framework 1.10.4 minimal est livré dans xmlperf/zf
- lancer dans votre navigateur : http://www.myserver.com/xmlperf/generateDb/generateCustomer.php
Ceci va générer MAXIMUM_RECORDS dans votre base de données.
Le script dure environ entre 30 secondes et 1 minute.
- lancer dans votre navigateur : http://www.myserver.com/xmlperf/flex
Ceci permet de tester les différentes méthodes.
Les sources
Les sources Flex et PHP sont livrés.
Flex
Si vous installez les sources sur votre serveur, il faut changer la variable serverURL dans initApp et les deux variables uri dans le fichier services-config.xml (pour amfphp et le Zend Framework).
Pour les méthodes XML, on utilise une classe Customer. Pour le class-mapping (de AMF), on utilise une classe CustomerVO.
PHP
Chaque méthode utilise un répertoire préfixé par ws (pour web-services) et contient les différents fichiers appelés dans les HTTPservice. On a donc :
ws_nogzip : pour les méthodes sans compression avec les fichiers :
- customerNode.php (liste des customers avec des noeuds XML),
- customerAttribute.php (liste des customers avec des attributs XML)
- customerShortAttribute.php (liste des customers avec des attributs XML courts).
ws_gzip : pour les méthodes avec compression
ws_amfphp : pour amfphp.
On a ajouté le service dans xmlperf/ws_amfphp/services : CustomerService.php et inway/CustomerVO.php.
On a modifié dans xmlperf/ws_amfphp/globals.php la variable $voPath (pour le class-mapping)
et dans xmlperf/ws_amfphp/gateway.php le charset UTF-8 : $gateway->setCharsetHandler("iconv","UTF-8","UTF-8"); (on suppose que iconv est installé sur le serveur).
ws_zend : pour Zend AMF
On a ajouté le service CustomerService.php dans gateway.php (qui effectue aussi le class-mapping).
Quelques références et liens
census : Ajax and Flex Data Loading Benchmarks de James Ward qui montrait que AMF était plus rapide que e4x (mais en utilisant la méthode avec noeuds et sans gzip)
http://www.jamesward.com/2007/04/30/ajax-and-flex-data-loading-benchmarks/
Zend AMF : http://framework.zend.com/download/latest (prendre le zend framework 1.x.x minimal en bas de page)
Blog de Wade Arnold auteur de Zend AMF
http://wadearnold.com/blog/Get to know Flex and Zend_Amf (Zend developper zone)
Data-centric Adobe Flash Builder development with the Zend Framework (Zend developper zone)
Amfphp : http://www.amfphp.org/
Sean Christmann : Optimizing Adobe AIR for Code Execution, Memory, and Rendering
MySQL Performance Blog : http://www.mysqlperformanceblog.com
Optimisation et configuration serveur Apache : http://www.askapache.com/
Version et release
version 1.02 - 4 Mai 2010
- updated to Zend Framework version 1.10.4 (Released 2010-04-28)
This version has a patch for Zend AMF performance issue
- updated to amfphp 1.9 (Released 2010-02-02)
version 1.01 - 14 January 2010
- updated to Zend Framework version 1.9.7
version 1.00 - 10 January 2010
- Initial version with :
. amfphp 1.9 beta2 - 2008-01-20
. Zend Framework 1.9.6