Wenn es darum geht, sich für ein Speichermedium zu entscheiden, so wird die DBM-Datei oft übersehen. Sie ist bekannt als eine Datenbank des „kleinen Mannes“. Dabei vergessen viele Anwender, dass DBM-Dateien äußerst schnell arbeiten und für schnellen und gleichzeitigen Lese/Schreib-Zugriff auf lokale Daten ausgelegt sind. Wie dies intern gewährleistet wird, wird dem Anwender von PHP nicht offenbart, ein sicherer Mehrfachzugruf wir jedoch garantiert.

DBM-Dateicaches übertreffen einfache Datei-Caches insofern, als sie dafür entwickelt wurden, mehrere Datenquellen in einer Datei zu speichern und gleichzeitig Updates zu unterstützen. Wohingegen einfache Datei-Caches am besten mit nur einer Information pro Datei arbeiten, sodass die Gleichzeitigkeit extra integriert werden muss.

DBM-Dateien sind eine gute Lösung, wenn wir Daten als Schlüssel-Paare speichern müssen, z. B. ein Cache für Datenbankabfragen. Für diese Vorgehensweise gibt es bereits vorgefertigte Lösungen für die DBM-Dateien:

dbm– der original Barkeley DB-Treiber.
gdbm– der Ersatz für dbm von GNU.
ndbm– als Ersatz für dbm gedacht, wird aber nicht mehr gewartet.

Wir können eine DBM-Datei verwenden, um einige Daten zu cachen. Wir nehmen an, das wir ein User-Interface für ein Web-Shop geschrieben haben, um möchten die Aktivitäten der User verfolgen und auswerten. Jeder Einkauf hat also eine ID, und wir haben auch eine Funktion geschrieben die und die Summe aller Käufe eines bestimmten Benutzers anzeigt. (Bitte beachten, dass die Funktionen und die Logik zum Teil im Pseudocode geschrieben ist.)

