Immutable
release. Only release title and notes can be modified.
celld v0.2.0 changes three foundations: how cells share memory, how their state replicates, and how a fleet stays available when its object store fails or slows.
Isolates and memory:
- Resident cells share isolates and run turn by turn on a shared pool. v0.1.0 held one OS thread and one isolate for each cell.
- A resident cell costs ~471 KB against ~3.4 MB in v0.1.0, measured linear to 2,500 resident cells (~1.2 GB) on one node. Memory no longer limits the resident population; admission and RSS shedding set the limit.
- A handler that awaits I/O releases its isolate between turns, so co-resident cells cannot stall each other.
- celld reclaims empty cell heaps and reuses retired isolate slots, so a long-lived node cannot leak isolates.
- jemalloc is the global allocator.
Two listeners:
- celld now separates the data plane from the control plane. The public listener (--listen) serves only your Worker's routes and /__celld/health. A new internal listener (--internal-listen) carries everything else: the operator API (/state, /shutdown) and all node-to-node peer traffic. Keep the internal port on a private network.
- A public listener on a non-loopback address requires an explicit internal listener. --advertise names the internal address, and celld diagnose probes peers at that address.
Replication and compaction:
- celld compacts many small level-0 replication objects into additive level-1 blocks (LTX v0.5.2 block format). Compaction is on by default and never deletes a source object.
- A takeover of a write-heavy cell reads tens of objects instead of thousands. In one measured case, a cell with 4,096 durable writes restored from 42 objects and 0.6 MB instead of 4,116 objects and 19 MB, 3.8 times faster.
- A restore downloads the objects in its plan concurrently, under one ceiling for the whole node.
- A takeover pins the exact point in the replicated history that it restored from. celld never restores data that a fenced former owner uploads after that point, so a cell's history cannot fork and a failed write cannot reappear after a failover.
- Compaction runs off the request path with a bounded duty cycle. A node that restarts with a large uncompacted backlog drains it without blocking durable writes.
- celld and Litestream v0.5.16 can read both block and frame files. Litestream v0.5.11 can read only frame files.
Availability under storage faults:
- The time that remains on the last renewed lease bounds each lease renewal attempt, so one hung conditional write can no longer use the whole authority window. In a test that silently dropped all established storage connections for 20 seconds, a v0.1.0-class node fenced; a v0.2.0 node kept serving.
- The core event loop owns the lease timer on a dedicated thread. Restores, compaction, and diagnostics cannot delay it.
- celld logs each lease attempt at start and at completion, so a storage hang is distinguishable from a timer failure.
Shutdown and restart:
- SIGTERM starts a graceful drain. The drain hands each cell to a peer directly, so takeover does not wait for the lease TTL.
- POST /shutdown?handoff=preserve on the internal listener prepares a same-node restart. The replacement process resumes its local cell databases and performs no owner or replica reads.
- The drain waits for its ownership release writes and bounds each connection, so one idle connection cannot use the full deadline.
- The process exits without unwinding. This fixes a crash when a warm node stops.
Storage backends:
- A gs:// bucket selects Cloud Storage's generation-based compare-and-swap and authenticates with Application Default Credentials.
- A bucket can take a key prefix, so several independent fleets can share one bucket without collisions.
Workers surface:
- Wasm modules deploy and load as compiled WebAssembly.Module values. A workers-rs example shows the path, and older nodes refuse the new wasm-v1 deployment feature at deploy time.
- A new RPC example exercises entrypoints, callbacks, RpcTarget returns, and promise pipelining end to end. Cross-isolate stubs remain limited to single method calls on named entrypoints.
- Web Crypto and node:crypto expand: asymmetric import and export (SPKI, PKCS#8, JWK, PEM), generateKey, P-384 and P-521, Ed25519 and X25519, ECDH derivation, AES-CBC and AES-CTR, DigestStream, timingSafeEqual, and one-shot sign and verify.
- setWebSocketAutoResponse answers ping-style messages without waking the cell.
OpenTelemetry support (off by default):
- CELLD_OTEL=1 turns on OpenTelemetry traces and logs with no collector required: celld writes them directly to the bucket as Parquet under a telemetry/ prefix, and DuckDB can query them.
- A standard OTLP/HTTP collector is the alternative sink (CELLD_OTEL_SINK=otlp), and the usual OTEL_* variables apply.
- W3C trace context propagates end to end: celld adopts an inbound traceparent, and fetch spans, cell dispatch, and console logs join the caller's trace.
Overload:
- A stateless request that finds every slot held now waits, bounded by CELLD_ADMISSION_WAIT_MS, instead of an immediate refusal. At overload, the node no longer spends its capacity on refusals.
- CPU-based shedding is removed. RSS shedding (on by default since v0.1.0) is the only pressure trigger.
Configuration:
- celld validates every variable at startup, and an invalid value stops startup. Boolean variables accept only 0 or 1.
- Removed: CELLD_WORKERS, CELLD_MAX_COHOSTED, CELLD_MAX_CPU_PERCENT, CELLD_RESIDENT_LOW_WATER (ignored since v0.1.0), CELLD_VALIDATE, and the /js route.
- Renamed: CELLD_HIBERNATIONS is now CELLD_EVICTIONS.
- Changed: CELLD_IDLE_EVICT_S no longer defaults to 300; idle eviction is off unless set. CELLD_UNSAFE_PUBLIC_ADVERTISE takes 1, not on.
- Added, among others: CELLD_INTERNAL_ADDR, CELLD_MAX_STATELESS_ISOLATES, CELLD_ADMISSION_WAIT_MS, CELLD_LTX_COMPACTION, CELLD_SHUTDOWN_DRAIN_MS, and the CELLD_OTEL family.
Upgrading from v0.1.0:
- A v0.1.0 command line with a non-loopback --listen refuses to start until an explicit --internal-listen is added.
- The upgrade must not be a rolling update: stop every v0.1.0 node, then start the v0.2.0 nodes. v0.2.0 advertises the internal listener, which v0.1.0 peers cannot follow, and writes compacted block objects, which a v0.1.0 reader cannot restore. A fleet must not mix the two versions.