Skip to main content

Journaling Results

Restate uses an execution log for replay after failures and suspensions. This means that non-deterministic results (e.g. database responses, UUID generation) need to be stored in the execution log. The SDK offers some functionalities to help you with this:

  1. Journaled actions: Run any block of code and store the result in Restate. Restate replays the result instead of re-executing the block on retries.
  2. Selectors: Log the order in which asynchronous actions are completed, to ensure deterministic replay.
  3. Random generators: Built-in helpers for generating stable UUIDs and random numbers.

Journaled actions

You can store the result of a (non-deterministic) operation in the Restate execution log (e.g. database requests, HTTP calls, etc). Restate replays the result instead of re-executing the operation on retries.

Here is an example of a database request for which the string response is stored in Restate:


result, err := restate.Run(ctx, func(ctx restate.RunContext) (string, error) {
return doDbRequest()
})
if err != nil {
return err
}

You cannot use the Restate context within a side effect. This includes actions such as getting state, calling another service, and nesting other journaled actions. You should only use methods available on the RunContext provided to your function.

You can return any payload that can be serialized. By default, serialization is done with JSONCodec which uses encoding/json. If you don't need to return anything, you can use restate.Void{} which serialises to a nil byte slice.

Selectors

Operations such as calls, awakeables, and restate.After return futures which can be safely selected over using restate.Select. Restate will log the order in which they complete, to make this deterministic on replay.


sleepFuture := restate.After(ctx, 100*time.Millisecond)
callFuture := restate.Service[string](ctx, "MyService", "MyHandler").RequestFuture("hi")
selector := restate.Select(ctx, sleepFuture, callFuture)
switch selector.Select() {
case sleepFuture:
if err := sleepFuture.Done(); err != nil {
return "", err
}
return "sleep won", nil
case callFuture:
result, err := callFuture.Response()
if err != nil {
return "", err
}
return fmt.Sprintf("call won with result: %s", result), nil
}

Don't use Goroutines!

Do not try to combine blocking Restate operations using goroutines, channels or select statements. These cannot be used deterministically, and will likely lead to non-determinism errors upon replay. The only place it is safe to use these types is inside of a restate.Run function.

Generating randoms

The SDK provides helper functions for the deterministic generation of UUIDs and random numbers. Restate seeds the random number generator with the invocation ID, so it always returns the same value on retries.

Generating UUIDs

You can use these UUIDs to generate stable idempotency keys, to deduplicate operations. For example, you can use this to let a payment service avoid duplicate payments during retries.

Do not use this in cryptographic contexts.


uuid := restate.Rand(ctx).UUID()

Generating UUIDs

You can use these UUIDs to generate stable idempotency keys, to deduplicate operations. For example, you can use this to let a payment service avoid duplicate payments during retries.

Do not use this in cryptographic contexts.


uuid := restate.Rand(ctx).UUID()

Generating random numbers

Methods exist on restate.Rand(ctx) for generating float64 and uint64, or otherwise restate.Rand(ctx).Source() can be provided to math/rand/v2 as a source for any random operation.


randomInt := restate.Rand(ctx).Uint64()
randomFloat := restate.Rand(ctx).Float64()
randomSource := rand.New(restate.Rand(ctx).Source())

Generating random numbers

Methods exist on restate.Rand(ctx) for generating float64 and uint64, or otherwise restate.Rand(ctx).Source() can be provided to math/rand/v2 as a source for any random operation.


randomInt := restate.Rand(ctx).Uint64()
randomFloat := restate.Rand(ctx).Float64()
randomSource := rand.New(restate.Rand(ctx).Source())