Voeg caching toe aan een gegevenstoegangslaag

Dynamische webpagina's zijn geweldig; u kunt de resulterende pagina aanpassen aan uw gebruiker, de activiteit van andere gebruikers laten zien, verschillende producten aanbieden aan uw klanten op basis van hun navigatiegeschiedenis, enzovoort. Maar hoe dynamischer een website is, hoe meer databasequery's u waarschijnlijk zult moeten uitvoeren. Helaas verbruiken deze databasequery's het grootste deel van uw bedrijfstijd.

In deze zelfstudie zal ik een manier demonstreren om de prestaties te verbeteren, zonder extra onnodige vragen te stellen. We zullen een query-cachingsysteem voor onze gegevenslaag ontwikkelen met kleine programmeer- en implementatiekosten.

1. De gegevenstoegangslaag

Het transparant toevoegen van een caching-laag aan een applicatie is vaak moeilijk vanwege het interne ontwerp. Met object-georiënteerde talen (zoals PHP 5) is het een stuk eenvoudiger, maar het kan nog steeds gecompliceerd zijn door een slecht ontwerp.

In deze zelfstudie hebben we ons startpunt ingesteld in een toepassing die alle databasetoegang uitvoert via een gecentraliseerde klasse waarvan alle gegevensmodellen de basistypatoegangsmethoden overerven. Het skelet voor deze startklasse ziet er als volgt uit:

 class model_Model beschermd statisch $ DB = null; functie __construct ()  beschermde functie doStatement ($ query)  beschermde functie quoteString ($ value) 

Laten we het stap voor stap implementeren. Ten eerste, de constructor die de PDO-bibliotheek gebruikt om te communiceren met de database:

 function __construct () // maak zo nodig een verbinding met de database if (is_null (self :: $ DB)) $ dsn = app_AppConfig :: getDSN (); $ db_user = app_AppConfig :: getDBUser (); $ db_pass = app_AppConfig :: getDBPassword (); self :: $ DB = nieuwe PDO ($ dsn, $ db_user, $ db_pass); self :: $ DB-> setAttribute (PDO :: ATTR_ERRMODE, PDO :: ERRMODE_EXCEPTION); 

We maken verbinding met de database met behulp van de PDO-bibliotheek. Voor de databasereferenties gebruik ik een statische klasse met de naam "app_AppConfig" die de configuratie-informatie van de toepassing centraliseert.

Voor het opslaan van de databaseverbinding gebruiken we een statisch kenmerk ($ DB). We gebruiken een statisch kenmerk om dezelfde verbinding te delen met alle instanties van "model_Model" en daarom is de verbindingscode beveiligd met een if (we willen niet meerdere keren verbinding maken).

In de laatste regel van de constructor hebben we het uitzonderingsfoutmodel voor PDO ingesteld. In dit model genereert voor elke fout die de PDO vindt, een uitzondering (klasse PDOException) in plaats van foutwaarden te retourneren. Dit is een kwestie van smaak, maar de rest van de code kan schoner worden gehouden met het uitzonderlijke model, wat goed is voor deze zelfstudie.

Het uitvoeren van query's kan erg ingewikkeld zijn, maar in deze klasse hebben we een eenvoudige aanpak gevolgd met een enkele doStatement () -methode:

 beschermde functie doStatement ($ query) $ st = self :: $ DB-> query ($ query); if ($ st-> columnCount ()> 0) return $ st-> fetchAll (PDO :: FETCH_ASSOC);  else return array (); 

Deze methode voert de query uit en retourneert een associatieve array met de volledige resultaatset (indien aanwezig). Merk op dat we de statische verbinding gebruiken (self :: $ DB). Merk ook op dat deze methode beveiligd is. Dit komt omdat we niet willen dat de gebruiker willekeurige query's uitvoert. In plaats daarvan zullen we de gebruiker concrete modellen bieden. We zullen dit later zien, maar laten we eerst de laatste methode implementeren:

 beschermde functie quoteString ($ value) return self :: $ DB-> quote ($ value, PDO :: PARAM_STR); 

De klasse "model_Model" is een zeer eenvoudige maar handige klasse voor gegevenslagen. Hoewel het eenvoudig is (het kan worden uitgebreid met geavanceerde functies zoals voorbereide verklaringen als je dat wilt), het doet de basis dingen voor ons.

