Ajouter la mise en cache à une couche d'accès aux données

Les pages Web dynamiques sont excellentes. vous pouvez adapter la page résultante à votre utilisateur, afficher l'activité d'autres utilisateurs, proposer différents produits à vos clients en fonction de leur historique de navigation, etc. Mais plus un site Web est dynamique, plus vous aurez probablement à effectuer de requêtes dans la base de données. Malheureusement, ces requêtes de base de données occupent la plus grande partie de votre temps d'exécution.

Dans ce didacticiel, je vais vous montrer comment améliorer les performances, sans exécuter de requêtes inutiles supplémentaires. Nous allons développer un système de mise en cache des requêtes pour notre couche de données avec un coût de programmation et de déploiement réduit.

1. La couche d'accès aux données

L'ajout transparent d'une couche de mise en cache à une application est souvent difficile en raison de la conception interne. Avec les langages orientés objet (comme PHP 5), c'est beaucoup plus facile, mais une mauvaise conception peut encore compliquer les choses..

Dans ce tutoriel, nous définissons notre point de départ dans une application qui effectue tous ses accès à la base de données via une classe centralisée à partir de laquelle tous les modèles de données héritent des méthodes d'accès de base à la base de données. Le squelette de cette classe de départ ressemble à ceci:

 class model_Model protected static $ DB = null; function __construct ()  function protégée doStatement ($ query)  function protégée quoteString ($ value) 

Mettons-le en œuvre étape par étape. Premièrement, le constructeur qui utilisera la bibliothèque PDO pour s’interfacer avec la base de données:

 function __construct () // se connecter à la base de données si nécessaire if (is_null (self :: $ DB)) $ dsn = app_AppConfig :: getDSN (); $ db_user = app_AppConfig :: getDBUser (); $ db_pass = app_AppConfig :: getDBPassword (); self :: $ DB = new PDO ($ dsn, $ db_user, $ db_pass); self :: $ DB-> setAttribute (PDO :: ATTR_ERRMODE, PDO :: ERRMODE_EXCEPTION); 

Nous nous connectons à la base de données en utilisant la bibliothèque PDO. Pour les informations d'identification de la base de données, j'utilise une classe statique nommée "app_AppConfig" qui centralise les informations de configuration de l'application..

