# How celld Manages Idle Cell Hibernation to Optimize Resource Usage

> celld optimizes resource usage by hibernating idle cells to local files and S3 storage. Learn how this preserves state for fast wake-up while evicting inactive cells from memory.

- Repository: [Deno/celld](https://github.com/denoland/celld)
- Tags: deep-dive
- Published: 2026-08-09

---

**celld optimizes resource usage by hibernating idle cells—copying their SQLite state to local `.hibernated` files and uploading them to S3-compatible storage—allowing the runtime to evict inactive cells from memory while preserving their state for fast wake-up.**

Idle cell hibernation is the primary mechanism `celld` uses to balance memory constraints with the responsiveness of stateful serverless functions. When a cell (a lightweight V8 isolate with a dedicated SQLite database) stops receiving requests, the runtime can persist its state remotely and reclaim local resources. This article examines the implementation details found in the `denoland/celld` repository, covering the snapshot process, eviction triggers, and wake-up mechanics.

## The Three-Phase Hibernation Process

### Local Snapshot Creation

When a cell becomes eligible for hibernation, the runtime first creates a local backup. In [`crates/celld/ltx_repl.rs`](https://github.com/denoland/celld/blob/main/crates/celld/ltx_repl.rs) at line 310, the `hibernate` function copies the current SQLite database to a `.hibernated` file on the local node. This step ensures data durability even before remote upload completes and can optionally preserve the snapshot locally using the `preserve_local` flag for faster subsequent wake-ups.

### Remote Persistence to Object Storage

The local snapshot is then uploaded to a configured S3-compatible bucket, transforming the active in-memory process into a static object that incurs minimal storage cost. As documented in the project's [`docs/README.md`](https://github.com/denoland/celld/blob/main/docs/README.md), this persistence model allows cells to exist as dormant objects rather than resident processes, dramatically reducing the memory footprint of the node.

### In-Memory Eviction and Permit Tracking

Once the snapshot is safely stored, the runtime closes the SQLite connection and removes the cell from the resident set. The system increments a **hibernation permit** counter defined in [`crates/logic/lib.rs`](https://github.com/denoland/celld/blob/main/crates/logic/lib.rs) at line 3463, tracking how many cells are currently hibernated. This counter is bounded by the `max_hibernations` configuration parameter (defined at line 87 and enforced at line 1257 in the same file), preventing the node from overwhelming the bucket with excessive concurrent snapshots.

## When Hibernation Occurs

### Pressure-Based Eviction

The runtime monitors resource utilization through environment variables like `CELLD_MAX_RSS_MB` and `CELLD_MAX_CPU_PERCENT`. When memory or CPU thresholds are exceeded, the scheduler traverses resident cells and evicts the least-recently used candidates. Idle cells—those without active WebSocket connections or pending alarms—are prioritized for hibernation because they can withstand the wake-up latency without disrupting active client sessions.

### Explicit Idle Detection

A cell is considered safely hibernatable when it has no active inbound WebSocket connections marked as non-hibernatable. The JavaScript harness in [`crates/celld/js/harness.js`](https://github.com/denoland/celld/blob/main/crates/celld/js/harness.js) at line 1093 sets the `ws._hibernatable` flag to indicate that a connection can be safely dropped during hibernation. Cells with pending alarms or active non-hibernatable WebSockets remain resident to ensure timely execution.

## The Wake-Up Flow

When a request arrives for a hibernated cell, the runtime executes a three-step restoration process:

1. **Bucket Lookup**: The system queries the S3-compatible bucket for the latest cell snapshot.
2. **Local Restoration**: The SQLite file is restored to the local filesystem. If a `.hibernated` copy was preserved locally (and is still valid), the runtime reuses it instead of downloading, as implemented in [`crates/celld/ltx_repl.rs`](https://github.com/denoland/celld/blob/main/crates/celld/ltx_repl.rs) lines 182-199.
3. **Activation**: The cell is loaded back into memory, the SQLite connection is re-established, and the hibernation permit is released back to the pool.

This wake-up process is intentionally more expensive than standard activation, incentivizing the scheduler to keep frequently accessed cells resident.

## Configuration and Implementation Examples

### Configuring Hibernation Limits

Control the maximum number of concurrently hibernated cells via the `CELLD_HIBERNATIONS` environment variable:

```rust
// crates/celld/main.rs – configuring limits
let max_hibernations = positive_environment::<usize>("CELLD_HIBERNATIONS")?;
let limits = Limits {
    // … other limits …
    hibernations: max_hibernations,
    // …
};

```

Start the node with a custom limit:

```bash
export CELLD_HIBERNATIONS=500   # allow up to 500 concurrent hibernated cells

celld --bucket s3://my-bucket …

```

### Triggering Hibernation Programmatically

The `RuntimeManager` exposes an async `hibernate` method that delegates to the LTX replication layer:

```rust
// RuntimeManager::hibernate – crates/celld/runtime.rs
async fn hibernate(&self, cell: &str, epoch: u64, preserve_local: bool) {
    self.ltx.hibernate(cell, epoch, preserve_local).await
}

```

The underlying implementation in [`crates/celld/ltx_repl.rs`](https://github.com/denoland/celld/blob/main/crates/celld/ltx_repl.rs) handles the file operations:

```rust
// crates/celld/ltx_repl.rs – line 310
pub async fn hibernate(&self, cell: &str, epoch: u64, preserve_local: bool) {
    let db = self.db_path(cell, epoch);
    let preserved = db.with_extension("hibernated");
    // Copies SQLite → .hibernated and uploads to bucket
}

```

### Checking Cell Status via JavaScript

Worker scripts can query the hibernation state of any cell:

```javascript
// In a Worker script
import { hibernationState } from "celld:control";

async function example(cellId) {
  const state = await hibernationState(cellId);
  console.log(`Cell ${cellId} is ${state ? "hibernated" : "resident"}`);
}

```

This API queries the Rust control plane, which checks the S3 bucket entry to determine if the cell exists in a hibernated state.

## Summary

- **Idle cell hibernation** in `celld` reduces memory usage by persisting cell state to S3-compatible storage and evicting inactive processes from RAM.
- The process involves three distinct phases: local snapshot creation (`.hibernated` files), remote upload, and in-memory eviction tracked via permit counters.
- Hibernation triggers include resource pressure (CPU/memory thresholds) and explicit idle detection when no non-hibernatable WebSockets or pending alarms exist.
- Wake-up requires downloading or reusing the local snapshot, restoring the SQLite database, and re-activating the V8 isolate, incurring higher latency than standard activation.
- The `max_hibernations` limit and `CELLD_HIBERNATIONS` environment variable prevent resource exhaustion on both the node and the backing object storage.

## Frequently Asked Questions

### What happens to active WebSocket connections when a cell hibernates?

WebSocket connections marked as hibernatable (via the `ws._hibernatable` flag in [`js/harness.js`](https://github.com/denoland/celld/blob/main/js/harness.js)) are terminated during hibernation. The client must reconnect upon wake-up. Cells with active non-hibernatable connections are excluded from hibernation to prevent connection drops.

### How does celld prevent data loss during hibernation?

Data durability is ensured through the two-phase persistence model. The SQLite database is first copied to a local `.hibernated` file before any upload begins. Only after successful upload to the S3-compatible bucket does the runtime close the database connection and evict the cell from memory.

### Can I disable hibernation for specific cells?

While there is no per-cell hibernation flag in the current implementation, cells automatically opt-out of hibernation when they hold non-hibernatable WebSocket connections or have pending alarms scheduled. Setting `max_hibernations` to zero effectively disables hibernation node-wide.

### What is the performance cost of waking up a hibernated cell?

Wake-up latency is significantly higher than cold-start or warm-start activation because it requires object storage retrieval and SQLite restoration. The system mitigates this by preferring to keep frequently accessed cells resident and by optionally preserving local `.hibernated` snapshots to bypass downloads when safe.