# CELLD_MAX_RSS_MB vs CELLD_MAX_RESIDENT_CELLS: Understanding CellD Memory Limits

> Understand the difference between CELLD_MAX_RSS_MB and CELLD_MAX_RESIDENT_CELLS. Learn how CELLD manages memory limits by triggering shedding or refusing new loads.

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

---

**`CELLD_MAX_RSS_MB` limits the total process memory in megabytes by triggering memory-pressure shedding, while `CELLD_MAX_RESIDENT_CELLS` limits the count of active cells held in memory, refusing new loads when the cap is reached.**

Both environment variables control independent resource-limiting mechanisms in the `celld` server from the `denoland/celld` repository. Understanding the difference between `CELLD_MAX_RSS_MB` and `CELLD_MAX_RESIDENT_CELLS` helps operators tune cell capacity without risking out-of-memory kills or excessive swapping.

## What CELLD_MAX_RSS_MB Controls

`CELLD_MAX_RSS_MB` sets a threshold on the **resident-set size (RSS)**—the total physical memory the operating system attributes to the `celld` process.

### How RSS Monitoring Works

By default, `celld` enforces a threshold of approximately 80% of the machine's available RAM. Setting `CELLD_MAX_RSS_MB` overrides this default:

- **Value in MB**: Sets explicit memory ceiling
- **`0`**: Disables RSS-based pressure shedding entirely
- **Behavior when exceeded**: The node begins shedding cells to reduce memory footprint

The implementation lives in [`crates/celld/memory.rs`](https://github.com/denoland/celld/blob/main/crates/celld/memory.rs), where the RSS monitor polls process statistics and triggers shedding when usage crosses the configured threshold.

```bash

# Cap memory usage at 2 GB

CELLD_MAX_RSS_MB=2048 \
celld --bucket s3://my-cells-bucket \
      --listen 0.0.0.0:8080 \
      --internal-listen 10.0.0.12:8081 \
      --advertise node-a.internal:8081

```

**Important caveat**: Shedding cells reduces active memory pressure but may not immediately lower reported RSS, as the allocator often retains freed pages for reuse.

## What CELLD_MAX_RESIDENT_CELLS Controls

`CELLD_MAX_RESIDENT_CELLS` caps the **number of cells held in memory**, regardless of their individual memory footprints.

### How Resident Cell Limits Work

Each loaded cell corresponds to an in-memory SQLite database. When the limit is reached:

- New cell loads are **refused** until existing cells are evicted
- Eviction happens via pressure shedding, idle timeout, or explicit close
- The limit operates independently of RSS—a node can stay under the memory cap while being throttled by the cell count cap

Check the enforcement logic in [`crates/celld/cell.rs`](https://github.com/denoland/celld/blob/main/crates/celld/cell.rs), where the load-decision code validates capacity before admitting a cell to residency.

```bash

# Allow at most 1,000 concurrent resident cells

CELLD_MAX_RESIDENT_CELLS=1000 \
celld --bucket s3://my-cells-bucket \
      --listen 0.0.0.0:8080 \
      --internal-listen 10.0.0.12:8081 \
      --advertise node-a.internal:8081

```

## Key Differences Between CELLD_MAX_RSS_MB and CELLD_MAX_RESIDENT_CELLS

| Aspect | `CELLD_MAX_RSS_MB` | `CELLD_MAX_RESIDENT_CELLS` |
|--------|---------------------|----------------------------|
| **What it limits** | Process memory size (megabytes) | Count of resident cells |
| **Mechanism** | Memory-pressure shedding | Load refusal / admission control |
| **Granularity** | Coarse (total process RSS) | Fine (individual cells) |
| **Failure mode** | Active shedding of loaded cells | Request blocking until capacity frees |
| **Use case** | Prevent OOM kills from OS | Bound concurrency regardless of cell size |

## When to Use Each Limit

**Use `CELLD_MAX_RSS_MB`** when your primary concern is the **overall memory footprint** of the `celld` process—especially in shared or containerized environments where cgroup limits apply.

**Use `CELLD_MAX_RESIDENT_CELLS`** when you need predictable **concurrency bounds** or when individual cells vary dramatically in size. A single large cell might consume hundreds of megabytes, making a pure count-based limit safer for capacity planning.

**Combine both** for defense in depth: `CELLD_MAX_RESIDENT_CELLS` provides a hard cap on active connections, while `CELLD_MAX_RSS_MB` acts as a safety net against unexpected memory growth from large cells or internal overhead.

## Source Code References

According to the `denoland/celld` source code:

- **[`crates/celld/memory.rs`](https://github.com/denoland/celld/blob/main/crates/celld/memory.rs)**: Implements RSS polling and shedding triggers for `CELLD_MAX_RSS_MB`
- **[`crates/celld/cell.rs`](https://github.com/denoland/celld/blob/main/crates/celld/cell.rs)**: Enforces `CELLD_MAX_RESIDENT_CELLS` during cell load operations
- **[`README.md`](https://github.com/denoland/celld/blob/main/README.md)**: Documents both variables under the "Memory pressure shedding" and "Resource limits" sections

## Summary

- **`CELLD_MAX_RSS_MB`** limits total process memory via shedding; measured in megabytes; located in [`memory.rs`](https://github.com/denoland/celld/blob/main/memory.rs)
- **`CELLD_MAX_RESIDENT_CELLS`** limits active cell count via admission control; unitless count; enforced in [`cell.rs`](https://github.com/denoland/celld/blob/main/cell.rs)
- **Both are independent**—configure together for robust resource management
- **Set to `0`** to disable RSS-based shedding (not recommended for production)

## Frequently Asked Questions

### Can I set both CELLD_MAX_RSS_MB and CELLD_MAX_RESIDENT_CELLS at the same time?

Yes. The two limits operate independently and complement each other. `CELLD_MAX_RESIDENT_CELLS` prevents unbounded cell loading, while `CELLD_MAX_RSS_MB` catches memory growth from large cells or internal fragmentation that the count limit cannot predict.

### Why doesn't RSS drop immediately after shedding cells?

The memory allocator (typically the system allocator or jemalloc) retains freed pages for reuse rather than returning them to the OS immediately. This is normal behavior—RSS may plateau while the resident cell count falls. The shedding mechanism still prevents new allocations from growing beyond your configured threshold.

### What happens if I set CELLD_MAX_RSS_MB to 0?

Setting `CELLD_MAX_RSS_MB=0` disables the RSS-based pressure-shedding mechanism entirely. Without this safety net, `celld` relies solely on `CELLD_MAX_RESIDENT_CELLS` and idle timeouts to manage memory. Running without RSS protection risks OOM kills if cells grow larger than expected or internal overhead increases.

### Which limit should I tune first for a new deployment?

Start with `CELLD_MAX_RSS_MB` set to 70–80% of your container or host memory limit to prevent crashes. Then add `CELLD_MAX_RESIDENT_CELLS` based on observed memory per cell in your workload—divide your RSS target by typical cell size to derive a safe count cap.