Restate 1.3: Concurrency without losing sleep

Posted April 10, 2025 by Restate Team ‐ 6 min read

Restate lets you write mission-critical applications, like payment workflows, user management, and AI agents, without worrying about resiliency. With Restate 1.3, you can now implement even complex, concurrent applications, and let Restate make them easy to implement and failure-proof.

Let’s have a look at the plethora of new SDK features that Restate 1.3 makes available to you!

The new append-only journal

A Restate application exists of a Restate Server and a Restate SDK embedded in your code. The Restate SDK records every operation you do (run , call , sleep, etc) in a data structure we call journal. On a failure, the Restate SDK replays the journal and skips operations that had already succeeded in previous attempts.

In previous Restate releases, some of these journal entries could get enriched/completed later on. For example, a call entry to another service would later on get enriched with its response. This means that journal entries were mutable. With a mutable journal, it’s hard to reason about the order in which entries took place, and especially the order in which completions took place.

This release changes this behavior and now uses an append-only journal, in which the operations (a.k.a commands) and their completions are separate journal entries. This establishes a total order between commands and completions.

With the append-only journal, we can now:

  • Reason about the order in which events were received
  • Use the journal as an audit trail of events for compliance reasons.
  • Develop programs featuring complex concurrent operations and events.

Let’s now look at the SDK features that were added in this release.

✅Deterministic, ✅ Resilient, ✅ Concurrent

In Restate, we always say that code needs to be deterministic, and guess what is enemy number one of determinism? Concurrency!

There are many problems that intrinsically have some form of concurrency, and Restate wants to empower developers to implement those use cases, while retaining all the resilience Restate provides.

Let’s take the following example: we want to implement an email verification flow with remainders. This requires to wait for three different concurrent events: the user to click on the link, the verification timeout, and the reminder timer.

id, confirmation_future = ctx.awakeable()
await ctx.run("send email", send_verification_email, args=(email, id))

# user has 1 day to click on the link
verification_timeout = ctx.sleep(timedelta(days=1))

while True: 
    match await restate.select(
            confirmation=confirmation_future, 
            reminder_timer=ctx.sleep(timedelta(minutes=30)),
            timeout=verification_timeout):
        
        case ['confirmation', 'ok']:
            return "success!"
        
        case ['reminder_timer', _ ]:
            await ctx.run("send reminder", send_reminder_email)
            continue
    
        case ['timeout', _]:
                raise TerminalError("Verification timer expired!")

Pause for a minute, and try to imagine how you would implement that without Restate, while retaining the same resilience!

There’s even more, it is now possible to concurrently execute several ctx.run actions, and asynchronously wait for all of them to complete, easily implementing fan-out/fan-in scenarios:

// Fan-out
const promises = tasks
    .map(task =>
        ctx.run(() => process(task))
    );
    
// Wait for all the requests to complete
const results = await CombineablePromise.all(promises)

You are now able to express such non-trivial, concurrent business logic so elegantly, while being fully resilient against failures.

Learn more:

Fine-grained control over service-to-service communication

In your Restate application, sometimes you need to build an orchestrator for other services, such as:

  • Control planes
  • PubSub
  • Task managers / Supervisors

To do that, you need to have finer control over service-to-service communication. We packaged Restate 1.3 with a set of new features exactly for that!

Let’s say you want to call several LLMs, and after the first one completes, just cancel the uncompleted ones:

claude = ctx.service_call(claude_sonnet, "please greet Bob")
openai = ctx.service_call(open_ai, "please greet Bob")

pending, done = await wait_completed(claude, openai)

greetings = [await f for f in done]

for f in pending:
    # $$$ Money saved
    await f.cancel_invocation()

Sometimes it is useful to launch a task, without awaiting immediately for its result. You can now use attach to re-connect to running invocations later on, for example:

@Handler
suspend fun start(ctx: Context): MyTaskHandle {
    val taskId = MyTaskWorkerClient.fromContext(ctx)
        .send()
        .runTask(MyTask("..."))
        .invocationId()
    return MyTaskHandle(taskId)
}

@Handler
suspend fun getResult(ctx: Context, handle: MyTaskHandle) =
    ctx.invocationHandle<String>(handle.id)
        .attach()
        .await()

It is now also possible to provide an idempotency key when sending requests between services. This can be particularly useful when ingesting webhooks where the idempotency key is stored in the request body. For example:

async function onStripeEvent(ctx, payload) {
	ctx
      .serviceClient(payment)
      .pay(payload, restate.rpc.opts({
        idempotencyKey: payload.eventKey
      }));
}

Learn more:

Improved service catalog

In Restate 1.2, we rolled out a new feature that generates an OpenAPI specification based on your service definition. This feature is used by the UI playground as well, to let you easily send requests to your services.

With the new SDKs, we now support generating JSON schemas for OpenAPI for Golang, TypeScript (via zod), Java, Kotlin and Python.

import * as restate from "@restatedev/restate-sdk";
import { serde } from "@restatedev/restate-sdk-zod";
import { z } from "zod";

const GreetingRequest = z.object({
  firstName: z.string().describe("First name"),
  lastName: z.string().describe("Last name")
});

const GreetingResponse = z.object({
  greeting: z.string().describe("Greeting, containing first and last name")
});

const greeter = restate.service({
  name: "greeter",
  handlers: {
    greet: restate.handlers.handler(
      {
        input: serde.zod(GreetingRequest),
        output: serde.zod(GreetingResponse),
      },
      async (ctx, request) => { /* Implementation */ }
    ),
  },
});

This is how the above example would look in the Restate playground:

image.png

In addition to this, you can now attach additional metadata to your service/handlers:

const greeter = service({
  name: "greeter",
  handlers: { /* Handlers */ },
  metadata: {
    // That's a very important service!
    "ownership": "CTO's Office"
  }
});

You can use this to build custom tools on top of Restate’s Admin API to introspect the registered services, for example when building MCP tools.

Ready to run Restate in the cloud

Many cloud deployment services use network attached disks for storage. These disks have different access characteristics in terms of IOPs, latency and throughput compared to local NVMes. To achieve the best possible performance also in these environments, Restate has been optimized to reduce the number of IOPs by batching and writing larger blocks while reducing the overall write sizes by more aggressive compression. Moreover, the underlying storage engine was tuned to leverage pipelining for more efficient writes and parallelism for faster compactions. With these improvements, we could significantly improve throughput and tail latencies in our benchmarks.

The easiest way to try these improvements out yourself is to deploy a distributed Restate cluster using our updated helm charts.

Start building with Restate

Try out all the new features by following the quickstart guide.

Are you wondering if Restate is the right fit for your project? Let’s brainstorm together on Discord or Slack.

Restate is open, free, and available at GitHub or at the Restate website. Star the GitHub project if you like what we are doing.

Restate is also available as a fully managed service running in our cloud or yours. Try Restate Cloud for free and contact our team for more information.

Changelogs

To use these SDKs and take advantage of their new features, you need to run at least Restate 1.3. Have a look at the documentation for more details about versioning and upgrades.

For more details on the individual SDK changes, check the respective changelogs: