# Memory-constrained hosts

infinidysk has a .NET backend and a Node frontend. Size the limit for both
processes, plus sockets, TLS, SQLite, and native allocations—not only the
managed .NET heap.

## Container memory limits

Prefer a real container memory limit (`docker run --memory` or the equivalent
Compose/Kubernetes setting). .NET detects a cgroup limit and uses it when it
derives its available-memory and unset streaming-budget defaults.

Set an explicit **In-flight article budget** in Settings → Streaming on small
hosts. Also cap provider connections and the streaming/queue connection
settings to the throughput the host can sustain.

## Hosts with `ulimit -v` / `RLIMIT_AS` {#hosts-with-ulimit-v-rlimit_as}

`ulimit -v` caps virtual address space, not resident memory. A process can
hit it while its RSS is comparatively small.

On 64-bit Linux, the .NET regions GC reserves virtual address space up front.
When only `DOTNET_GCHeapHardLimit` is configured, the default region range is
up to five times that limit, subject to the virtual address space limit. Set
`DOTNET_GCRegionRange` explicitly if that reservation does not fit alongside
the rest of the process.

Environment-variable byte values for these .NET GC settings are hexadecimal:

```yaml
environment:
  # 0x20000000 bytes (about 537 MB) managed heap ceiling
  DOTNET_GCHeapHardLimit: "20000000"
  # 0x40000000 bytes (about 1.07 GB) regions reservation, 2× the heap ceiling
  DOTNET_GCRegionRange: "40000000"
  # Upper bound (0–9). Prefer 5–7 as a first measured canary; see below.
  DOTNET_GCConserveMemory: "9"
  # Bound worker growth separately from the GC heap.
  THREADPOOL_MAX_THREADS: "200"
```

Treat a 2× region range as a starting point, not a universal value. Workloads
that frequently make large object allocations can need a larger range; a range
that is too small trades virtual address space for more full compacting GCs.
`DOTNET_GCHeapHardLimitPercent` is usually unsuitable for a host that has only
an address-space limit, because its percentage is based on physical/cgroup
memory rather than `RLIMIT_AS`.

Set these values before the backend starts. Confirm the active values in
`environment.json` inside the support pack ZIP (**Settings → Support**):
`runtime.addressSpaceLimitBytes`
is the finite `RLIMIT_AS` when Linux exposes one, and the `gc` section reports
the region range, size, hard limit, committed heap, detected heap limit,
collection identity, memory-load, and LOH-specific hard limits.

`DOTNET_GCConserveMemory` is a restart-required runtime canary, not an
infinidysk default. The image does not set it. A value in the 5–7 range can
reduce LOH fragmentation at the cost of more frequent collections and longer
pauses; measure playback and import latency before keeping it.

## Repeated 67 MB `PROT_NONE` mappings {#repeated-64-mib-prot_none-mappings}

On a **glibc** host, a repeated pair of anonymous RW and `PROT_NONE` mappings
whose combined size is 67 MB (64 × 2²⁰ bytes) commonly identifies glibc malloc
arenas. It is not evidence that each NNTP socket has allocated 67 MB.

After verifying that map shape, try a conservative arena cap:

```yaml
environment:
  MALLOC_ARENA_MAX: "2"
```

Fewer arenas can add allocator lock contention under highly concurrent TLS or
download workloads, so measure throughput after changing it. This tuning is
glibc-specific; the shipped Alpine image uses musl and does not use
`MALLOC_ARENA_MAX`.

## Additional safeguards

- Lower provider **Max connections** and explicit download/queue connection
  limits before reducing buffers. NNTP I/O is asynchronous; it does not create
  a dedicated OS thread per socket.
- Disable [warm connections](https://www.infinidysk.com/v1/features/connection-warming/) or shorten the
  idle timeout if many pooled sockets are unnecessary.
- Lower **Streaming body batch width** and set an explicit **In-flight article
  budget** to reduce active decoded-pipe retention.
- Keep `usenet.segment-cache.write-behind-mb` at `0`, or include its full
  additional pooled-buffer budget when sizing the container. Under pressure,
  write-behind skips new cache writes instead of exceeding its byte/job limits.
- `DOTNET_GCRetainVM=0` is already the .NET default; setting it does not shrink
  the initial regions-GC reservation.

See [.NET runtime GC configuration](https://learn.microsoft.com/dotnet/core/runtime-config/garbage-collector)
for the complete runtime-setting semantics.