scalo 2.10.11

Self-regulating runtime for Rust data-plane services. Backpressure, load shedding and adaptive scaling are on by default.
docs.rs failed to build scalo-2.10.11
Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
Visit the last successful build: scalo-2.10.10

scalo

Build Status Crates.io docs.rs License

There's plenty of sage advice about running services in production at scale -- config cascades, structured logging, secret masking, Prometheus, OpenTelemetry, health probes, backpressure, graceful shutdown -- but almost none of it as code you can just install and use.

This is that code.

scalo is an integrated, self-regulating runtime for data-plane services. Config, logging and metrics come as one pre-wired trinity -- global singletons you just use, no plumbing, no init dance. Everything else leans on that same integration: the config cascade flows straight into the CLI so run/version/config-check just work; the metrics and health wiring feed the K8s probes; and the deployment contract generates your Helm, Dockerfile and Argo manifests from the config the app already declares.

Attach scalo to your service and a whole class of production pain -- the kind done wrong a hundred times elsewhere -- just goes away. Battle-tested, and almost no code on your side to do it properly. It's not a bag of utility functions you wire up yourself; it's the wiring, done right, for free.

scalo comes in two halves that share one set of conventions, idiomatic in each language. scalo-rs (this crate) is the data plane -- the Rust hot path where every microsecond and byte counts (cargo add scalo). scalo-py is the control plane -- orchestration, APIs and integration in Python (pip install scalo).

Opinionated about correctness -- backpressure, memory safety and the health probes are on by default. Unopinionated about your domain -- no web framework, no ORM, no enforced transport. Built as the foundation for PB/hr data services.

This module exists because of this -- https://www.youtube.com/watch?v=xE9W9Ghe4Jk -- but for the backend. And of course, no microservices.

Quick Start

[dependencies]
scalo = "2"

Default features: config, logger. Add the others you want explicitly.

use scalo::{config, logger, env};

fn main() -> anyhow::Result<()> {
    let environment = env::Environment::detect();
    logger::setup_default()?;
    config::setup(config::ConfigOptions {
        env_prefix: "MYAPP".into(),
        ..Default::default()
    })?;

    tracing::info!("Running in {environment:?}");
    Ok(())
}

Features

Pick the slice you need; pay only for what you use.

Feature Description
env Environment detection (K8s, Docker, Container, BareMetal)
runtime Runtime path resolution (XDG/container-aware)
config 7-layer config cascade (figment-based)
config-reload SharedConfig<T> + ConfigReloader hot-reload
logger Structured logging, JSON/text auto-detect, sensitive-field masking
metrics Prometheus metrics + process/container metrics
otel-metrics OpenTelemetry metrics export (OTLP)
otel-tracing OpenTelemetry distributed tracing
http HTTP client with retry middleware (reqwest)
http-server Axum HTTP server with health probes (/livez and /readyz)
transport-kafka Kafka transport (rdkafka, dynamic-linking)
transport-grpc gRPC transport (tonic/prost)
transport-memory In-memory transport (testing/dev)
transport-redis Redis / Valkey Streams transport
transport-grpc-vector-compat Vector wire-protocol compatibility
spool Disk-backed async FIFO queue (yaque + zstd, CRC32C integrity)
tiered-sink Resilient delivery: hot buffer + circuit breaker + disk spillover
sink-stack Outbound control stack: timeout / load-shed / concurrency-limit / retry / rate-limit (tower)
worker Adaptive worker pool + BatchEngine (SIMD parse, pre-route, field interning)
memory Cgroup-aware MemoryGuard (OOM prevention)
governor Unified self-regulation gate (hard memory + weighted soft signals)
secrets Secrets management core (file backend)
secrets-vault OpenBao / HashiCorp Vault provider
secrets-aws AWS Secrets Manager provider
directory-config YAML directory-backed config store
directory-config-git Git integration for directory-config (git2)
scaling Back-pressure / scaling-pressure primitives
cli Standard CLI framework (clap)
top TUI metrics dashboard (ratatui)
io File rotation, NDJSON writer
dlq Dead-letter queue (file backend)
dlq-kafka DLQ Kafka backend
output-file File output sink
expression CEL expression evaluation
deployment Deployment-contract validation
version-check Optional startup version check
full Everything

Native System Dependencies

This crate dynamically links against system C libraries for several features. Both build hosts and deployment targets need the appropriate packages.

Build Host (CI / Development)

Feature Crate Build Package Notes
transport-kafka rdkafka-sys librdkafka-dev (>= 2.12.1) Requires Confluent APT repo - Ubuntu's default is too old
directory-config-git libgit2-sys libgit2-dev, libssh2-1-dev System lib avoids vendored C build
spool, tiered-sink zstd-sys libzstd-dev System lib avoids vendored C build
(transitive) libz-sys zlib1g-dev Used by multiple deps
(transitive) openssl-sys libssl-dev Dynamic linking via pkg-config
secrets-aws aws-lc-sys - C/C++ compiled from source (no system lib available); ~20-30s first build, cached by sccache

For librdkafka-dev >= 2.12.1, add the Confluent APT repo. The suite below is bookworm, which is what a Debian trixie host uses - Confluent publishes no trixie suite and the bookworm .deb installs cleanly on trixie. On an Ubuntu 24.04 host use noble:

curl -fsSL https://packages.confluent.io/clients/deb/archive.key \
  | sudo gpg --dearmor -o /usr/share/keyrings/confluent-clients.gpg
echo "deb [signed-by=/usr/share/keyrings/confluent-clients.gpg] \
  https://packages.confluent.io/clients/deb bookworm main" \
  | sudo tee /etc/apt/sources.list.d/confluent-clients.list
