Hindi (हिंदी) translation by Ashish Rampal (you can also view the original English article)
डायनामिक वेब पेज बहुत बढ़िया हैं; आप रिजल्ट वाले पेज को अपने यूजर को एडाप्ट कर सकते हैं, अन्य यूजर की गतिविधियां दिखा सकते हैं, अपने नेविगेशन इतिहास के आधार पर अपने ग्राहकों को विभिन्न प्रोडक्ट्स की पेशकश कर सकते हैं। लेकिन एक वेबसाइट जितनी अधिक डायनामिक है, उतनी अधिक डेटाबेस क्वेरीज़ होती हैं जिन्हें आपको शायद करने की आवश्यकता होगी। दुर्भाग्यवश, ये डेटाबेस क्वेरीज आपके चलने वाले समय का सबसे बड़े हिस्से का उपभोग करती हैं।
इस ट्यूटोरियल में, मैं अतिरिक्त अनावश्यक क्वेरीज के बिना प्रदर्शन में सुधार करने का एक तरीका प्रदर्शित करूंगा। हम छोटे डेटा प्रोग्रामिंग और डिप्लॉयमेंट कॉस्ट के साथ हमारी डेटा लेयर के लिए एक क्वेरी कैशिंग सिस्टम डेवेलप करेंगे।
1. डेटा एक्सेस लेयर
आंतरिक डिजाइन की वजह से एक कैशिंग लेयर को पारदर्शी रूप से जोड़ना अक्सर मुश्किल होता है। ऑब्जेक्ट ओरिएंटेड लैंग्वेज (जैसे PHP 5) के साथ यह बहुत आसान है, लेकिन यह अभी भी खराब डिज़ाइन द्वारा जटिल हो सकता है।
इस ट्यूटोरियल में, हमने अपने शुरुआती पॉइंट को उस एप्लिकेशन में सेट किया है जो एक केंद्रीकृत क्लास के माध्यम से अपने सभी डेटाबेस एक्सेस करता है, जिससे सभी डेटा मॉडल मूल डेटाबेस एक्सेस मेथड्स का इन्हेरिट होते हैं। इस शुरुआती क्लास के लिए स्केलेटन इस तरह दिखता है:
class model_Model { protected static $DB = null; function __construct () {} protected function doStatement ($query) {} protected function quoteString ($value) {} }
आइए इसे चरण-दर-चरण लागू करें। सबसे पहले, कन्स्ट्रक्टर जो डेटाबेस के साथ इंटरफेस करने के लिए PDO लाइब्रेरी का उपयोग करेगा:
function __construct () { // connect to the DB if needed 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 ); } }
हम PDO लाइब्रेरी का उपयोग कर डेटाबेस से कनेक्ट होते हैं। डेटाबेस क्रेडेंशियल के लिए मैं "app_AppConfig" नामक एक स्टेटिक क्लास का उपयोग करता हूं जो एप्लिकेशन की कॉन्फ़िगरेशन जानकारी को केंद्रीकृत करता है।
डेटाबेस कनेक्शन को स्टोर करने के लिए, हम एक स्टेटिक एट्रिब्यूट ($DB) का उपयोग करते हैं। हम "model_Model" के सभी इंस्टैंस के साथ समान कनेक्शन साझा करने के लिए एक स्टेटिक एट्रिब्यूट का उपयोग करते हैं, और इसके कारण, कनेक्शन कोड को अगर (हम एक से अधिक बार कनेक्ट नहीं करना चाहते हैं) से सुरक्षित है।
कंसट्रक्टर की आखिरी लाइन में हमने PDO के लिए एक्सेप्शन एरर मॉडल सेट किया है। इस मॉडल में, PDO के हर एरर के लिए, यह एरर वैल्यू रीटर्न करने के बजाय एक एक्सेप्शन (class PDOException) थ्रो करता है। यह अपने टेस्ट का विषय है, लेकिन बाकी कोड को असाधारण मॉडल के साथ क्लीनर रखा जा सकता है, जो इस ट्यूटोरियल के लिए अच्छा है।
एक्सेक्यूटिंग क्वेरीज बहुत जटिल हो सकती हैं, लेकिन इस क्लास में हमने सिंगल doStatement() मेथड के साथ एक सरल दृष्टिकोण लिया है:
protected function doStatement ($query) { $st = self::$DB->query($query); if ( $st->columnCount()>0 ) { return $st->fetchAll(PDO::FETCH_ASSOC); } else { return array(); } }
यह मेथड क्वेरी एक्सेक्यूट करता है, और संपूर्ण परिणाम सेट (यदि कोई हो) के साथ एक अस्सोसिएटिव ऐरे रीटर्न करता है। ध्यान दें कि हम स्टेटिक कनेक्शन (self::$DB) का उपयोग कर रहे हैं। ध्यान दें, यह भी कि यह मेथड प्रोटेक्टेड है। ऐसा इसलिए है क्योंकि हम नहीं चाहते हैं कि यूजर आरबिटरेरी क्वेरीज को एक्सेक्यूट करे। इसके बजाय हम यूजर को कंक्रीट मॉडल प्रदान करेंगे। हम इसे बाद में देखेंगे, लेकिन आखिरी मेथड को लागू करने से पहले:
protected function quoteString ($value) { return self::$DB->quote($value,PDO::PARAM_STR); }
"model_Model" क्लास डेटा लेयरिंग के लिए एक बहुत ही सरल लेकिन सुविधाजनक क्लास है। यद्यपि यह आसान है (यदि आप चाहें तो prepared स्टेटमेंट्स जैसे एडवांस्ड फीचर्स के साथ बढ़ाया जा सकता है), यह हमारे लिए मूलभूत चीजें करता है।
हमारे एप्लीकेशन के कॉन्फ़िगरेशन भाग को पूरा करने के लिए, आइए "app_Config" स्टेटिक क्लास लिखें:
class app_AppConfig { static public function getDSN () { return "mysql:host=localhost;dbname=test"; } static public function getDbUser () { return "test"; } static public function getDbPassword () { return "MyTest"; } }
जैसा कि पहले बताया गया है, हम डेटाबेस तक पहुंचने के लिए कंक्रीट मॉडल प्रदान करेंगे। एक छोटे से उदाहरण के रूप में, हम इस सरल स्कीमा का उपयोग करेंगे: एक डाक्यूमेंट्स टेबल और एक इनवर्टेड इंडेक्स यह जानने के लिए कि किसी डॉक्यूमेंट में कोई दिया गया शब्द है या नहीं:
CREATE TABLE documents ( id integer primary key, owner varchar(40) not null, server_location varchar(250) not null ); CREATE TABLE words ( word char(30), doc_id integer not null references documents(id), PRIMARY KEY (word,doc_id) )
बेसिक डेटा एक्सेस क्लास (model_Model) से, हम अपने एप्लीकेशन के डेटा डिज़ाइन द्वारा आवश्यकतानुसार कई क्लासेज प्राप्त करते हैं। इस उदाहरण में, हम उन दो आत्म-व्याख्यात्मक क्लासेज प्राप्त कर सकते हैं:
class model_Index extends model_Model { public function getWord ($word) { return $this->doStatement("SELECT doc_id FROM words WHERE word=" . $this->quoteString($word)); } } class model_Documents extends model_Model { public function get ($id) { return $this->doStatement( "SELECT * FROM documents WHERE id=" . intval($id) ); } }
वे डिराइव्ड मॉडल वो हैं जहां हम पब्लिक इनफार्मेशन जोड़ते हैं। उनका उपयोग करना बेहद सरल है:
$index = new model_Index(); $words = $index->getWord("coche"); var_dump($words);
इस उदाहरण का नतीजा कुछ इस तरह का दिख सकता है (जाहिर है यह आपके वास्तविक डेटा पर निर्भर करता है):
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" } ...
हमने जो लिखा है वह अगले UML क्लास डायग्राम में दिखाया गया है:



2. हमारी कैशिंग Scheme की योजना बनाना
जब आपके डेटाबेस सर्वर में चीजे कोलैप्स होनी शुरू होती हैं, तो ब्रेक लेने और डेटा लेयर को कस्टमाइज करने का समय लगता है। अपने क्वेरीज को ऑप्टिमाइज़ करने के बाद, उचित इंडेक्स जोड़ना, दूसरा कदम अनावश्यक क्वेरीज से बचने का प्रयास करना है: यदि यह डेटा शायद ही कभी बदलता है, तो प्रत्येक यूजर रिक्वेस्ट पर डेटाबेस को एक ही रिक्वेस्ट क्यों करें?
एक अच्छी तरह से योजनाबद्ध और अच्छी तरह से डीकपल्ड क्लास आर्गेनाइजेशन के साथ, हम लगभग किसी भी प्रोग्रामिंग लागत के बिना हमारे एप्लीकेशन में एक अतिरिक्त लेयर जोड़ सकते हैं। इस मामले में, हम अपने डेटाबेस लेयर में पारदर्शी कैशिंग जोड़ने के लिए "model_Model" क्लास को एक्सटेंड करने जा रहे हैं।
कैशिंग का बेसिक
चूंकि हम जानते हैं कि हमें एक कैशिंग सिस्टम की आवश्यकता है, चलिए उस विशेष समस्या पर ध्यान केंद्रित करते हैं और, एक बार हल हो जाने पर, हम इसे अपने डेटा मॉडल में इंटेग्रेट करेंगे। अभी के लिए, हम SQL क्वेरीज के संदर्भ में नहीं सोचेंगे। थोड़ा सा सार बनाना आसान है और एक सामान्य पर्याप्त योजना का निर्माण करना आसान है।
सबसे सरल कैशिंग स्कीम में [key,data] जोड़े होते हैं, जहां key उस वास्तविक डेटा की पहचान करती है जिसे हम स्टोर करना चाहते हैं। यह स्कीमा नया नहीं है, वास्तव में, यह PHP के असोसिएटिव ऐरे के समान है, और हम इसे हर समय उपयोग करते हैं।
इसलिए हमें एक जोड़ी को स्टोर करने, इसे पढ़ने और इसे हटाने के लिए एक तरीका चाहिए। कैश हेल्पर्स के लिए हमारा इंटरफ़ेस बनाने के लिए पर्याप्त है:
interface cache_CacheHelper { function get ($key); function put ($key,$data); function delete ($key); }
इंटरफ़ेस काफी आसान है: गेट मेथड को एक वैल्यू प्राप्त होती है, इसकी पहचान key के अनुसार, दिए गए key के लिए put मेथड सेट (या updates) वैल्यू, और delete मेथड इसे हटा देती है।

इस इंटरफेस को ध्यान में रखते हुए, यह हमारे पहले असली कैशिंग मॉड्यूल को लागू करने का समय है। लेकिन ऐसा करने से पहले, हम डेटा स्टोरेज मेथड का चयन करेंगे।
अंतर्निहित (Underlying) स्टोरेज सिस्टम
कैशिंग हेल्पर्स के लिए एक सामान्य इंटरफ़ेस (जैसे cache_CacheHelper) बनाने का निर्णय हमें लगभग हर स्टोरेज के शीर्ष पर लागू करने की अनुमति देगा। लेकिन किस स्टोरेज सिस्टम के शीर्ष पर? उनमें से बहुत से हम उपयोग कर सकते हैं: शेयर्ड मेमोरी, फ़ाइलें, memcached सर्वर या यहां तक कि SQLite डेटाबेस।
अक्सर कम करके आंका गया, DBM फाइलें हमारे कैशिंग सिस्टम के लिए बिल्कुल सही हैं, और हम इन ट्यूटोरियल में उनका उपयोग करने जा रहे हैं।
DBM फाइलें (key,data) जोड़े पर अच्छी तरह से काम करती हैं, और अपने आंतरिक बी-ट्री संगठन के कारण इसे बहुत तेज करती हैं। वे हमारे लिए एक्सेस कंट्रोल भी करते हैं: हमें लिखने से पहले कैश को ब्लॉक करने की चिंता करने की आवश्यकता नहीं है (जैसे हमें अन्य स्टोरेज सिस्टम पर करना होगा); DBM हमारे लिए यह करता है।
DBM फाइलों को एक्सपेंसिव सर्वर द्वारा संचालित नहीं किया जाता है, वे क्लाइंट साइड पर हल्के लाइब्रेरी के अंदर अपना काम स्थानीय रूप से वास्तविक फ़ाइल तक पहुंचते हैं जो डेटा स्टोर करता है। असल में वे वास्तव में फ़ाइल फोर्मट्स का एक परिवार हैं, उनमें से सभी एक ही बेसिक API (key,data) पहुंच के लिए हैं। उनमें से कुछ बार-बार key की अनुमति देते हैं, अन्य कांस्टेंट होते हैं और पहली बार फ़ाइल बंद करने के बाद लिखने की अनुमति नहीं देते हैं। आप इसके बारे में http://www.php.net/manual/en/dba.requirements.php पर और अधिक पढ़ सकते हैं
लगभग हर यूनिक्स सिस्टम इन लाइब्रेरीज में से एक प्रकार या अधिक इनस्टॉल करता है (शायद Berkeley DB या GNU dbm)। इस उदाहरण के लिए, हम "db4" फॉर्मेट (Sleepycat DB4 फॉर्मेट: http://www.sleepycat.com) का उपयोग करेंगे। मैंने पाया है कि यह लाइब्रेरी प्रायः प्रीइन्सटाल्ड है, लेकिन आप जो भी लाइब्रेरी चाहते हैं उसका उपयोग कर सकते हैं (cdb को छोड़कर, ज़ाहिर है: हम फाइल पर लिखना चाहते हैं)। असल में आप इस निर्णय को "app_AppConfig" क्लास में ले जा सकते हैं और इसे आपके द्वारा किए जाने वाले प्रत्येक प्रोजेक्ट के लिए एडाप्ट कर सकते हैं।
PHP के साथ, हमारे पास DBM फाइलों से निपटने के लिए दो विकल्प हैं: "dba" एक्सटेंशन (http://php.net/manual/en/book.dba.php) या "PEAR::DBA" मॉड्यूल (http://pear.php.net/package/DBA)। हम "dba" एक्सटेंशन का उपयोग करेंगे, जो शायद आपने पहले से ही अपने सिस्टम में इनस्टॉल किया है।
एक मिनट रुको, हम SQL और रिजल्ट सेट से निपट रहे हैं!
DBM फाइलें key और वैल्यू के लिए स्ट्रिंग के साथ काम करती हैं, लेकिन हमारी समस्या SQL रिजल्ट सेट को स्टोर करना है (जो स्ट्रक्चर में काफी भिन्न हो सकती है)। हम उन्हें एक दुनिया से दूसरी में कैसे परिवर्तित कर सकते हैं?
खैर, keys के लिए, यह बहुत आसान है क्योंकि वास्तविक SQL क्वेरी स्ट्रिंग डेटा के एक सेट को बहुत अच्छी तरह से पहचानती है। हम key को छोटा करने के लिए क्वेरी स्ट्रिंग के MD5 डाइजेस्ट का उपयोग कर सकते हैं। वैल्यूज के लिए, यह ट्रिकियर है, लेकिन यहां आपके सहयोगी serialize() / unserialize() PHP फ़ंक्शंस हैं, जिनका उपयोग ऐरे से स्ट्रिंग और इसके विपरीत में कनवर्ट करने के लिए किया जा सकता है।
हम अगले सेक्शन में देखेंगे कि यह सब कैसे काम करता है।
3. स्टेटिक कैशिंग
हमारे पहले उदाहरण में, हम कैशिंग करने के लिए सबसे आसान तरीके से निपटेंगे: स्टेटिक वैल्यूज के लिए कैशिंग। हम इंटरफ़ेस "cache_CacheHelper" को कार्यान्वित करने वाले "cache_DBM" नामक एक क्लास लिखेंगे, जैसे कि:
class cache_DBM implements cache_CacheHelper { protected $dbm = null; function __construct ( $cache_file = null ) { $this->dbm = dba_popen($cache_file, "c", "db4"); if ( !$this->dbm ) { throw new Exception("$cache_file: Cannot open cache file"); } } 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) ) { throw new Exception("$key: Couldn't store"); } } function delete ($key) { if ( ! dba_delete($key, $this->dbm) ) { throw new Exception("$key: Couldn't delete"); } } }
यह क्लास बहुत आसान है: हमारे इंटरफेस और dba फंक्शन्स के बीच मैपिंग। कन्स्ट्रकटर में, दी गई फ़ाइल खोली जाती है,
और अन्य मेथड्स में इसका उपयोग करने के लिए ऑब्जेक्ट में रीटर्न किया हुआ हैंडलर स्टोर किया जाता है।
उपयोग का एक सरल उदाहरण:
$cache = new cache_DBM( "/tmp/my_first_cache.dbm" ); $cache->put("key1", "my first value"); echo $cache->get("key1"); $cache->delete("key1"); $data = $cache->get("key1"); if ( is_null($data) ) { echo "\nCorrectly deleted!"; }
नीचे, आप पाएंगे कि हमने यहां UML क्लास डायग्राम के रूप में व्यक्त किया है:

