Morphium 6.2.2 — Bugfix-Release mit PoppyDB-Härtung und Performance
Nur zwei Wochen nach 6.2.0 ist Morphium 6.2.2 da. Kein großes Feature-Release diesmal — stattdessen ein konzentrierter Bugfix-Sprint, der vor allem PoppyDB deutlich stabiler und schneller macht. Und ein paar Dinge, die mir schon länger auf den Nägeln brannten.
PoppyDB: Von "funktioniert meistens" zu "funktioniert"
PoppyDB hat in 6.2.0 seinen eigenen Namen und sein eigenes Maven-Modul bekommen. In 6.2.2 bekommt es die Zuverlässigkeit, die es verdient. Fast 20 Fixes betreffen den Server — hier die wichtigsten:
Wire Protocol Corruption gefixt
Das war der übelste Bug: Wenn ein Change-Stream-Event eintraf, während der Netty I/O-Thread gerade eine andere Antwort auf derselben Connection schrieb, wurden die Bytes interleaved. Das Resultat: Illegal opcode 0, korrupte Messages, und Clients die sich nicht mehr erholen konnten.
Die Ursache: CompletableFuture.whenComplete() schrieb direkt auf den Netty-Channel aus einem Background-Thread. Fix: Responses werden jetzt über ctx.channel().eventLoop().execute() zurück auf den Event-Loop-Thread dispatcht. Serialisiert alle Writes pro Connection, wie es sich für Netty gehört.
Gleiches Problem existierte Client-seitig: SingleMongoConnection.sendQuery() war nicht synchronized — bei shared Connections von mehreren Threads kamen die Bytes durcheinander. Jetzt sind sendQuery, sendCommand und sendAndWaitForReply synchronisiert.
Thread-Leak: 20.000 Threads pro Node
Wenn Clients ohne killCursors disconnecteten (was jeder MongoDB-Driver tut), blieben Watch-Cursors und Change-Stream-Subscriptions ewig am Leben. Jeder verwaiste Cursor blockierte einen Thread im Executor-Pool und akkumulierte Virtual Threads für Event-Dispatch. Unter Testlast: über 20.000 Threads pro PoppyDB-Node, OOM.
Fix: Cursor-IDs werden jetzt pro Netty-Channel getrackt und in channelInactive() automatisch gekillt.
Raft Election Stabilität
Drei PoppyDB-Nodes auf dem gleichen Host mit gleicher Priority führten zu endlosen Split-Vote-Elections. Dazu kam: onLeaderDiscovered feuerte bei jedem Heartbeat statt nur bei Leader-Wechseln, und isLeader()/getCurrentLeader() in getHelloResult() waren nicht atomar — der PooledDriver sah primäre Flapping-Events mehrmals pro Sekunde.
Fix: Generation Guard für Election-Timer, atomare Leader-Snapshots, und Nodes sollten unterschiedliche Priorities verwenden.
Weitere PoppyDB-Fixes
- Update-Responses:
"matched"statt dem MongoDB-Standard"n"→ alle Updates schlugen fehl - Find-Cursor-Batching: Alle Dokumente in einer Antwort statt batched → Iteratoren kaputt
- Upsert-Count:
n: 0stattn: 1bei Upserts →storeMap()Assertions schlugen fehl - writeErrors nicht weitergeleitet: Duplicate-Key-Errors beim Upsert wurden verschluckt
- Hostname 0.0.0.0: Hello-Response mit Bind-Adresse statt Hostname → Clients konnten nicht verbinden
- Tailable Cursors: Direct-Insert-Path hat
notifyTailableCursorsnicht aufgerufen
Performance: 4.7x schnellerer Start
ClassGraphCache
Jedes new Morphium() hat bisher 2-4 ClassGraph-Classpath-Scans ausgelöst — jeweils 100-500ms. Bei Tests mit vielen Morphium-Instanzen dominierte das die Startzeit. Jetzt gibt es einen JVM-weiten Singleton-Cache: der erste Scan wird gecacht, alle weiteren Instanzen nutzen das Ergebnis. In den Tests: BasicFunctionalityTest von 67s auf 14s.
Weitere Performance-Verbesserungen
- Zero-Copy BSON Decoder — weniger Allokationen pro Dokument
- Shallow Copy statt Deep Copy für Change-Stream-Events
- Direct Dispatch für Hot-Path-Commands (insert, update, delete, find, count)
- PoppyDB 3x schneller als MongoDB für Einzeloperationen in lokalen Benchmarks (insert 0.74ms vs 4.48ms)
Wichtige Bugfixes im Core
Change-Stream-Events nach Collection-Drop verloren
Mehrere Race Conditions im InMemoryDriver konnten dazu führen, dass Events nach einem drop() verloren gingen oder dupliziert wurden. Fix: Sequence-Counter wird beim Drop um 100 vorgerückt, stale Events werden gefiltert, und die History wird vor und nach der Drop-Notification gepurgt.
Network-Retry auf toten Connections
Wenn eine MorphiumDriverNetworkException die Connection schloss, hat der NetworkCallHelper auf derselben toten Connection retried — garantiert erfolglos. Jetzt wird vor jedem Retry isConnected() geprüft und bei Bedarf eine frische Connection aus dem Pool geholt.
IndexDescription.equals() False Mismatches
MongoDB gibt explizit false für boolean-Felder zurück, Java lässt sie null. Der Vergleich sah die als unterschiedlich → Indizes erschienen bei jedem Start als "fehlend" → wiederholte Create-Index-Versuche die mit "Index already exists" fehlschlugen.
Upgrade
<groupId>de.caluga</groupId>
<artifactId>morphium</artifactId>
<version>6.2.2</version>
</dependency>
Keine Breaking Changes gegenüber 6.2.0. Das vollständige Changelog gibt's auf GitHub.