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. CombineablePromise combinators: Log the order in which CombineablePromises are resolved/rejected, 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:


const result = await ctx.run<string>(async () => doDbRequest());

Result value serialization

You can store any result value that can be serialized as a Buffer with Buffer.from(JSON.stringify(yourObject)) and deserialized with JSON.parse(result.toString()) as T.

Immediately await journaled actions

Always immediately await ctx.run, before doing any other context calls. If not, you might bump into non-determinism errors during replay, because the journaled result can get interleaved with the other context calls in the journal in a non-deterministic way.

info

You cannot invoke any methods on the Restate context within a side effect. This includes actions such as getting state, calling another service, and nesting other journaled actions.

CombineablePromise combinators

Operations such as calls, awakeables, and sleep return a CombineablePromise. The SDK provides combinators for working with CombineablePromise. Restate then logs the order in which they are resolved or rejected, to make them deterministic on replay.

Imagine the following promises:


const sleepPromise = ctx.sleep(100);
const callPromise = ctx.serviceClient(MyService).myHandler("Hi");

Await all


const resultArray = await CombineablePromise.all([sleepPromise, callPromise]);

Creates a promise that is either:

  • Resolved with an array of results, once all input promises are resolved.
  • Rejected when any input promise is rejected.

Similar to Promise.all(), but the outcome is stored in the Restate journal to be deterministically replayable.

Await any


const anyResult = await CombineablePromise.any([sleepPromise, callPromise]);

Creates a promise that is:

  • Resolved with the result of the first successfully resolved input promise.
  • Rejected when all the input promises are rejected (including when an empty iterable is passed). This results in an AggregateError containing an array of the reasons for rejection.

Similar to Promise.any(), but the outcome is stored in the Restate journal to be deterministically replayable.

Race


const raceResult = await CombineablePromise.race([sleepPromise, callPromise]);

Creates a Promise that is:

  • Resolved when any of the input promises are resolved.
  • Rejected when any of the input promises are rejected.

Similar to Promise.race(), but the outcome is stored in the Restate journal to be deterministically replayable.

Await all settled


const allSettledResult = await CombineablePromise.allSettled([sleepPromise, callPromise]);

Creates a promise that resolves once all the input promises have settled (including when an empty iterable is passed). It returns an array of objects describing the outcome of each promise, whether resolved or rejected.

Similar to Promise.allSettled(), but the outcome is stored in the Restate journal to be deterministically replayable.

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


const uuid = ctx.rand.uuidv4();

Do not use this in cryptographic contexts.

Generate stable idempotency tokens in one line of code.

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.

Generating random numbers


const randomNumber = ctx.rand.random();

This returns a new pseudorandom float within the range [0,1]. This is the equivalent of JS Math.random() but deterministically replayable.