Modern QA2026Statistical Significance for Quality Gates — tiles
Log inJoin
16 / 67 · 06 Observability-Driven Testing · A/B Tests as Quality Gates← prev⊞ allnext →☰ Read as one page

3.3Statistical Significance for Quality Gates

Quality gate decisions must be statistically rigorous. A naive comparison ("canary error rate is 2.1% vs. control 2.0%") can lead to false conclusions.

Proportions Z-Test for Quality Gates

# quality_gate_statistics.py
import numpy as np
from scipy import stats

def is_canary_safe(control_errors, canary_errors, significance_level=0.05):
    """
    Determine if the canary version is statistically no worse than control.
    Uses a one-tailed proportions z-test.

    Args:
        control_errors: list of 0/1 (0=success, 1=error) for control group
        canary_errors: list of 0/1 for canary group
        significance_level: p-value threshold (default 0.05)

    Returns: (is_safe: bool, p_value: float, details: str)
    """
    n_control = len(control_errors)
    n_canary = len(canary_errors)

    p_control = sum(control_errors) / n_control
    p_canary = sum(canary_errors) / n_canary

    # Pooled proportion under the null hypothesis
    p_pool = (sum(control_errors) + sum(canary_errors)) / (n_control + n_canary)

    # Standard error of the difference
    se = np.sqrt(p_pool * (1 - p_pool) * (1/n_control + 1/n_canary))

    if se == 0:
        return True, 1.0, "No errors in either group"

    # Z-score: is canary WORSE than control?
    z = (p_canary - p_control) / se
    p_value = 1 - stats.norm.cdf(z)  # one-tailed test

    is_safe = p_value > significance_level

    details = (
        f"Control error rate: {p_control:.4%} ({sum(control_errors)}/{n_control})\n"
        f"Canary error rate:  {p_canary:.4%} ({sum(canary_errors)}/{n_canary})\n"
        f"Z-score: {z:.3f}, P-value: {p_value:.4f}\n"
        f"Decision: {'SAFE - no significant degradation' if is_safe else 'UNSAFE - canary is significantly worse'}"
    )

    return is_safe, p_value, details


# Example: control vs canary with identical error rates
control_results = [0]*9800 + [1]*200      # 2.0% error rate (10,000 requests)
canary_results = [0]*980 + [1]*20          # 2.0% error rate (1,000 requests)

safe, p_val, details = is_canary_safe(control_results, canary_results)
print(details)
# Control error rate: 2.0000%
# Canary error rate:  2.0000%
# Z-score: 0.000, P-value: 0.5000
# Decision: SAFE - no significant degradation

Sample Size Requirements

The number of requests needed for a statistically valid comparison depends on the baseline error rate and the minimum detectable effect:

Baseline Error Rate Minimum Detectable Increase Required Samples (per group)
0.1% 0.1% (doubling) ~38,000
0.5% 0.25% ~12,000
1.0% 0.5% ~7,000
2.0% 1.0% ~4,000
5.0% 2.5% ~1,500

Practical implication: For services with very low error rates, you need either high traffic volume or longer observation windows to reach statistical significance.