ZODB reparieren

Eine Zope-Datenbank kann z.B. durch einen Systemabsturz oder einen Festplattendefekt korrumpiert werden. Dies macht sich meist durch einen POSKeyError oder einen CorruptedError und nicht mehr bearbeitbare Objekte bemerkbar.

CorruptedError

Dieser Fehler kann verschiedene Ursachen haben, z.B. falsche Längen oder Zeiten von Transaktionen.

fsrecover.py ist ein Skript, das die Integrität von Transaktionen überprüft und diejenigen mit korrupten Daten entfernt. Daher ist es auch nicht für POSKeyErrors geeignet sondern empfiehlt sich vielmehr für CorruptedErrors. Darüberhinaus kann es auch zu weiteren POSKeyErrors führen wenn eine fehlerhafte Transaktion entfernt wird und dadurch den Verweis auf ein nicht mehr vorhandenes Objekt zurücklässt:

$ ./bin/zopepy -m ZODB.scripts.fsrecover -P 0 var/filestorage/Data.fs var/filestorage/Data.fs.recovered &> logrecover.txt

In logrecover.txt können Sie anschließend nachschauen, wieviele Daten Ihnen verloren gingen, z.B.:

Recovering var/filestorage/Data.fs into var/filestorage/Data.fs.recovered
. 1 . 2 . 3 . 4 . 5 . 6 . 7 . 8 . 9 . 0
0 bytes removed during recovery
Packing ...

POSKeyError

Um diesen Fehler zu verstehen ist es wichtig zu wissen, dass jedes Objekt in der Datenbank eine eindeutige ID (OID) zugewiesen bekommen hat. Diese OID ist eine binäre Zahl, wie z.B. 0x40A90L, die auf ein serialisiertes Objekt verweist. Bei einem POSKeyError kann nun für eine OID kein passendes Objekt gefunden werden. So speichert z.B. ein Ordner, der von OFS.ObjectManager abgeleitet ist, die enthaltenen Objekte als Werte des _objects-Attributs. Die daraus resultierende Liste wird beim Speichern in eine Liste von OIDs übersetzt. Kann nun beim Laden von objectValues() eine OID nicht mehr einem serialisierten Objekt zugewiesen werden, wird ein POSKeyError ausgegeben.

  1. Mit zc.zodbdgc kommt ein Skript mit, das die Überprüfung von mehreren ZODBs erlaubt: multi-zodb-check-refs. Dabei traversiert es ab der Wurzel durch die gesamten Datenbanken. Dies soll sicherzustellen, dass alle Objekte erreichbar sind und jedes nicht-erreichbare Objekt protokolliert werden kann. Darüberhinaus wird bei Blob-Eintragen überprüft, ob ihre Dateien geladen werden können.

  2. Zum Installieren von zc.zodbdgc wird zunächst eine virtualenv- Umgebung aufgesetzt:

    $ easy_install-2.6 virtualenv
    $ virtualenv --no-site-packages zeo_check
    
  3. Anschließend wird in dieser virtuelenv-Umgebung zc.zodbdgc installiert:

    $ cd zeo_check
    $ ./bin/easy_install zc.zodbdgc
    
  4. Packen Sie anschließend Ihre ZODB und kopieren diese in Ihre virtuelenv- Umgebung.

  5. Erzeugen Sie eine Konfigurationsdatei storages.cfg mit folgendem Inhalt:

    <zodb>
      <filestorage my>
         path var/filestorage/my.fs
         blob-dir var/blobstorage-my
      </filestorage>
    </zodb>
    
  6. Anschließend kann das multi-zodb-check-refs-Skript aufgerufen werden mit:

    $ ./bin/multi-zodb-check-refs storages.cfg
    

    Sind alle Referenzen Ihrer Datenbank gültig, so erhalten Sie keine Ausgabe. Bei POSKeyErrors sieht die Ausgabe beispielsweise so aus:

    !!! main 26798 ?
    POSKeyError: 0x68ae
    

Regelmäßige Überprüfung und E-Mail-Benachrichtigung

Dieses Skript sollte nun regelmäßig als Cronjob ausgeführt werden:

# Check ZEO Storages
0 6 * * * cd /home/veit/zeo_check; ./bin/multi-zodb-check-refs |mailx -s "Check Storages" -c admin@veit-schiele.de

