Daniel Vigovszky
Golem is a serverless durable execution platform
Golem is a serverless durable execution platform
Server-side WebAssembly applications
Server-side WebAssembly applications
Why WASM?
Examples
What can we contribute?
Golem is a serverless durable execution platform
Before Golem
Before Golem
zio-flow
JVM based
Scala DSL
Durable execution
Sandbox
Performance
Language support
Sandbox
Performance
Language support
Sandbox
Performance
Language support
Growing support
No lock-in
Sandbox
Performance
Language support
Safety
Transparent durability
oplog (journal)
automatic snapshots
custom snapshots
Full control of outside world
Deterministic
Restore app state by recording everything non-deterministic
Example
/// Updates the component's state by adding a random value to the total.
fn add() {
STATE.with_borrow_mut(|state| {
let value = rand::rng().next_u64();
state.total += value;
println!("The new total is {}", state.total);
});
}
/// Returns the current total.
fn get() -> u64 {
STATE.with_borrow(|state| state.total)
}(import "wasi_snapshot_preview1" "random_get" (func ...))
(import "wasi_snapshot_preview1" "fd_write" (func ...))
(import "wasi_snapshot_preview1" "environ_get" (func ...))
(import "wasi_snapshot_preview1" "environ_sizes_get" (func ..))
(import "wasi_snapshot_preview1" "proc_exit" (func ..))
Simplified oplog:
- exported function called: 'add'
- imported function 'random_get' returned 1234
- exported function called: 'add'
- imported function 'random_get' returned 4567
- exported function called: 'get'
Contains much more information in reality
To recover the application state, we just replay this log
Simplified oplog:
- exported function called: 'add'
- imported function 'random_get' returned 1234
- exported function called: 'add'
- imported function 'random_get' returned 4567
- exported function called: 'get'
Replay loop: 'add', 'add', 'get'
Host function implementation returning the stored response
async fn get_random_u64(&mut self) -> anyhow::Result<u64> {
let durability = Durability::<u64, SerializableError>::new(
self,
"golem random",
"get_random_u64",
DurableFunctionType::ReadLocal,
)
.await?;
if durability.is_live() {
let result = Host::get_random_u64(&mut self.as_wasi_view()).await;
durability.persist(self, (), result).await
} else {
durability.replay(self).await
}
}Cannot recover to a specific arbitrary instruction
But this does not matter
Not good for CPU intensive applications
Good enough for many use cases
Full snapshot of the program
Linear memories
Tables
File system
Resource states
Prototype
On the roadmap
Custom snapshotting
interface save-snapshot {
save: func() -> list<u8>;
}
interface load-snapshot {
load: func(bytes: list<u8>) -> result<_, string>;
}
Custom snapshotting
Compact
Allows arbitrary updates
Requires contribution from the user
Updates
Soon:
recovery optimization
Restoring a running program is a core feature
Not always needed
Durable workers
Ephemeral workers
Stateless, fast, short lived, single call
Long running
Multiple invocations
Stateful
Idle workers can be dropped from memory
Between invocations
When waiting
Special trap
Triggered by wasi:io/poll
if let Some(Some(deadline)) = all_supports_suspend {
let duration = deadline.duration_since(Instant::now());
if duration >= self.ctx().suspend_threshold {
return Err((self.ctx().suspend_signal)(duration));
}
}
Suspend signal
Drop
Schedule recovery
Golem is built on the Component Model
Impl in wasmtime starts ~ May, 2022
Golem starts ~ Apr, 2023
(using wasmtime 8.0.0)
Enables many features
Trying to not reinvent the wheel
Golem is built on the Component Model
Golem workers = WASM component instances
Expose a typed API from our workers
Multiple invocations
Multiple exposed functions
Golem invocation API
WASM Component exports
=
Golem invocation API
WASM Component exports
=
package wasmio:example1;
interface api {
add: func(value: u64);
get: func() -> u64;
}
world wasmio-example1 {
export api;
}
Component name: wasmio:example1
Component version: 0
Component size: 1.93 MiB
Created at: 2025-03-04 19:17:52.625270002 UTC
Exports:
wasmio:example1-exports/api.{add}(value: u64)
wasmio:example1-exports/api.{get}() -> u64
Component name: wasmio:example1
Component version: 0
Component size: 1.93 MiB
Created at: 2025-03-04 19:17:52.625270002 UTC
Exports:
wasmio:example1-exports/api.{add}(value: u64)
wasmio:example1-exports/api.{get}() -> u64
How do we extract this?
golem-wasm-ast crate
wit-parser
planned migration
Golem runs multiple components
Exposes their exports
Components can be composed
Worker to Worker Invocations
Component
Composition
=
Worker to Worker Invocations
Component
Composition
=
Same behavior
Architectural decision
golem-wasm-rpc crate
wRPC
first version ~February 2024
planning to migrate
Let's see how Golem's WASM RPC works!
wasm-rpc host interface
stub generator
wasm-rpc host interface
stub generator
resource wasm-rpc {
constructor(target: worker-id);
ephemeral: static func(target: component-id) -> wasm-rpc;
invoke-and-await: func(
function-name: string,
function-params: list<wit-value>) -> result<wit-value, rpc-error>;
// .. async version
// .. scheduled version
}
wasm-rpc host interface
stub generator
record wit-value {
nodes: list<wit-node>,
}
variant wit-node {
record-value(list<node-index>),
variant-value(tuple<u32, option<node-index>>),
enum-value(u32),
flags-value(list<bool>),
tuple-value(list<node-index>),
list-value(list<node-index>),
option-value(option<node-index>),
result-value(result<option<node-index>, option<node-index>>),
prim-u8(u8),
// ...
handle(tuple<uri, u64>)
}wasm-rpc host interface
stub generator
Implemented by Golem
Any Golem component can import it
Invoke another workers
Hard to use (constructing wit-value)
Not type safe
wasm-rpc host interface
stub generator
worker A
worker B
worker A
worker B
package example:counter;
interface api {
inc-by: func(value: u64);
get: func() -> u64;
}
world counter {
export api;
}
package example:caller;
world caller {
export test: func() -> u64;
}
worker A
worker B
package example:counter;
interface api {
inc-by: func(value: u64);
get: func() -> u64;
}
world counter {
export api;
}
package example:caller;
world caller {
import example:counter/api;
export test: func() -> u64;
}
Regular function call
Let Golem solve it
Which worker?
worker A
worker B
package example:counter;
interface api {
inc-by: func(value: u64);
get: func() -> u64;
}
world counter {
export api;
}
package example:caller;
world caller {
import example:counters/api;
export test: func() -> u64;
}
Which worker?
package ex:counter-client;
interface counter-client {
resource counter {
constructor(t: worker-id);
inc-by: func(value: u64);
get: func() -> u64;
}
}
world counter-client {
export api;
}
Generated stub WIT
Generate implementation
Rust
Compile to WASM
Compose with caller
Mark the stub interface names in metadata
Dynamic linking
DEPRECATED
Inspired by wRPC
Instantiating the component
Missing imports
Check if they are marked as RPC stubs
Link with host function directly calling the RPC implementation
Generic idea
Generic idea
OpenAPI client
gRPC client
Smithy client
GraphQL client
Generic idea
Generate a client WIT from some spec
Generate a composable client component
or
Directly support via dynamic linking
Generic idea
Typed configurations
Generic idea
Typed configurations
Specify a set of configuration properties
Generate a WIT interface
Dynamic linking provides the actual configuration values
Invocations are processed
sequentially
limitation
feature
limitation
feature
user choice
Worker to Worker Invocations
Component
Composition
=
Sync/async components
Interleaving external invocations
Client 1
Client 2
websocket
Golem worker
API gateway
Client 1
Client 2
websocket
Golem worker
Connected client: remote resource
API gateway
RPC
Streaming interface
Connected client: remote resource
Streaming interface
REQUIREMENTS
Connected client: remote resource
Streaming interface
RPC with first-class resource support
WASI P3 streams
Interleaved invocations
REQUIREMENTS
Another use case for resources, composition and wRPC
How to constrain worker-to-worker communication?
Another use case for resources, composition and wRPC
Resources as permits
Derive more strict ones
Root permit associated with the worker
RPC calls require permit
Permits can be shared via RPC
Another use case for resources, composition and wRPC
Generalize this further
Another use case for resources, composition and wRPC
Make WASM RPC the primary protocol
to manipulate Golem itself
using the same permission model
Make WASM RPC the primary protocol
Golem services become "fake" WASM RPC instances
Side effect
Evolve non-Golem specific tooling / libraries
Composition exposed to users
Composition exposed to users
Composition exposed to users
oplog processor plugins
component transformer plugins
library plugins
Composition exposed to users
oplog processor plugins
component transformer plugins
library plugins
Dynamically re-compose your Golem app
Implement durability as a composable WASI wrapper
(instead of wrapping the host functions in the executor)
FAILED
poll
poll
User component
Golem executor
Durable WASI
poll
poll
poll
poll
User component
Golem executor
Durable WASI
poll
poll
package golem:wasi;
world durable-wasi {
// ...
import wasi:io/poll@0.2.0;
export wasi:io/poll@0.2.0;
// ...
// Golem durability API
import golem:durability/durability@1.2.0;
}poll
poll
User component
Golem executor
Durable WASI
poll
poll
The composed component uses the wrapped WASI implementations
poll
poll
User component
Golem executor
Durable WASI
poll
poll
RPC stub
Generated RPC client
RPC stub
RPC
RPC
poll
poll
User component
Golem executor
Durable WASI
poll
poll
RPC stub
Generated RPC client
RPC stub
RPC
RPC
resource future-invoke-result {
subscribe: func() -> pollable;
get: func() -> option<result<wit-value, rpc-error>>;
}poll
poll
User component
Golem executor
Durable WASI
poll
poll
RPC stub
Generated RPC client
RPC stub
RPC
RPC
RPC
RPC
FIXED!
poll
poll
User component
Golem executor
Durable WASI
poll
poll
RPC stub
RPC
RPC
RPC
FIXED?
Dynamic RPC linking
Can't create the wrapped pollable in the dynamic linked host function
Workaround?
Poll was just an example
FAILED
package golem:durability@1.2.0;
interface durability {
// ...
current-durable-execution-state: func() -> durable-execution-state;
persist-typed-durable-function-invocation: func(
function-name: string,
request: value-and-type,
response: value-and-type,
function-type: durable-function-type,
);
read-persisted-durable-function-invocation: func() ->
persisted-durable-function-invocation;
// ...
}
Golem should run "any" program
Golem should run "any" program
No custom host API
Golem should run "any" program
No custom host API
if there is a WASI interface for it
Interfaces provided by Golem
Everything wasmtime-wasi has
wasi:cli, wasi:clocks, wasi:filesystem, wasi:http, wasi:io, wasi:random, wasi:sockets
Additional storage interfaces
wasi:blobstore, wasi:keyvalue
Logging
wasi:logging
Relational database access
golem:rdbms
Golem specificÂ
golem:api, golem:durability, golem:rpc
Scripting language to work with Golem components
Scripting language to work with Golem components
Primarily for describing APIs
- method: Post
path: "/{user-id}/add-item"
binding:
response: |
let quantity: f64 = request.body.quantity;
let product_id: u64 = request.body.product-id;
let name: string = request.body.name;
let price: f64 = request.body.price;
let input = {product-id: product_id, name: name, price: price, quantity: quantity};
let worker = instance("shopping-cart-${request.path.user_id}");
worker.add-item(input);
{status: 200u64, body: success}API gateway
HTTP request/response
Worker invocation
let quantity: f64 = request.body.quantity;
let product_id: u64 = request.body.product-id;
let name: string = request.body.name;
let price: f64 = request.body.price;
let input = {product-id: product_id, name: name, price: price, quantity: quantity};
let worker = instance("shopping-cart-${request.path.user_id}");
worker.add-item(input);
{status: 200u64, body: success}
Calling WASM components
Access context
Statically typed with type inference
Types are component model types
Value syntax is WAVE
Pattern matching
List comprehensions and reduce
String interpolation
First-class worker and resource support
WIT package/interface inference
let res: result<str, str> = ok("foo");
match res {
ok(v) => v,
err(msg) => msg
}
let x = foo("abc");
match x {
bar({a: _, b: _}) => "success",
foo(some_list) => some_list[0]
} let x = ["foo", "bar"];
for p in x {
yield p;
}
let ages: list<u16> = [1, 2, 3];
reduce z, a in ages from 0 {
yield z + a;
}let worker = instance("my-worker");
let cart = worker.cart("bar");
cart.add-item({
product-id: a,
name: b,
quantity: c,
price: d}
);
cart.remove-item(a);
cart.update-item-quantity(a, 2);
let result = cart.get-cart-contents();let worker1 = instance();
let result1 = worker1.qux("bar");
let worker2 = instance();
let result2 =
worker2.qux[amazon:shopping-cart]("bar");
let worker3 =
instance[amazon:shopping-cart]();
let result3 = worker.qux("bar")
No need to only use for API mappings!
Testing
Rib REPL to interact with workers
Not Golem specific!
Scripting language for WASM components
Scripting language for wRPC
or
Rust crate implementing type checking / compilation / execution
Planned to be published completely separated from Golem
A yaml file
A yaml file
Describing one or more Golem components
A yaml file
Describing one or more Golem components
Relationships (for example RPC)
A yaml file
Describing one or more Golem components
Relationships (for example RPC)
Build and compose components
Fully customizable, and built-in templates for:
Build tool that understands...
WIT directories
Stub generation
Composing components
Example
golem app new demo rust
cd demo
golem component new rust demo:rust-component
golem component new ts demo:ts-component
golem component new moonbit demo:moonbit-componentAssembles wit/deps directories
cargo component build
componentize-js
moon build
wasm-tools component ...
Caching and up-to-date checks
components:
demo:rust-component:
template: rust
demo:ts-component:
template: ts
demo:moonbit-component:
template: moonbit
dependencies:
demo:rust-component: []
demo:ts-component:
- type: static-wasm-rpc
target: demo:rust-component
demo:moonbit-component:
- type: wasm-rpc
target: demo:ts-component
Generate stub WITs
Auto-import stubs into components
Generates and build static stubs
Compose components when needed
Golem specific at the moment..
but it does not have to be!
Could be a glue for guest-language tooling
We want to build on standard interfaces
but sometimes we cannot wait
WIT package for working with PostgreSQL and MySQL
Not Golem specific
Implemented in Golem, ready to try out
Would be happy to cooperate to make it one of the standard interfaces
Golem itself is open-source
Golem itself is open-source
Some core libraries can be useful for non-Golem use cases
Golem itself is open-source
Some core libraries can be useful for non-Golem use cases
golem-wasm-astgolem-wasm-rpcgolem-rib+ app manifest shared library?
higher level component model AST
various component model "Value" encodings
the scripting language
JS/TS support is very important for Golem
JS/TS support is very important for Golem
Testing heavily the ecosystem - jco/componetnize-js/StarlingMonkey/...
Reporting issues
Pull requests with fixes
We love Scala
We love Scala
Scala.js WASM component model support is WIP
until then..
wit-bindgen-scalajs
+ sbt plugin
+ Golem app manifest template
Scala.js can be used to create WASM components right now!
Better support for WIT evolution
Code-first WIT tooling
Dynamic linking for JS/PY components (?)
Snapshotting support in wasmtime
wishlist
Better support for WIT evolution
wishlist
SemVer support in wasmtime
SemVer support in other places
Data evolution?
new variant case
new optional record fields
wishlist
Code-first WIT tooling
Spec (WIT) first
Alternative: code first
Native component model support
Language-specific libraries (macros, etc)
Goal: users do not have to learn WIT
wishlist
Dynamic linking for JS/PY components
WASM generated with componentize-js and componentize-py are huge
Native image caching helps
Can the JS/Py engines be separated from the user code?
wishlist
Snapshotting support in wasmtime
Serialize the state of an instance and ability to restore it
Prototype works (but only snapshots memory)
What about function tables?
Host-owned resources?
Snapshot during execution
Snapshot during execution
Daniel Vigovszky