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. DurableFuture combinators: Log the order in which DurableFutures were 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:

String output = ctx.run(String.class, () -> doDbRequest());

Have a look at the serialization docs to learn how to serialize and deserialize your objects.

You cannot use the Restate context within a ctx.run function. This includes actions such as getting state, calling another service, and nesting other journaled actions.

Failure semantics

Failures in ctx.run have the same semantics as failures in the rest of your handler code. By default, the ctx.run function is retried infinitely on failure, unless you specify differently in the retry policy (see below) or throw a TerminalException.

Run-block Retry Policies

You can configure the retry policy for the ctx.run block:

try {
RetryPolicy myRunRetryPolicy =
RetryPolicy.defaultPolicy()
.setInitialDelay(Duration.ofMillis(500))
.setExponentiationFactor(2)
.setMaxDelay(Duration.ofSeconds(10))
.setMaxAttempts(10)
.setMaxDuration(Duration.ofMinutes(5));
ctx.run("my-run", myRunRetryPolicy, () -> writeToOtherSystem());
} catch (TerminalException e) {
// Handle the terminal error after retries exhausted
// For example, undo previous actions (see sagas guide) and
// propagate the error back to the caller
}

This way you can override the default retry behavior of your Restate service for specific operations. Have a look at the RetryPolicy JavaDoc and KotlinDoc for more information.

If you set a maximum number of attempts, then the ctx.run block will fail with a TerminalException once the retries are exhausted. Have a look at the error handling docs and Sagas guide to learn how to handle these.

Durable Future combinators

Operations such as ctx.run, calls, awakeables, and sleep return a DurableFuture. The SDK provides APIs to join DurableFutures, or wait for the first of them to complete. Restate then logs the order in which they are resolved or rejected, to make them deterministic on replay.

Await all creates a DurableFuture that awaits for all the provided DurableFutures to resolve. The semantics are similar to CompleteableFuture.allOf(), but the outcome is stored in the Restate journal to be deterministically replayable.

DurableFuture.all(a1, a2, a3).await();

Select creates a DurableFuture that returns on the first completed DurableFuture of the provided ones. The semantics are similar to CompleteableFuture.anyOf(), but the outcome is stored in the Restate journal to be deterministically replayable.

boolean res = Select.<Boolean>select().or(a1).or(a2).or(a3).await();

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 uuid = ctx.random().nextUUID();

Generating random numbers

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

You can use any of the methods of java.util.Random to generate random numbers: for example, nextBoolean(), nextLong(), nextFloat(), etc.

int value = ctx.random().nextInt();