अब चलिए अपने डेटा मॉडल में कैशिंग सिस्टम जोड़ें। हम अपने प्रत्येक डीराइव्ड क्लासेज में कैशिंग जोड़ने के लिए "model_Model" क्लास बदल सकते थे। लेकिन, अगर हमने ऐसा किया होता, तो हम केवल विशिष्ट मॉडलों के लिए कैशिंग कैरेक्टरिस्टिक को असाइन करने के लिए लचीलापन खो देते थे, और मुझे लगता है कि यह हमारे काम का एक महत्वपूर्ण हिस्सा है।
तो हम "model_StaticCache" नामक एक और क्लास बनाएंगे, जो "model_Model" का विस्तार करेगा और कैशिंग फंक्शनलिटी जोड़ देगा। चलो स्केलेटन से शुरू करते हैं:
class model_StaticCache extends model_Model { protected static $cache = array(); protected $model_name = null; function __construct () { } protected function doStatement ($query) { } }
कन्स्ट्रक्टर में, हम डेटाबेस से कनेक्ट करने के लिए पहले पैरेंट कन्स्ट्रक्टर को कॉल करते हैं। फिर, हम स्टेटिक रूप से "cache_DBM" ऑब्जेक्ट बनाते हैं और स्टोर करते हैं (यदि कहीं और नहीं बनाया गया है)। हम प्रत्येक डीराइव्ड क्लास नाम के लिए एक इंस्टैंस स्टोर करते हैं क्योंकि हम उनमें से प्रत्येक के लिए एक DBM फ़ाइल का उपयोग कर रहे हैं। उस उद्देश्य के लिए, हम स्टेटिक ऐरे "$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] = new cache_DBM( $cache_dir . $this->model_name); } }
यह निर्धारित करने के लिए कि हमें कैश फ़ाइलों को कौन सी डायरेक्टरी लिखनी है, हमने फिर से एप्लिकेशन की कॉन्फ़िगरेशन क्लास का उपयोग किया है: "app_AppConfig"।
और अब: doStatement() मेथड। इस मेथड के लिए लॉजिक है: SQL स्टेटमेंट को वैलिड key में कनवर्ट करें, कैश में key सर्च करें, अगर फाउंड वैल्यू रीटर्न करता है। यदि नहीं मिला, तो डेटाबेस में इसे एक्सेक्यूट करें, रिजल्ट को स्टोर करें और इसे रीटर्न करें:
protected function doStatement ($query) { $key = md5($query); $data = self::$cache[$this->model_name]->get($key); if ( ! is_null($data) ) { return unserialize($data); } $data = parent::doStatement($query); self::$cache[$this->model_name]->put($key,serialize($data)); return $data; }
ध्यान देने योग्य दो और चीजें हैं। सबसे पहले, हम key के रूप में क्वेरी के MD5 का उपयोग कर रहे हैं। वास्तव में, यह जरूरी नहीं है, क्योंकि अंडरलाइंग DBM लाइब्रेरी मनमाने ढंग से साइज की keys स्वीकार करती है, लेकिन फिर भी key को छोटा करना बेहतर लगता है। यदि आप prepared स्टेटमेंट्स का उपयोग कर रहे हैं, तो key बनाने के लिए क्वेरी स्ट्रिंग में वास्तविक वैल्यूज को जोड़ना याद रखें!
एक बार "model_StaticCache" बनने के बाद, इसके उपयोग के लिए एक ठोस मॉडल को संशोधित करना मामूली है, आपको क्लास डिक्लेरेशन में केवल "extends" क्लॉज़ को बदलने की आवश्यकता है:
class model_Documents extends model_StaticCache { }
और यह सब कुछ है, जादू हो गया है! "model_Document" प्रत्येक डॉक्यूमेंट को रिट्रीव करने के लिए केवल एक क्वेरी करेगा। लेकिन हम इसे बेहतर कर सकते हैं।
4. कैशिंग एक्सपायर होना
हमारे पहले एप्रोच में, कैश में एक बार एक क्वेरी स्टोर होने के बाद, यह दो चीजें होने तक हमेशा के लिए वैलिड रहती है: हम इसकी key को स्पष्ट रूप से डिलीट करते हैं, या हम DBM फ़ाइल को अनलिंक करते हैं।
हालांकि यह एप्रोच केवल हमारे एप्लीकेशन के कुछ डेटा मॉडल के लिए वैलिड है: स्टेटिक डेटा (जैसे मेनू ऑप्शंस और इस तरह की चीजें)। हमारे एप्लीकेशन में सामान्य डेटा उस से अधिक डायनामिक होने की संभावना है।
हमारे वेब पेज में बेचे जाने वाले प्रोडक्ट्स के टेबल के बारे में सोचें। इसके हर मिनट में बदलने की संभावना नहीं है, लेकिन यह संभावना है कि यह डेटा बदल जाएगा (नए प्रोडक्ट्स को जोड़कर, सेलिंग प्राइस को बदलकर इत्यादि)। हमें कैशिंग इम्प्लीमेंट करने के लिए एक तरीका चाहिए, लेकिन डेटा में बदलावों पर प्रतिक्रिया करने का एक तरीका है।
इस समस्या की एक एप्रोच कैश में स्टोर्ड डेटा में एक समाप्ति समय निर्धारित करना है। जब हम कैश में नया डेटा संग्रहीत करते हैं, तो हम उस समय की एक विंडो सेट करते हैं जिसमें यह डेटा वैलिड होगा। उस समय के बाद, डेटा डेटाबेस से फिर से पढ़ा जाएगा और समय की एक और अवधि के लिए कैश में संग्रहीत किया जाएगा।
पहले की तरह, हम इस फंक्शनलिटी के साथ "model_Model" से एक और डीराइव्ड क्लास बना सकते हैं। इस बार, हम इसे "model_ExpiringCache" कहते हैं। स्केलेटन "model_StaticCache" के समान है:
class model_ExpiringCache extends model_Model { protected static $cache = array(); protected $model_name = null; protected $expiration = 0; function __construct () { } protected function doStatement ($query) { } }
इस क्लास में हमने एक नया एट्रिब्यूट पेश किया है: $expiration। यह वैलिड डेटा के लिए कॉन्फ़िगर किए गए समय विंडो को स्टोर करेगा। हमने इस वैल्यू को कन्स्ट्रक्टर में सेट किया है, शेष कन्स्ट्रक्टर "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] = new cache_DBM( $cache_dir . $this->model_name); } $this->expiration = 3600; // 1 hour }
जॉब का थोक doStatement में आता है। DBM फाइलों के पास डेटा की समाप्ति को नियंत्रित करने का कोई आंतरिक तरीका नहीं है, इसलिए हमें अपना खुद का इम्प्लीमेंट करना होगा। हम इसे एरे स्टोर करके करेंगे, इस तरह:
array( "time" => 1250443188, "data" => (the actual data) )
इस प्रकार के ऐरे को हम serialize करते हैं, और कैश में स्टोर करते हैं। "time," key कैश में डेटा का मॉडिफिकेशन समय है, और "data" वह वास्तविक डेटा है जिसे हम स्टोर करना चाहते हैं। पढ़ने के समय, यदि हमें लगता है कि key मौजूद है, तो हम वर्तमान समय के साथ क्रिएशन स्टोर्ड समय की तुलना करते हैं और समाप्त होने पर डेटा रीटर्न कर देते हैं।
protected function 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']; } }
यदि key मौजूद नहीं है या समाप्त हो चुकी है, तो हम क्वेरी को एक्सेक्यूट करना जारी रखते हैं और इसे रीटर्न करने से पहले कैश में नया रिजल्ट सेट स्टोर करते हैं।
$data = parent::doStatement($query); self::$cache[$this->model_name]->put( $key, serialize( array("data"=>$data,"time"=>$now) ) ); return $data; }
आसान है!
आइए अब "model_Index" को कैश की समाप्ति के साथ मॉडल में कनवर्ट करें। जैसा कि होता है, "model_Documents" के साथ, हमें केवल क्लास डिक्लेरेशन को संशोधित करने और "extends" क्लॉज़ को बदलने की आवश्यकता होती है:
class model_Documents extends model_ExpiringCache { }
समाप्ति समय के बारे में... कुछ विचार किए जाने चाहिए। सादगी के लिए हम एक कांस्टेंट समाप्ति समय (1 घंटा = 3,600 सेकंड) का उपयोग करते हैं, और क्योंकि हम अपने शेष कोड को मॉडिफाई नहीं करना चाहते हैं। लेकिन, हम इसे विभिन्न मॉडलों के लिए अलग-अलग समाप्ति समय का उपयोग करने की अनुमति देने के कई तरीकों से आसानी से मॉडिफाई कर सकते हैं। बाद में हम देखेंगे कि कैसे।
हमारे सभी जॉब के लिए क्लास डायग्राम इस प्रकार है:



