Config Hot Reload in Practice: config.ini/YAML Without Restarting
Restart a running bot every time you want to change a parameter and the market won't wait for you. Here's the hot reload pattern for swapping config without a restart, worked out while operating trading bots. Three things matter: periodic reloading, concurrency safety, and validation.
Config files tend to accumulate sensitive values such as API keys. Never commit them to your repository — share only an example file (
config.example.ini).
Why INI/YAML
Bot configuration gets edited by hand, often. That favors a format humans read and write easily. INI's flat section structure is convenient for splitting settings per pair or per symbol; YAML handles nesting better. Both support comments, so you can record what a value means inside the file itself.
[COMMON]
GapEvalIntervalMs = 1000 ; gap evaluation interval (ms)
EntryDebounceSec = 30 ; duplicate-entry guard window (sec)
[PAIR_01]
EntryGapPct = 0.50 ; entry threshold (%)
Enabled = true
Auto-Reload Every 5 Minutes
The simplest pattern that holds up in production is re-reading the file on a fixed interval. It's simpler to implement and more predictable than filesystem watching (inotify and friends).
func configReloader() {
ticker := time.NewTicker(5 * time.Minute)
for range ticker.C {
if err := reloadConfig(); err != nil {
logCh <- "[config] auto reload failed: " + err.Error()
continue // keep the existing config on failure
}
logCh <- "[config] auto reload complete"
}
}
The important line is continue. A failed reload doesn't stop the bot; it keeps running on the last known-good config. One typo should never take the whole bot down.
Concurrency — Read via Snapshots
Here's the trap people miss. A reload writes the config while the bot's various goroutines read it. With no protection, values change mid-read and you have a race.
The fix is sync.RWMutex plus a snapshot copy. Readers shouldn't hold the config for long — they take an independent copy briefly and work from that.
var cfgMu sync.RWMutex
// Return an independent copy of the active config — unaffected if a reload swaps the original
func snapshotPairs() []Pair3Config {
cfgMu.RLock()
defer cfgMu.RUnlock()
out := make([]Pair3Config, len(cfg.Pairs))
copy(out, cfg.Pairs) // copy by value
return out
}
Now readers hold the lock only briefly and work freely from their own copy afterward. Even if a reload replaces the original wholesale, a calculation already in flight stays safe. "Never do long work while holding a lock" is the governing principle.
Validation — Filter Out Bad Values
The biggest risk of hot reload is a bad config taking effect instantly. So validate on every reload.
- Type and range checks: skip any entry whose threshold is negative or zero.
- Required values: if a symbol code is blank, mark that pair inactive.
- Limit what's hot-reloadable: hot reload only what can be changed safely, like thresholds and enabled flags. Items that require re-subscription — symbol codes, WebSocket subscriptions — should recommend a restart instead.
if pair.EntryGapPct <= 0 || pair.MaxInvestPerLeg <= 0 {
continue // out-of-range -> ignore this pair for this reload
}
Ignore anything that fails validation, but always log it. You need to be able to trace "why didn't my setting take effect?" later.
Summary
- Use human-readable INI/YAML, and never commit secrets
- Periodic reloading (say, 5 minutes) is simpler and more predictable than file watching
- Reload writes, the bot reads → block races with RWMutex plus snapshot copies
- Hot-reload only validated, safe items; recommend a restart for the rest
- Failures and skips should log, not stop the bot
Working hot reload changes how operating a bot feels entirely. Tune parameters in real time while watching the market — just make sure that convenience is belted in by validation so it doesn't cost you stability.
댓글
댓글 쓰기