sudo apt-get update
sudo apt-get install -y librdkafka-dev libssl-dev libsasl2-dev pkg-config

Deployment Host (Runtime)

The compiled binary links against .so files at runtime. Install the runtime packages (not -dev) on deployment hosts or in Docker images.

Feature Runtime Package Shared Object
transport-kafka librdkafka1 (from Confluent repo) librdkafka.so.1
directory-config-git libgit2-1.9 on trixie, libgit2-1.7 on noble libgit2.so
spool, tiered-sink libzstd1 libzstd.so.1
(transitive) zlib1g libz.so.1
(transitive) libssl3t64 on trixie/noble, libssl3 on bookworm/jammy libssl.so.3

Only install what you use. Check the features your binary enables to determine which runtime packages are needed.

Docker Example

This is the shape the deployment feature's generator emits, minus the generated LABEL and APT blocks. The WORKDIR /app in the build stage is what makes /app/target/release/... resolvable from the runtime stage.

# Build stage
FROM rust:1 AS builder
WORKDIR /app
RUN apt-get update && apt-get install -y \
    pkg-config libssl-dev librdkafka-dev libgit2-dev libzstd-dev
COPY . .
RUN cargo build --release

# Runtime stage - the contract's base_image (default: the org base, currently
# Debian trixie slim)
FROM debian:trixie-slim AS runtime
RUN apt-get update && apt-get install -y --no-install-recommends \
    librdkafka1 libssl3t64 libgit2-1.9 libzstd1 ca-certificates curl \
    && rm -rf /var/lib/apt/lists/*

COPY --from=builder /app/target/release/myapp /usr/local/bin/myapp
RUN chmod +x /usr/local/bin/myapp

RUN useradd --create-home --uid 1000 appuser
USER appuser

EXPOSE 9090

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD curl -sf http://localhost:9090/livez > /dev/null || exit 1

ENTRYPOINT ["myapp"]

libgit2-1.9 and libssl3t64 are the trixie package names; on a noble base they are libgit2-1.7 and libssl3t64, on bookworm libgit2-1.5 and libssl3. The deployment feature works these out for you from the contract's release - see the release table in docs/deployment/native-deps.md, which also covers adding the Confluent APT repo to both stages for librdkafka1. The generator also drops any pre-existing UID 1000 account (ubuntu bases ship one) before creating appuser; trixie slim does not, so the example skips it. Note that Kubernetes ignores HEALTHCHECK - it is there for plain Docker and Compose. K8s uses the probe paths above.

Health Check Endpoints

For services deployed to Kubernetes, the paths are the K8s-standard ones. Two routers can serve them and they do NOT carry the same set, so the "served by" column is the one to read before pointing a probe or a scrape at a port:

Path Serves Served by Checks On failure
/livez liveness metrics server + http-server Process not deadlocked Restart pod
/readyz readiness metrics server + http-server Deps healthy + ready flag set Stop routing traffic
/metrics Prometheus scrape metrics server ONLY - -

Those are the whole surface -- there are no aliases, and every retired path returns 404. A second path meaning the same thing eventually stops meaning the same thing, and an alias that keeps answering 200 hides a probe still aimed at the old name.

The deployment contract's metrics_path defaults to /metrics, and the generated Helm chart puts the Prometheus scrape annotations on the contract's metrics_port. That only answers if the METRICS server is the thing listening on that port -- an app that stands up only the http-server router there will serve the two health paths and 404 the scrape.

There is no startup path. Point a K8s startupProbe at /livez: Kubernetes suspends liveness until the startup probe passes, so one path gives both a generous boot budget (failureThreshold) and a tight liveness period, without the two drifting apart.

Liveness MUST NEVER check downstream dependencies (a DB outage shouldn't restart your replicas). Readiness checks dependencies AND requires an explicit set_ready() call - cleared during graceful shutdown.

Self-regulation (default vertical scaling)

A scalo data-plane app regulates its own intake. Sized for steady state, a pod slows down or speeds up WITHIN itself first -- the default, fast, local response to a burst, a stalled upstream, or a transform that balloons memory. Only when that vertical headroom is exhausted does it escalate to horizontal scale (KEDA adding pods), driven by the same pressure signal. Memory is the hard, never-OOM authority; CPU is left to the kernel scheduler (CFS), which the byte-budget loop reads through longer process times. It is ON by default and opt-out via self_regulation.enabled = false.

The loop, since "it tunes itself" is a claim and this is the part you can check:

  • AIMD on the byte budget. The streaming sub-block budget grows additively while things are healthy and is cut multiplicatively when they are not -- the same additive-increase/multiplicative-decrease shape TCP congestion control has used since the 1980s.
  • HARD signals are never masked. The memory guard contributes its raw reading with no weight applied. A saturated soft signal cannot pull the level below what memory demands, and a busy soft signal cannot hide a missing hard one. That is the never-OOM guarantee.
  • SOFT signals are weighted and compete for the level, so a low-weight source at full saturation cannot force a hold that memory would not.
  • Hysteresis, because coupled controllers oscillate. The latch arms at pause_above and releases at resume_below; between the two it holds. A reading hunting around a single threshold cannot flap pause/resume, which is the failure mode that makes people distrust self-tuning systems.
  • It gates the SOURCE, never the sink. Backpressure stops intake. It never slows the write side and calls that regulation.

Full write-up, including the three pressure brains and why CPU was deliberately dropped as a source, in docs/self-regulation.md and docs/backpressure.md.

Architecture

See docs/ for the full documentation index - docs/architecture.md for the module map and layering, and docs/core-pillars/config.md for the 7-layer config cascade reference.

License

Apache-2.0.

Related

  • scalo-py -- sister library for Python services. Same opinions, same patterns, expressive Python ergonomics for control planes, APIs, and integration layers.