-
-
Notifications
You must be signed in to change notification settings - Fork 96
Description
Motivation
The current KvStore interface provides basic key–value operations (get, set, delete) and an optional cas() for compare-and-swap. However, there is no way to enumerate keys matching a certain prefix, which limits the kinds of data structures that can be efficiently built on top of KvStore.
A concrete example is the distributed trace storage proposed in #497. To store trace data efficiently, we want to write each activity record under its own key (e.g., ["traces", traceId, spanId]) and later retrieve all records for a given trace by scanning keys with prefix ["traces", traceId]. Without a list() operation, the only alternative is to store all records for a trace as a single list value and use cas() for concurrent appends—this works but becomes inefficient as the list grows and may suffer from contention under high write loads.
Beyond trace storage, a list() operation would enable other use cases such as:
- Enumerating all cached public keys for a given actor
- Listing all pending outbox messages for debugging
- Implementing cache invalidation by prefix
Most key–value store backends already support prefix scanning natively, so exposing this capability through the KvStore interface is straightforward.
Proposed interface
The list() method should be added as an optional method in Fedify 1.10.0 to maintain backward compatibility with existing KvStore implementations:
interface KvStore {
get<T = unknown>(key: KvKey): Promise<T | undefined>;
set(key: KvKey, value: unknown, options?: KvStoreSetOptions): Promise<void>;
delete(key: KvKey): Promise<void>;
// Optional (since 1.8.0)
cas?: (
key: KvKey,
expectedValue: unknown,
newValue: unknown,
options?: KvStoreSetOptions,
) => Promise<boolean>;
// Optional (since 1.10.0)
list?: (
options: KvStoreListOptions,
) => AsyncIterable<KvStoreListEntry>;
}
interface KvStoreListOptions {
prefix: KvKey;
}
interface KvStoreListEntry {
key: KvKey;
value: unknown;
}The method returns an AsyncIterable to support backends that may need to paginate results internally. Callers can use for await...of to iterate through entries:
for await (const entry of kv.list({ prefix: ["traces", traceId] })) {
console.log(entry.key, entry.value);
}Implementation for existing backends
Each official KvStore implementation should add support for list(). Most backends already provide native prefix scanning capabilities that can be directly leveraged:
MemoryKvStore: Filter in-memory keys by prefixRedisKvStore: UseSCANwith a patternPostgresKvStoreandSqliteKvStore: UseLIKEqueriesDenoKvStore: Delegate to Deno KV's built-inlist()method
The implementation should be straightforward for all backends since prefix scanning is a common operation supported by most storage systems.
Migration path
In Fedify 1.10.0, list() will be optional. Code that depends on list() should check for its presence and fall back to alternative approaches (such as cas()-based list management) when it is not available.
In Fedify 2.0.0, list() will become a required method on the KvStore interface. This gives KvStore implementers approximately one major version cycle to add support.
Related issues
This is a prerequisite for #497 (distributed trace storage for the debug dashboard).