---
title: "Admission back-pressure - LatticeQuotaExceededException - Lattice Public API Reference"
url: "https://nsta1.github.io/Orleans.Lattice/docs/lattice/api/admission-back-pressure-latticequotaexceededexception.html"
source: "https://github.com/NSTA1/Orleans.Lattice/blob/release/9.9/docs/lattice/api.md?plain=1#L1402-L1426"
package: "Orleans.Lattice"
version: "9.9.0"
documents: "Orleans.Lattice 9.9.0 (release line 9.9)"
built: "2026-10-04"
all-pages: "https://nsta1.github.io/Orleans.Lattice/llms.txt"
bundle: "https://nsta1.github.io/Orleans.Lattice/docs/lattice/llms-full.txt"
---
# Admission back-pressure - `LatticeQuotaExceededException`

Part of [Lattice Public API Reference](../api.md).

Public typed exception thrown when a locally-authored write call is refused because the target tree has reached a configured per-tree admission-control cap - either [`LatticeOptions.MaxLiveKeys`](../configuration/options-reference-2.md#maxlivekeys) (the `Dimension` property is `keys`) or [`LatticeOptions.MaxEstimatedBytes`](../configuration/options-reference-2.md#maxestimatedbytes) (the `Dimension` is `bytes`). The per-tree caps are checked only by `SetAsync` (both overloads), `SetIfVersionAsync`, `GetOrSetAsync`, `SetManyAsync`, `SetManyWherePredicateAsync`, the single-tree atomic batches `SetManyAtomicAsync` and `SetManyAtomicWhereAsync` (every overload, except a delete-only `SetManyAtomicAsync` batch), `ApplyCrdtDeltaAsync` (both overloads, which the typed CRDT accessors call), `ApplyCrdtDeltaManyAsync` and the cross-tree `SetManyAtomicAsync` extension (for every participating tree whose slice carries an upsert), once per call and before an atomic batch's saga starts, so a batch is admitted or refused as a whole; the bulk-load calls (`BulkLoadAsync`, `BulkAppendChunkAsync`) and `MergeAsync` do not check them. Admission control is strictly **opt-in**: both caps default to `null` (unbounded), so a tree that has not configured a cap never sees this exception. Distinct from [`LatticeSaturatedException`](saturation-back-pressure-latticesaturatedexception.md): saturation is a transient storage-drain regime, whereas a quota breach persists until the tree's live footprint drops back under its cap.

The typed slot carries `TreeId`, `Dimension`, `Current` (the observed value), `Limit` (the configured cap), and `TenantId` (the breached tenant when the tenancy add-on refused the call, the empty string for a per-tree cap) for caller-side attribution without parsing the message. It derives from `InvalidOperationException` for backwards compatibility, but that inheritance is a hazard rather than a convenience: a broad `catch (InvalidOperationException)` absorbs the refusal and retries, which cannot succeed until the tree falls back under its cap. The type implements [`ILatticeDomainFault`](../api.md#domain-faults---ilatticedomainfault), so a broad handler declines it with `catch (InvalidOperationException ex) when (ex is not ILatticeDomainFault)`; catching it by name remains correct. The core per-tree caps report `keys` or `bytes`. With the optional [`Orleans.Lattice.Tenancy`](../../lattice.tenancy/README.md) add-on registered the same exception also surfaces for a per-tenant aggregate breach, adding the `memory`, `trees`, and `ops-per-second` dimensions, from every call that passes the tenant write admission - the calls above (a delete-only atomic batch included) plus the bulk-load calls and the key deletes (`DeleteAsync`, `DeleteRangeAsync`, `DeleteRangeWherePredicateAsync`); the cross-tree `SetManyAtomicAsync` extension does not pass it. `ops-per-second` alone surfaces from the read surface too: every read the access gate allows - point, range, and backup reads, and each snapshot-cursor page - is charged against the tenant's request-rate budget, while the footprint dimensions are never evaluated on a read, so an over-quota tenant can still read its data back; its key deletes, though, pass the same write admission and are refused while a footprint dimension is breached. `ops-per-second` is a **transient** back-off signal (the tenant's rate budget refills continuously, so retry after a short backoff) rather than a condition that persists until a footprint drops.

Caller contract: treat as back-pressure. For a per-tree cap, either reduce the tree's live footprint (delete keys, let TTLs expire and compaction reap tombstones) or, if the ceiling is genuinely too low, raise the cap. The cap is evaluated against a cached, eventually-consistent per-tree aggregate, so it is **best-effort / approximate**: every call admitted before a refreshed sample lands - including the whole of a batch admitted on one check - can carry the tree past the cap, and each new activation of the tree's grain **fails open** until its own first sample lands. Replication and atomic-write-saga apply paths bypass admission control, so an incoming replicated write is never refused. Every per-tree cap rejection also increments the `orleans.lattice.admission.rejected` counter (tagged `tree`, `dimension`); a tenancy breach does not. See [Metrics - admission control](../metrics/instrument-catalog-5.md#per-tree-admission-control) for the advisory-first-then-enforce adoption workflow.

Over a remote transport the refusal is mapped to the canonical `ResourceExhausted` gRPC status - the same code the sibling saturation refusal uses - by the [data gRPC binding](../../lattice.api.data.grpc/README.md#quota-refusals) and the [schema gRPC binding](../../lattice.api.schema.grpc/architecture.md#quota-refusals), each attaching the breached `Dimension` (and, where the dimension carries one, `Current` and `Limit`) as response trailers so a remote client can branch on the outcome exactly as an in-process caller branches on the typed slot. No trailer carries a tenant id, but the status message is the exception's own text, which names the tenant for a per-tenant refusal, and a tenant-scoped tree's id carries it as its `t/{tenant}` segment.

```csharp verify
byte[] value = [1, 2, 3];
try
{
    await tree.SetAsync("k", value, cancellationToken);
}
catch (LatticeQuotaExceededException ex)
{
    // The tree reached its configured admission cap on the named
    // dimension. The write did not commit. Shed load or raise the cap.
    Console.WriteLine(
        $"tree {ex.TreeId} over {ex.Dimension} quota: {ex.Current} >= {ex.Limit}");
}
```

Previous: [Saturation back-pressure - LatticeSaturatedException](saturation-back-pressure-latticesaturatedexception.md). Next: [Operator tooling: orphaned-leaf repair](operator-tooling-orphaned-leaf-repair.md). Contents: [Lattice Public API Reference](../api.md).
