DEV Community

Cover image for Kiponos vs Statsig — Product Gates & Experiments vs Live Ops Config Hub (Architecture)
Moshe Avdiel
Moshe Avdiel

Posted on • Originally published at github.com

Kiponos vs Statsig — Product Gates & Experiments vs Live Ops Config Hub (Architecture)

Tuesday 09:14. Growth ships a checkout experiment in Statsig — gate new_payment_sheet, 8% exposure, funnel metrics wired to the warehouse. Same standup, the payments SRE reports processor latency spiking: they need failure_rate_threshold at 28, fraud.block_score at 79, and the Python embedding service needs worker_pool_size cut from 48 to 24 before GPUs OOM.

The growth lead asks:

"Statsig has dynamic config — put the circuit threshold in a config parameter. One SDK."

The principal engineer on authorization pushes back:

"Statsig is built for who enters the experiment and what we measure. Our auth path runs 14k evaluations per second for floats that change during incidents — not for cohort bucketing with event logging overhead."

Statsig is a strong product development platform — feature gates, A/B experiments, dynamic config, and analytics that tie flag exposure to business metrics. Kiponos.io is a live operational config hub — nested trees, WebSocket deltas, and local get*() reads in Java and Python on the money path. Mature teams use Statsig where product learns; Kiponos where production survives.

The problem — experiment SDK semantics on the authorization hot path

Typical Statsig integration for a product gate:

// Product path — correct use of Statsig
StatsigUser user = new StatsigUser.Builder()
        .setUserID(customerId)
        .setEmail(email)
        .build();
boolean showNewSheet = Statsig.checkGate(user, "new_payment_sheet");
Enter fullscreen mode Exit fullscreen mode

Teams then extend dynamic config for ops:

// Anti-pattern — ops float stuffed into experiment infrastructure
DynamicConfig resilience = Statsig.getConfig(user, "resilience_tuning");
int failureThreshold = resilience.getInt("failure_rate_threshold", 40);

if (rollingFailureRate > failureThreshold) {
    return CircuitDecision.open();
}
Enter fullscreen mode Exit fullscreen mode

Problems compound quickly:

  • User context required — circuit breakers are system-bound, not identity-bound; fake system users are a smell
  • Evaluation + exposure logging — optimized for product analytics, not bare-metal hot-path floats
  • Flat config namespacesfraud.block_score, resilience.payments.wait_ms, and ml.pool_size lack a shared ops tree across four services
  • Python workers and Java APIs — same dynamic config duplicated or synced through custom glue

Statsig dynamic config is legitimate for product-tunable parameters (onboarding copy variants, default tab selection). It is the wrong primitive for incident knobs on saturated authorization paths.

What teams believe vs production reality

Belief Production reality
"Statsig dynamic config replaces a config hub" Built for parameter experiments, not nested ops trees
"One SDK simplifies architecture" Product gates and fraud floats have different latency budgets
"Exposure events are free" At 14k TPS, even lightweight logging adds up on the hot path
"Engineers can share one dashboard" PM experiments and SRE incident keys collide in the same namespace
"We will use Redis for the rest" Now you operate Statsig + Redis + YAML for one platform

The Aha

Statsig decides which users see which product experiences and measures outcomes. Kiponos decides how hard production runs when processors degrade and fraud spikes. Keep new_payment_sheet in Statsig with full experiment analytics. Move failure_rate_threshold, block_score, and worker_pool_size to Kiponos — local reads, no per-transaction experiment evaluation.

What Kiponos.io is for Statsig-heavy product orgs

Kiponos is a real-time configuration hub. Java and Python SDKs connect once via WebSocket, hydrate a typed profile tree, and serve getInt(), getDouble(), and getBoolean() from in-process memory. Dashboard edits push single-key deltas — change fraud/thresholds/block_score from 85 to 79; every pod sees it without redeploy.

Profile path for this comparison:

['payments']['api']['prod']['live']
Enter fullscreen mode Exit fullscreen mode

Product gates stay in Statsig. Operational knobs live beside them in Kiponos if you want one mental model for "live values" — but the read contract is always local cache lookup, not gate evaluation with user context.

Architecture — Statsig product plane vs Kiponos ops plane

Architecture diagram

Hybrid is the norm: Statsig owns identity-bound experiments; Kiponos owns system-bound thresholds both runtimes read.

Config tree — ops keys that do not belong in experiment config

fraud/
  thresholds/
    block_score: 79
    review_score: 65
    velocity_per_hour: 18
    bin_attack_mode: true
resilience/
  payments/
    failure_rate_threshold: 28
    wait_duration_open_ms: 22000
    half_open_permitted_calls: 8
  inventory/
    failure_rate_threshold: 42
    slow_call_threshold_ms: 3500
ml/
  embedding/
    worker_pool_size: 24
    batch_size: 32
    max_sequence_length: 512
    oom_guard_enabled: true
limits/
  partner_checkout/
    rpm: 9500
    burst: 1400
statsig_bridge/
  # Optional: document which gates remain on Statsig
  new_payment_sheet_gate: statsig_owned
  onboarding_copy_experiment: statsig_owned
Enter fullscreen mode Exit fullscreen mode

Java integration — authorization path stays local

@Configuration
public class KiponosConfig {