Om het configuratiegedeelte van onze applicatie te voltooien, laten we de statische klasse "app_Config" schrijven:

 class app_AppConfig statische openbare functie getDSN () return "mysql: host = localhost; dbname = test";  statische openbare functie getDbUser () return "test";  statische openbare functie getDbPassword () terug "MyTest"; 

Zoals eerder vermeld, zullen we concrete modellen leveren voor toegang tot de database. Als een klein voorbeeld zullen we dit eenvoudige schema gebruiken: een documententabel en een geïnverteerde index om te zoeken of een document een bepaald woord bevat of niet:

 CREATE TABLE-documenten (id integer primary key, owner varchar (40) not null, server_location varchar (250) not null); MAAK TABLE woorden (word char (30), doc_id integer not null references documents (id), PRIMARY KEY (word, doc_id))

Van de basis data toegangsklasse (model_Model), ontlenen we zoveel klassen als nodig is door het data ontwerp van onze applicatie. In dit voorbeeld kunnen we deze twee zelfverklarende lessen afleiden:

 class model_Index breidt model_Model uit public function getWord ($ woord) return $ this-> doStatement ("SELECT doc_id FROM words WHERE word =". $ this-> quoteString ($ word));  class model_Documents breidt model_Model public function get ($ id) return $ this-> doStatement ("SELECT * FROM documents WHEREcoche"); var_dump ($ woorden);

Het resultaat voor dit voorbeeld kan er ongeveer zo uitzien (dit hangt natuurlijk af van uw werkelijke gegevens):

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

Wat we hebben geschreven, wordt weergegeven in het volgende UML-klassediagram:

2. Planning van onze caching-regeling

Wanneer dingen beginnen te instorten in uw databaseserver, is het tijd om te stoppen en te overwegen de gegevenslaag te optimaliseren. Na uw vragen te hebben geoptimaliseerd, de juiste indexen toe te voegen, enz., Is de tweede zet om onnodige query's te vermijden: waarom dezelfde aanvraag voor de database maken op elk gebruikersverzoek, als deze gegevens nauwelijks veranderen?

Met een goed geplande en goed ontkoppelde klassenorganisatie kunnen we bijna zonder programmeerkosten een extra laag aan onze applicatie toevoegen. In dit geval gaan we de klasse "model_Model" uitbreiden om transparante caching toe te voegen aan onze databaselaag.

De basisprincipes van Caching

Omdat we weten dat we een caching-systeem nodig hebben, laten we ons concentreren op dat specifieke probleem en, eenmaal opgelost, zullen we het integreren in ons datamodel. Voorlopig denken we niet in termen van SQL-query's. Het is eenvoudig om een ​​beetje te abstraheren en een schema op te bouwen dat algemeen genoeg is.

Het eenvoudigste caching-schema bestaat uit [key, data] -paren, waarbij de sleutel de feitelijke gegevens identificeert die we willen opslaan. Dit schema is niet nieuw, sterker nog, het is analoog aan PHP's associatieve matrices, en we gebruiken het de hele tijd.

We hebben dus een manier nodig om een ​​paar op te slaan, te lezen en te verwijderen. Dat is genoeg om onze interface voor cache-helpers te bouwen:

 interface cache_CacheHelper functie krijgen ($ sleutel); functie put ($ key, $ data); functie verwijderen ($ -sleutel); 

De interface is vrij eenvoudig: de get-methode krijgt een waarde, gezien de identificerende sleutel, de put-methode stelt (of update) de waarde in voor een gegeven sleutel en de verwijder-methode verwijdert het.

Met deze interface in het achterhoofd is het tijd om onze eerste echte cachemodule te implementeren. Maar voordat we het doen, zullen we de methode voor gegevensopslag kiezen.

Het onderliggende opslagsysteem

De beslissing om een ​​gemeenschappelijke interface te bouwen (zoals cache_CacheHelper) voor caching-helpers, stelt ons in staat om deze vrijwel overal te implementeren. Maar boven op welk opslagsysteem? We kunnen er veel van gebruiken: gedeeld geheugen, bestanden, memcached-servers of zelfs SQLite-databases.

Vaak onderschat, DBM-bestanden zijn perfect voor ons caching-systeem, en we gaan ze gebruiken in deze tutorial.

DBM-bestanden werken naïef op (sleutel, gegevens) paren en doen het erg snel dankzij de interne B-tree-organisatie. Ze doen ook de toegangscontrole voor ons: we hoeven ons geen zorgen te maken over het blokkeren van de cache voordat we schrijven (zoals we zullen moeten doen op andere opslagsystemen); DBM doet het voor ons.

