Skip to main content

CapacitySection

Struct CapacitySection 

Source
pub struct CapacitySection {
    pub max_accounts: Option<u64>,
    pub max_records_per_account: Option<u64>,
    pub log_budget_bytes: Option<u64>,
    pub checkpoint_log_bytes: Option<u64>,
    pub checkpoint_interval_secs: Option<u64>,
    pub evaluation_log_retention_days: Option<u64>,
    pub write_log_retention_days: Option<u64>,
    pub disk_warning_free_percent: Option<u8>,
    pub disk_critical_free_percent: Option<u8>,
    pub firehose_replay_frames: Option<usize>,
    pub firehose_replay_bytes: Option<u64>,
}
Expand description

How many accounts this deployment may hold at once, and other bounds on what this deployment keeps rather than what an agent may do.

Reload: needs a restart today. Enforced against the value AppState was built with, the same as BlobsSection’s quotas.

Fields§

§max_accounts: Option<u64>

Mirrors --max-accounts. See plan/capacity.md.

Reasonable range: 1 to 100_000. One account is a deployment that exists to run a single agent. The high end is where the costs this cap is holding back stop being about accounts at all: every account is a repository replayed from the log at startup, so a deployment near it should be sized against its restart time rather than against this number. The built-in default is 1000.

§max_records_per_account: Option<u64>

The most records one account’s repository may hold. No flag mirrors this, only this key. A caller’s write that would add records past it is refused with 403 TooManyRecords. Updates and deletes are always accepted, and the server’s own records are written past it. An account already past it keeps what it has and cannot create more.

Reasonable range: 1_000 to 100_000. A write to a repository the server holds built costs the same at any size, about 200µs. A write that finds it not held builds it from every record: after a restart, or once more than 32 repositories have written since. That build took 10ms at a thousand records, 107ms at ten thousand, 690ms at fifty thousand and 2.6s at a hundred thousand, on a loaded 16-thread laptop. Below a thousand, an agent writing a record an hour fills it in six weeks. The built-in default is 50_000. Startup refuses a value outside the range.

§log_budget_bytes: Option<u64>

Bytes the write-ahead log may reach before a further write is refused with StorageFull. Mirrors --log-budget. Applied by didbot_pds::wal::Wal::set_capacity.

Reasonable range: a tenth of the volume to most of it. This is a budget, not a disk check: it exists so a full volume is a deployment that says StorageFull at a size the operator chose, rather than one that discovers the size of its disk mid-write. Set it below what the volume holds, with room for the blobs beside it, which this does not count and [blobs] total_quota_bytes bounds. Unset, the log grows until the filesystem stops it — an append that fails is still refused cleanly, just at a size nobody picked.

§checkpoint_log_bytes: Option<u64>

Bytes the write-ahead log reaches before the running server takes a checkpoint and trims the log against it. No flag mirrors this, only this key. Applied by didbot_pds::Durable::checkpoint_when.

Reasonable range: 128 MiB (134_217_728) to three quarters of log_budget_bytes. Each checkpoint writes the whole state, so a low watermark rewrites the state for every few segments of log it reclaims. Near the budget, the writes that land while a checkpoint runs can reach the budget first, and those are refused with StorageFull. Unset, it is half of log_budget_bytes, or 1 GiB with no budget. Startup refuses a value below 1 MiB or at or above the budget.

§checkpoint_interval_secs: Option<u64>

How often the running server takes a checkpoint while the log stays under checkpoint_log_bytes, in seconds, if anything was written since the last. No flag mirrors this, only this key.

Reasonable range: 3_600 to 604_800 (an hour to a week). This bounds how much log a restart replays and how long churn sits in the log. An hour costs a write of the whole state every hour; a week lets a restart replay a week of writes. The built-in default is 86_400, a day. Startup refuses a value outside the range.

§evaluation_log_retention_days: Option<u64>

