Skip to main content

Overview

The canary middleware enables canary deployments by routing a percentage of traffic to different handlers, allowing gradual rollouts. Use it when you need:
  • Gradual feature rollouts
  • A/B testing
  • Blue-green deployments

Installation

import "github.com/go-mizu/mizu/middlewares/canary"

Quick Start

app := mizu.New()

// Route 10% to canary
app.Use(canary.New(canary.Options{
    Canary:  canaryHandler,
    Percent: 10,
}))

Configuration

Options

OptionTypeDefaultDescription
Canarymizu.HandlerRequiredCanary handler
Percentint0Percentage to canary
Headerstring""Force canary via header
Cookiestring""Force canary via cookie
StickyboolfalseSticky sessions

Examples

Percentage-Based

app.Use(canary.New(canary.Options{
    Canary:  v2Handler,
    Percent: 5, // 5% to v2
}))

With Sticky Sessions

app.Use(canary.New(canary.Options{
    Canary:  v2Handler,
    Percent: 10,
    Sticky:  true, // Same user always gets same version
}))

Header Override

app.Use(canary.New(canary.Options{
    Canary:  v2Handler,
    Percent: 10,
    Header:  "X-Canary", // Force canary with X-Canary: true
}))
app.Use(canary.New(canary.Options{
    Canary: v2Handler,
    Cookie: "canary", // Check canary=true cookie
}))

Gradual Rollout

// Start at 1%, increase over time
percent := atomic.Int32{}
percent.Store(1)

app.Use(canary.New(canary.Options{
    Canary: v2Handler,
    PercentFunc: func() int {
        return int(percent.Load())
    },
}))

// Increase percentage via admin endpoint
app.Post("/admin/canary", func(c *mizu.Ctx) error {
    p, _ := strconv.Atoi(c.Query("percent"))
    percent.Store(int32(p))
    return c.Text(200, "OK")
})

API Reference

Functions

// New creates canary middleware
func New(opts Options) mizu.Middleware

Technical Details

Implementation Overview

The canary middleware uses a deterministic counter-based approach for traffic distribution:
  • Counter-Based Distribution: Uses an atomic counter (atomic.AddUint64) that increments on each request. The modulo operation (counter % 100 < percentage) ensures predictable distribution over time.
  • Context Storage: Stores the canary decision in the request context using a private context key, accessible via IsCanary(c).
  • Override Precedence: Decision order is: Header override > Cookie override > Custom Selector > Percentage-based selection.

Key Components

Core Functions:
  • New(percentage int): Creates middleware with simple percentage-based routing
  • WithOptions(opts Options): Creates middleware with full configuration options
  • IsCanary(c *mizu.Ctx): Checks if current request is using canary version
  • Route(canary, stable Handler): Routes to different handlers based on canary status
  • Middleware(canaryMw, stableMw): Applies different middleware chains based on canary status
ReleaseManager:
  • Manages multiple named canary releases
  • Each release has independent counter and percentage
  • Useful for managing multiple feature rollouts simultaneously
Selectors:
  • RandomSelector(percentage): Uses math/rand for random selection (non-cryptographic)
  • HeaderSelector(header, value): Selects based on header value
  • CookieSelector(name, value): Selects based on cookie value
  • Custom selectors via Options.Selector function

Security Notes

The implementation intentionally uses math/rand (not crypto/rand) for performance:
  • Canary selection is non-security-critical
  • gosec G404 warnings are suppressed with explanation
  • The counter-based default approach is fully deterministic

Best Practices

  • Start with low percentages (1-5%)
  • Monitor error rates for canary traffic
  • Use sticky sessions for stateful applications
  • Provide override mechanism for testing

Testing

Test Coverage

Test CaseDescriptionExpected Behavior
TestNewBasic percentage-based canary routing with 50% splitApproximately 50% of 100 requests are marked as canary (40-60% range)
TestWithOptions_Header (with header)Header override with X-Canary: trueRequest is marked as canary when header is present
TestWithOptions_Header (without header)No header with 0% percentageRequest is not marked as canary
TestWithOptions_CookieCookie-based canary selectionRequest is marked as canary when cookie matches
TestWithOptions_SelectorCustom selector based on User-AgentRequest is marked as canary when User-Agent equals “Canary”
TestRouteRoute function with stable trafficReturns “stable” response for non-canary requests
TestRoute_CanaryRoute function with canary headerReturns “canary” response when X-Canary header is set
TestMiddleware (stable)Middleware selection for stable trafficApplies stable middleware, sets X-Version: stable header
TestMiddleware (canary)Middleware selection for canary trafficApplies canary middleware, sets X-Version: canary header
TestReleaseManagerReleaseManager set/get operationsCan set and retrieve release configurations by name
TestReleaseManager_ShouldUseCanaryReleaseManager percentage distributionApproximately 50% canary selection over 100 calls (40-60% range)
TestHeaderSelector (matching)HeaderSelector with matching valueSelector returns true for matching header value
TestHeaderSelector (non-matching)HeaderSelector with non-matching valueSelector returns false for non-matching header value
TestCookieSelectorCookieSelector with matching cookieSelector returns true for matching cookie name and value