DBM-bestanden worden niet aangedreven door dure servers, ze doen hun werk in een lichtgewicht bibliotheek aan de clientzijde die lokaal toegang zoekt tot het eigenlijke bestand waarin de gegevens zijn opgeslagen. In feite zijn ze eigenlijk een familie van bestandsformaten, allemaal met dezelfde basis API voor (sleutel, data) toegang. Sommigen van hen laten herhaalde sleutels toe, andere zijn constant en staan ​​geen schrijven toe na het voor de eerste keer sluiten van het bestand (cdb), enz.. Je kunt daar meer over lezen op http://www.php.net/manual/en/dba.requirements.php

Bijna elk UNIX-systeem installeert één type of meer van deze bibliotheken (waarschijnlijk Berkeley DB of GNU dbm). Voor dit voorbeeld gebruiken we het "db4" -formaat (Sleepycat DB4-formaat: http://www.sleepycat.com). Ik heb ontdekt dat deze bibliotheek vaak vooraf is geïnstalleerd, maar je kunt elke gewenste bibliotheek gebruiken (behalve cdb, natuurlijk: we willen in het bestand schrijven). In feite kunt u deze beslissing verplaatsen naar de klasse "app_AppConfig" en deze aanpassen voor elk project dat u doet.

Met PHP hebben we twee alternatieven voor DBM-bestanden: de "dba" -extensie (http://php.net/manual/en/book.dba.php) of de "PEAR :: DBA" -module (http: / /pear.php.net/package/DBA). We zullen de "dba" -extensie gebruiken, die waarschijnlijk al in uw systeem is geïnstalleerd.

Wacht even, we hebben te maken met SQL- en resultatensets!

DBM-bestanden werken met reeksen voor sleutel en waarden, maar ons probleem is om SQL-resultaatsets op te slaan (die in structuur nogal kunnen variëren). Hoe konden we ze omzetten van de ene wereld naar de andere?

Welnu, voor toetsen is dit heel eenvoudig omdat de werkelijke SQL-queryreeks een set gegevens heel goed identificeert. We kunnen de MD5-samenvatting van de queryreeks gebruiken om de sleutel in te korten. Voor waarden is het lastiger, maar hier zijn je bondgenoten de serialize () / unserialize () PHP-functies, die kunnen worden gebruikt om te converteren van arrays naar string en omgekeerd.

We zullen zien hoe dit allemaal werkt in de volgende sectie.

3. Statische caching

In ons eerste voorbeeld behandelen we de eenvoudigste manier om caching uit te voeren: caching voor statische waarden. We zullen een klasse schrijven met de naam "cache_DBM" en de interface "cache_CacheHelper" implementeren, zomaar:

 klasse cache_DBM implementeert cache_CacheHelper protected $ dbm = null; function __construct ($ cache_file = null) $ this-> dbm = dba_popen ($ cache_file, "c", "db4"); if (! $ this-> dbm) gooi nieuwe uitzondering ("$ cache_file: kan cachebestand niet openen");  functie 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)) gooi nieuwe uitzondering ("$ key: Could not store");  functie delete ($ key) if (! dba_delete ($ key, $ this-> dbm)) throw new Exception ("$ key: Could not delete"); 

Deze klasse is heel eenvoudig: een koppeling tussen onze interface en dba-functies. In de constructor wordt het opgegeven bestand geopend,
en de geretourneerde afhandelaar wordt opgeslagen in het object om het in de andere methoden te gebruiken.

Een eenvoudig voorbeeld van gebruik:

 $ cache = nieuwe cache_DBM ("/tmp/my_first_cache.dbm"); $ cache-> put ("key1", "mijn eerste waarde"); echo $ cache-> get ("key1"); $ Cache-> delete ( "key1"); $ data = $ cache-> get ("key1"); if (is_null ($ data)) echo "\ nCorrectly deleted!"; 

Hieronder vindt u wat we hier hebben gedaan, uitgedrukt als een UML-klassendiagram:

Laten we nu het caching-systeem toevoegen aan ons datamodel. We hadden de klasse "model_Model" kunnen wijzigen om caching aan elk van de afgeleide klassen toe te voegen. Maar als we dat hadden gedaan, zouden we de flexibiliteit hebben verloren om de caching-eigenschap alleen toe te wijzen aan specifieke modellen, en ik denk dat dit een belangrijk onderdeel van ons werk is..

