Wer die “neue” Version kennt, weiss vermutlich auch, dass diese Version das cachen entdeckt hat. Datensätze, die sich nicht oft ändern, können einfach als Datei gespeichert werden, und diese Datei selbst wird wieder aufgerufen, was natürlich schneller geht, als zuerst Bedinnungen zu prüfen und so.
Den Cache haben auch viele andere Softwareapplikationen. Warum aber sollte das die 0.9.7 nicht können?
Im Rahmen meiner Generalüberhohlung von LoGD hab ich das Problem angenommen und es (natürlich) auch gelöst. Das Objekt veröffentliche ich nun hiermit - es benötigt allerdings PHP5, und die Anwendung erfordert PHP-Kenntnisse.
Achtung, Doppellizenz: Hiermit lizenziere ich diese Dateien ausdrücklich unter der GNU GPL der Version 2.0 oder jedweder späteren Version. Zusätzlich zur GNU GPL gilt allerdings auch die “Creative Commons Licence by-nc-sa”, das Objekt kann also unter der neuen Version gebraucht werden!
$this->bbcode_second_pass_code('', 'class Datacache {
/**
* Cache-Objekt
* (c) 2007-2008 by Basilius "Wasili" Sauter
*
* Version 1.0 vom 4. August 2007
* * Cache verwaltung mit laden, speichern, überschreiben
* Version 2.0 vom 7. Februar 2008
* * Mit RAMCache-Unterstützung (Memcache-Hülse)
* Version 2.1 vom 18. Februar 2008
* * Singleton-Pattern
*/
protected $cache;
protected $usecache = false;
protected static $instance = NULL;
// Only Singleton-Creating is allowed
protected function __construct() {
$this->usecache = CACHE_USE;
}
// No clone allowed.
private final function __clone() {}
public static function getInstance() {
if (self::$instance === NULL) {
self::$instance = new Datacache;
}
return self::$instance;
}
public function &loadCache($cachehandle, $lifetime = 1000) {
if($this->usecache) {
if(isset($this->cache[$cachehandle])) {
return $this->cache[$cachehandle];
}
else {
if(MEMCACHE_USE === true) {
$memcache = RAMCache::getInstance();
$c = $memcache->get($cachehandle);
}
else {
$c = false;
}
if($c === false) {
$filename = $this->getCacheFilename($cachehandle);
if(file_exists($filename)) {
$lastmodified = filemtime($filename);
if((ER_TIMESTAMP - $lastmodified) > $lifetime) {
// Abgelaufen
return false;
}
else {
$this->cache[$cachehandle] = unserialize(file_get_contents($filename));
if(MEMCACHE_USE === true) {
$memcache->set($cachehandle, serialize($this->cache[$cachehandle]));
}
return $this->cache[$cachehandle];
}
}
else {
return false;
}
}
else {
$this->cache[$cachehandle] = unserialize($c);
return $this->cache[$cachehandle];
}
}
}
else {
// Kein Datacaching => False zurück geben.
return false;
}
}
public function updateCache($cachehandle, &$data) {
if($this->usecache) {
$this->cache[$cachehandle] = serialize($data);
$filename = $this->getCacheFilename($cachehandle);
if(file_exists($filename) AND is_writable($filename)) {
$write = true;
}
elseif(is_writable(CACHE_DIR)) {
$write = true;
}
else {
$write = false;
}
// Umgeht einen Bug für Windowssysteme (#27609; http://bugs.php.net/bug.php?id=27609)
if(SERVER_OS == 'Windows') {
$write = true; # Hals und Beinbruch...
}
if($write) {
if(CACHE_USE_SEMAPHORE) {
$semid = $this->getLock($filename);
}
file_put_contents($filename, $this->cache[$cachehandle]);
@chmod($filename, 0777);
if(MEMCACHE_USE === true) {
// Auch in den Arbeitsspeicher speichern.. Hihi
$memcache = RAMCache::getInstance();
$memcache->set($cachehandle, $this->cache[$cachehandle]);
}
if(CACHE_USE_SEMAPHORE) {
$this->releaseLock($semid);
}
}
else {
return false;
}
}
else {
return false;
}
}
public function dropCache($cachehandle) {
$filename = $this->getCacheFilename($cachehandle);
if(file_exists($filename)) {
if(is_writable($filename)) {
if(CACHE_USE_SEMAPHORE) {
$semid = $this->getLock($filename);
}
unlink($filename);
if(MEMCACHE_USE === true) {
// Auch in aus dem RAM löschen.
$memcache = RAMCache::getInstance();
$memcache->delete($cachehandle);
}
if(CACHE_USE_SEMAPHORE) {
$this->releaseLock($semid);
}
return true;
}
else {
return false;
}
}
else return true;
}
// Löscht alle Dateien, die mit $pattern beginnen.
public function massiveDropCache($pattern) {
$pattern = str_replace('*', '__STAR__', $pattern);
$pattern = str_replace(' ', '_', $pattern);
$pattern = preg_replace('/[^[:alnum:]_-]/', '', $pattern);
$pattern = str_replace('__STAR__', '*', $pattern);
$files = glob(CACHE_DIR."$pattern*.tmp");
foreach($files as $file) {
if(CACHE_USE_SEMAPHORE) {
$semid = $this->getLock($file);
}
unlink($file);
if(MEMCACHE_USE === true) {
// Auch in aus dem RAM löschen.
$memcache = RAMCache::getInstance();
$cachehandle = substr($file, 0, strpos($file, '-{'));
$memcache->delete($cachehandle);
}
if(CACHE_USE_SEMAPHORE) {
$this->releaseLock($semid);
}
}
}
public function getCacheFilename($cachehandle) {
$cachehandle = str_replace(' ', '_', $cachehandle);
$cachehandle = preg_replace('/[^[:alnum:]_-]/', '', $cachehandle);
$cachehandle = CACHE_DIR.$cachehandle.'-{'.sha1($cachehandle).'}.tmp';
return $cachehandle;
}
protected function getLock($filename) {
if(SERVER_OS === 'Unix' AND function_exists('sem_get')) {
if(!file_exists($filename)) {
file_put_contents($filename, '');
@chmod($filename, 0777);
}
$sem_key = ftok($filename, 'L');
$sem_id = sem_get($sem_key, 1, null, 1);
// Blockierung, bis Semaphore freigegeben ist
if(sem_acquire($sem_id) == true) {
return $sem_id;
}
else {
return false;
}
}
else {
return -1;
}
}
protected function releaseLock($sem_id) {
if(SERVER_OS === 'Unix' AND function_exists('sem_get')) {
if($sem_id !== false) {
return sem_release($sem_id);
}
else {
return true;
}
}
else {
return -1;
}
}
}')
Als letztes benötigen wir nun noch die Funktion, welche Datenbank und Cache verbindet - nennen wir sie db_cached_query:
$this->bbcode_second_pass_code('', 'function &db_cached_query($sql, $cachehandle, $lifetime = 1000) {
global $dbqueriesthishit,$dbtimethishit;
$DataCache = DataCache::getInstance();
$data = $DataCache->loadCache($cachehandle, $lifetime);
if(!is_Array($data)) {
$dbqueriesthishit++;
$dbtimethishit -= microtime(true);
$res = db_query($sql);
$data = array();
if(db_num_rows($res) > 0) {
$data = array();
while($row = db_Fetch_Assoc($res)) {
$data[] = $row;
}
}
$dbtimethishit += microtime(true);
$DataCache->updateCache($cachehandle, $data);
}
return $data;
}')
Zur AnwendungIch habe bei mir sogar die Commentary gecached. Leider weiss ich nicht, in wiefern sich das von der Geschwindigkeit ändert, da mein Server nicht unter Last läuft - Entwicklunsserver auch. Allerdings will ich die Anwendung am Beispiel meiner Datenbankfarben erklären.
Da cached_query alle Datensätze als Array liefert, ist es notwendig, den Datensatz anders zu behandeln, als normalerweise. Anstatt einer While()-Schleife nehmen wir die für Arrays gedachte foreach().
Die Funktion “load_Tags” würde also wie folgt aussehen:
$this->bbcode_second_pass_code('', 'function Load_Tags() {
global $db,$link;
/* (c) 2005 by Eliwood & Serra */
$tags = array();
$rows = db_cached_query('SELECT * FROM appoencode', 'appoencode', 60*60*24);
foreach($rows as $row) {
$tags[$row['code']] = $row;
}
return $tags;
}')
Als drittes benötigt man noch 4 Konstanten, die in den Kopf der common.php einzupflegen sind:
$this->bbcode_second_pass_code('', '// Cache-Erweiterung
define('CACHE_DIR', './cache/');
define('CACHE_USE', true);
define(ER_TIMESTAMP, time());
// Server-OS definieren. Unterscheidet Windows/Unixoid
if(strpos($_ENV['OS'], 'Win')) {
define('SERVER_OS', 'Windows');
}
else {
define('SERVER_OS', 'Unix');
}')
Bitte achtet darauf, dass PHP im gewählten Ordner Schreibrechte besitzt! Für Unix-User kann man auch einfach /tmp angeben, das sollte PHP immer Schreibrechte besitzen. /tmp ist der Ordner im Verzeichnisbaum, der genau für so etwas vorgesehen wurde.
Bei der Funktion db_cached_query ist der erste Parameter der eigentliche Query (der selbst nicht ständig anders sein sollte, sonst machts keinen Sinn). Der Zweite Parameter ist der Name der Cache-Datei - also bitte eine Eindeutige Indentifikation vergeben. Der dritte Parameter schliesslich stellt die Lebenszeit in Sekunden ein. Oben ist es nun ein Tag, bei den Farben rentiert sich sogar eine viel grössere Lebenszeit, Monate, wenn nicht sogar ein volles Jahr.
Was nun beachtet werden muss, ist, dass man den Cache bei einer Änderung wieder löscht. Hat man den Farbeneditor (Hier verfügbar, nicht von mir), so kann das noch schnell passieren. Hier lässt sich aber auch hervorragend dagegen vorgehen: Man löscht einfach Automatisch die Cache-Datei. Bei Änderungen in der Datenbank muss man das selbst machen.
Das löschen der Datei sollte in etwa so aussehen:
$this->bbcode_second_pass_code('', '$DataCache = Datacache::getInstance();
$DataCache->massiveDropCache("appoencode");')
Oder:
$this->bbcode_second_pass_code('', '$DataCache = Datacache::getInstance();
$DataCache->dropCache("appoencode");')
Damit der Cache aber funktioniert, muss man noch in der common.php eine Änderung durchführen. Man muss die beiden Objekte und die Funktion einbinden, sowie den Cache “aktivieren”, kurz nach der Verbindung mit der Datenbank:
$this->bbcode_second_pass_code('', '// Datacache erstellen und registrieren
$DataCache = DataCache::getInstance();')
Viel Spass damit
PS: Ich spare mit dem Ding etwa 6 von 10 Queries auf der Startseite. Und viel eingearbeitet ist es noch nichtmal
Zitat:
Mit Cache: 4 Queries in 0.00717s
Ohne Cache: DB: 10 Queries in 0.0874s
Einfachprobe, unter Ubuntu GNU/Linux 7.10 mit Apache 2.2.4 und PHP 5.2.3-1 und MySQL 5.0.45-Debian_1ubuntu3-log
(Achtung zu den Daten: Ich cache noch viel, was es in der Standardversion gar nicht gibt! Nicht enttäuscht sein, wenn die Werte anders sind.)