Pour stocker la connexion à la base de données, nous utilisons un attribut statique ($ DB). Nous utilisons un attribut statique afin de partager la même connexion avec toutes les instances de "model_Model" et, à cause de cela, le code de connexion est protégé par un if (nous ne voulons pas nous connecter plus d'une fois)..

Dans la dernière ligne du constructeur, nous définissons le modèle d'erreur d'exception pour PDO. Dans ce modèle, pour chaque erreur détectée par le PDO, il génère une exception (classe PDOException) au lieu de renvoyer des valeurs d'erreur. C'est une question de goût, mais le reste du code peut être gardé plus propre avec le modèle exceptionnel, ce qui est bon pour ce tutoriel..

L'exécution de requêtes peut être très complexe, mais dans cette classe, nous avons adopté une approche simple avec une seule méthode doStatement ():

 fonction protégée doStatement ($ query) $ st = self :: $ DB-> query ($ query); if ($ st-> columnCount ()> 0) return $ st-> fetchAll (PDO :: FETCH_ASSOC);  else return array (); 

Cette méthode exécute la requête et renvoie un tableau associatif avec l'ensemble des résultats (le cas échéant). Notez que nous utilisons la connexion statique (self :: $ DB). Notez également que cette méthode est protégée. En effet, nous ne souhaitons pas que l'utilisateur exécute des requêtes arbitraires. Au lieu de cela, nous fournirons des modèles concrets à l'utilisateur. Nous verrons cela plus tard, mais avant implémentons la dernière méthode:

 fonction protégée quoteString ($ value) return self :: $ DB-> quote ($ value, PDO :: PARAM_STR); 

La classe "model_Model" est une classe très simple mais pratique pour la superposition de données. Bien que ce soit simple (il peut être amélioré avec des fonctionnalités avancées telles que des instructions préparées si vous le souhaitez), il fait les choses de base pour nous..

Pour terminer la partie configuration de notre application, écrivons la classe statique "app_Config":

 class app_AppConfig fonction publique statique getDSN () return "mysql: host = localhost; nombd = test";  fonction publique statique getDbUser () return "test";  fonction publique statique getDbPassword () return "MyTest"; 

Comme indiqué précédemment, nous fournirons des modèles concrets pour accéder à la base de données. Comme petit exemple, nous allons utiliser ce schéma simple: une table de documents et un index inversé pour rechercher si un document contient un mot donné ou non:

 Documents CREATE TABLE (clé primaire id entière, propriétaire varchar (40) non nul, emplacement_serveur varchar (250) non nul); CREATE TABLE mots (word char (30), doc_id integer et null références de documents (id), PRIMARY KEY (word, doc_id))

De la classe d'accès aux données de base (model_Model), nous dérivons autant de classes que nécessaire par la conception des données de notre application. Dans cet exemple, nous pouvons dériver ces deux classes explicites:

 class model_Index étend model_Model function publique getWord ($ word) return $ this-> doStatement ("SELECT doc_id FROM mots WHERE word =". $ this-> quoteString ($ word));  class model_Documents étend model_Model fonction publique get ($ id) return $ this-> doStatement ("SELECT * FROM documents WHEREcoche"); var_dump ($ mots);

Le résultat de cet exemple peut ressembler à celui-ci (cela dépend évidemment de vos données réelles):

 array (119) [0] => array (1) ["doc_id"] => chaîne (4) "4630" [1] => array (1) ["doc_id"] => chaîne (4 ) "4635" [2] => array (1) ["doc_id"] => chaîne (4) "4873" [3] => array (1) ["doc_id"] => chaîne (4 ) "4922" [4] => array (1) ["doc_id"] => string (4) "5373"… 

Ce que nous avons écrit est montré dans le prochain diagramme de classes UML:

2. Planification de notre programme de mise en cache

Lorsque le serveur de base de données commence à s'effondrer, il est temps de prendre une pause et d'envisager d'optimiser la couche de données. Après avoir optimisé vos requêtes, ajouté les index appropriés, etc., le deuxième mouvement consiste à éviter les requêtes inutiles: pourquoi faire la même requête à la base de données à chaque requête de l'utilisateur, si ces données changent à peine?

Avec une organisation de classe bien planifiée et bien découplée, nous pouvons ajouter une couche supplémentaire à notre application presque sans aucun coût de programmation. Dans ce cas, nous allons étendre la classe "model_Model" pour ajouter une mise en cache transparente à notre couche de base de données..

Les bases de la mise en cache

Puisque nous savons que nous avons besoin d’un système de mise en cache, concentrons-nous sur ce problème particulier et, une fois résolu, nous l’intégrerons dans notre modèle de données. Pour le moment, nous ne penserons pas en termes de requêtes SQL. Il est facile de résumer un peu et de construire un schéma assez général.

Le schéma de mise en cache le plus simple consiste en des paires [clé, données], où la clé identifie les données réelles que nous voulons stocker. Ce schéma n'est pas nouveau, en fait, il est analogue aux tableaux associatifs de PHP et nous l'utilisons tout le temps..

Il nous faut donc un moyen de stocker une paire, de la lire et de la supprimer. C'est assez pour construire notre interface pour les helpers de cache:

 interface cache_CacheHelper function get ($ key); fonction put ($ key, $ data); fonction delete (touche $); 

L'interface est assez simple: la méthode get obtient une valeur, étant donné sa clé d'identification, la méthode put définit (ou met à jour) la valeur d'une clé donnée et la méthode delete la supprime..

Avec cette interface en tête, il est temps de mettre en œuvre notre premier vrai module de mise en cache. Mais avant de le faire, nous choisirons la méthode de stockage des données.

Le système de stockage sous-jacent

La décision de créer une interface commune (telle que cache_CacheHelper) pour la mise en cache des aides nous permettra de les implémenter presque au-dessus de chaque stockage. Mais sur quel système de stockage? Nous pouvons en utiliser beaucoup: mémoire partagée, fichiers, serveurs memcached ou même bases de données SQLite..

Souvent sous-estimés, les fichiers DBM conviennent parfaitement à notre système de mise en cache. Nous allons les utiliser dans ce tutoriel..

Les fichiers DBM fonctionnent naïvement sur des paires (clé, données) et le font très rapidement en raison de son organisation B-tree interne. Ils effectuent également le contrôle d'accès pour nous: nous n'avons pas besoin de nous inquiéter du blocage du cache avant l'écriture (comme nous devrons le faire sur d'autres systèmes de stockage); DBM le fait pour nous.