We zullen dus een andere klasse maken, genaamd "model_StaticCache", die "model_Model" zal uitbreiden en cachefunctionaliteit zal toevoegen. Laten we beginnen met het skelet:

 class model_StaticCache breidt model_Model uit protected static $ cache = array (); beschermd $ model_name = null; function __construct ()  beschermde functie doStatement ($ query) 

In de constructor bellen we eerst de bovenliggende constructor om verbinding te maken met de database. Vervolgens maken en bewaren we statisch een "cache_DBM" -object (als dit niet eerder is gemaakt). We slaan één exemplaar op voor elke afgeleide klassenaam, omdat we voor elk ervan één DBM-bestand gebruiken. Voor dat doel gebruiken we de statische array "$ cache".

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

Om te bepalen in welke map we de cachebestanden moeten schrijven, hebben we opnieuw de configuratieklasse van de toepassing gebruikt: "app_AppConfig".

En nu: de methode doStatement (). De logica voor deze methode is: converteer de SQL-instructie naar een geldige sleutel, zoek de sleutel in de cache, indien gevonden, retourneert u de waarde. Als het niet wordt gevonden, voert u het uit in de database, slaat u het resultaat op en retourneert u het:

 beschermde functie doStatement ($ query) $ key = md5 ($ query); $ data = self :: $ cache [$ this-> model_name] -> get ($ key); if (! is_null ($ data)) return unserialize ($ data);  $ data = parent :: doStatement ($ query); zelf :: $ cache [$ this-> MODEL_NAME] -> put ($ key, serialize ($ data)); return $ data; 

Er zijn nog twee dingen die het vermelden waard zijn. Ten eerste gebruiken we de MD5 van de query als sleutel. In feite is dit niet nodig, omdat de onderliggende DBM-bibliotheek sleutels van willekeurige grootte accepteert, maar het lijkt beter om de sleutel toch in te korten. Als u voorbereide instructies gebruikt, vergeet dan niet om de werkelijke waarden aan de querytekenreeks te koppelen om de sleutel te maken!

Zodra de "model_StaticCache" is gemaakt, is het wijzigen van een concreet model voor het gebruik ervan triviaal, u hoeft alleen de clausule "extends" in de klassendeclaratie te wijzigen:

 class model_Documents breidt model_StaticCache uit 

En dat is alles, de magie is klaar! Het "model_Document" voert slechts één query uit voor elk document dat moet worden opgehaald. Maar we kunnen het beter doen.

4. Verloop van caching

In onze eerste benadering, als een query eenmaal in de cache is opgeslagen, blijft deze voor altijd geldig tot er twee dingen gebeuren: we verwijderen de sleutel expliciet of we ontkoppelen het DBM-bestand.

Deze benadering is echter alleen geldig voor een paar datamodellen van onze applicatie: de statische gegevens (zoals menu-opties en dit soort dingen). De normale gegevens in onze applicatie zijn waarschijnlijk dynamischer dan dat.

Denk aan een tabel met de producten die we verkopen op onze webpagina. Het is niet waarschijnlijk dat deze elke minuut zal veranderen, maar de kans bestaat dat deze gegevens veranderen (door nieuwe producten toe te voegen, verkoopprijzen te wijzigen, enz.). We hebben een manier nodig om caching te implementeren, maar hebben een manier om te reageren op wijzigingen in gegevens.

Eén benadering voor dit probleem is om een ​​vervaltijd in te stellen voor de gegevens die in de cache zijn opgeslagen. Wanneer we nieuwe gegevens in de cache opslaan, stellen we een tijdsperiode in waarin deze gegevens geldig zijn. Na die tijd zullen de gegevens opnieuw uit de database worden gelezen en voor een andere tijdsperiode in de cache worden opgeslagen.

Zoals eerder, kunnen we met deze functionaliteit nog een afgeleide klasse maken van "model_Model". Deze keer noemen we het "model_ExpiringCache". Het skelet lijkt op "model_StaticCache":

 class model_ExpiringCache breidt model_Model uit protected static $ cache = array (); beschermd $ model_name = null; beschermde $ expiration = 0; function __construct ()  beschermde functie doStatement ($ query) 

In deze klasse hebben we een nieuw kenmerk geïntroduceerd: $ expiration. Deze zal het geconfigureerde tijdvenster voor geldige gegevens opslaan. We zetten deze waarde in de constructor, de rest van de constructor is hetzelfde als in "model_StaticCache":

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

