From 658aadbb81a4ecd5c6960e9d6f82b8b72134ac5c Mon Sep 17 00:00:00 2001 From: clawbot <35+clawbot@noreply.example.org> Date: Mon, 21 Sep 2026 17:29:29 +0200 Subject: [PATCH] Set GOMEMLIMIT in the image and document the memory budget (closes #13) Add ENV GOMEMLIMIT=1536MiB to the runtime stage so the Go runtime keeps a 1.5 GiB soft heap limit. runuser preserves it the way it already does XDG_DATA_HOME, so the routewatch process inherits it. Add a Memory section to the README describing the budget (Go 1.5 GiB soft, SQLite 640 MiB pool cache and 1.5 GiB hard heap, ~0.2 GiB other), the required 5 GiB container limit, how to override GOMEMLIMIT, what happens at each limit, and the DEBUG=routewatch System stats line. Every sentence matches the behaviour already merged to next. Model: opus-4-8 --- Dockerfile | 5 +++++ README.md | 39 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 04e60ea..46e3169 100644 --- a/Dockerfile +++ b/Dockerfile @@ -78,6 +78,11 @@ RUN chown -R routewatch:routewatch /app ENV XDG_DATA_HOME=/var/lib +# Cap the Go heap at 1.5 GiB so the runtime collects harder before the +# container's memory limit is reached. runuser preserves this the way it does +# XDG_DATA_HOME above. +ENV GOMEMLIMIT=1536MiB + # Expose HTTP port EXPOSE 8080 diff --git a/README.md b/README.md index 5bfe36c..e856eb4 100644 --- a/README.md +++ b/README.md @@ -165,14 +165,51 @@ bgp_peers(id, peer_ip, peer_asn, last_message_type, last_seen) Configuration is handled via environment variables and OS-specific paths: | Variable | Default | Description | -|----------|---------|-------------| +|----------|----------|-------------| | `PORT` | `8080` | HTTP server port | | `DEBUG` | (empty) | Set to `routewatch` for debug logging | +| `GOMEMLIMIT` | `1536MiB` (in the Docker image) | Go soft memory limit; see Memory | State directory (database location): - macOS: `~/Library/Application Support/routewatch/` - Linux: `/var/lib/routewatch/` or `~/.local/share/routewatch/` +## Memory + +The daemon holds a live routing table, so its memory grows with the size of the +data it tracks. The image sets ceilings that keep it inside a 5 GiB container. + +Budget: +- Go heap: a 1.5 GiB soft limit (`GOMEMLIMIT=1536MiB`, set in the image). +- SQLite: at most 640 MiB of page cache across the connection pool (64 MiB per + connection, 10 connections) and a 1.5 GiB hard heap limit for the C library. +- About 0.2 GiB for everything else in the runtime. + +Run the container with a memory limit of 5 GiB and swap disabled: + +```bash +docker run --memory=5g --memory-swap=5g ... +``` + +or the equivalent in your deployment tool. This leaves headroom above the +ceilings for spikes and the kernel page cache. + +Override the Go soft limit by setting `GOMEMLIMIT` in the environment (for +example `-e GOMEMLIMIT=1GiB`); this replaces the image default. + +What happens at each limit: +- Go soft limit: as the heap approaches `GOMEMLIMIT`, the runtime runs garbage + collection more aggressively rather than growing further. +- SQLite: at a 1 GiB soft heap limit it recycles its page cache instead of + allocating more; at the 1.5 GiB hard heap limit a statement fails with an + out-of-memory error, and the handler logs it and drops that batch. The process + keeps running. +- Handler queues: each of the four handler queues holds at most 20,000 messages. + When a queue fills, the streamer drops messages instead of blocking. + +With `DEBUG=routewatch` the daemon logs a `System stats` line every 60 seconds +with the goroutine count and Go memory figures. + ## Development ```bash