Expand description
Read dynamic-config configuration from a Redis key.
Redis speaks a plain request/response protocol, so this implements the
blocking RemoteSource trait: nothing here needs an async runtime,
and neither does using it.
use dynamic_config_redis::Redis;
DbConfig::set_remote(Redis::new("redis://redis.internal:6379", "myapp/db.json")?);
// Fetching is explicit; the load that follows touches no network.
DbConfig::refresh_remote()?;§What it reads
One key, whose value is a whole configuration document — the same bytes
that would be in a config file. The format comes from the key’s extension,
or from with_format.
A Redis hash would be the other obvious mapping — one field per setting — and is deliberately not what this does. A hash cannot hold a nested table without inventing a flattening convention, and a document already has one.
§Several keys as one document
A deployment that splits its configuration across several keys can have one
source read the lot, and Keys says which:
// Named keys: a list of layers, merged in the order given, later wins.
let redis = Redis::new(url, Keys::several(["myapp/base.json", "myapp/local.json"]))?;
// A prefix: disjoint sections, and an overlap between two of them is an error.
let redis = Redis::new(url, Keys::prefix("myapp/"))?
.with_format(dynamic_config::Format::Json);The two forms cost different things, and Redis is the store where the difference matters most:
- A named list is one
MGET— one command, one round trip, and Redis runs it as one operation, so the set is consistent. It is also the only multi-key shape here that can be watched; seeRedis::watch. - A prefix is a
SCANand then anMGET, and theSCANis deliberately notKEYS:KEYSwalks the whole key space in one blocking operation and is the classic way to stall a production Redis. The price is thatSCANis not atomic — a key written while the cursor is moving may or may not be in the set — so a prefix read here can catch a deployment mid-write in a way a named list cannot, and cannot be watched at all. Prefer a named list where the keys are known.
The prefix is matched as a literal, not as a pattern. SCAN MATCH takes
a glob, so *, ?, [ and \ in the prefix are escaped before the
command goes out, and every key the server answers with is checked against
the literal prefix before it is used — a prefix means the prefix, not
whatever a glob would have made of it.
Three more consequences that belong here rather than in an incident:
- A prefix that matches more than 512 keys is refused.
- Provenance becomes store-grained. The merged document is one layer, so
source_ofanswers “from redis … keys a, b” and not which key supplied a value.describenames the set. - One unreadable key fails the whole fetch.
§Credentials
In the URL, which is where Redis puts them and where every deployment
already has them: redis://user:password@host:6379/0, or rediss:// for
TLS — which needs this crate’s tls feature to supply the client’s rustls
stack. from_client takes a client the program
already built, for anything the URL cannot say.
A password Redis will not accept — NOAUTH, WRONGPASS, NOPERM — is
reported as ErrorKind::Auth rather than Remote, because reconnecting
does not change the server’s mind. The password itself never reaches the
message: the URL is redacted before it is stored.
§Timeouts
Redis::with_timeout is the deadline for a single fetch attempt,
excluding retries the underlying client performs — the sentence every store
in this family answers to. Ten seconds by default.
Redis has three separate knobs and this sets all of them from the one value: connect, write and read. A deadline covering only the connect would sail past a server that accepted the socket and then went quiet, which is what a wedged Redis actually looks like.
§Watching
Keyspace notifications, so Redis::watch is genuinely change-driven —
no polling, no timer. It runs on a thread rather than a future, because
nothing here needs a runtime, so stopping it has to come from outside —
hence the Watching token.
A named list can be watched; a prefix cannot. A watch on a set is only
honest if the store says the set changed and the set can then be read as
of one instant. A subscription per key answers the first, and MGET
answers the second: it is one command, and Redis runs one command as one
operation, so the values it returns are the set as of one point in the
command stream. The document delivered is therefore a state the server
really held. A prefix has to find its keys again first, and SCAN is a
cursor walked over many commands with writes free to land between them —
so it refuses at watch, before the first notification;
name the keys with Keys::several, or poll refresh_remote() on a
timer instead.
let watch = RemoteWatch::new();
let watching = watch.watching();
std::thread::spawn(move || redis.watch(&watching, move |document| sink(document)));
// Dropping `watch` — or calling `watch.stop()` — ends the loop.A failing watch says so, if it is asked to.
reporting_to hands the loop the same sink it
delivers through, and the failures inside it — a re-read that came back
with nothing, and a subscription that died — are reported to the
RemoteStatus as they happen. Without it a watch is the half of a store
dynamic-config cannot see: only deliveries are recorded, so
dynamic_config_remote_up describes the last delivery rather than the
last attempt.
§Every failure branch of the watch loop, and what it reports
A watch is the half of a store dynamic-config cannot see, and
reporting_to is what lets it speak: the sink the
loop already holds is told about every attempt that came back with
nothing. Which attempts those are is a table rather than prose, because
the question an operator asks is which silence is deliberate.
Three rules decide the column, and they are the same three in all seven store crates:
- A failure the loop survives by retrying reports. That is the case the whole feature exists for: the stream is down, the last delivery is old, and nothing else would ever say so out loud.
- A recovery that worked stays silent. Only a delivery or a fetch
clears the streak, so reporting a five-minute token turning over on a
healthy cluster would drive
remote_upto zero and leave it there. - A refusal that never asked the store reports nowhere. No format, a
key shape that cannot be watched, material that will not build a
client:
RemoteStatus::reachable()is whether the store answered the last time it was asked, and these never ask. They are returned to the caller, who is the one holding the mistake — and a status cannot correct them, since it carries a kind and a path and no message.
| Branch | Reports |
|---|---|
| the format is missing, or the keys cannot be watched | no — rule 3: nothing has been asked of the server |
| keyspace notifications are switched off on the server | yes — a CONFIG GET answered, and it answered that this watch cannot work |
the subscriber connection, the database index, a SUBSCRIBE, or the read timeout fails | yes — every one of those is a round trip, or a socket that has already made one |
| the read timeout expires with no message | no — that is how stop is noticed |
| the subscription fails | yes, and the watch ends |
a del or expired event | no — see the note below |
| the re-read after a notification fails | yes, and the loop waits for the next notification |
| a coalesced duplicate: the set came back the same | no — the server answered |
on_change refuses the document | no — the server answered; apply counted the delivery, and what the document did next is ConfigStatus’s half |
The deletion row is a difference between stores, deliberately left
standing. Here and in dynamic-config-etcd a key holding nothing leaves
the running snapshot alone and says nothing, because the server is
answering and only a delivery clears a streak — reporting it would park
remote_up at zero for as long as nobody recreated the key.
dynamic-config-consul records it instead, on the argument that a fetch
of the same key fails. Both are written down at the branch, and neither
moves in a patch release.
Structs§
- Client
- Redis’ own client, re-exported so
from_clientneeds no direct dependency. The client type. - Redis
- A key in Redis, as a configuration source.
- TlsConfig
- A private certificate authority and a client certificate, as data.
Enums§
- Keys
- What a source reads: one key, several named keys, or a prefix.