Les fichiers DBM ne sont pas gérés par des serveurs coûteux, ils travaillent dans une bibliothèque légère côté client, qui accède localement au fichier contenant les données. En fait, il s’agit d’une famille de formats de fichiers, tous dotés de la même API de base pour l’accès (clé, données). Certains permettent des clés répétées, d'autres sont constants et n'autorisent pas les écritures après la fermeture du fichier pour la première fois (cdb), etc.. Vous pouvez en savoir plus à ce sujet sur http://www.php.net/manual/en/dba.requirements.php

Presque tous les systèmes UNIX installent un type ou plusieurs de ces bibliothèques (probablement Berkeley DB ou GNU dbm). Pour cet exemple, nous utiliserons le format "db4" (format Sleepycat DB4: http://www.sleepycat.com). J'ai trouvé que cette bibliothèque est souvent préinstallée, mais vous pouvez utiliser la bibliothèque de votre choix (sauf cdb, bien sûr: nous voulons écrire sur le fichier). En fait, vous pouvez déplacer cette décision dans la classe "app_AppConfig" et l'adapter à chaque projet que vous réalisez..

Avec PHP, nous avons deux alternatives pour traiter les fichiers DBM: l’extension "dba" (http://php.net/manual/en/book.dba.php) ou le module "PEAR :: DBA" (http: / /pear.php.net/package/DBA). Nous allons utiliser l'extension "dba", que vous avez probablement déjà installée sur votre système.

Attendez une minute, nous avons affaire à SQL et aux jeux de résultats!

Les fichiers DBM fonctionnent avec des chaînes pour la clé et les valeurs, mais notre problème est de stocker des ensembles de résultats SQL (dont la structure peut varier considérablement). Comment pourrions-nous réussir à les convertir d'un monde à l'autre?

Eh bien, pour les clés, cela est très facile car la chaîne de requête SQL identifie très bien un ensemble de données. Nous pouvons utiliser le condensé MD5 de la chaîne de requête pour raccourcir la clé. Pour les valeurs, c'est plus compliqué, mais ici, vos alliés sont les fonctions PHP serialize () / unserialize (), qui peuvent être utilisées pour convertir des tableaux en chaînes et vice versa..

Nous verrons comment tout cela fonctionne dans la section suivante.

3. Mise en cache statique

Dans notre premier exemple, nous allons traiter de la manière la plus simple de mettre en cache: la mise en cache de valeurs statiques. Nous allons écrire une classe appelée "cache_DBM" mettant en oeuvre l'interface "cache_CacheHelper", juste comme ça:

 la classe cache_DBM implémente cache_CacheHelper protected $ dbm = null; function __construct ($ cache_file = null) $ this-> dbm = dba_popen ($ cache_file, "c", "db4"); if (! $ this-> dbm) lève une nouvelle exception ("$ cache_file: impossible d'ouvrir le fichier de cache");  function get ($ key) $ data = dba_fetch ($ key, $ this-> dbm); if ($ data! == false) return $ data;  return null;  function put ($ key, $ data) if (! dba_replace ($ key, $ data, $ this-> dbm)) nouvelle exception ("$ key: impossible de stocker");  function delete ($ key) if (! dba_delete ($ key, $ this-> dbm)) lancer une nouvelle exception ("$ key: impossible de supprimer"); 

Cette classe est très simple: une correspondance entre notre interface et les fonctions de DBA. Dans le constructeur, le fichier donné est ouvert,
et le gestionnaire retourné est stocké dans l'objet afin de pouvoir l'utiliser dans les autres méthodes.

Un exemple simple d'utilisation:

 $ cache = new cache_DBM ("/tmp/my_first_cache.dbm"); $ cache-> put ("key1", "ma première valeur"); echo $ cache-> get ("key1"); $ cache-> delete ("key1"); $ data = $ cache-> get ("key1"); if (is_null ($ data)) echo "\ n Correctement supprimé!"; 

Vous trouverez ci-dessous ce que nous avons fait ici sous forme de diagramme de classes UML:

Ajoutons maintenant le système de mise en cache à notre modèle de données. Nous aurions pu changer la classe "model_Model" afin d'ajouter du cache à chacune de ses classes dérivées. Mais si nous l'avions fait, nous aurions perdu la flexibilité d'attribuer la caractéristique de mise en cache uniquement à des modèles spécifiques, et je pense que c'est une partie importante de notre travail..

Nous allons donc créer une autre classe, appelée "model_StaticCache", qui étendra "model_Model" et ajoutera une fonctionnalité de mise en cache. Commençons par le squelette:

 class model_StaticCache étend model_Model protected static $ cache = array (); protected $ nom_modèle = null; function __construct ()  function protégée doStatement ($ query) 

Dans le constructeur, nous appelons d’abord le constructeur parent afin de se connecter à la base de données. Ensuite, nous créons et stockons, de manière statique, un objet "cache_DBM" (s'il n'a pas été créé auparavant ailleurs). Nous stockons une instance pour chaque nom de classe dérivée car nous utilisons un fichier DBM pour chacun d'entre eux. Pour cela, nous utilisons le tableau statique "$ cache".

 function __construct () parent :: __ construct (); $ this-> model_name = get_class ($ this); if (! isset (self :: $ cache [$ this-> nom_modèle])) $ cache_dir = app_AppConfig :: getCacheDir (); self :: $ cache [$ this-> nom_modèle] = new cache_DBM ($ cache_dir. $ this-> nom_modèle); 

Pour déterminer dans quel répertoire nous devons écrire les fichiers de cache, nous avons utilisé à nouveau la classe de configuration de l'application: "app_AppConfig".

Et maintenant: la méthode doStatement (). La logique de cette méthode est la suivante: convertissez l'instruction SQL en une clé valide, recherchez la clé dans le cache, le cas échéant, renvoyez la valeur. S'il n'est pas trouvé, exécutez-le dans la base de données, stockez le résultat et renvoyez-le:

 fonction protégée doStatement ($ query) $ key = md5 ($ query); $ data = self :: $ cache [$ this-> nom_modèle] -> get ($ key); if (! is_null ($ data)) return unserialize ($ data);  $ data = parent :: doStatement ($ query); self :: $ cache [$ this-> nom_modèle] -> put ($ key, serialize ($ data)); retourne $ data; 

Il y a deux autres choses à noter. Premièrement, nous utilisons le MD5 de la requête comme clé. En fait, ce n'est pas nécessaire, car la bibliothèque DBM sous-jacente accepte des clés de taille arbitraire, mais il semble préférable de raccourcir la clé de toute façon. Si vous utilisez des instructions préparées, n'oubliez pas de concaténer les valeurs réelles dans la chaîne de requête pour créer la clé.!

Une fois que "model_StaticCache" est créé, la modification d'un modèle concret pour son utilisation est simple, il vous suffit de modifier sa clause "extend" dans la déclaration de classe:

 class model_Documents étend model_StaticCache 

Et c'est tout, la magie est faite! Le "model_Document" effectuera une seule requête pour chaque document à récupérer. Mais on peut mieux le faire.

4. Caching Expiration

Dans notre première approche, une fois qu'une requête est stockée dans le cache, elle reste valable jusqu'à ce que deux choses se produisent: nous supprimons sa clé de manière explicite ou nous dissocions le fichier DBM..

Cependant, cette approche n’est valable que pour quelques modèles de données de notre application: les données statiques (comme les options de menu et ce genre de choses). Les données normales de notre application risquent d'être plus dynamiques que celles.

Pensez à un tableau contenant les produits que nous vendons sur notre page Web. Il est peu probable que cela change à chaque minute, mais il est possible que ces données changent (en ajoutant de nouveaux produits, en modifiant les prix de vente, etc.). Nous avons besoin d'un moyen d'implémenter la mise en cache, mais nous avons un moyen de réagir aux changements de données.

Une solution à ce problème consiste à définir un délai d’expiration pour les données stockées dans le cache. Lorsque nous stockons de nouvelles données dans le cache, nous définissons une fenêtre temporelle dans laquelle ces données seront valides. Passé ce délai, les données seront à nouveau lues dans la base de données et stockées dans le cache pendant une autre période..

Comme auparavant, nous pouvons créer une autre classe dérivée à partir de "model_Model" avec cette fonctionnalité. Cette fois, nous l'appellerons "model_ExpiringCache". Le squelette est similaire à "model_StaticCache":

 class model_ExpiringCache étend model_Model protected static $ cache = array (); protected $ nom_modèle = null; protected $ expiration = 0; function __construct ()  function protégée doStatement ($ query) 

Dans cette classe, nous avons introduit un nouvel attribut: $ expiration. Celui-ci stockera la fenêtre de temps configurée pour les données valides. Nous définissons cette valeur dans le constructeur, le reste du constructeur est identique à celui de "model_StaticCache":

 function __construct () parent :: __ construct (); $ this-> model_name = get_class ($ this); if (! isset (self :: $ cache [$ this-> nom_modèle])) $ cache_dir = app_AppConfig :: getCacheDir (); self :: $ cache [$ this-> nom_modèle] = new cache_DBM ($ cache_dir. $ this-> nom_modèle);  $ this-> expiration = 3600; // 1 heure 

Le gros du travail vient dans le doStatement. Les fichiers DBM ne disposent d'aucun moyen interne pour contrôler l'expiration des données. Nous devons donc implémenter les nôtres. Nous le ferons en stockant des tableaux, comme celui-ci:

 array ("time" => 1250443188, "data" => (les données réelles))

Ce type de tableau correspond à ce que nous sérialisons et stockons dans le cache. La clé "time" correspond à l'heure de modification des données dans le cache, et les "données" correspondent aux données que nous souhaitons stocker. À l'heure de lecture, si nous constatons que la clé existe, nous comparons l'heure de création stockée à l'heure actuelle et renvoyons les données si elles n'ont pas expiré..

 fonction protégée doStatement ($ query) $ key = md5 ($ query); $ maintenant = heure (); $ data = self :: $ cache [$ this-> nom_modèle] -> get ($ key); if (! is_null ($ data)) $ data = unserialize ($ data); if ($ data ['time'] + $ this-> expiration> $ now) return $ data ['data']; 

Si la clé n'existe pas ou a expiré, nous continuons à exécuter la requête et à stocker le nouveau jeu de résultats dans le cache avant de le renvoyer..

 $ data = parent :: doStatement ($ query); self :: $ cache [$ this-> nom_modèle] -> put ($ key, serialize (array ("data" => $ data, "time" => $ now))); retourne $ data; 

Simple!

Convertissons maintenant "model_Index" en un modèle dont le cache expire. En l'occurrence, avec "model_Documents", il suffit de modifier la déclaration de classe et de modifier la clause "extend":

 class model_Documents étend model_ExpiringCache 

À propos de l'heure d'expiration… certaines considérations doivent être prises en compte. Nous utilisons un délai d'expiration constant (1 heure = 3 600 secondes), par souci de simplicité et parce que nous ne souhaitons pas modifier le reste de notre code. Toutefois, nous pouvons facilement le modifier de nombreuses manières pour nous permettre d’utiliser différents délais d’expiration, un pour chaque modèle. Ensuite, nous verrons comment.

Le diagramme de classes pour tout notre travail est le suivant:

5. Expiration différente

Dans chaque projet, je suis sûr que les délais d’expiration seront différents pour presque tous les modèles: de quelques minutes à quelques heures, voire plusieurs jours..

Si seulement nous pouvions avoir un délai d'expiration différent pour chaque modèle, ce serait parfait… mais attendez! On peut le faire facilement!

L'approche la plus directe consiste à ajouter un argument au constructeur. Le nouveau constructeur de "model_ExpiringCache" sera donc celui-ci:

 function __construct ($ expiration = 3600) parent :: __ construct (); $ this-> expiration = $ expiration;…

Ensuite, si nous voulons un modèle avec un délai d'expiration d'un jour (1 jour = 24 heures = 1 440 minutes = 86 400 secondes), nous pouvons le réaliser de la manière suivante:

 class model_Index étend model_ExpiringCache function __construct () parent :: __ construct (86400); …

Et c'est tout. Cependant, l’inconvénient est qu’il faut modifier tous les modèles de données.

Une autre façon de procéder consiste à déléguer la tâche à "app_AppConfig":

 class app_AppConfig … fonction statique publique getExpirationTime ($ nom_modèle) switch ($ nom_modèle) case "modèle_index": return 86400; ... par défaut: return 3600; 

Et ajoutez ensuite l'appel à cette nouvelle méthode sur le constructeur "model_ExpiringCache", comme ceci:

 function __construct () parent :: __ construct (); $ this-> model_name = get_class ($ this); $ this-> expiration = app_AppConfig :: getExpirationTime ($ this-> nom_modèle);…

Cette dernière méthode nous permet de faire des choses fantaisistes, comme utiliser différentes valeurs d'expiration pour les environnements de production ou de développement de manière plus centralisée. Quoi qu'il en soit, vous pouvez choisir le vôtre.

En UML, le projet total ressemble à ceci:

6. Quelques mises en garde

Certaines requêtes ne peuvent pas être mises en cache. Les plus évidents sont la modification de requêtes telles que INSERT, DELETE ou UPDATE. Ces requêtes doivent arriver au serveur de base de données.

Mais même avec les requêtes SELECT, il existe certaines circonstances dans lesquelles un système de mise en cache peut créer des problèmes. Regardez une requête comme celle-ci:

 SELECT * FROM banners WHERE zone = "home" ORDER BY rand () LIMIT 10

Cette requête sélectionne au hasard 10 bannières pour la zone "accueil" de notre site Web. Ceci est destiné à générer un mouvement dans les bannières affichées dans notre page d'accueil, mais si nous mettons cette requête en cache, l'utilisateur ne verra aucun mouvement du tout jusqu'à l'expiration des données en cache..

La fonction rand () n'est pas déterministe (comme ce n'est pas le cas maintenant () ou autre); il retournera donc une valeur différente à chaque exécution. Si nous le mettons en cache, nous ne gèlerons qu'un seul de ces résultats pour toute la période de mise en cache, ce qui briserait la fonctionnalité.

Mais avec un simple re-factorisation, nous pouvons obtenir les avantages de la mise en cache et montrer le pseudo-aléatoire:

 class modèle_Banners étend le modèle_ExpiringCache fonction publique getRandom ($ zone) $ numéro_aléatoire = rand (1,50); $ banners = $ this-> doStatement ("SELECT * FROM banners WHERE zone =". $ this-> quoteString ($ zone). "AND $ random_number = $ random_number ORDER BY rand () LIMIT 10"); renvoyer des bannières $; …

Ce que nous faisons ici est de mettre en cache cinquante configurations différentes de bannières aléatoires et de les sélectionner au hasard. Le 50 SELECT ressemblera à ceci:

 SELECT * FROM banners WHERE zone = "home" AND 1 = 1 ORDER BY rand () LIMIT 10 Sélectionnez * bannières WHERE zone = "home" AND 2 = 2 ORDER BY rand () LIMITE 10… SELECT * FROM bannières WHERE zone = "home" ET 50 = 50 ORDER BY rand () LIMIT 10

Nous avons ajouté une condition constante à la sélection, qui n'a aucun coût pour le serveur de base de données mais restitue 50 clés différentes pour le système de mise en cache. Un utilisateur devra charger la page cinquante fois pour voir toutes les différentes configurations de la bannière. de sorte que l'effet dynamique est atteint. Le coût est de cinquante requêtes à la base de données pour récupérer le cache.

7. Un repère

Quels avantages pouvons-nous attendre de notre nouveau système de mise en cache??

Premièrement, il faut dire que, en termes de performances brutes, notre nouvelle implémentation fonctionnera parfois plus lentement que les requêtes de base de données, spécialement avec des requêtes très simples et bien optimisées. Mais pour les requêtes avec jointures, notre cache DBM s'exécutera plus rapidement.

Cependant, le problème que nous avons résolu n’est pas la performance brute. Vous ne disposerez jamais d'un serveur de base de données disponible pour vos tests en production. Vous aurez probablement un serveur avec des charges de travail élevées. Dans cette situation, même la requête la plus rapide peut s'exécuter lentement, mais avec notre système de mise en cache, nous n'utilisons même pas le serveur et, en fait, nous réduisons sa charge de travail. Ainsi, l’augmentation réelle des performances se présentera sous la forme de plus de pétitions par seconde servie.

Dans un site Web que je suis en train de développer, j'ai réalisé un test de performances simple pour comprendre les avantages de la mise en cache. Le serveur est modeste: il exécute Ubuntu 8.10 sur un AMD Athlon 64 X2 5600+, avec 2 Go de RAM et un ancien disque dur PATA. Le système exécute Apahce et MySQL 5.0, qui est fourni avec la distribution Ubuntu sans aucun réglage..

Le test consistait à exécuter le programme de référence d'Apache (ab) avec 1, 5 et 10 clients simultanés chargeant une page 1 000 fois à partir de mon site Web de développement. La page actuelle était un détail de produit comportant pas moins de 20 requêtes: contenu du menu, détails du produit, produits recommandés, bannières, etc..

Les résultats sans cache étaient de 4,35 p / s pour 1 client, 8,25 pour 5 clients et 8,29 pour 10 clients. Avec la mise en cache (expiration différente), les résultats étaient de 25,55 p / s avec 1 client, 49,01 pour 5 clients et 48,74 pour 10 clients.

Dernières pensées

Je vous ai montré un moyen simple d'insérer la mise en cache dans votre modèle de données. Bien sûr, il existe une pléthore d’alternatives, mais celle-ci n’est qu’un choix que vous avez.

Nous avons utilisé des fichiers DBM locaux pour stocker les données, mais il existe des alternatives encore plus rapides que vous pourriez envisager d'explorer. Quelques idées pour l'avenir: utilisation des fonctions apc_store () d'APC comme système de stockage sous-jacent, mémoire partagée pour les données vraiment critiques, utilisation de memcached, etc..

J'espère que vous avez apprécié ce tutoriel autant que je l'ai écrit. Bonne mise en cache!