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_ENDPOINT—Tempo 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