Expand description
F3’s per-actor write quota: how much one actor may turn into permanent storage.
docs/14 F3 splits the “events are forever”
problem in two. Channel (a) — rejected garbage — was closed by §3.7’s rule that only validated
events are durably logged, so a refused command leaves nothing behind. Channel (b) is the one
this module is for:
validated spam from a legitimate but abusive session — permanent by design. Remediation: per-actor rate/volume quotas … on by default with generous limits.
On by default is the load-bearing half. A quota a program has to ask for is a quota most programs do not have, and F3’s whole point is that the default deployment should not turn an abusive session into a permanent cost.
§The table is bounded, which is the part that is easy to get wrong
The obvious implementation is a map from actor to a counter. That map is unbounded memory
keyed by a string the client chooses — the same denial of service the quota exists to prevent,
moved one level down and made harder to see. Under DevIdentity the actor is whatever the
client says, so an attacker sending a fresh name per proposal would both evade the quota and
grow the table.
So the counters are sharded: a fixed number of buckets, an actor hashed into one, and no
per-actor allocation ever. Memory is BUCKETS × a few bytes, for the life of the process,
whatever arrives. Two consequences, both deliberate:
- Two actors can share a bucket, and therefore a budget. That is why the limit is generous
rather than tight: a shared bucket must still be ample for both.
Quota::defaultsays what the numbers are and why. - The hash is keyed per process (
RandomState), so a client cannot compute a name that lands in a chosen bucket. Without that, sharing a bucket stops being an accident an operator accepts and becomes a way to spend somebody else’s budget on purpose.
§What this is not
It binds an actor, so it is worth exactly what the actor is worth. Under
crate::identity::DevIdentity the actor is the claim the client sent, so an attacker who
rotates names spreads across buckets rather than being stopped — the total is still bounded by
BUCKETS × the limit, which is a bound rather than the bound anybody wanted.
docs/48 is the seam that fixes this, and
docs/82 §82.5 is the
composition written out rather than left to be inferred.
Structs§
- Quota
- The limits, and the window they are counted over.
- Rate
Limit - The sharded counters, and the process-random hash that decides which bucket an actor lands in.
Constants§
- BUCKETS
- How many counters exist, for all actors, forever.