function showPurchaseAmount($userid)
{
    $db = new DB_MySQL_Conn();
    $row = $db->execute('SELECT count(distinct(purchase_id)) amount
                            FROM purchase_table
                            WHERE user_id = ' . $userid)->fetch_assoc();
    return $row['amount'];
}

Bei einem großen Datenbestand, ist die Abfrage nicht sehr schnell. Hier sollten wir auf jeden Fall einen Cache einsetzen. Zudem können sich die SQL Funktionen count() und distinct() in solch einer Abfrage, als sehr Performance raubend erweisen. Wir möchten also den Cache direkt in der Funktion integrieren. Dafür müssen wir eine DBM-Datei öffnen und dort das Ergebnis auslesen, sofern es in der Datei vorhanden ist.

function showPurchaseAmount($userid)
{
    $gdbm = dba_popen('purchaseCounter.dbm', 'c', 'gdbm');
    $count = dba_fetch($userid, $gdbm);

    if (false !== $count)
    {
        return $count;
    }

    $db = new DB_MySQL_Conn();
    $row = $db->execute('SELECT count(distinct(purchase_id)) amount
                            FROM purchase_table
                            WHERE user_id = ' . $userid)->fetch_assoc();

    dba_replace($userid, $row['amount'], $gdbm);

    return $row['amount'];
}

Dieses Beispiel verwendet gdbm. Hier ist jedoch zu beachten das gdbm unter Windows nicht unterstützt wird. Eine Liste aller verfügbaren DBA-Handler auf unseren System erhalten wir durch den Aufruf von dba_handlers() Funktion. Hier werden uns alle verfügbaren handlers als ein Array zurück geliefert. Wer einen benchmark Vergleich der Datenbanken im „Berkeley DB“ Stil braucht um sich für die richtige Datenbank zu entscheiden, kann es in dieser (http://qdbm.sourceforge.net/benchmark.pdf) PDF Datei nachlesen. Alle weiteren Informationen wie Anforderungen für die Installation und DBA-Funktionen über die Abstraktionsschicht der dbm-style-Datenbanken können auf der Seite von php.net nachgelesen werden. http://de3.php.net/manual/de/book.dba.php

4 thoughts on “Cachen mit DBM Dateien

  1. Davon habe ich ja noch nie gehört, interessant! Wenn ich keinen memcached oder etwas ähnlich schnelles zur Verfügung habe (sprich nur ein Webhosting-Paket) habe ich bisher immer SQLite genommen, oder mich auf den MySQL-Query-Cache verlassen.

    Wie würde man eine festgelegte Cache-Zeit mit den DBA-Funktionen umsetzen? Ein Cache-Eintrag soll also nach 5 Minuten ungültig werden. Memcached hat sowas bereits eingebaut, bei SQLite würde man das mit einer Timestamp Spalte machen.

    Vielleicht sollte man auch erwähnen dass die DBA-Funktionen mit einkompiliert werden müssen. Eventuell sind sie also auf Webhosting-Angeboten nicht verfügbar, problematisch also für Applikationen die auf möglichst vielen Webservern laufen sollen.

    1. Caching mit DBM-Dateien – Teil 2

      @PHPGangasta: Sehr gut, du bist ein Aufmerksamer Leser. Beim DBM-basiertes Caching gibt es keine Möglichkeit die Verfallszeit eines Eintrages zu bestimmen. Deswegen implementieren wir das selber. Wir können beim speichern der Einträge die Modifizierungszeiten setzen um somit veraltetet Einträge löschen zu können. Das könnte ungefähr so aussehen:

      function put_object_to_cache($identifier, $object)
      {
      	$gdbm 	   = dba_popen('purchaseCounter.dbm', 'c', 'gdbm');   
      	$storage_object = array('object' => $object, 'time' => time());
      	
      	dba_replace($identifier, serialize($storage_object), $gdbm);
      }
      

      Wir benötigen hierfür einen Schlüsselnamen. Da der nicht automatisch generiert wird, müssen wir uns hier ein eigenes Namensschema ausdenken, das die Objekte eindeutig identifiziert. Danach erstellen wir einen Array mit dem Schlüssel [objekt] das den Wert des zu speichernden Objektes hat und mit dem Schlüssel [time] das die Zeit des Erstellens als Wert zugewiesen bekommt. Und da die DBM-Funktionen nur strings speichern kann, serialisieren wir das neue $storage_object. Das auslesen aus dem Cache könnte so aussehen:

      function get_object_from_cache($identifier, $expiration = 300)
      {
      	$gdbm        = dba_popen('purchaseCounter.dbm', 'c', 'gdbm'); 
      	$get_object = unserialize(dba_fetch($identifier, $gdbm));
      	
      	if ((time() - $get_object['time']) < $expiration)
      	{
      		return $get_object['object'];
      	}
      	
      	dba_delete($identifier, $gdbm);
      	
      	return false;
      }
      

      Mittels des Schlüsselnamen lesen wir das Objekt aus dem Cache aus. Danach de-serialisieren wir es, und vergleichen den Zeitstempel mit der Verfallszeit. Wenn der Verfallstest erfolgreich ist, wird es zurückgegeben, sonst wird es aus dem Cache gelöscht. Zuletzt müssen wir noch eine Methode implementieren die sich um das bereinigen von veralteten Einträgen kümmert. Wenn der Cache sehr viel genutzt wird, dann ist eine Wartung des Caches ohnehin notwendig. Die Methode könnte etwa so aussehen:

      function clean_garbage_collection()
      {
      	$gdbm         = dba_popen('purchaseCounter.dbm', 'c', 'gdbm');
      	$pointer       = dba_firstkey($gdbm);
      	$cache_keys = array();
      	
      	while ($pointer)
      	{
      		get_object_from_cache($pointer, 300)
      
      		$pointer = dba_nextkey($gdbm);
      	}
      }
      

      Es werden also alle Schlüsselnamen ausgelesen und mit der Funktion get_object_from_cache(… …) nach ihre Verfallszeit überprüft, und die veralteten Einträge gelöscht. Jedoch, sollte das bereinigen des Caches nicht nach jedem Prozess ausgeführt werden. Hier reicht ein wiederkehrender Cron-Job aus. Denn das DBM-Cache funktioniert solange ohne unserer eingreifen, wie wir ihn freien Speicherplatz auf das System bieten.

Schreibe einen Kommentar zu PHPGangsta Antworten abbrechen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.