State
Actor state provides the best of both worlds: it's stored in-memory and persisted automatically. This lets you work with the data without added latency while still being able to survive crashes & upgrades.
Actor state is isolated to itself and cannot be accessed from other actors or clients. All reads & writes to state are done via RPC.
There are two ways of storing actor state:
- Native state is the most common persistence mechanism. State is a native JavaScript object stored in memory.
- Key-Value (KV) state allows you deal with larger datasets than cannot fit in memory.
Note
While Rivet Actors can serve as a complete database solution, they can also complement your existing databases effectively. For example, you might use Rivet Actors to handle frequently-changing data that needs real-time access, while keeping less frequently accessed data in your traditional database.
Native state
State is a native JavaScript object stored in-memory on this.state
. This makes building realtime & stateful applications as simple as updating native JavaScript objects.
State type
Actor states can be typed in TypeScript using the first generic argument on Actor<State>
. For example:
Initializing state
Each requires an initializeState
method. This is only called once when the actor is created.
For example:
Updating state
State can be updated using this.state
. State will automatically be persisted.
For example:
Notice
Only state stored on the this.state
property will be persisted. Properties of the Counter
class are kept
in-memory and not persisted.
State saves
Rivet automatically handles persisting state transparently to recover from a crash or upgrade. This happens at the end of every remote procedure call if the state has changed.
In the rare occasion you need to force a state change mid-RPC, you can use _saveState
. This should only be used if your remote procedure call makes an important state change that needs to be persisted before the RPC exits in case of a crash.
Valid data types
Only JSON-serializable types can be stored in state. State is persisted under the hood in a compact, binary format. This is because JavaScript classes cannot be serialized & deserialized.
Limitations
State is constrained to the available memory (see limitations). For larger datasets, use KV.
Key-Value (KV)
The KV state is used for storing large datasets that cannot fit in to memory.
Native & KV state can be used together side-by-side without issue..
Info
KV is specific to each actor and is not global. To fetch data from other actors, use RPC.
If you need a shared state, you have two options:
- Create an actor that holds the shared state that actors can make RPCs to
- Use an external database, such as PostgreSQL
Performance
KV has the same performance as using native state, but with a more flexible API & unlimited storage.
KV stores native JavaScript values in a compact binary format so you don't need to write extra serialization & deserialization code.
Operations
Raw KV operations can be called via this.#ctx.kv.<op>
.
get
get(key: any, options?: GetOptions): Promise<any | null>
Retrieves a value from the key-value store.
Options:
getBatch
getBatch(keys: any[], options?: GetBatchOptions): Promise<Map<any, any>>
Retrieves a batch of key-value pairs.
Options:
list
list(options?: ListOptions): Promise<Map<any, any>>
Retrieves all key-value pairs in the KV store. When using any of the options, the keys lexicographic order is used for filtering.
Options:
put
put(key: any, value: any | ArrayBuffer, options?: PutOptions): Promise<void>
Stores a key-value pair in the key-value store.
Options:
putBatch
putBatch(obj: Map<any, any | ArrayBuffer>, options?: PutBatchOptions): Promise<void>
Stores a batch of key-value pairs.
Options:
delete
delete(key: any): Promise<void>
Deletes a key-value pair from the key-value store.
deleteBatch
deleteBatch(keys: any[]): Promise<void>
Deletes a batch of key-value pairs from the key-value store.
deleteAll
deleteAll(): Promise<void>
Deletes all data from the key-value store. This CANNOT be undone.
Keys
Keys used for KV storage can be any JavaScript type that can be cloned via the structured clone algorithm:
Structured Keys
Structured keys provide security and ease of use for applications with layered storage criteria such as lists within lists or deeply nested hashmaps.
In general, it is more efficient to your data structure with small chunks in individual keys instead of all in one key. This is where structured keys come in:
It is strongly advised to always use structured keys instead of manually implementing them yourself to reduce possible attack vectors from end-users:
The difference here is that with a manual approach it is possible to retrieve data that was otherwise not public via injection:
Note that single-value keys are automatically converted into single item lists for consistency:
Sorting Keys
Keys are automatically sorted in lexicographic order.
This means when using the list
command, you can fetch all values between two keys in order:
You can also use this to list all values under a common prefix key:
Sorted keys also enable you to create ordered lists, like this:
Values
Values stored in the KV can be any JavaScript type which can be cloned via the structured clone algorithm.
To store raw binary data, it is recommended to set the format
option in your KV operation to arrayBuffer
and pass in an ArrayBuffer
object. Alternatively, you can put
an ArrayBuffer
or Blob
directly without
changing the format but this has additional space overhead from the JS type system.
Limitations
See limitations.