Documentation
¶
Overview ¶
Package mirror provides a request-shadowing (traffic-mirroring) middleware: it tees a copy of matched/sampled REQUESTS to a separate destination chain (a "canary"), fire-and-forget, without ever affecting the primary request or its response. Use it to exercise a new build with real production traffic.
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Mirror ¶
type Mirror struct {
// Match selects which requests are eligible to mirror; nil matches all.
Match func(r *http.Request) bool
// Observe receives one MirrorInfo per decision/result; nil disables it. It is
// called SYNCHRONOUSLY and concurrently — on the request goroutine for the
// dispatch/drop outcomes (so it must be fast and non-blocking, or it stalls the
// primary) and on worker goroutines for completed/panicked — so it must be safe
// for concurrent use. See prom.Mirror for a ready, concurrency-safe hook.
Observe func(info MirrorInfo)
// ErrorLog logs a recovered mirror panic; nil uses the standard logger.
ErrorLog *log.Logger
// MarkHeader/MarkValue stamp the mirrored request so the canary can detect it and
// no-op side effects. Default "X-Mirror"/"1". Set DisableMark to turn marking off.
MarkHeader string
MarkValue string
// SampleRate is the fraction in [0,1] of MATCHED requests to mirror. The zero
// value means 1.0 (mirror all matched) — disable mirroring by NOT installing the
// middleware, not by SampleRate=0. Values > 1 clamp to 1; negative clamps to 1.
SampleRate float64
// Timeout bounds a mirror's total in-system lifetime — queue wait plus the
// round-trip — as one deadline set at dispatch. It is cooperative: the destination
// chain must honor the request context (upstream/httputil do). Default 10s.
Timeout time.Duration
// MaxBodyBytes caps the request body buffered per mirror; a larger body skips the
// mirror (the primary is unaffected). Default 1<<20.
MaxBodyBytes int64
// Workers is the fixed number of dispatch goroutines (the real concurrency cap).
// Default 8.
Workers int
// QueueSize is the buffered job-queue depth; a full queue drops (never blocks the
// primary). Default 256.
QueueSize int
// DisableBody, when true, always mirrors with no body (http.NoBody) — the
// zero-buffer mode for large-payload services. The zero value mirrors bodies.
DisableBody bool
// DisableMark, when true, omits the MarkHeader stamp. The zero value marks (so a
// canary can detect shadow traffic by default); set it for a fully transparent
// mirror.
DisableMark bool
// contains filtered or unexported fields
}
Mirror is a traffic-shadowing Middleware. It tees the REQUEST (not the response) of matched/sampled requests to its destination chain on a fixed worker pool, fire-and-forget, and never affects the primary request. The primary always runs unmodified; a mirror that is slow, full, or panicking is dropped or recovered, not propagated.
Config fields and the destination chain (Use) are read once on the first ServeHandler and then FROZEN — set them before serving; a later Use is silently ignored (unlike block.Block, which rebuilds per request). Effective mirror concurrency is Workers, not Workers+QueueSize: the queue only absorbs bursts that drain at worker speed, so under a slow-but-responsive canary the pool caps at Workers and the surplus is dropped (DroppedFull) — size Workers for the canary's latency, and wire Observe to watch the drop counters.
The worker pool is started on first use and lives for the PROCESS LIFETIME (no Close): construct one Mirror per destination and reuse it. On graceful shutdown in-flight/queued mirrors are abandoned (each bounded only by Timeout), which is acceptable for fire-and-forget shadow traffic. End-to-end credentials (Authorization, Cookie) are replayed to the canary by design (replay fidelity); use Match to exclude sensitive routes from shadowing.
func New ¶
func New() *Mirror
New creates a request-mirroring middleware. Configure the mirror destination with Use (typically upstream.SingleHost, or a balancer wrapped by upstream.New) and any config fields, then install it ahead of the real chain.
Example ¶
Shadow a sample of production GET traffic to a canary backend, fire-and-forget: the mirror never affects the primary request or its response. Mark and sample so the canary can detect (and no-op side effects of) shadow traffic and so only a fraction is teed; Observe wires outcome/latency metrics.
package main
import (
"net/http"
"github.com/moonrhythm/parapet"
"github.com/moonrhythm/parapet/pkg/mirror"
"github.com/moonrhythm/parapet/pkg/prom"
"github.com/moonrhythm/parapet/pkg/upstream"
)
func main() {
mr := mirror.New()
mr.Match = func(r *http.Request) bool { return r.Method == http.MethodGet }
mr.SampleRate = 0.1 // shadow 10% of matched requests
mr.Observe = prom.Mirror() // optional outcome/latency metrics
mr.Use(upstream.SingleHost("canary:8080", &upstream.HTTPTransport{}))
s := parapet.NewFrontend()
s.Use(mr) // tees, then falls through to the real chain
s.Use(upstream.SingleHost("prod:8080", &upstream.HTTPTransport{})) // the production backend
}
Output:
func (*Mirror) ServeHandler ¶
ServeHandler implements parapet.Middleware.
func (*Mirror) Use ¶
func (m *Mirror) Use(x parapet.Middleware)
Use appends a middleware to the mirror destination chain. SETUP ONLY: it must be called before the first request; the chain is frozen on first ServeHandler and a later Use is silently ignored.
func (*Mirror) UseFunc ¶
func (m *Mirror) UseFunc(x parapet.MiddlewareFunc)
UseFunc appends a middleware func to the mirror destination chain. SETUP ONLY (see Use).
type MirrorFunc ¶
type MirrorFunc func(info MirrorInfo)
MirrorFunc is the observation-hook shape (mirrors upstream.RoundTripFunc), returned by prom.Mirror for wiring into Mirror.Observe.
type MirrorInfo ¶
type MirrorInfo struct {
Outcome Outcome
Status int // set on OutcomeCompleted
Duration time.Duration // set on OutcomeCompleted
}
MirrorInfo reports one mirror decision/result to Observe.
type Outcome ¶
type Outcome uint8
Outcome classifies one mirror decision/result, reported via Observe.
const ( // OutcomeDispatched: the mirror request was enqueued for a worker. OutcomeDispatched Outcome = iota // OutcomeCompleted: a worker finished the round-trip (Status and Duration set). OutcomeCompleted // OutcomeDroppedFull: the queue was full; the mirror was dropped. OutcomeDroppedFull // OutcomeDroppedOversize: the body exceeded MaxBodyBytes; the mirror was skipped. OutcomeDroppedOversize // OutcomePanicked: the mirror chain panicked (recovered; the proxy is unaffected). OutcomePanicked )