Het grootste deel van de klus komt in de doStatement. De DBM-bestanden hebben geen interne manier om het verlopen van gegevens te beheren, dus we moeten onze eigen implementeren. We doen het door arrays op te slaan, zoals deze:

 array ("tijd" => 1250443188, "data" => (de feitelijke gegevens))

Dit soort array is wat we serialiseren en opslaan in de cache. De "tijd" -toets is de modificatietijd van de gegevens in de cache en de "gegevens" zijn de feitelijke gegevens die we willen opslaan. Tijdens de leestijd, als we vaststellen dat de sleutel bestaat, vergelijken we de aanmaaktijd die is opgeslagen met de huidige tijd en retourneren we de gegevens als deze niet zijn verlopen.

 beschermde functie doStatement ($ query) $ key = md5 ($ query); $ now = time (); $ data = self :: $ cache [$ this-> model_name] -> get ($ key); if (! is_null ($ data)) $ data = unserialize ($ data); if ($ data ['time'] + $ this-> expiration> $ now) return $ data ['data']; 

Als de sleutel niet bestaat of is verlopen, blijven we de query uitvoeren en de nieuwe resultaatset opslaan in de cache voordat deze wordt geretourneerd.

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

Eenvoudig!

Laten we nu de "model_Index" converteren naar een model met een verlopen cache. Het is namelijk zo dat we met "model_Documents" alleen de klassenverklaring hoeven aan te passen en de clausule "extends" moeten wijzigen:

 class model_Documents breidt model_ExpiringCache uit 

Over de vervaltijd ... er moeten enkele overwegingen worden gemaakt. We gebruiken een constante vervaltijd (1 uur = 3600 seconden) omwille van de eenvoud en omdat we de rest van onze code niet willen wijzigen. Maar we kunnen het op veel verschillende manieren eenvoudig aanpassen om ons in staat te stellen verschillende vervaltijden te gebruiken, één voor elk model. Naderhand zullen we zien hoe.

Het klassediagram voor al onze taken is als volgt:

5. Verschillende expiratie

In elk project weet ik zeker dat je voor bijna elk model andere vervaltijden zult hebben: van een paar minuten tot uren of zelfs dagen.

Als we voor elk model een andere vervaltijd hadden, zou het perfect zijn ... maar wacht! We kunnen het gemakkelijk doen!

De meest directe benadering is om een ​​argument toe te voegen aan de constructor, dus de nieuwe constructor voor "model_ExpiringCache" is deze:

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

Als we dan een model willen met een vervaltijd van 1 dag (1 dag = 24 uur = 1.440 minuten = 86.400 seconden), kunnen we het op deze manier volbrengen:

 class model_Index breidt model_ExpiringCache uit function __construct () parent :: __ construct (86400);  ...

En dat is alles. Het nadeel is echter dat we alle datamodellen moeten aanpassen.

Een andere manier om dit te doen is om de taak te delegeren naar "app_AppConfig":

 class app_AppConfig ... public static function getExpirationTime ($ model_name) switch ($ model_name) case "model_Index": retourneer 86400; ... standaard: retour 3600; 

En voeg vervolgens de aanroep van deze nieuwe methode toe aan de constructor "model_ExpiringCache", zoals deze:

 function __construct () parent :: __ construct (); $ this-> model_name = get_class ($ this); $ this-> expiration = app_AppConfig :: getExpirationTime ($ this-> model_name); ...

Met deze nieuwste methode kunnen we mooie dingen doen, zoals het gebruik van verschillende vervalwaarden voor productie- of ontwikkelingsomgevingen op een meer gecentraliseerde manier. Hoe dan ook, je kunt de jouwe kiezen.

In UML ziet het totale project er als volgt uit:

6. Sommige kanttekeningen

Er zijn enkele zoekopdrachten die niet in de cache kunnen worden opgeslagen. De meest voor de hand liggende zijn het aanpassen van zoekopdrachten zoals INSERT, DELETE of UPDATE. Deze query's moeten aankomen bij de databaseserver.

Maar zelfs met SELECT-query's zijn er enkele omstandigheden waarin een cachingsysteem problemen kan veroorzaken. Bekijk een zoekopdracht als deze:

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

Deze query selecteert willekeurig 10 banners voor de "thuis" -zone van onze website. Dit is bedoeld om beweging te genereren in de banners die in ons huis worden getoond, maar als we deze query cachen, ziet de gebruiker helemaal geen beweging totdat de gegevens in de cache verlopen.

