Skip to content

Storage, quotas & retention

This page covers every limit on what the server stores, what happens when one is reached, and how to see it coming.

/etc/ricochet/config.yaml
storage:
data_directory: /var/lib/ricochet/sf_storage # default: ./sf_storage
max_storage_gb: 50 # default: 10
retention_days: 30 # default: 30
max_messages_per_mailbox: 1000 # default: 1000
max_mailboxes: 100000 # default: 100000, server-wide
max_mailboxes_per_owner: 100 # default: 100
mailbox_cache_size: 10000 # default: 10000
max_entries_per_feed: 10000 # default: 10000
near_capacity_ratio: 0.9 # default: 0.9

data_directory holds only the server’s identity key, not stored data. All data lives in PostgreSQL.

max_storage_gb is the budget for the database’s size on disk, including indexes and unreclaimed space. Once the database reaches it, the server refuses message submissions and document, feed and collection writes with status 507 (insufficient storage). Reads keep working. Writes resume once a capacity sample finds the database under the budget again.

The check uses the periodic capacity sample, taken every intervals.cleanup_min (5 minutes by default, and once at startup). A burst of writes can run past the budget by up to one interval’s worth before the gate closes. Before the first sample completes, nothing is refused.

When the server refuses a write for this reason, it logs write refused: storage over budget with databaseBytes and maxStorageBytes.

A mailbox gets its settings when it is created, either by an explicit create or by the first message delivered to a folder that does not exist yet:

  • max_messages_per_mailbox: how many messages it may hold.
  • retention_days: how long a message may stay.

These are also ceilings. A client that asks for a larger cap or a longer retention on create or update is lowered to the server’s value. A request for zero or a negative value is refused.

An existing mailbox keeps the settings it was created with. Changing these keys affects new mailboxes only.

A mailbox at its cap refuses further deliveries with a 507. Nothing is evicted to make room. The condition clears only when the owner deletes or acknowledges messages, so clients are told not to retry on a timer.

The exception is a mailbox its owner created with a retention count: a rolling window that keeps only the newest N messages. When such a mailbox fills, the server prunes it to its window and stores the new message.

  • max_mailboxes_per_owner caps the folders one peer may hold, however they were created.
  • max_mailboxes caps mailboxes across the whole server.

A create or delivery that would exceed either is refused with a 507. The per-owner quota matters most: a sender names the folder it delivers into, and delivery creates the folder if it does not exist. Without this quota, any peer could give any other peer an unbounded number of folders.

Peer identities are free to create, so these quotas bound what one well-behaved client uses, not what a determined one can. See Admission & rate limits.

max_entries_per_feed is the ceiling on entries in one feed. A feed’s owner can choose a smaller rolling window; this bounds the rest, including collaborative feeds that any peer may append to. An append past the ceiling is a 507.

The maintenance sweep runs every intervals.cleanup_min. On each run it:

  1. Deletes messages past their expiry.
  2. Deletes messages older than their mailbox’s retention_days, for every mailbox type.
  3. Trims mailboxes that have a retention count to their newest N messages.
  4. Reconciles each mailbox’s stored message count, logging message counts had drifted if it had to fix any.
  5. Applies feed retention.

A message’s own expiry is clamped to its mailbox’s retention on delivery, so a sender cannot ask the server to keep it longer than the owner does.

retention_days must be at least 1. Zero or omitted means “use the default”, not “keep nothing”. A retention shorter than a day is rounded up to one day.

Retrieval never removes messages. A client removes them by acknowledging (non-persistent messages are deleted, persistent ones marked seen), deleting, or expunging. Anything left is removed by retention.

mailbox_cache_size is how many loaded mailboxes the delivery path keeps in memory. It is a memory bound, not a rate: a cache miss costs one indexed SELECT, and the least recently used entry makes room. Size it to the number of mailboxes active at once. /readyz reports the cache’s current size and capacity under details.mailboxCache.

near_capacity_ratio sets how full a mailbox must be, as a fraction of its own cap, to count as near capacity. At 0.9, a mailbox with a 1,000-message cap counts once it holds 900. Pick the ratio from how fast you can act.

Where to look:

Question Where
How many mailboxes are near their cap? ricochet_mailboxes_near_capacity metric, or server.mailboxesNearCapacity in /ops/storage
Which mailboxes are closest to full? /ops/mailboxes/top?n=20 (add sort=count to order by message count)
What is one peer storing? /ops/mailboxes?owner=<peer-id>
How much of the budget is used? ricochet_database_bytes against ricochet_storage_max_bytes, or server.databaseBytes and server.availableBytes in /ops/storage
Who is using the space? owners in /ops/storage
How old are these figures? ricochet_stats_age_seconds, or server.sampledAt and server.ageSeconds
Terminal window
OPS=http://127.0.0.1:9090
curl -s "$OPS/ops/mailboxes/top?n=20" | jq '.mailboxes[] | select(.full)'
curl -s "$OPS/ops/storage" | jq '{server, top: (.owners[:5])}'

The server-wide figures come from the capacity sample, so they carry their age and are null in /ops/storage until the first sample lands. Per-owner totals are live. See Metrics & the ops view.

Any peer can ask the server for its capacity over the admin protocol (queryCapacity; QueryCapacity in the Go client). The answer comes from the same sample: total, used and available bytes, message count, active mailboxes, a health score (the fraction of the budget still free) and the sample time. Before the first sample completes, the request fails with a 503 rather than returning zeroes.

Servers also advertise their storage budget and retention in their service announcements. See Service discovery.

  • Clients get 507 on delivery to one peer: that peer’s mailbox is full, or the peer has hit max_mailboxes_per_owner. Check /ops/mailboxes?owner=<peer-id>. Only the owner can clear it.
  • Every write gets 507: the database reached max_storage_gb. Check /ops/storage. Raising the budget takes effect after a restart and the next capacity sample. The budget is measured with pg_database_size, and deleting rows does not shrink the database files by itself: plain VACUUM makes the space reusable inside PostgreSQL but seldom returns it, so the measured size can stay at the budget after a cleanup. VACUUM FULL returns it, at the cost of locking each table while it runs.
  • A lowered cap seems to do nothing: existing mailboxes keep their settings. Only new mailboxes get the new value.