Optimisation et sécurisation du code de PrestaShop<br />
<ul><li>GroupeSitti (www.sitti.fr)
Équipe de 6 développeurs & intégrateurs
400 PrestaShop installés – versions de 0.9.6 a 1.3.1
Hébergementmutualisé – cluster de 10+ machines (load balancers, serveurs web, serveurs de fichiers, serveurs de bdd) </li>...
Les 4 pilliers de la performance<br /><ul><li>Infrastructure(serveurs, bases de données)
Focus sur : Code côtéserveur (1ère couche, PHP + SQL)
Réseau, protocolede transport
Code côté client (2eme couche: HTML+ CSS + Javascript)</li></li></ul><li>(…) Programmers waste enormous amounts of time th...
<ul><li>Votre architecture doitêtreéfficace(une bonne planification)
Vousdevez coder en utilisant les meilleurespratiques(ne pas faire de choses stupides ;))
Préférezplutôt la  "maintabilité" et la lisibilité du code aux performances
Lorsquela performance n'estpas critique (ex. systèmes temps réel, sites à fort trafic), vouspourrezl'améliorerdansdes vers...
<ul><li>Mesurezd'abord ! Vousdevezconnaitre les points critiques, les goulotsd'étranglement
Mesurezdifférents scénarii et configurations
Passer sous Linux? Test Linux, non Win. => différences
Aurez-vous plus de 10000 produits? Tester votre module avec une base de 10000 élémentset non 5
Est-ceque 1% d'amélioration justifie un travail additionnel?
Et 5%? 10%?
Essayez  d'estimer le coût de développement vs. coûtmatériel : parfoisc'est moins cherd'ajouter de la mémoire vive</li></u...
Les petits gains de performance<br />En utilisant (int) au lieu de intval () estpeutêtre 4x plus rapidemaisle gain global ...
Charge serveur:<br />ab, siege, multi-mechanize ...<br />Charge de la base de donnée:<br />MySql Slow Query Log, mysql pro...
Serveur:<br />Tâchedifficile, voire impossible sur des hébergementsmutualisés<br />Demandez à votre admin sys ;)<br />Le C...
Ramdisk pour les fichierssouventappelés (frameworks, configuration, tmp) </li></ul>Le point généralement critique: I/O (fi...
Chaque appel aux fichiers a un coût qui dépend de l'OS, du système de fichiers et du nombre de fichiers<br />Toujoursutili...
Chaqueproduit a une image, chaque image a 6 vignettes
Debian+ Apache 1.3 (hébergement mutualisé, nfs):</li></ul>Système de fichiers<br /># Files<br />Glob('*') exec. in sec.<br...
Fractionnement du contenu des répertoires<br />img/p/534-189-small.jpg<br />	devient <br />img/p/small/534-189.jpg<br />Le...
Base de donnée<br /><ul><li>Vérifiezvos indexes!
Attention aux jointures!</li></ul>SELECT * FROM ps_feature` f LEFT JOIN ps_feature_lang` fl ON ( f.`id_feature` = fl.`id_f...
Utilisez des VIEWs à la place de SELECTs compliqués<br />Avezvousbesoin de ps_connections& ps_connections_page?<br />Si vo...
<ul><li>Grosproblème, requêtes non uniques: </li></ul>1.3.10, simulation du processus de commande:<br />Index – recherche ...
Requêtes répétées<br />
Requêtes non optimisées<br />
<ul><li>Le mieuxestd'utiliser un proxy MySQL oumemcached
Utiliserun cache en interne:</li></ul>Peutêtreplacé en local ou global<br /><ul><li>PrestaShop utilisepartiellementun cach...
Prochain SlideShare
Chargement dans…5
×

Performance et optimisation de PrestaShop

8 383 vues

Publié le

Atelier "Performance et optimisation de PrestaShop" - Barcamp² PrestaShop - 22 juin 2010

Publié dans : Business
  • Soyez le premier à commenter

Performance et optimisation de PrestaShop

  1. 1. Optimisation et sécurisation du code de PrestaShop<br />
  2. 2. <ul><li>GroupeSitti (www.sitti.fr)
  3. 3. Équipe de 6 développeurs & intégrateurs
  4. 4. 400 PrestaShop installés – versions de 0.9.6 a 1.3.1
  5. 5. Hébergementmutualisé – cluster de 10+ machines (load balancers, serveurs web, serveurs de fichiers, serveurs de bdd) </li></ul>A propos de nous ?<br />
  6. 6. Les 4 pilliers de la performance<br /><ul><li>Infrastructure(serveurs, bases de données)
  7. 7. Focus sur : Code côtéserveur (1ère couche, PHP + SQL)
  8. 8. Réseau, protocolede transport
  9. 9. Code côté client (2eme couche: HTML+ CSS + Javascript)</li></li></ul><li>(…) Programmers waste enormous amounts of time thinking about, or worrying about, the speed of noncritical parts of their programs, and these attempts at efficiency actually have a strong negative impact when debugging and maintenance are considered. We should forget about small efficiencies, say about 97% of the time: 1<br />premature optimization is the root <br />of all evil.<br />Yet we should not pass up our opportunities in that critical 3%. A good programmer will not be lulled into complacency by such reasoning, he will be wise to look carefully at the critical code; but only after that code has been identified.<br />Très important(Donald E. Knuth)<br />
  10. 10. <ul><li>Votre architecture doitêtreéfficace(une bonne planification)
  11. 11. Vousdevez coder en utilisant les meilleurespratiques(ne pas faire de choses stupides ;))
  12. 12. Préférezplutôt la  "maintabilité" et la lisibilité du code aux performances
  13. 13. Lorsquela performance n'estpas critique (ex. systèmes temps réel, sites à fort trafic), vouspourrezl'améliorerdansdes versions ultérieures</li></ul>Quand faut-il optimiser ?<br />
  14. 14. <ul><li>Mesurezd'abord ! Vousdevezconnaitre les points critiques, les goulotsd'étranglement
  15. 15. Mesurezdifférents scénarii et configurations
  16. 16. Passer sous Linux? Test Linux, non Win. => différences
  17. 17. Aurez-vous plus de 10000 produits? Tester votre module avec une base de 10000 élémentset non 5
  18. 18. Est-ceque 1% d'amélioration justifie un travail additionnel?
  19. 19. Et 5%? 10%?
  20. 20. Essayez  d'estimer le coût de développement vs. coûtmatériel : parfoisc'est moins cherd'ajouter de la mémoire vive</li></ul>Quand faut-il optimiser ?<br />
  21. 21. Les petits gains de performance<br />En utilisant (int) au lieu de intval () estpeutêtre 4x plus rapidemaisle gain global estnégligeable (saufsivousêtesFacebook)<br /><ul><li>Le code exécutéuneseulefois</li></ul>Tools::setCookieLanguagepourraitêtreaméliorée, maisellen'estexécutéequ'uneseulefois<br /><ul><li>Les optimisationsmythiques ( ” vs ' )</li></ul>Mais”$a $b $c” … est plus rapideque $a.” ”.$b.” ”.$c<br /> Qu'est-ce qu'il n'est pas nécessaire d'optimiser ?<br />
  22. 22. Charge serveur:<br />ab, siege, multi-mechanize ...<br />Charge de la base de donnée:<br />MySql Slow Query Log, mysql proxy, ...<br />EXPLAIN <br />PHP:<br />xdebug, dbg, xhprof ...<br />Network / client side<br />Yslow, Firebug, WebKitinspector, dynaTrace AJAX, fiddler, Google webmaster tools<br />Comment mesurer ?<br />
  23. 23. Serveur:<br />Tâchedifficile, voire impossible sur des hébergementsmutualisés<br />Demandez à votre admin sys ;)<br />Le CPU est rarement un point critique, il indique généralement des problèmes avec le code non-optimisé<br />La RAM est bon marché mais pas illimitée - attention à la consommation de mémoire des scripts<br /><ul><li>Problèmetypique : gd + jpg -> 2 Mb surdisque, 33 Mb décompressésen mémoire
  24. 24. Ramdisk pour les fichierssouventappelés (frameworks, configuration, tmp) </li></ul>Le point généralement critique: I/O (filesystem, dbs)<br />Amélioration de l’infrastructure<br />
  25. 25. Chaque appel aux fichiers a un coût qui dépend de l'OS, du système de fichiers et du nombre de fichiers<br />Toujoursutiliser des cheminsabsolusdans require/ include<br /><ul><li>Les performances peuventcommencer à se dégradersivousavez plus de 50 000 fichiersdans un répertoire
  26. 26. Chaqueproduit a une image, chaque image a 6 vignettes
  27. 27. Debian+ Apache 1.3 (hébergement mutualisé, nfs):</li></ul>Système de fichiers<br /># Files<br />Glob('*') exec. in sec.<br />file_exists / sec.<br />1000<br />4,59<br />36000<br />11000<br />13,30<br />21000<br />65000<br />55,81<br />1475<br />122000<br />142,16<br />718<br />
  28. 28. Fractionnement du contenu des répertoires<br />img/p/534-189-small.jpg<br /> devient <br />img/p/small/534-189.jpg<br />Lecture transparente avec un .htaccess<br />RewriteRule (.*)/p/([^/]*)home.jpg $1/p/home/$2home.jpg<br />Ecriture transparente en php <br /> if (!imageResize($file, $dir.$imageType['name'].'/'.$language['iso_code'].'-default- '.stripslashes($imageType['name']).'.jpg', ...<br />Solution<br />
  29. 29. Base de donnée<br /><ul><li>Vérifiezvos indexes!
  30. 30. Attention aux jointures!</li></ul>SELECT * FROM ps_feature` f LEFT JOIN ps_feature_lang` fl ON ( f.`id_feature` = fl.`id_feature` AND fl.`id_lang` = 1) WHERE f.`id_feature` = 1SELECT * FROM ps_feature_lang` fl WHER fl.`id_feature` = 1 AND fl.`id_lang` = 1 <br />Version<br />Tables<br />Columns<br />Without index<br />1.1.0.5<br />88<br />458<br />50<br />1.2.0.5<br />134<br />670<br />50<br />1.3.10<br />135<br />679<br />2 (cool! :)<br />
  31. 31. Utilisez des VIEWs à la place de SELECTs compliqués<br />Avezvousbesoin de ps_connections& ps_connections_page?<br />Si vousavez un traficconséquent, celles-cipeuventgrossir de plus de 10+ Mb / jour<br />Base de donnée<br />
  32. 32. <ul><li>Grosproblème, requêtes non uniques: </li></ul>1.3.10, simulation du processus de commande:<br />Index – recherche – connexion – commande(11 pages au total)<br />3001 requêtes SQL, maisseulement 1314 uniques! (44%) <br />PHP - SQL<br />
  33. 33. Requêtes répétées<br />
  34. 34. Requêtes non optimisées<br />
  35. 35. <ul><li>Le mieuxestd'utiliser un proxy MySQL oumemcached
  36. 36. Utiliserun cache en interne:</li></ul>Peutêtreplacé en local ou global<br /><ul><li>PrestaShop utilisepartiellementun cache local
  37. 37. Simple à implémenter, à régler et .... à oublier
  38. 38. Chaqueméthode/classedoitmettre en cache sespropresrequêtes</li></ul>Solutions<br />
  39. 39. static public function getCurrency($id_currency){<br /> return Db::getInstance()->getRow('SELECT * FROM `'._DB_PREFIX_.'currency` WHERE `deleted` = 0 AND `id_currency` = '.intval($id_currency));<br /> }<br />static public functiongetCurrency($id_currency){<br /> if (!isset(self::$_cache[$id_currency])) {<br /> self::$_cache[$id_currency] = Db::getInstance()->getRow('SELECT * FROM `'._DB_PREFIX_.'currency` WHERE `deleted` = 0 AND `id_currency` = '.intval($id_currency));<br /> }<br /> return self::$_cache[$id_currency];<br /> }<br />Cache local<br />
  40. 40. <ul><li>Directementdans la classe MySQL 
  41. 41. Récupèretous les résultats de requêtes
  42. 42. Difficilea implémenter :
  43. 43. Certainesrequêtespeuventêtrerépétéesmaisrenvoient des résultatsdifférents (->cart)
  44. 44. Nous avonsbesoind'unesorte de "blacklist"
  45. 45. Uneunique implémentation rend la maintenance du code plus simple
  46. 46. Devraitêtreimplémentécommeunefonctionnalité du coeur</li></ul>Cache global<br />
  47. 47. <ul><li>Regexpestcoûteux, et compliqué</li></ul> return preg_match('/^[a-z0-9!#$%&'*+/=?^`{}|~_-]+[.a-z0- 9!#$%&'*+/=?^`{}|~_-]*@[a-z0-9]+[._a-z0-9-]*.[a-z0-9]+$/ui', $email);<br /><ul><li>Utilisez des filtres(> PHP 5.2):</li></ul> return filter_var($email, FILTER_VALIDATE_EMAIL);<br /><ul><li>Effectuezdes tests avant de lancer des expressions régulièresgourmandes:</li></ul> if (strpos($email, '@')!==false)<br /><ul><li>Utilisezstr_replace à la place de preg_replace:</li></ul>preg_replace('/"/', '&quot;', $value)<br /> Faster: str_replace('"', '&quot;', $value)<br />Eviter les regexpSome people, when confronted with a problem, think  “I know, I'll use regular expressions.” Now they have two problems. (jwz)<br />
  48. 48. <ul><li>Eviterles sous-masques capturants</li></ul>return preg_match('/^([^<>{}]|<br />)*$/ui', $text);<br /> return preg_match('/^(?:[^<>{}]|<br />)*$/ui', $text);<br />?: = sous-masque non-capturant (pas d'allocation de mémoire!)<br /><ul><li>Vousn'êtes pas obligéd'utiliserregexp</li></ul>return trim($table,'a..zA..Z0..9_') == '';<br />estidentiqueà<br /> return preg_match('/^[a-z0-9_-]+$/ui', $table);<br />maisestjusqu'à 2 fois plus rapide!<br />Eviter les regexp(2)<br />
  49. 49. foreach($cart->getProducts() as $product)<br />   if ($orderStatus->logable)<br />      ProductSale::addProductSale(intval($product['id_product']), intval($product['cart_quantity']));<br />Devraitêtre:<br />if ($orderStatus->logable)<br />     foreach($cart->getProducts() as $product)<br />            ProductSale::addProductSale(intval($product['id_product']), intval($product['cart_quantity']));<br />(pas utile de tester le if  à chaqueitérations'iln'y a pas de changement)<br />Bien placer les conditions<br />
  50. 50. // Send an e-mail to customer<br />if ($id_order_state!= _PS_OS_ERROR_ AND $id_order_state!= _PS_OS_CANCELED_ AND $customer->id)<br />{<br />$invoice = new Address(intval($order->id_address_invoice));<br />$delivery = new Address(intval($order->id_address_delivery));<br />$carrier = new Carrier(intval($order->id_carrier));<br />$delivery_state= $delivery->id_state ? new State(intval($delivery->id_state)) : false;<br />$invoice_state= $invoice->id_state ? new State(intval($invoice->id_state)) : false;<br />$data = array( <br />'{firstname}' => $customer->firstname,<br />'{lastname}' => $customer->lastname,<br />'{email}' => $customer->email,<br />'{delivery_company}' => $delivery->company,<br />'{delivery_firstname}' => $delivery->firstname,<br />'{delivery_lastname}' => $delivery->lastname,<br />'{delivery_address1}' => $delivery->address1,<br />'{delivery_address2}' => $delivery->address2,<br />'{delivery_city}' => $delivery->city,<br />'{delivery_postal_code}' => $delivery->postcode,<br />'{delivery_country}' => $delivery->country,<br />'{delivery_state}' => $delivery->id_state ? $delivery_state->name : '',<br />'{delivery_phone}' => $delivery->phone,<br />'{delivery_other}' => $delivery->other,<br />'{invoice_company}' => $invoice->company,<br />'{invoice_firstname}' => $invoice->firstname,<br />'{invoice_lastname}' => $invoice->lastname,<br />'{invoice_address2}' => $invoice->address2,<br />'{invoice_address1}' => $invoice->address1,<br />'{invoice_city}' => $invoice->city,<br />'{invoice_postal_code}' => $invoice->postcode,<br />'{invoice_country}' => $invoice->country,<br />'{invoice_state}' => $invoice->id_state ? $invoice_state->name : '',<br />'{invoice_phone}' => $invoice->phone,<br />'{invoice_other}' => $invoice->other,<br />{order_name}' => sprintf("#%06d", intval($order->id)),<br />'{date}' => Tools::displayDate(date('Y-m-d H:i:s'), intval($order->id_lang), 1),<br />'{carrier}' => (strval($carrier->name) != '0' ? $carrier->name : Configuration::get('PS_SHOP_NAME')),<br />'{payment}' => $order->payment,<br />Voyezvous le problème ?<br />
  51. 51. '{products}' => $productsList,<br />'{discounts}' => $discountsList,<br />'{total_paid}' => Tools::displayPrice($order->total_paid, $currency, false, false),<br />'{total_products}' => Tools::displayPrice($order->total_paid - $order->total_shipping - $order->total_wrapping + $order->total_discounts, $currency, false, false),<br />'{total_discounts}' => Tools::displayPrice($order->total_discounts, $currency, false, false),<br />'{total_shipping}' => Tools::displayPrice($order->total_shipping, $currency, false, false),<br />'{total_wrapping}' => Tools::displayPrice($order->total_wrapping, $currency, false, false));<br />if (is_array($extraVars))<br /> $data = array_merge($data, $extraVars);<br />// Join PDF invoice<br />if (intval(Configuration::get('PS_INVOICE')) AND Validate::isLoadedObject($orderStatus) AND $orderStatus->invoice AND $order->invoice_number)<br />{<br /> $fileAttachment['content'] = PDF::invoice($order, 'S');<br /> $fileAttachment['name'] = Configuration::get('PS_INVOICE_PREFIX', intval($order->id_lang)).sprintf('%06d', $order->invoice_number).'.pdf';<br /> $fileAttachment['mime'] = 'application/pdf';<br />}<br />else<br /> $fileAttachment= NULL;<br />if ($orderStatus->send_email AND Validate::isEmail($customer->email))<br /> Mail::Send(intval($order->id_lang), 'order_conf', 'Order confirmation', $data, $customer->email, $customer->firstname.' '.$customer->lastname, NULL, NULL, $fileAttachment);<br />$this->currentOrder = intval($order->id);<br />return true;<br />}<br />$this->currentOrder = intval($order->id);<br />return true;<br />
  52. 52. Ici, on prépare tout l'envoi du mail avec le pdf en pièce jointemêmesi on ne l'envoie pas.<br /> “Every times you do it, a little kitten dies”<br />Conditions non optimisés<br />
  53. 53. <ul><li>Considérezl'export/import commeopérationslourdes
  54. 54. Pour le flux BeezUP, nous utilisonsObjectModel
  55. 55. Celafonctionnemais nous avons 17 requêtes SQL par produitpour collectertoutes les informations (product, features, attributes, images...)
  56. 56. Ok pour 100 produits,... et pour 100 000 ?
  57. 57. Risqué sil’ondoit le générersurdemande
  58. 58. La tâcheCronprépare le fichieravantque le robot n'yaccède
  59. 59. Le robot accède au xml en cache</li></ul>Utilisez un CRON pour générer le cache<br />
  60. 60. <ul><li>L'envoide contenucompresséréduit la bandepassante
  61. 61. Pour du contenustatique, utilisezmod_gzip/ mod_deflate
  62. 62. Pour les fichiersphputilisez :
  63. 63. Dansinit.phpchangez:</li></ul>ob_start();<br /> to:<br />ob_start('ob_gzhandler');<br />And enjoy! (page index.php: avant: 21.1 kb, après: 5.3 kb)<br />(mais, le mieuxestd'utiliser zlib.* dans php.ini)<br /><ul><li>Minimisez vos js et css placez-les dans un fichier unique </li></ul>Réseau<br />
  64. 64. <ul><li>Utilisez un CDN (Content Delivery Network) pour vos .js
  65. 65. Utilisez le cache (mod_expires, Etags) pour les fichiersstatiquescomme les images</li></ul>Vouspouvez le faire dans un .htaccessoudanshttpd.conf<br />ExpiresActiveOn<br />ExpiresDefault"access plus 15 days“<br />ExpiresByTypeimage/gif A2592000<br />Réseau<br />
  66. 66. <ul><li>Utilisez les sélecteursDOM simples. Evitezles recherchescompliquéesUtilisez des ids si possible.
  67. 67. Jqueryn'est pas toujours plus rapide.Utilisez des méthodes natives
  68. 68. Evitezd’utiliser du HTML / XML en résultat AJAX; utilisezplutôt du JSON. Vouspouvezréduire la quantité de données de l'ordre de 75%!</li></ul>Coté client<br />
  69. 69. Botnets<br />Concurrents malhonnêtes<br />Acheteurs frauduleux<br />…<br />Pourquoi se protéger ?<br />
  70. 70. Injection SQL<br />CSRF<br />XSS<br />Path transversal<br />…<br />Differents types d’attaque<br />
  71. 71. Permet d’intéragir avec la base de donnée<br />Sécuriseztoutesvos variables avantl’utilisationdans des requêtes SQL!<?php<br /> ......<br /> $order_detail = Db::getInstance()->ExecuteS('<br /> SELECT * <br /> FROM .'_DB_PREFIX_.'order_detail<br /> WHERE id_order='.(int)$_GET['id_order']<br /> AND payment=''.pSQL($_GET['payment']).''');<br />Injection SQL<br />
  72. 72. Exploite votre identité auprès de votre site<br />Utilisez des tokens<br />Utilisezuneauthentificationdans les paramètres GET ou POST<br />index.php?tab=AdminOrders&token=e84b3fda0b04b922b3bc27b08d4fe136<br />CSRF<br />
  73. 73. Injection de code HTML / JavaScript dans la page<br />Sécuriseztoutesvos variables avantl’affichage!<br /><input type="text" name="lastname" value="{$smarty.post.lastname|htmlentities}" /><br />preg_replace('/.*script/ui', '', $_POST['lastname']);<br />preg_replace('/.*onmousedown|onmousemove|onmmouseup|onmouseover|onmouseout|onload|onunload|onfocus|onblur|onchange|onsubmit|ondblclick|onclick|onkeydown|onkeyup|onkeypress|onmouseenter|onmouseleave/ui', '', $_POST['lastname']);<br />...<br />XSS<br />
  74. 74. Accès à des données non autorisées<br />Sécuriseztoutesvos variables avant de charger le fichier<br />Vérifiezl’extention du fichier<br />include (dirname(__FILE__).'/mails/'. preg_replace(‘/.{2,}/', '.', Tools::getValue('mail')).'html'); <br />Path transversal<br />
  75. 75. ?<br />Questions<br />

×