De functie rand () is niet deterministisch (zoals het nu is () of anderen); dus het zal bij elke uitvoering een andere waarde teruggeven. Als we het cachen, zullen we slechts één van die resultaten voor alle caching-perioden bevriezen en daardoor de functionaliteit breken.

Maar met een eenvoudige re-factoring, kunnen we de voordelen van caching verkrijgen en pseudo-willekeur tonen:

 class model_Banners breidt model_ExpiringCache uit openbare functie getRandom ($ zone) $ random_number = rand (1,50); $ banners = $ this-> doStatement ("SELECT * FROM banners WHERE zone =". $ this-> quoteString ($ zone). "AND $ random_number = $ random_number ORDER BY rand () LIMIT 10"); geef $ banners terug;  ...

Wat we hier doen, is vijftig verschillende willekeurige bannersconfiguraties in de cache opslaan en willekeurig selecteren. De 50 SELECT's zien er als volgt uit:

 SELECT * FROM banners WHERE zone = "home" EN 1 = 1 BESTELLING BY rand () LIMIT 10 SELECT * FROM banners WHERE zone = "home" EN 2 = 2 BESTELLING BY rand () LIMIT 10 ... SELECT * FROM banners WHERE zone = "thuis" EN 50 = 50 VOLGORDE BY rand () LIMIT 10

We hebben een constante voorwaarde toegevoegd aan de selectie, die geen kosten voor de databaseserver heeft maar 50 verschillende sleutels voor het cachingsysteem rendert. Een gebruiker moet de pagina vijftig keer laden om alle verschillende configuraties van de banner te zien; dus het dynamische effect wordt bereikt. De kosten zijn vijftig query's voor de database om de cache op te halen.

7. Een benchmark

Welke voordelen kunnen we verwachten van ons nieuwe cachingsysteem?

Ten eerste moet worden gesteld dat in ruwe uitvoering soms onze nieuwe implementatie langzamer verloopt dan databasequery's, met name met zeer eenvoudige, goed geoptimaliseerde query's. Maar voor die query's met joins wordt onze DBM-cache sneller uitgevoerd.

Het probleem dat we hebben opgelost is echter geen onbewerkte prestatie. U zult nooit een reserve databaseserver voor uw tests in productie hebben. Je hebt waarschijnlijk een server met hoge workloads. In deze situatie kan zelfs de snelste query langzaam worden uitgevoerd, maar met ons caching-schema gebruiken we niet eens de server, en in feite verlagen we de werklast. Dus de echte prestatieverbetering zal komen in de vorm van meer petities per seconde geserveerd.

In een website die ik momenteel aan het ontwikkelen ben, heb ik een eenvoudige benchmark gedaan om de voordelen van caching te begrijpen. De server is bescheiden: hij draait Ubuntu 8.10 bovenop een AMD Athlon 64 X2 5600+, met 2 GB RAM en een oude PATA-harde schijf. Het systeem draait Apahce en MySQL 5.0, dat zonder enige afstemming wordt meegeleverd met de Ubuntu-distributie.

De test moest het Apache-benchmarkprogramma (ab) uitvoeren met 1, 5 en 10 gelijktijdige clients die een pagina 1000 keer laadden vanaf mijn ontwikkelingswebsite. De eigenlijke pagina was een productdetail dat niet minder dan 20 vragen bevatte: menu-inhoud, productdetails, aanbevolen producten, banners, enz.

De resultaten zonder cache waren 4,35 p / s voor 1 client, 8,25 voor 5 clients en 8,29 voor 10 clients. Met caching (verschillende vervaldatum) waren de resultaten 25,55 p / s met 1 klant, 49,01 voor 5 klanten en 48,74 voor 10 klanten.

Laatste gedachten

Ik heb je een eenvoudige manier getoond om caching in te voegen in je datamodel. Natuurlijk zijn er een overvloed aan alternatieven, maar deze is maar één keuze die je hebt.

We hebben lokale DBM-bestanden gebruikt om de gegevens op te slaan, maar er zijn nog snellere alternatieven die u misschien zou willen onderzoeken. Enkele ideeën voor de toekomst: APC's apc_store () -functies gebruiken als onderliggend opslagsysteem, gedeeld geheugen voor echt cruciale gegevens, memcached gebruiken, enz..

Ik hoop dat je deze tutorial net zo leuk hebt gevonden als ik het heb geschreven. Happy caching!