Große PHP-Arrays, SPL und Sessions

Folgende Problemstellung: eine große Datenmenge wird auf einmal abgefragt, soll aber nicht direkt komplett an den Client gesendet werden, also wird sie in der Session zwischengespeichert. Vielleicht im allgemeinen nicht die geschickteste Lösung, in meinem Fall fielen die Nachteile jedoch nicht ins Gewicht. “Groß” bedeutete dabei im Bereich von 10-50 MB in 50K-100K Datensätzen.

Das ist nun leider eine Menge, bei der PHP-Arrays nur noch mit Vorsicht einzusetzen sind. Der Flaschenhals war in diesem Fall array_shift(), womit Einträge aus dem in der Session befindlichen Arrays entnommen wurden. Was läge da näher, als auf eine der SPL-Datenstrukturen zurückzugreifen? Leider sind sowohl SplStack als auch SplFixedArray nicht serialisierbar und somit nicht ohne Weiteres mit Sessions zu gebrauchen.

Dies lässt sich nachrüsten, dabei muss allerdings doch wieder auf PHP-Arrays zurückgegriffen werden. Mit dem Performance-Verlust beim Serialisieren und Deserialisieren erkauft man sich allerdings eine deutlich effizientere Daten-Verarbeitung. In meinem Fall war SplStack bzw. SplDoublyLinkedList perfekt, da die Daten nur noch der Reihe nach abgeholt werden sollten. Die Erweiterung sieht wie folgt aus:

Serialisierbare SPL-Datenstruktur

class SerializableList extends SplDoublyLinkedList
{
    private $_data;
    
    public function __sleep()
    {
        $this->_data = array();
        
        $this->rewind();
        
        while ($this->valid()) {
            $this->_data[] = $this->current();
            $this->next();
        }
        
        return array('_data');
    }
    
    public function __wakeup()
    {
        foreach ($this->_data as $row) {
            $this->push($row);
        }
        
        $this->_data = array ();
    }
}

Kurz erkärt

Beim Serialisieren (__sleep()) wird die Datenstruktur in ein PHP-Array (im Attribut _data) konvertiert und mit return array('_data') festgelegt, dass genau dieses Attribut serialisiert werden soll. Beim Deserialisieren (__wakeup()) ist _data wiederhergestellt und kann zurück konvertiert werden. Anschließend wird mit $this->_data = array() der Speicher wieder freigegeben.

Vorsicht

Ob diese Lösung im konkreten Fall sinnvoll ist, kann nur durch eigene Messungen ermittelt werden. Dabei sollte vor allen Dingen darauf geachtet werden, die Anzahl der Serialisierungsvorgänge so gering wie möglich zu halten, denn wie schon gesagt sind die durch die zusätzliche Konvertierung teurer als zuvor. Als Beispiel: Bei 8 Abfragen a 10000 Datensätzen war meine Anwendung mindestens 20 mal schneller als bei 80 Abfragen a 1000 Datensätzen. Und beide Varianten schlagen die Implementierung nur mit PHP-Arrays um Längen.

Eine Herausforderung für weitere Optimierung wäre es noch, einen eigenen Serialisierer zu schreiben, der ohne PHP-Array auskommt. Das wäre allerdings eher etwas für die PECL, sprich direkt in C gehackt. In PHP selbst sehe ich da wenig Hoffnung in puncto Effizienz.