Skip to content

Releases

WasmKeep v0.3.0

WasmKeep v0.3.0 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 13 Nov 20:38
v0.3.0
8574b6a

Reliable enough to leave running. This release fixes the known ways
a cluster stalled or lost an answer when a node or the bucket misbehaved,
keeps a request for a dead node's actor until the takeover instead of
refusing it, and adds the guides that running an application on a cluster
showed missing. Still a pre-alpha pre-release: for trying WasmKeep,
side projects and staging, not production. The interface between an actor
and the host is unchanged (wasmkeep:actor@0.2.0), and so are the SDKs'
APIs: a component built for 0.1.0 or 0.2.0 runs as it is. See the
roadmap for what comes next.

Soaked on three nodes against a real bucket, a node killed every three
minutes: twelve hours on a release candidate (239 kills, failover p99
22.43 s, no acknowledged write lost or duplicated, no alarm missed;
bench/results/2026-11-12-b849bfe-soak-ovh-bhs-3vps-12h.csv), then three
hours on this release's code (59 kills, failover p99 20.9 s, the same
result; bench/results/2026-11-13-648c2d1-soak-ovh-bhs-3vps-3h.csv).

What changed

  • A request waits for a takeover. When an actor's node dies, a GET or
    HEAD without a body, or a WebSocket upgrade, for one of its actors is
    kept by the node that received it until the actor can be taken over (15
    to about 21 s after the dead node's last write, unchanged), then served,
    instead of being answered 503 meanwhile. A request with a body, an
    actor-to-actor call and a forwarded request still get the 503 at once,
    which still means it did not run. A client that leaves while its
    request is kept is dropped, and its request never runs. At most a
    quarter of the node's connection limit, and 64 per actor, are kept;
    beyond that, the 503 at once. --failover-wait-secs sets the wait
    (about 25 s by default, from the lease timing); 0 turns it off. Set the
    load balancer's server timeout above it.
    wasmkeep_failover_waits_total counts how each wait ended
    (what a caller sees,
    front door).
  • A snapshot upload is bounded, tried again after a failure, and
    counted.
    The snapshot an actor's owner adds every 64 writes holds the
    actor while it uploads. The upload had no bound of its own, so a bucket
    that left it unanswered held the actor until the bucket client ended the
    request (30 s by default). It now gets one attempt, given up after 5 s
    plus a second per megabyte, 10 s at most. A snapshot that fails costs no
    write and fails no event, and is tried again once the actor has made one
    more write and 30 s have passed, then 8 writes later, then every 64; a
    failure used to put the next snapshot 64 more writes away. The node
    keeps these waits while an idle actor is out of its memory.
    wasmkeep_snapshot_upload_failures_total counts the snapshot uploads
    that were not acknowledged
    (replication,
    monitoring).
  • A restore's reads have one deadline, a refused read is sent again,
    and failed restores are counted.
    Restoring an actor from the bucket
    bounded each read but not the reads together, so reads that each stayed
    under their limit added up with the length of the history while every
    caller of the actor waited. The reads of one restore now end within
    30 s, plus a second per megabyte of what it reads and a second per
    eight objects. The clock runs only while the restore waits for the
    bucket to give it the object it applies next: the listing, work on the
    local disk and the wait for a turn among the node's other restores are
    not counted. Neither are the seconds that other restores' reads on the
    node add to the limit of a read (a second per megabyte in flight, 256 at
    most), so restores that read side by side after a takeover are not
    ended for each other's bytes; a restore alone on the node has no more
    than its own time. Running out of time is answered like a bucket that
    does not answer, a 503 with Retry-After, and leaves the local file as
    it was. A read the bucket refuses with an error a resend can get past
    (a 5xx or a broken connection, after the bucket client's own retries)
    used to fail the whole restore; it is now sent again after 0.5 s, then
    1 s, three tries per object. wasmkeep_restore_failures_total counts
    the restores that did not complete
    (replication,
    monitoring).
  • A request body the handler left unread no longer costs the upload or
    the answer.
    A handler may answer without reading the body. The server
    now reads and discards what is left, up to the body cap and for at most
    30 s, so a large upload is no longer cut off and, when the request came
    through another node, its answer is no longer lost. A body held back
    with Expect: 100-continue is not asked for, and a node that forwards a
    request does not pass Expect on (concepts).
  • Between nodes, a WebSocket upgrade the owner never read is sent once
    more, and a forwarded request that was written is never sent twice.

    An upgrade relayed to the actor's node that the node provably never read
    (it refused the TLS handshake, or the upgrade came back unsent on a new
    TLS connection) is sent once more while the client waits; it used to
    get a 503 asking the client to retry. Between nodes, the only other
    thing sent a second time is an actors.call the peer provably never
    read in the same way, once, as 0.2.0 already did after a refused
    handshake. A forwarded request is never sent again, whether its
    connection could not be opened or it was written into a connection that
    then failed: one that was written may have run on the owner, so the
    client gets a 503, which does not say whether it ran
    (running a cluster).
  • A client that only listens no longer misses a room's messages
    without a close.
    A room that hibernates keeps its WebSockets open on
    its node and releases its ownership record. When a request then woke it
    on another node, its sockets on the first node closed only one by one,
    each when its client next sent a message: a client that only listens
    stayed connected and silently missed everything the room said from then
    on. Now every socket of the room on that node closes with 1012 as soon
    as one of them sends, and an owner sweep, once a minute, closes the
    sockets of rooms another node took even if none of them sends. The
    sweep reads at most 5,000 records a pass, counted in
    wasmkeep_websocket_sweep_reads_total. A node's sockets also close with
    1012 when its lease runs out (8 s without a write reaching the bucket,
    with the defaults), before any other node can take its actors; nothing
    closed them before. A drain closes the sockets of hibernated rooms, and
    reports drained only once the node holds no socket. Each such close is
    counted in wasmkeep_websocket_owner_closes_total
    (running a cluster, monitoring).
  • A WebSocket the server closed stops at the close. Its client used to
    keep it open by not answering the close, and its messages still ran
    events; a node draining waited on it forever. Now nothing it sends after
    the close is delivered, and it is dropped if its client has not answered
    within 5 s.
  • An idle WebSocket is pinged after 25 s of silence from its client,
    instead of 60 s, so a load balancer's usual idle timeout of 60 s no
    longer cuts it first. The front door needs an idle timeout above 30 s
    (front door).
  • A relayed WebSocket upgrade carries no x-wasmkeep-* header from the
    client.
    The relay to the actor's node removed a fixed list of node
    headers; it now removes every header in that range, so a client cannot
    pass a marker only nodes may set.
  • Take and alarm hand-off calls between nodes keep their own record of
    used signatures
    , so a flood of other node calls that fills the shared
    record (500,000 signatures at once) can no longer refuse the calls a
    drain and a move depend on. The take and hand-off record holds 50,000.
    Refusals because a record is full are counted in
    wasmkeep_signatures_refused_full_total and logged as a full record
    rather than as a replay.
  • A database set aside as <name>.db.replaced no longer has an older
    WAL beside it.
    When a node replaces a local database with the bucket's
    copy and keeps the old file, a replacement without a WAL left the
    previous replacement's WAL next to the newer copy, where SQLite would
    apply it if the copy were opened for recovery. The previous copy's WAL
    and shared memory are now removed first.
  • wasmkeep_admission_refused_total{reason} counts the requests,
    WebSocket upgrades and actor calls a node refused before their event
    ran: access, capacity, overloaded or unavailable
    (monitoring).
  • deploy refuses a second app of a tenant on a bucket prefix.
    Actors are keyed by tenant, class and name, so a second app would share
    the first app's actors without a word; until now only a node refused to
    start, and only while a node of the other app was live. The first deploy
    of an app now lists apps/<tenant>/ and refuses when another app there
    has a current, naming it and s...
Read more

WasmKeep v0.2.0

WasmKeep v0.2.0 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 12 Nov 03:13
v0.2.0
48f03fc

The installable release. With 0.1.0 the SDKs were used from a clone of
the repository; from this release each comes from where its language
expects it, and trying WasmKeep needs no toolchain. Still a pre-alpha
pre-release
: for trying WasmKeep, side projects and staging, not
production. The interface between an actor and the host is unchanged
(wasmkeep:actor@0.2.0, a version of its own), so a component built for
0.1.0 runs as it is. See the roadmap for what comes next.

What is new

  • The SDKs install from their registries, each at the server's
    version, 0.2.0:

    Language Install Then
    Rust cargo add wasmkeep-sdk crate, tutorial
    TypeScript npm install @wasmkeep/sdk npx wasmkeep-ts build (package)
    Python pip install wasmkeep python -m wasmkeep build, new in this release (package)
    Go (TinyGo) go get github.com/wasmkeep/wasmkeep/sdks/go@v0.2.0 module
    C, Zig wasmkeep-c-sdk-0.2.0.tar.gz and wasmkeep-zig-sdk-0.2.0.tar.gz, attached to this release C, Zig

    The crate, the npm package and the wheel are published from this
    release's tag once a maintainer approves each, so a registry can be a
    little behind this page. The server and every SDK carry one version;
    use an SDK with the server of the same version
    (versions).

  • Prebuilt components: the release carries counter.wasm, kv.wasm,
    queue.wasm, workflows-orders.wasm and workflows-ledger.wasm, built
    from the examples and signed like its other files. The
    quickstart is now a download and one command,
    and the callers of the key-value, queue and
    workflow classes in other
    languages need no Rust toolchain. The notices of the libraries compiled
    into them are in THIRD_PARTY_NOTICES-components.txt. The binary's
    tarball has a short README of its own.

  • The C and Zig SDKs as archives, signed like the other files: each
    SDK with the C bindings already generated, the WIT and the componentize
    script, and a README whose build command works in the extracted
    directory, without a clone or wit-bindgen. WASMKEEP_SDK_VERSION (C)
    and wk.version (Zig) carry the version.

  • A durable write on a cluster node is no longer synced to the local
    disk as well as uploaded.
    exec-durable answers once the bucket holds
    the write, as it was documented to: one disk sync less on every durable
    write. The bucket is the only durable copy of such a write, and a node
    trusts a local file again only after checking it against the bucket. For
    whoever runs a cluster: start a node again on the same bucket and
    prefix, since after a crash or a power loss its local files may lack
    writes the bucket has (replication). A
    node without a bucket still syncs to its disk.

  • Languages: Go through TinyGo moves from experimental to supported,
    with a full Go SDK and its counter, chat room and jobs app. D
    and MoonBit are experimental, each with an SDK, a counter and a chat
    room; a C# counter (componentize-dotnet) and a standard-Go counter
    (componentize-go) pass the shared tests.
    Tiers and numbers.

  • Compared with celld: the same counter on one node and on three, a
    node killed under writes, with a check for lost writes that gives every
    write a token of its own, repeats the takeovers with the killed node's
    disk removed, and includes a run that must lose writes
    (WasmKeep and celld,
    benchmarks).

  • How releases are made: a tag's release waits for CI to pass on the
    tagged commit (0.1.0 was tagged before its run had finished), and a
    version's image tag is written once. Publishing the release publishes
    the SDKs to crates.io, npm and PyPI from its tag, without stored tokens,
    each after a maintainer's approval (releasing).

Upgrading from 0.1.0

  • Upgrade every node of a cluster together, as before
    (upgrading). The formats in the
    bucket and the command line are unchanged.
  • Components built for 0.1.0 need no rebuild. To move an actor's source
    to a published SDK, replace the path into a clone with the registry's
    package at 0.2.0.
  • The Go module's v0.1.0 has only the key-value, queue and workflow
    clients; the wasmkeep package (hooks, storage, alarms) starts at
    v0.2.0.

Known limits

The limits listed for 0.1.0 still hold:

  • Not production-ready: soaked against Cloudflare R2 and OVHcloud Object
    Storage with nodes killed under load, nothing lost, but not yet carrying
    real users' traffic, and not run against AWS S3
    (measured).
  • Nodes of different versions are not supported in one cluster; one
    tenant per process (security); no
    independent third-party security audit yet
    (security).
  • Binaries are for Linux; on macOS and Windows, use the image or build
    from source. The Zig SDK is an archive, not yet a package zig fetch
    resolves; D and MoonBit are used from the source.

Install and verify

Install downloads a binary or runs the image.
Single node shows the
cosign check, the same for every file of the release;
Compose checks the image.

WasmKeep v0.1.0

WasmKeep v0.1.0 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 10 Nov 14:34
v0.1.0
43e0119

The first release, published as a pre-alpha pre-release: for trying
WasmKeep, side projects and staging, not production. See the
roadmap for what comes next.

What is in it

  • Actors: WebAssembly components (wasmkeep:actor@0.2.0 WIT world),
    one instance per name, events one at a time, a SQLite database each,
    hibernation, alarms that fire at least once, calls between actors,
    WebSockets that stay open while an actor sleeps, outbound HTTP limited to
    public addresses, and hot reload with wasmkeep dev.
  • Clusters: several nodes on one S3-compatible bucket with conditional
    writes, each actor on one node at a time, its database replicated to the
    bucket and restored on another node when its node dies (free 15 to about
    21 s after the node's last write, then the restore). One lease per node,
    checked in deterministic simulation. Mutual TLS between nodes, required
    by default.
  • Operating a cluster: wasmkeep deploy (rolling, with rollbacks,
    access rules and configuration), wasmkeep cluster status, drain,
    fence and forget, wasmkeep actor move, wasmkeep secrets (sealed
    in the bucket), garbage collection of old history in the bucket,
    Prometheus metrics, health and readiness endpoints, and a Grafana
    dashboard.
  • Languages: Rust (recommended); Zig and C/C++; TypeScript (QuickJS
    by default, StarlingMonkey as an option) and Python; Go through TinyGo
    (experimental). Tiers and numbers.
  • SDK patterns on actors: sharded key-value store, queues and durable
    workflows (KV and queues,
    workflows).
  • Packaging: binaries for x86_64 and aarch64 Linux (glibc 2.28 or
    newer), each with a CycloneDX SBOM and third-party notices; a
    distroless image for linux/amd64 and linux/arm64 at
    ghcr.io/wasmkeep/wasmkeep:0.1.0; all signed with keyless cosign. A
    three-node Compose example against MinIO in
    deploy/compose/.

Known limits

  • Not production-ready: soaked against Cloudflare R2 and OVHcloud Object
    Storage with nodes killed under load, nothing lost, but not yet carrying
    real users' traffic, and not run against AWS S3
    (measured).
  • Upgrade every node of a cluster together: nodes of different versions
    are not supported in one cluster
    (upgrading).
  • One tenant per process (security).
  • No independent third-party security audit yet
    (security).
  • The performance targets on dedicated hardware are not measured yet;
    millions of actors per cluster are a goal, not a measurement.

Install and verify

Single node checks the
binaries with cosign; Compose checks the image.