    @Bean
    public Kiponos kiponos(
            @Value("${kiponos.team-id}") String teamId,
            @Value("${kiponos.access-key}") String accessKey,
            @Value("${kiponos.profile-path}") String profilePath) {
        return Kiponos.builder()
                .teamId(teamId)
                .accessKey(accessKey)
                .profilePath(profilePath)
                .build();
    }
}
Enter fullscreen mode Exit fullscreen mode
@Service
public class PartnerCircuitEvaluator {

    private final Kiponos kiponos;

    public PartnerCircuitEvaluator(Kiponos kiponos) {
        this.kiponos = kiponos;
    }

    public CircuitState evaluate(String partnerId, double rollingFailureRate) {
        var resilience = kiponos.path("resilience", "payments");
        int threshold = resilience.getInt("failure_rate_threshold");
        long waitOpenMs = resilience.getLong("wait_duration_open_ms");

        if (rollingFailureRate > threshold) {
            return CircuitState.open(partnerId, waitOpenMs);
        }
        return CircuitState.closed();
    }
}
Enter fullscreen mode Exit fullscreen mode

Product gate — keep Statsig on the checkout path where analytics matter:

public boolean renderNewPaymentSheet(String customerId, String email) {
    StatsigUser user = new StatsigUser.Builder()
            .setUserID(customerId)
            .setEmail(email)
            .build();
    return Statsig.checkGate(user, "new_payment_sheet");
    // Do not route fraud.block_score through this SDK
}
Enter fullscreen mode Exit fullscreen mode

Python integration — ML worker reads same ops tree

import os
from kiponos import Kiponos

os.environ["KIPONOS_PROFILE"] = "['payments']['api']['prod']['live']"
kiponos = Kiponos.create_for_current_team()

def current_pool_size() -> int:
    return kiponos.path("ml", "embedding").get_int("worker_pool_size", 48)

def on_config_change(change):
    if change.path.startswith("ml/embedding/worker_pool_size"):
        resize_process_pool(int(change.new_value))

kiponos.after_value_changed(on_config_change)
Enter fullscreen mode Exit fullscreen mode

Statsig has no first-class story for a Python GPU worker and a Java authorization cluster sharing ml/embedding/worker_pool_size with sub-second incident edits.

Real scenarios

Event Statsig alone Statsig + Kiponos
Launch new_payment_sheet gate at 8% Native gate + funnel metrics Keep Statsig; unchanged
Processor brownout — open circuit faster Dynamic config hack + system user resilience/payments/failure_rate_threshold live
BIN attack — lower block score Wrong tool / awkward config param fraud/thresholds/block_score in seconds
GPU OOM — shrink embedding pool Not the tool ml/embedding/worker_pool_size in Python
Cross-service saga timeout alignment Flat config keys Shared nested resilience/ tree
Measure experiment lift on checkout Statsig analytics Keep Statsig; ops keys in Kiponos audit log

Performance — hot path economics on authorization

  • Statsig gate check — user context, bucketing, exposure pipeline — right for product paths at human scale
  • Statsig dynamic config on auth — per-evaluation config fetch semantics; not designed for 14k bare floats/sec
  • Kiponos getInt() — in-memory tree lookup; no network on read path
  • Delta updates — incident changes one key; no full dynamic config document redeploy
  • One WebSocket per JVM/worker — background sync; hot path never blocks on vendor RTT
  • Polyglot parity — Java Spring Boot 3 and Python workers share one profile; Statsig SDK coverage varies by runtime role

Honest comparison table

Criterion Statsig Kiponos Honest verdict
Feature gates & exposure logging Excellent App-side bucketing possible Statsig wins product gates
A/B experiments + funnel metrics Core strength Ops change log only Statsig for measured experiments
Dynamic config for product params Good Good for ops trees Statsig for copy/UX tuning
Numeric incident knobs (fraud, circuits) Awkward fit First-class Kiponos on money path
Nested cross-service ops trees Flat namespaces Hierarchical paths Kiponos for platform ops
Hot-path read at 14k TPS Evaluation model Local cache Kiponos on authorization
Java + Python same hub Partial / role-dependent Both SDKs Kiponos for polyglot ops
Product analytics warehouse tie-in Built-in Not a product analytics tool Complementary
Pricing model Event / MAU oriented Team/hub pricing Model your experiment vs ops split

When not to use Kiponos

Use case Better tool
Gated rollouts with experiment lift measurement Statsig
Funnel metrics tied to flag exposure Statsig
Non-technical PM self-serve experiment creation Statsig
Bootstrap secrets and API keys Vault / cloud secret manager
Infrastructure desired state GitOps / Terraform

Getting started (15 minutes) — split product from ops

  1. Inventory every live key: mark product experiment (gate, A/B, UX param) vs operational knob (fraud, circuit, pool).
  2. TeamPro at kiponos.io — profile ['payments']['api']['prod']['live'].
  3. Migrate three ops keys off Statsig dynamic config: block_score, one failure_rate_threshold, one worker_pool_size.
  4. Wire Java PartnerCircuitEvaluator and Python embedding worker to the same profile.
  5. Document RFC: "Statsig owns gates and experiments with analytics; Kiponos owns ops floats on hot paths."

Further reading


Kiponos.io — Statsig for which users see the experiment. Live hub for how hard production runs during the incident.

Top comments (0)