Skip to content

Troubleshooting

The failures worth documenting are the ones that are silent, or that say something other than what they mean. Most of these were found the hard way.

My change vanished after restart

Almost always: you enlisted the wrong object. Enlisting does not cascade — ctx.store(root) covers the root's own fields, not the map hanging off it.

db.write(ctx -> {
    root.products.put("SKU-1", product);
    ctx.store(root);              // WRONG: the map changed, not the root
});

db.write(ctx -> {
    root.products.put("SKU-1", product);
    ctx.store(root.products);     // right
});

If you changed a field on the root and the map, enlist both.

Rollback did not restore my object

The snapshot is taken when the object is enlisted. If you mutate first and enlist afterwards, the snapshot already contains the mutation. Use ctx.edit(obj) before changing it.

"Cannot start a write while this thread holds a read block"

You called db.write(...) or db.beginWrite() inside db.read(...). A read lock cannot be upgraded to a write lock, so this would deadlock against itself; the engine throws instead. Move the write outside the read block.

Closing a store does not return

It does now — it throws StoreBusyException after 30 seconds and names the thread holding the write lock. Read that message: it says which thread opened the block, what kind it was, and how long ago.

Almost always a leaked write transaction: a beginWrite() whose commit(), rollback() or close() never ran, typically because a host framework's begin()/commit() layer sits on top of it. The write lock stays held until the process ends, and only the thread that took it could release it, so the store can no longer be written to or closed.

Cannot close store /data/tenant-7: gave up after 30.0 s waiting for the write lock. ...
Holding it: a write transaction opened by thread "main" (id 1) 92.4 s ago and is still open.

To find the line that leaked it, restart with -Dzerozdb.traceWriteBlockOrigins=true; every such message then carries the stack that opened the block. To catch it at the point it happens rather than at close, assert db.hasOpenWriteBlock() is false at the end of each request.

If instead the block is genuine — a bulk import that really does take longer than 30 seconds — call db.close(Duration.ofMinutes(5)), or raise the default with -Dzerozdb.closeTimeoutSeconds=300.

The store is left open when the wait expires. Shutting the storage manager down underneath a writer that might still wake up would trade a diagnosable hang for an undiagnosable corruption.

"Could not obtain access to jdk.internal.misc.Unsafe"

Your DbCommand or DbQuery is a record. EclipseStore's serializer reaches fields directly, and the JVM refuses that for records unless started with --add-exports java.base/jdk.internal.misc=ALL-UNNAMED.

Use a plain class with public fields and a public no-arg constructor. The failure appears at the first remote call, not at compile time, so it looks like a networking problem.

A second process cannot open the store

By design: exactly one JVM owns a store. Either that is what you want and the second process should be a client (ZeroZDbNode with AUTO_SERVER or CLIENT_ONLY), or a previous process did not shut down. An OS file lock is released when a process dies, so a genuine crash does not leave a store wedged; a still-running process does.

A field is null after a release that renamed it

Schema matching is strict by default: only same-name fields are matched, so a renamed field arrives unset rather than being filled from whatever else looks similar. Declare the rename:

SchemaEvolution.strict().rename("com.x.Product#sku", "com.x.Product#code")

This is deliberate. EclipseStore's own lenient matching pairs leftover fields by type, so removing one field and adding an unrelated one of the same type silently moves the old value into the new field — wrong data with no error. SchemaEvolution.lenient() restores that behaviour if you need it.

The build fails with ROLLBACK_BREAKING

SchemaCompatibility found a model change that would prevent rolling back to the previous release — a removed field, a changed type, or a removed class. Once the new release rewrites a record, the old build cannot read it.

Either the change was accidental and should be reverted, or it was intended, in which case regenerate the baseline in the same commit so the schema change is reviewed like any other API change. A CRITICAL finding is different: a field was removed and another of the same type added, which is the pattern that silently moves data. Confirm it is a deliberate rename.

A client sees stale data

node.localReads() is a replica, refreshed the instant the owner commits but still trailing by about one round trip. That is the trade for reading at heap speed. When a read must be exactly current — permission checks especially — use node.query(...), which always executes on the owner.

Durability settings seem to have no effect

ZeroZDb.attach(manager) uses the storage configuration of the manager you handed it. Durability and schema-evolution settings passed to ZeroZDb.open(...) do not apply, because the manager already exists. Open the store through ZeroZ DB if you want it to control those.

The server refuses to start

Binding to anything other than loopback without a secret is refused deliberately: a reachable server with no authentication lets anyone who can open a socket read and write your data. Call secret(...), and add tls(...) for anything off-host.

Writes are slower than expected

Durability.SYNC is the default and adds the fsync EclipseStore does not perform itself, which costs roughly 10× throughput (~280 commits/s versus ~2.4k on a dev laptop). That buys survival of power loss, not just process death. Use OS_BUFFERED for bulk imports and rebuildable data, where losing the last second of writes is acceptable.

Also note writes are serialized per store — one writer at a time is the design. Stores are independent, so more stores means more concurrent writers.

The process holds far more memory than expected

There is no store eviction yet. A process that opens stores lazily and never closes them holds them all. Close what you no longer need, and note that EclipseStore Lazy references are what keep a single store's footprint proportional to what you actually touch.