5. अलग समाप्ति (Expiration)
प्रत्येक प्रोजेक्ट में, मुझे यकीन है कि आपके पास लगभग हर मॉडल के लिए अलग-अलग समाप्ति समय होगा: कुछ मिनट से लेकर घंटे या यहां तक कि दिन भी।
काश हमारे प्रत्येक मॉडल के लिए एक अलग समाप्ति समय हो सकता, तो यह सही होता... लेकिन, प्रतीक्षा करें! हम इसे आसानी से कर सकते हैं!
सबसे प्रत्यक्ष दृष्टिकोण कन्स्ट्रक्टर में आर्गुमेंट को जोड़ना है, इसलिए "model_ExpiringCache" के लिए नया कन्स्ट्रक्टर यह होगा:
function __construct ( $expiration=3600 ) { parent::__construct(); $this->expiration = $expiration; ... }
फिर, अगर हम 1 दिन का समाप्ति समय (1 दिन = 24 घंटे = 1,440 मिनट = 86,400 सेकंड) वाला मॉडल चाहते हैं, तो हम इसे इस तरह से पूरा कर सकते हैं:
class model_Index extends model_ExpiringCache { function __construct() { parent::__construct(86400); } ... }
और बस यही। हालांकि, नुक्सान यह है कि हमें सभी डेटा मॉडल मॉडिफाई करने होंगे।
ऐसा करने का एक और तरीका है "app_AppConfig" को कार्य सौंपना:
class app_AppConfig { ... public static function getExpirationTime ($model_name) { switch ( $model_name ) { case "model_Index": return 86400; ... default: return 3600; } } }
और फिर इस मॉडल को "model_ExpiringCache" कन्स्ट्रक्टर पर कॉल करें, इस तरह:
function __construct () { parent::__construct(); $this->model_name = get_class($this); $this->expiration = app_AppConfig::getExpirationTime($this->model_name); ... }
यह लेटेस्ट मेथड हमें फैंसी चीजें करने की अनुमति देता है, जैसे कि अधिक केंद्रीकृत तरीके से प्रोडक्शन या डेवलपमेंट एनवायरनमेंट के लिए अलग-अलग एक्सपायर होने वाली वैल्यूज का उपयोग करना। वैसे भी, आप अपना खुद चुन सकते हैं।
UML में, पूरा प्रोजेक्ट इस तरह दिखता है:



6. कुछ चेतावनी
कुछ क्वेरीज हैं जिन्हें कैश नहीं किया जा सकता है। सबसे स्पष्ट हैं INSERT, DELETE या UPDATE जैसी क्वेरीज को मॉडिफाई करना। ये क्वेरीज डेटाबेस सर्वर पर पहुंचनी चाहिए।
लेकिन यहां तक कि SELECT क्वेरीज के साथ, कुछ परिस्थितियां हैं जिनमें एक कैशिंग सिस्टम समस्याएं पैदा कर सकता है। इस तरह की एक क्वेरी पर एक नज़र डालें:
SELECT * FROM banners WHERE zone='home' ORDER BY rand() LIMIT 10
यह क्वेरी हमारी वेबसाइट के "home" जोन के लिए रैंडम रूप से 10 बैनर का चयन करती है। इसका उद्देश्य हमारे होम में दिखाए गए बैनर में मूवमेंट उत्पन्न करना है, लेकिन यदि हम इस क्वेरी को कैश करते हैं, तो कैश किए गए डेटा की अवधि समाप्त होने तक यूजर को कोई भी मूवमेंट दिखाई नहीं देगा।
rand() फ़ंक्शन निर्धारिती नहीं है (क्योंकि यह now() या कोई और नहीं है); इसलिए यह प्रत्येक एक्सेक्यूशन पर एक अलग वैल्यू रीटर्न कर देगा। यदि हम इसे कैश करते हैं, तो हम सभी कैशिंग अवधि के लिए केवल उन परिणामों में से एक को फ्रीज कर देंगे, और इसलिए फंक्शनलिटी को तोड़ देंगे।
लेकिन एक साधारण री-फैक्टरिंग के साथ, हम कैशिंग के लाभ प्राप्त कर सकते हैं और pseudo-randomness दिखा सकते हैं:
class model_Banners extends model_ExpiringCache { public function 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" ); return $banners; } ... }
हम यहां क्या कर रहे हैं पचास अलग रैंडम बैनर कॉन्फ़िगरेशन कैश करें, और उन्हें रैंडम रूप से चुनें। 50 SELECT इस तरह दिखेंगे:
SELECT * FROM banners WHERE zone='home' AND 1=1 ORDER BY rand() LIMIT 10 SELECT * FROM banners WHERE zone='home' AND 2=2 ORDER BY rand() LIMIT 10 ... SELECT * FROM banners WHERE zone='home' AND 50=50 ORDER BY rand() LIMIT 10
हमने सेलेक्ट में कांस्टेंट कंडीशन जोड़ दी है, जिसका डेटाबेस सर्वर पर कोई कॉस्ट नहीं है लेकिन कैशिंग सिस्टम के लिए 50 अलग-अलग keys प्रदान करता है। सभी बैनर की विभिन्न कॉन्फ़िगरेशन देखने के लिए यूजर को पचास बार पेज लोड करने की आवश्यकता होगी; तो डायनामिक प्रभाव हासिल किया जाता है। कैश लाने के लिए डेटाबेस को कॉस्ट पचास क्वेरीज है।
7. एक बेंचमार्क
हम अपने नए कैशिंग सिस्टम से क्या लाभ प्राप्त कर सकते हैं?
सबसे पहले, यह कहा जाना चाहिए कि, रॉ प्रदर्शन में, कभी-कभी हमारा नया इम्प्लीमेंटेशन डेटाबेस क्वेरीज से धीमा हो जाएगा, विशेष रूप से बहुत ही सरल, अच्छी तरह से कस्टमाइज क्वेरीज के साथ। लेकिन उन क्वेरीज के लिए जुड़ने के साथ, हमारे DBM कैश तेजी से चलेंगे।
हालांकि, हमने जो समस्या हल की है वह रॉ परफॉरमेंस नहीं है। प्रोडक्शन में आपके परीक्षणों के लिए आपके पास कभी भी एक अतिरिक्त डेटाबेस सर्वर नहीं होगा। आपके पास शायद उच्च वर्कलोड वाले सर्वर होंगे। इस स्थिति में, यहां तक कि सबसे तेज़ क्वेरी धीरे-धीरे चल सकती है, लेकिन हमारी कैशिंग योजना के साथ, हम सर्वर का भी उपयोग नहीं कर रहे हैं, और वास्तव में, हम इसके वर्कलोड को कम कर रहे हैं। तो असली प्रदर्शन वृद्धि प्रति सेकंड अधिक पेटिशन के रूप में आ जाएगा।
एक ऐसी वेबसाइट में जिसे मैं वर्तमान में डेवेलोप कर रहा हूं, मैंने कैशिंग के लाभों को समझने के लिए एक सरल बेंचमार्क किया है। सर्वर मामूली है: यह 2 GB रैम और पुरानी PATA हार्ड डिस्क के साथ AMD Athlon 64 X2 5600+ के शीर्ष पर Ubuntu 8.10 चलाता है। सिस्टम Apahce और MySQL 5.0 चलाता है, जो बिना किसी ट्यूनिंग के Ubuntu डिस्ट्रीब्यूशन के साथ आता है।
परीक्षण Apache के बेंचमार्क प्रोग्राम (ab) को 1, 5 और 10 समवर्ती क्लाइंट्स के साथ चलाने के लिए था, जो मेरी डेवलपमेंट वेबसाइट से 1,000 बार एक पेज लोड कर रहा था। वास्तविक पेज एक प्रोडक्ट डिटेल था जिसमें 20 से कम क्वेरीज नहीं हैं: मेनू कंटेंट्स, प्रोडक्ट डिटेल्स, रेकमेंडेड प्रोडक्ट्स, बैनर इत्यादि।
कैश के बिना परिणाम 1 क्लाइंट के लिए 4.35 p/s, 5 क्लाइंट के लिए 8.25 और 10 क्लाइंट के लिए 8.29 थे। कैशिंग (विभिन्न समाप्ति) के साथ, परिणाम 1 क्लाइंट के साथ 25.55 p/s, 5 क्लाइंट के लिए 49.01 और 10 क्लाइंट्स के लिए 48.74 थे।
अंतिम विचार
मैंने आपको अपने डेटा मॉडल में कैशिंग डालने का एक आसान तरीका दिखाया है। बेशक, विकल्पों में काफी कुछ है, लेकिन यह एक विकल्प है जो आपके पास है।
हमने डेटा स्टोर करने के लिए स्थानीय DBM फाइलों का उपयोग किया है, लेकिन यहां तक कि तेज़ विकल्प भी हैं जिन्हें आप एक्सप्लोर करने पर विचार कर सकते हैं। भविष्य के लिए कुछ विचार: APC के apc_store() फंक्शन्स का उपयोग अंडरलाइंग स्टोरेज सिस्टम के रूप में, वास्तव में महत्वपूर्ण डेटा के लिए शेयर्ड मेमोरी, memcached, आदि का उपयोग कर।
मुझे उम्मीद है कि आपने इस ट्यूटोरियल का आनंद लिया है जितना मैंने इसे लिखा था। हैप्पी कैशिंग!
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Update me weekly