Skip to content

Reliability

SLOzy implements several reliability patterns to ensure stable operation under adverse conditions: circuit breakers to prevent cascading failures, exponential backoff with jitter to avoid thundering herds, and request tracing for latency analysis.

Circuit Breaker

The circuit breaker wraps all external HTTP calls — Prometheus queries, notification delivery, and license phone-home — with a state machine that prevents requests from reaching a failing dependency.

States

StateMeaningTransition
ClosedNormal operation, requests pass through→ Open after 5 consecutive failures
OpenRequests fail immediately without calling the dependency→ Half-Open after 30 seconds
Half-OpenLimited requests allowed to probe recovery→ Closed after 3 consecutive successes, or back to Open on failure

Configuration

go
resilience.NewCircuitBreaker(resilience.BreakerConfig{
    Name:            "prometheus-client",
    MaxFailures:     5,
    HalfOpenMaxReqs: 3,
    OpenTimeout:     30 * time.Second,
})

Affected components

ComponentFileCircuit
Prometheus queriesinternal/prometheus/client.goprometheus-client

When the circuit is open, the Prometheus client returns ErrBreakerOpen immediately, preventing downstream requests from reaching the database.

Exponential Backoff with Jitter

Retries use exponential backoff with full jitter to spread retry attempts across a time window and avoid thundering herds.

Formula

delay = random(0, min(baseDelay × 2^attempt, maxDelay))

Retry configuration

ComponentMax attemptsBase delayMax delay
Prometheus queries3100 ms5 s
License phone-home31 s5 s
Notification delivery3 (configurable)5 min5 min

Context cancellation

All retry loops respect context cancellation. If the parent context is cancelled or times out during a backoff sleep, the retry loop exits immediately with ctx.Err(). This prevents retries from blocking application shutdown.

Implementation

go
func BackoffDuration(attempt int, base time.Duration, max time.Duration) time.Duration {
    exp := 1 << uint(attempt)
    d := time.Duration(float64(base) * float64(exp))
    if d > max { d = max }
    return time.Duration(rand.Int63n(int64(d)))
}

func Retry(ctx context.Context, maxAttempts int, base, max time.Duration, fn func(context.Context) error) error {
    for attempt := 0; attempt < maxAttempts; attempt++ {
        if attempt > 0 {
            delay := BackoffDuration(attempt-1, base, max)
            timer := time.NewTimer(delay)
            select {
            case <-ctx.Done():
                timer.Stop()
                return ctx.Err()
            case <-timer.C:
            }
        }
        if err := fn(ctx); err == nil {
            return nil
        } else {
            lastErr = err
        }
    }
    return lastErr
}

Request Tracing

SLOzy uses OpenTelemetry SDK with OTLP HTTP export to send traces to Grafana Tempo. The tracer is initialised in main.go via resilience.InitTracerProvider().

Configuration

VariableDefaultDescription
OTEL_EXPORTER_OTLP_ENDPOINTTempo OTLP HTTP endpoint (e.g. http://tempo:4318)
OTEL_TRACES_SAMPLERparentbased_traceidratioSampling strategy
OTEL_TRACES_SAMPLER_ARG0.110% of requests sampled

When OTEL_EXPORTER_OTLP_ENDPOINT is set, the app exports spans to Tempo. If unset, tracing is a no-op.

Slow request detection

Requests taking longer than 100 ms are logged as potential performance issues:

[trace] slow request: GET /api/v1/slos took 1.2s (status=200)

Span types

TypeDescription
SpanKindServerIncoming HTTP request (Gin middleware via otelgin)
SpanKindClientOutbound HTTP call (Prometheus query)

Middleware registration

Registered in cmd/slozy-web/main.go:

go
app.router.Use(resilience.TracingMiddleware("slozy-web"))

The middleware wraps otelgin.Middleware, which automatically captures HTTP attributes (method, path, status, etc.) and propagates trace context.

Grafana integration

Traces are available in Grafana via the Tempo datasource. Use Explore → Tempo to search by service name (slozy-web), duration, or tags. Span metrics (service graphs, span metrics) are generated by Tempo's metrics_generator and available in VictoriaMetrics.

  • Monitoring — Prometheus metrics and health checks
  • Dashboards — Grafana dashboards for host, container, and application monitoring