Dynamic config
Ductor's two dynamic-config systems — the tenant-scoped registry and the file-based feature-flag/A-B system — and how they differ.
Ductor has two distinct dynamic-config systems, and they are easy to
confuse — the configuration guide and the code both flag the overlap. They
solve different problems, live in different files, and are backed by different
stores. This page disambiguates them. Neither is the same as static boot
config (configs/ductor.yaml), which is loaded once by viper at process start
and is not re-read.
Which config lives where
configs/README.md is the authoritative ownership rule. Boot-time service
wiring (DB DSN, bind addresses, OIDC issuer, the AEAD master key) belongs in
configs/ductor.yaml. Restart-free, per-tenant flips belong in the
dynamicconfig registry. A key must have exactly one canonical owner — the
other file must not redeclare it.
1. The dynamicconfig registry (Postgres/Redis-backed)
This is the operator-facing hot-reload plane: tenant-scoped settings that flip
live, without a restart, per tenant where the setting's scope allows.
- Codegen source:
configs/dynamicconfig.yamldeclares eachSetting(itsvar_name,key,type,scope, anddefault). It is consumed bytools/dynamicconfig-gen/to produce a typed registry; regenerate withmake dynamicconfig-generate. - Runtime store: the live values are in Postgres + Redis — not in the YAML. The YAML only declares the settings and their defaults.
- Reads: application code reads a value through the typed registry
(
dynconfig.DynamicConfig.Get(key, ctx)), which applies the per-tenant override chain.
Typical contents are kill switches, capacity quotas, and cluster rollout gates — things you want to flip during a canary or an incident without redeploying:
| Example key | Scope | Purpose |
|---|---|---|
routing.kill_switch | tenant | Emergency per-tenant routing kill switch |
routing.disable_flow_control | tenant | Disable flow-control checks during routing |
ductor.queue.shadow_mode | global | Shadow-partition queue rollout (off/shadow/canary/on) |
ductor.scheduling.enabled | global | Master kill switch for the scheduling integration |
2. The file-based feature-flag / A-B system
This is a separate system for feature flags and A/B experiments, driven by a
watched file rather than the registry. Enable it under dynamic_config.*:
dynamic_config:
enabled: false
config_file: /etc/ductor/dynamic.yaml
watch_interval: 30s # debounce for file-change detectionThe referenced file carries three capabilities:
- Config values — key/value settings that reload when the file changes.
- Feature flags — boolean flags with percentage rollout (deterministic
FNV-1a bucketing on flag key + context ID, so the same tenant always lands the
same way) and targeting rules matching context attributes such as
tenant_idorregion. - A/B tests — weighted variant selection; weights are proportional ranges in
[0, 1)and must sum to1.0, each variant carrying aconfigmap your code reads.
The file source watches with fsnotify. On change, the new file is parsed and
validated, flags and tests are swapped atomically, and registered
OnChange callbacks fire with the old and new snapshots.
values:
routing_timeout_ms: 5000
features:
- key: experimental_strategy
enabled: true
percentage: 25 # 25% rollout
- key: premium_features
enabled: true
rules:
- attribute: tenant_id
values: ["acme-corp", "enterprise-inc"]
ab_tests:
- id: routing_strategy_test
status: active # active | paused | completed
variants:
- id: round_robin
weight: 0.6
- id: weighted
weight: 0.4Telling them apart
| dynamicconfig registry | file-based feature flags | |
|---|---|---|
| Source | configs/dynamicconfig.yaml (codegen) | a watched YAML file (dynamic_config.config_file) |
| Runtime store | Postgres + Redis | the file itself (via fsnotify) |
| Enabled by | always available in the graph | dynamic_config.enabled=true |
| Scope | per-tenant override chain | percentage rollout + targeting rules |
| Regenerate | make dynamicconfig-generate | n/a (edit the file) |
| Best for | kill switches, quotas, rollout gates | feature flags, A/B experiments |
Neither re-reads configs/ductor.yaml: boot-time static config is loaded once
and only changes on restart.
Related
Key management
Rotating the AEAD master key that protects connector credentials and payloads, plus BYOK/KEK boot unwrapping.
Durable Route Runtime
Journal payload budgets, transition-history compaction, dual-stream archival, and the reliability safety nets that keep run history bounded and secret-free.