How long a sealed segment of the policy evaluation log is kept before it is trimmed, in days. No flag mirrors this yet, only this key. Applied by didbot-serve/src/bin/didbot-pds/’s Enforcement::open via didbot_pds::evaluation_log::FileSink::set_retention; unset leaves that sink’s own DEFAULT_RETENTION, thirty days.

This log’s volume is driven by what gets refused, which an attacker controls: a single client retrying a denied write writes a row every time. A week (7) is enough to answer “why was this refused” while it is still being asked; a quarter (90) keeps a season of denials for looking at patterns, and costs disk in proportion to how noisy the deployment’s callers are. One day is the shortest that survives an operator sleeping through an incident.

§write_log_retention_days: Option<u64>

How long a sealed segment of the write log is kept before it is trimmed, in days. The same shape as Self::evaluation_log_retention_days and applied the same way, against didbot_pds::write_log::FileWriteLog.

This log grows with real use rather than with refusals: one row per record written or deleted, naming the app that presented it. It is the only record of which app wrote what, so the window is how far back an attribution question can be answered. Thirty days (the default) covers “what did this app do last month”; a year (365) makes attribution answerable for as long as most operators keep backups, at roughly a row per write. The two logs are set separately because they fill at unrelated rates.

§disk_warning_free_percent: Option<u8>

The data volume’s free space, as a percentage of its size, below which GET /health’s disk.alert reads warning and the journal writes a warn line. The built-in default is 20.

Reasonable range: 10 to 50. The log and the blobs refuse at their own budgets, but the record heap, a checkpoint and the small files beside the log grow until the volume is full. Twenty percent of the default 20 GiB volume is 4 GiB, days of ordinary writes to act in. Above 50 the alert stays raised on a volume whose budgets are in use. Startup refuses a value outside the range.

§disk_critical_free_percent: Option<u8>

The same, for critical. The built-in default is 10.

Reasonable range: 2 to 20, and below Self::disk_warning_free_percent. A boot writes a checkpoint as large as the state, so this should leave room for one. Startup refuses a value outside the range, or at or above the warning.

§firehose_replay_frames: Option<usize>

How many frames subscribeRepos keeps in memory for a consumer to resume from. No flag mirrors this, only this key. Default 4096, didbot_serve::DEFAULT_REPLAY_FRAMES.

Range: 256 to 65_536, and startup refuses a value outside it. This is a relay’s reconnect window: a cursor older than the frames kept gets OutdatedCursor and must read the repositories to fill the hole. Below 256 a short relay outage on a busy server is already a hole. Each frame kept also costs a few dozen bytes that Self::firehose_replay_bytes does not count, which is what this bound limits when frames are small.

§firehose_replay_bytes: Option<u64>

How many encoded bytes those frames may come to. No flag mirrors this, only this key. Default 67_108_864 (64 MiB), didbot_serve::DEFAULT_REPLAY_BYTES. The oldest frames are evicted first, whichever of the two bounds is reached. Frames an open stream has not sent yet are kept past both bounds, up to twice them.

Range: 16_777_216 (16 MiB) to 1_073_741_824 (1 GiB), and startup refuses a value outside it. Within that, stay at or under a sixteenth of the instance’s memory. A commit may carry 2,000,000 bytes, so this is the bound that holds when records are large. The firehose’s memory is at most twice this, plus the one frame each open connection is writing, up to subscribeRepos’ connection limit. Below 16 MiB a burst of large records evicts frames before a relay has read them. Above a sixteenth, twice this competes with the rest of the server for memory.

Trait Implementations§

Source§

impl Clone for CapacitySection

Source§

fn clone(&self) -> CapacitySection

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for CapacitySection

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for CapacitySection

Source§

fn default() -> CapacitySection

Returns the “default value” for a type. Read more
Source§

impl<'de> Deserialize<'de> for CapacitySection

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl PartialEq for CapacitySection

Source§

fn eq(&self, other: &CapacitySection) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl Serialize for CapacitySection

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more
Source§

impl Eq for CapacitySection

Source§

impl StructuralPartialEq for CapacitySection

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,