faucet-state-redis
Redis-backed StateStore for the faucet-stream ecosystem. Persists incremental-replication bookmarks in Redis so a pipeline can resume exactly where it left off after a crash, restart, or scheduled invocation.
Why durable state
A faucet-stream pipeline records a bookmark — the source's resume position (a CDC LSN, a WHERE id > ? watermark, a Kafka offset) — every time the sink confirms a batch. Where that bookmark lives decides what happens after a restart:
memory(built intofaucet-core) — fast, but the bookmark dies with the process. Every run starts from scratch.file(built intofaucet-core) — survives restarts on a single host, but isn't shared and doesn't fit ephemeral or multi-replica deployments.redis(this crate) — a network-accessible, shared store. Ideal when the runner is containerized, scheduled, or horizontally placed, and you want resume state to outlive any one process or host.
The bookmark is read before each fetch and persisted only after the sink confirms the batch — so a crash mid-write replays the last batch rather than losing it (at-least-once). This crate is the storage layer behind that guarantee; you wire it in once and the pipeline handles the rest.
Who depends on this
You only depend on faucet-state-redis directly if you are building a pipeline in Rust and want Redis-backed resume state. If you drive faucet-stream through the faucet CLI, you don't add this crate by hand — it's compiled in via the state-redis feature and selected with a state: block in your YAML config (see below).
Installation
# Library — add alongside faucet-core:
# CLI — enable the Redis state backend (opt-in feature):
The state-redis feature is not in the CLI's default build — enable it explicitly (or use --features full).
Configuration (CLI state: block)
In a faucet YAML/JSON config, select this backend with type: redis:
version: 1
pipeline:
source:
type: postgres-cdc
config:
# ... your CDC source ...
sink:
type: postgres
config:
# ... your sink ...
state:
type: redis
config:
url: redis://127.0.0.1:6379
namespace: faucet
| Field | Type | Default | Description |
|---|---|---|---|
url |
string | — (required) | Standard Redis URL: redis://[:password@]host:port[/db]. Use rediss:// for TLS. |
namespace |
string | faucet |
Prefix applied to every key ({namespace}:{key}). Use it to isolate pipelines that share one Redis instance — e.g. prod, staging, team-a. Must be non-empty and contain only ASCII alphanumerics plus _ - . (no :). |
Point url at a secret directive to keep credentials out of the file, e.g. url: ${env:REDIS_URL} or url: ${secret:redis-url}.
Library usage
use Arc;
use ;
use RedisStateStore;
# async #
To share one Redis connection across several stores (e.g. one per pipeline, each with its own namespace), open the connection yourself and use from_connection:
use RedisStateStore;
# async
How it works
Storage layout
Each bookmark is a single Redis string keyed by namespace plus state key:
| Redis key | Value |
|---|---|
{namespace}:{state_key} |
UTF-8 JSON serialization of the bookmark Value. |
The namespace is fixed once in connect() / from_connection() and prefixed onto every key. The state_key is supplied by the pipeline (e.g. postgres-cdc:<slot> for a CDC source, or the CLI's {name}::{row_id} composite for matrix runs), so multiple sources sharing one namespace never collide.
Operations
The three StateStore methods map directly onto Redis commands on a namespaced key:
get(key)→GET {namespace}:{key}, then parse the JSON; a missing key returnsNone.put(key, value)→SET {namespace}:{key} <serialized JSON>.delete(key)→DEL {namespace}:{key}; deleting a missing key is not an error.
Connections use redis::aio::MultiplexedConnection, which is cheaply cloneable and safe to share across concurrent tasks — the store clones it per call, so no locking or pooling is needed on the caller's side. The connection is opened once in connect() and reused for the lifetime of the store.
Validation & preflight
- State keys are validated by [
faucet_core::state::validate_state_key] — ASCII alphanumerics plus_ - : ., max 256 characters, no leading dot. - Namespaces are validated at construction: non-empty, and the same character set minus
:(since the namespace itself becomes a key prefix). An invalid namespace or URL fails fast asFaucetError::Config. faucet doctorexercises a real put → get → delete round-trip on a sentinel key, so the preflight validates connectivity, auth, and read/write permissions through the actual code path and leaves no residue.
Feature flags
This crate has no optional features of its own. Enable it in the CLI or the faucet-stream umbrella crate via the state-redis feature.
Troubleshooting / FAQ
| Symptom | Likely cause & fix |
|---|---|
Config: invalid Redis URL |
The url isn't a valid Redis URL. Use the redis://[:password@]host:port[/db] form (rediss:// for TLS). |
State: Redis connection failed |
The server is unreachable, the port is wrong, or auth was rejected. Check the host/port, that Redis is running, and that any password in the URL is correct. |
Config: ... namespace contains illegal character / must not be empty |
The namespace is empty or contains a disallowed character (only [A-Za-z0-9_.-] allowed — no :, /, or spaces). |
| Pipeline always restarts from scratch | The state: block is missing, set to type: memory, or a different namespace/source state_key is used each run. Resume needs a stable, durable backend and a stable key. |
State: stored value ... is not valid JSON |
The namespaced key holds a non-JSON string written by something other than faucet. Use a dedicated namespace (or db) for faucet state so it doesn't collide with other Redis users. |
| Multiple pipelines clobber each other's bookmarks | Two pipelines share both a namespace and a source state_key. Give each pipeline its own namespace. |
See also
- State & resume cookbook — bookmarks, resume semantics, and choosing a backend.
faucet-core— theStateStoretrait this crate implements, plus the built-inmemoryandfilebackends.faucet-state-postgres— the PostgreSQL-backed sibling backend.- faucet-stream on GitHub — the full ecosystem.
License
Licensed under either of Apache License, Version 2.0 or MIT license at your option.