Hallo
ich habe jetzt bereits mehrfach (leider nicht reproduzierbare) Probleme bei Kunden mit der Integrität der Datenbank. Bei uns ist dieser Fehler zum Glück (oder leider) noch nicht aufgetreten. Insgesamt gab es drei Vorfälle bei der aktuellen 6.4.48.10991 (stable) bzw. der Vorgängerversion 6.4.38.10595 (stable) im client-server-Modus.
Über die Ursache kann ich derzeit nur mutmaßen. Die Datenbank enthält relativ komplexe Objektverknüpfungen.
Ich habe die betroffenen Datenbanken analysiert. In der Regel war (durch welche Aktion auch immer) irgendein Objekt in der Weise beschädigt, dass eine Query welche dieses Objekt als result liefert, zu einer Ausnahme auf Seiten des Servers führt. Es sind leider völlig unterschiedliche Objekte (Klassen) betroffen und zwar teilweise auch solche, die normalierweise überhaupt nicht verändert sondern mit der Datenbank ausgeliefert werden (z.B: Gerichte). Allerdings werden bei einer Schemaänderung alle Objekte einmal angefasst und gespeichert.
Die Nachricht des Servers für den Fall einer solchen Ausnahme lautet dann z.B.
Exception in thread "db4o server message dispatcher System" java.lang.ArrayIndexOutOfBoundsException
at java.lang.System.arraycopy(Native Method)
at com.db4o.io.CachedIoAdapter$Page.read(Unknown Source)
at com.db4o.io.CachedIoAdapter.read(Unknown Source)
at com.db4o.internal.IoAdaptedObjectContainer.readBytes(Unknown Source)
at com.db4o.internal.IoAdaptedObjectContainer.readBytes(Unknown Source)
at com.db4o.internal.Buffer.readEncrypt(Unknown Source)
at com.db4o.internal.LocalObjectContainer.readReaderOrWriterByID(Unknown Source)
at com.db4o.internal.LocalObjectContainer.readReaderByID(Unknown Source)
at com.db4o.internal.query.processor.QCandidate.read(Unknown Source)
at com.db4o.internal.query.processor.QCandidate.readYapClass(Unknown Source)
at com.db4o.internal.query.processor.QCandidate.classReflector(Unknown Source)
at com.db4o.internal.query.processor.QConClass.evaluate(Unknown Source)
at com.db4o.internal.query.processor.QConObject.visit(Unknown Source)
at com.db4o.foundation.Tree.traverse(Unknown Source)
at com.db4o.internal.query.processor.QCandidates.filter(Unknown Source)
at com.db4o.internal.query.processor.QConClass.evaluateSelf(Unknown Source)
at com.db4o.internal.query.processor.QCandidates.evaluate(Unknown Source)
at com.db4o.internal.query.processor.QCandidates$4.map(Unknown Source)
at com.db4o.foundation.MappingIterator.moveNext(Unknown Source)
at com.db4o.foundation.CompositeIterator4.moveNext(Unknown Source)
at com.db4o.foundation.IntIterator4Adaptor.moveNext(Unknown Source)
at com.db4o.internal.StatefulBuffer.writeIDs(Unknown Source)
at com.db4o.internal.cs.messages.MsgQuery.writeQueryResult(Unknown Source)
at com.db4o.internal.cs.messages.MQueryExecute.processAtServer(Unknown Source)
at com.db4o.internal.cs.ServerMessageDispatcherImpl.messageProcessor(Unknown Source)
at com.db4o.internal.cs.ServerMessageDispatcherImpl.messageLoop(Unknown Source)
at com.db4o.internal.cs.ServerMessageDispatcherImpl.run(Unknown Source)
anschließend blockieren sich Client und Server gegenseitig, d.h. die (Client) Anwendung steht, bis der Server beendet wird (was dann allerdings auch nichts mehr bringt). Alternativ kann auch die (Client) Anwendung beendet werden, der Server läuft dann wie gehabt weiter und kann (mit Ausnahme der query gegen defekte Objekte) weiter verwendet werden. Ein Neustart des Server ändert nichts am Verhalten gegenüber den 'defekten Objekten', d.h. die Ausnahme tritt immer wieder an dieser Stelle auf.
Auch mit dem ObjectManager kann man zwar (sowohl client/server als auch local Modus) die Datenbank öffnen, bei einer Query gegen die betroffenen Objekte (z.B. Akte oder Gerichte oder was auch immer) steht dann der ObjectManager und die Nachricht auf der (mitgestarteten) Konsole ist wie oben.
Leider kann ich die defekten Objekte nicht isolieren, weil die Ausnahme (auf Serverseite) noch vor dem Aufruf von Activate bei Aufruf von iterator.hasNext() (auf der Clientseite) erfolgt
Query irgendeine_query ...
ObjectSet result_set = irgendeine_query.execute();
Iterator<Object> object_it = result_set.iterator();
while (iterator_it.hasNext() { <--- hier
...
}
ich kann also nicht einmal die uuid des betroffenen Objekts herausbekommen sondern nur die uuid des Vorgängerobjets im result_set.
Die Beschädigung eines Objekts macht somit u.U. die Verwendung der gesamten Datenbank unmöglich, weil eine Query gegen alle Objekte eines bestimmten Typs nicht mehr möglich ist. Mittlerweile gibt es ein zwangsweises tägliches automatisches Backup (das auch nicht ganz trivial abzuschaltenden ist.). Das nutzt aber dann nichts, wenn der Fehler nicht sofort bemerkt wird (es ist ja nur ein Objekt betroffen) und sozusagen laufend mitgesichert wird oder weil die Anwender zunächst versuchen sich selbst zu behelfen.
Ich hatte zwischenzeitlich mal die (in einem anderen
Thread laufenden) EventCallbacks in Verdacht. Vorsorglich schalte diese
bei umfangreicheren Transaktionen mittlerweile ab.
Dass Objekte (ob nun durch einen Fehler unserer Anwendung oder wodurch auch immer) beschädigt werden ist die eine Sache. Aber es sollte eine Möglichkeit geben diese Objekte entweder zu reparieren oder alternativ defekte Objekte zu erkennen und zu entfernen (oder sonst unschädlich zu machen).
Man kann ja händisch nur über den ObjectManager in die Datenbank und da bringt in diesen Fällen (siehe oben) nichts mehr. Ich weiss ja nicht, in welcher Struktur die Objekte in der Datenbank gesichert sind, aber ein Hilfstool zum Reparieren defekter Einträge wäre sehr hilfreich.
Jetzt meine Fragen:
Gibt es ein solches Tool oder ist so etwas geplant ?
Gibt es irgendeinen Hinweis auf die Fehlerursache ?
Gruß Arne
PS.:
1. Client und Server verwenden die gleiche Konfigurationseinstellung (zugriff nur über singleton). Neben den Indexeinstellungen sind das folgende:
Db4o.configure().detectSchemaChanges(true);
Db4o.configure().allowVersionUpdates(true);
Db4o.configure().automaticShutDown(false); <--- wir verwenden einen eigenen Shutdownhook
Db4o.configure().clientServer().singleThreadedClient(false);
Db4o.configure().freespace().useBTreeSystem();
Db4o.configure().clientServer().batchMessages(false); <-- true führte bei simultanem Anlegen von mehreren
1.000 Objekten manchmal zu Ausnahmen
2. Sowohl beim Server aus auch beim Client liegen immer die gleichen Klassen im Classpath (die Applikation ist in Java entwickelt)
3. Solange der Anwender angemeldet ist, bleiben alle Container geöffnet.
4. Ich verwende mehrere Container für die gleiche Datenbank. Einige nicht zeitkritische Querys werden in einem anderen Thread gegen einen gesonderten Container ausgeführt. Als Ergebnis werden nur die uuid in einer Liste gesammelt und diese uuid-liste dem Hauptthread übergeben. Im Hauptthread werden dann die Objekte über die uuid aus dem Hauptcontainer nachgeladen.
5. Auf einigen Rechnern wird zusammen mit dem Bildschirmschoner nach einer Weile die Netzwerkverbindung abgeschaltet, die Verbindung mit der Datenbank geht dann verloren. Kann es u.U. daran liegen ?
6. Wir verwenden ausschließlich SODA Querys.
Arne Stocker