Queryable State
Expose per-key state to ad-hoc SQL queries without running the full pipeline.
Sometimes you want to ask "what's the current fraud score for user 42?" without standing up a full streaming query. Queryable State exposes a per-key state slot as a table that can be queried with SQL.
How it works
You mark a state descriptor as queryable. Behind the scenes:
- A background thread in the executor keeps a hot in-memory copy of the state values.
- The state is exposed as a virtual table named after the descriptor:
SELECT * FROM queryable_state('fraud_score')(Rust API) or via the SQL Gateway. - Reads are eventually consistent — they reflect the value as of the most recent completed checkpoint, with a small lag (typically < 1 s).
API
use krishiv_state::QueryableStateStore;
let store = QueryableStateStore::new("fraud_score", state_descriptor);
let value: Option<f64> = store.get(&key_bytes).await?;
let all: Vec<(Vec<u8>, f64)> = store.scan_prefix(&prefix).await?;
HTTP API (via the coordinator):
# List queryable state tables
curl http://coord:2002/api/v1/queryable
# Get one key
curl http://coord:2002/api/v1/queryable/fraud_score/key/0x2A
# Scan a prefix
curl 'http://coord:2002/api/v1/queryable/fraud_score/scan?prefix=0x00&limit=100'
Python:
from krishiv import session
score = session.queryable_get("fraud_score", key=42)
Consistency
| Profile | Read freshness |
|---|---|
dev-local | Read-after-write in the same task. |
single-node-durable | Reads reflect the last completed checkpoint (typically < 1 s lag). |
distributed-durable | Reads reflect the last committed snapshot on the executor that owns the key. Cross-executor reads may be a checkpoint behind. |
Performance and limits
- Each queryable descriptor costs one thread per executor. Don't enable more than ~20 per executor.
- Stored values are kept in a bounded LRU per descriptor. Configure with
QueryableStateStore::with_capacity(n). - Read latency: p50 < 1 ms, p99 < 10 ms for values < 1 KB.