Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Limits and quotas

Engine-wide ceilings, each with a safe shipped default and a consistent override shape — the same canonical sutra.* / SUTRA_* pair described in Configuration reference.

Inbound payload byte cap

Every inbound channel — broker or HTTP — delivers raw bytes to the engine before a codec ever parses them, and a producer can publish an arbitrarily large payload. The cap closes that gap: a message is byte-length-checked before codec.decode() runs, so nothing is allocated or parsed for an oversized message.

ConfigDefaultDisabled value
sutra.codec.max-payload-bytes (SUTRA_CODEC_MAX_PAYLOAD_BYTES)10 MiB (10485760)0

A per-channel override lives on the channel’s own definition (not as a separate engine-wide property), and is not clamped by the global value — it can raise or lower the effective cap for just that channel:

channels:
  - name: bulk-statements-acme
    payload-cap-bytes: 104857600   # raised for a trusted large-file channel
  - name: heartbeat-acme
    payload-cap-bytes: 4096        # tightened for a strict small-event channel

Negative values are rejected at startup (SUTRA.CONFIG.PROPERTY.INVALID) — this catches a typo like -1 before it silently disables the cap the way some other libraries interpret that value.

On rejection. Exceeding the effective cap rejects the message with SUTRA.INBOUND.PAYLOAD_TOO_LARGE (ERROR), carrying the channel id, the actual payload size, and the effective cap that was applied — enough for an operator to know exactly which config key to raise. This is a permanent reject, not a retryable one:

TransportRejection translates to
HTTP413
RabbitMQ / AMQPbasic_nack with requeue=false → the broker’s DLX (or dropped if none configured)
KafkaOffset committed — the poison record is skipped, not replayed (wire a dead-letter topic in BPMN if you need one)
AWS SQSDeleteMessage — removed, not redelivered
GCP Pub/Submessage.ack() — removed, not redelivered
FileThe source file is moved to the failed/ sub-directory
Dapr500 to the sidecar
Knative500 to the Broker

Dapr and Knative are the exception to “permanent” today. Both answer this rejection — and every other intake rejection except a malformed CloudEvent — with 500, and both pushers treat 500 as retryable: the Dapr sidecar retries under the component’s retry policy, and a Knative Broker redelivers until the Trigger’s delivery retries are spent. The same oversized message therefore arrives again until the pusher gives up. Configure a dead-letter topic on the Dapr subscription, or a deadLetterSink on the Trigger, so that it ends somewhere visible.

Per-tenant quotas

Two more admission checks, enforced before a message reaches the executor — see Multi-tenancy and isolation for the full detail:

  • maxConcurrentInstances — a hard cap on simultaneously in-flight instances for a tenant, coherent across every replica.
  • maxInboundRatePerMinute — a per-replica sliding 60-second admission window.

Neither is applied unless a tenant opts in — an unconfigured tenant is unlimited on both dimensions.

What’s not yet a configurable limit

The threat-model backlog names a few more ceilings that aren’t wired yet — a FEEL evaluation wall-clock/memory budget per expression, and a per-tenant audit-write rate cap. Until they land, the payload cap and the two tenant quotas above are the complete set.

Next