Wiederherstellen

  1. Möglicherweise können die fehlenden Objekte aus dem Backup zurückgespielt werden.

  2. Mit der -r-Option erhalten Sie eine Datenbank mit entgegengesetzten Referenzen, womit sich gegebenenfalls herausfinden lässt, welche Objekte fehlen:

    $ ./bin/multi-zodb-check-refs -r var/filestorage/refdb.fs storages.cfg
    !!! main 26798 main 16717
    POSKeyError: 0x68ae
    
  3. Nun schreiben Sie eine refdb.cfg mit folgendem Inhalt:

    <zodb main>
        <filestorage 1>
              path /home/veit/zeo_check/var/filestorage/refdb.fs
        </filestorage>
    </zodb>
    
  4. Anschließend können Sie die Datenbank öffnen:

    $ ../myproject/bin/zopepy
    >>> import ZODB.config
    >>> db = ZODB.config.databaseFromFile(open('./refdb.cfg'))
    >>> conn = db.open()
    >>> refs = conn.root()['references']
    

    Sie dürften nun eine Fehlermeldung wie diese bekommen:

    !!! main 13184375 ?
    POSKeyError: 0xc92d77
    
  5. Nun können Sie die OID desjenigen Objekts herausfinden, von dem aus referenziert wird:

    >>> parent = list(refs['main'][13184375])
    >>> parent
    [13178389]
    
  6. Wird nun dieses Objekt geladen, sollten Sie einen POSKeyError erhalten:

    >>> app._p_jar.get('13178389')
    2010-07-16 15:30:18 ERROR ZODB.Connection Couldn't load state for 0xc91615
    Traceback (most recent call last):
    …
    ZODB.POSException.POSKeyError: 0xc92d77
    
  7. Wir können jedoch die aktuellen Daten des Elternobjekts laden um eine Vorstellung von diesem Objekt zu erhalten:

    >>> app._p_jar.db()._storage.load('\x00\x00\x00\x00\x00\xc9\x16\x15', '')
    ('cBTrees.IOBTree
    IOBucket
    q\x01.((J$KT\x02ccopy_reg
    _reconstructor
    q\x02(cfive.intid.keyreference
    KeyReferenceToPersistent
    …
    
  8. Nun erzeugen wir ein Fake-Objekt, das dieselbe OID (13184375) wie das fehlenden Objekt hat mit:

    $ ./bin/instance-debug debug
    Starting debugger (the name "app" is bound to the top-level Zope object)
    …
    >>> import transaction
    >>> transaction.begin()
    >>> from ZODB.utils import p64
    >>> p64(26798)
    '\x00\x00\x00\x00\x00\x00h\xae'
    >>> from persistent import Persistent
    >>> a = Persistent()
    >>> a._p_oid = '\x00\x00\x00\x00\x00\x00h\xae'
    >>> a._p_jar = app._p_jar
    >>> app._p_jar._register(a)
    >>> app._p_jar._added[a._p_oid] = a
    >>> transaction.commit()
    
  9. Sie sollten nun wieder das Objekt selbst wie auch das Elternobjekt aufrufen können:

    >>> app._p_jar.get('\x00\x00\x00\x00\x00\x00h\xae')
    <persistent.Persistent object at 0xab7f9cc>
    >>> app._p_jar.get('\x00\x00\x00\x00\x00\xc9\x16\x15')
    BTrees.IOBTree.IOBucket([(39078692, <five.intid.keyreference…
    
  10. Schließlich sollten Sie noch die Verbindung zur Datenbank schließen:

    >>> conn.close()
    >>> db.close()
    

Fehlende BLOB-Dateien

Falls Sie die Fehlermeldung erhalten POSKeyError: 'No blob file', hat Mikko Ohtamaa das Skript fixblobs.py geschrieben, mit dem sich Inhalte aus der ZODB löschen lassen, für die kein BLOB mehr vorhanden ist. Siehe auch Fixing POSKeyError: ‘No blob file’ content in Plone.

Weitere nützliche Werkzeuge

analyze.py

zeigt Informationen wie OID, Größe etc. der Objekte in der Datenbank, z.B.:

$ ./Processed 123816 records in 2601 transactions
Average record size is 1276.43 bytes
Average transaction size is 60762.18 bytes
Types used:
Class Name                                       Count    TBytes    Pct AvgSize
---------------------------------------------- ------- ---------  ----- -------
AccessControl.User.UserFolder                        1       185   0.0%  185.00
App.ApplicationManager.ApplicationManager            1       189   0.0%  189.00
App.Product.ProductFolder                            1        34   0.0%   34.00
BTrees.IIBTree.IIBTree                            6705   1783379   1.1%  265.98
BTrees.IIBTree.IIBucket                           6957   4584392   2.9%  658.96
…
webdav.LockItem.LockItem                          1203    323529   0.2%  268.94
...PersistentAdapterRegistry                         2      7074   0.0% 3537.00
zope.ramcache.ram.RAMCache                           1       288   0.0%  288.00
============================================== ======= =========  ===== =======
                            Total Transactions    2601                   59.34k
                                 Total Records  123816   154338k 100.0% 1276.43
                               Current Objects   74107    78439k  50.8% 1083.87
                                   Old Objects   47124    75898k  49.2% 1649.27
fstest.py
überprüft die Datenbank auf korrupte Transaktionen.
fsrecover.py
repariert Transaktionsfehler in der Datenbank.