Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 21 additions & 29 deletions vendor/docs/docs/content/architecture.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,38 +4,30 @@ description: "How object storage, the control plane, and nodes work together to
skill: true
---

Rivet has three parts: **object storage** keeps Actor data, the **control plane** schedules Actors and routes requests, and **actor nodes** run your code.

<svg viewBox="0 0 640 292" role="img" aria-label="Clients send requests to the backend's control plane. Actor nodes running your code connect bidirectionally to the control plane, which persists Actor data to object storage such as S3." style="width:100%;max-width:640px;height:auto;display:block;margin:2.5rem auto;font-family:system-ui,sans-serif">
<defs><marker id="architecture-overview-arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0 0 L10 5 L0 10 z" fill="rgb(var(--site-pine, 46 64 52))"/></marker></defs>
<rect x="20" y="140" width="600" height="132" rx="10" fill="rgb(var(--site-paper-mid, 250 248 243))" stroke="rgb(var(--site-pine, 46 64 52))" stroke-width="1.4" stroke-dasharray="7 6"/>
<text x="36" y="161" font-size="11" font-family="ui-monospace,monospace" font-weight="600" letter-spacing="0.14em" fill="rgb(var(--site-pine, 46 64 52))">BACKEND</text>
<rect x="245" y="20" width="150" height="84" rx="7" fill="rgb(var(--site-surface, 255 255 255))" stroke="rgb(var(--site-ink, 27 25 22))" stroke-width="1.4"/>
<g transform="translate(310 33)" stroke="rgb(var(--site-pine, 46 64 52))" stroke-width="1.6" fill="none" stroke-linecap="round" stroke-linejoin="round"><rect x="0" y="2" width="20" height="13" rx="2"/><path d="M7 19h6M10 15v4"/></g>
<text x="320" y="73" text-anchor="middle" font-size="13" font-weight="600" fill="rgb(var(--site-ink, 27 25 22))">Clients</text>
<text x="320" y="90" text-anchor="middle" font-size="11" fill="rgb(var(--site-ink-soft, 86 82 74))">Browsers, apps</text>
<rect x="40" y="172" width="150" height="84" rx="7" fill="rgb(var(--site-surface, 255 255 255))" stroke="rgb(var(--site-ink, 27 25 22))" stroke-width="1.4"/>
<g transform="translate(105 185)" stroke="rgb(var(--site-pine, 46 64 52))" stroke-width="1.6" fill="none" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="4" width="12" height="12" rx="2"/><rect x="8" y="8" width="4" height="4"/><path d="M7 1v3M13 1v3M7 16v3M13 16v3M1 7h3M1 13h3M16 7h3M16 13h3"/></g>
<text x="115" y="225" text-anchor="middle" font-size="13" font-weight="600" fill="rgb(var(--site-ink, 27 25 22))">Actor nodes</text>
<text x="115" y="242" text-anchor="middle" font-size="11" fill="rgb(var(--site-ink-soft, 86 82 74))">Your code</text>
<rect x="245" y="172" width="150" height="84" rx="7" fill="rgb(var(--site-pine, 46 64 52) / 0.12)" stroke="rgb(var(--site-ink, 27 25 22))" stroke-width="1.4"/>
<g transform="translate(310 185)" stroke="rgb(var(--site-pine, 46 64 52))" stroke-width="1.6" fill="none" stroke-linecap="round" stroke-linejoin="round"><circle cx="10" cy="10" r="3"/><circle cx="3" cy="3" r="2"/><circle cx="17" cy="3" r="2"/><circle cx="3" cy="17" r="2"/><circle cx="17" cy="17" r="2"/><path d="M4.5 4.5L8 8M15.5 4.5L12 8M4.5 15.5L8 12M15.5 15.5L12 12"/></g>
<text x="320" y="225" text-anchor="middle" font-size="13" font-weight="600" fill="rgb(var(--site-ink, 27 25 22))">Control plane</text>
<text x="320" y="242" text-anchor="middle" font-size="11" fill="rgb(var(--site-ink-soft, 86 82 74))">Schedule, route, persist</text>
<rect x="450" y="172" width="150" height="84" rx="7" fill="rgb(var(--site-surface, 255 255 255))" stroke="rgb(var(--site-ink, 27 25 22))" stroke-width="1.4"/>
<g transform="translate(515 185)" stroke="rgb(var(--site-pine, 46 64 52))" stroke-width="1.6" fill="none" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="10" cy="4" rx="8" ry="3"/><path d="M2 4v12c0 1.7 3.6 3 8 3s8-1.3 8-3V4M2 10c0 1.7 3.6 3 8 3s8-1.3 8-3"/></g>
<text x="525" y="225" text-anchor="middle" font-size="13" font-weight="600" fill="rgb(var(--site-ink, 27 25 22))">Object storage</text>
<text x="525" y="242" text-anchor="middle" font-size="11" fill="rgb(var(--site-ink-soft, 86 82 74))">S3, GCS or Azure</text>
<g stroke="rgb(var(--site-pine, 46 64 52))" stroke-width="1.4" fill="none">
<line x1="320" y1="105" x2="320" y2="171" marker-end="url(#architecture-overview-arrow)"/>
<line x1="191" y1="214" x2="244" y2="214" marker-start="url(#architecture-overview-arrow)" marker-end="url(#architecture-overview-arrow)"/>
<line x1="396" y1="214" x2="449" y2="214" marker-end="url(#architecture-overview-arrow)"/>
Rivet has three parts: **object storage** keeps Actor data, the **control plane** schedules Actors and routes requests, and **nodes** run your code.

<svg viewBox="0 0 760 150" role="img" aria-label="Object storage such as S3 connects bidirectionally to the control plane, which connects bidirectionally to nodes running your code." style="width:100%;max-width:760px;height:auto;display:block;margin:2rem auto;font-family:inherit">
<defs><marker id="architecture-arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"><path d="M0 0 L10 5 L0 10 z" fill="rgb(var(--runtime-highlight))"/></marker></defs>
<g fill="none" stroke="rgb(var(--runtime-highlight))" stroke-width="1.5">
<rect x="10" y="30" width="190" height="85" rx="8"/>
<rect x="285" y="30" width="190" height="85" rx="8"/>
<rect x="560" y="30" width="190" height="85" rx="8"/>
<path d="M205 72 H280" marker-start="url(#architecture-arrow)" marker-end="url(#architecture-arrow)"/>
<path d="M480 72 H555" marker-start="url(#architecture-arrow)" marker-end="url(#architecture-arrow)"/>
</g>
<g text-anchor="middle" fill="currentColor">
<text x="105" y="65" font-size="16" font-weight="600">Object storage</text>
<text x="105" y="88" font-size="13">S3, GCS, or Azure</text>
<text x="380" y="65" font-size="16" font-weight="600">Control plane</text>
<text x="380" y="88" font-size="13">Schedule, route, persist</text>
<text x="655" y="65" font-size="16" font-weight="600">Nodes</text>
<text x="655" y="88" font-size="13">Your code</text>
</g>
</svg>

## Actor nodes
## Nodes

An actor node is a process running your app with the Rivet SDK, started with `registry.start()`. It opens an outbound connection to the control plane and hosts the Actors assigned to it. Nodes need no public URL or inbound port for Actor traffic. Add nodes when you need more capacity.
A node is a process running your app with the Rivet SDK, started with `registry.start()`. It opens an outbound connection to the control plane and hosts the Actors assigned to it. Nodes need no public URL or inbound port for Actor traffic. Add nodes when you need more capacity.

## Control plane

Expand All @@ -55,4 +47,4 @@ A namespace separates Actors, keys, and node pools. For example, use `production

## Node pools

A node pool groups nodes by name within a namespace. Configure a pool before connecting nodes to it. A node joins the pool selected by `RIVET_POOL`, which defaults to `default`. For example, create a `gpu` pool for Actors that need GPUs alongside a `default` pool for the rest. See [Nodes & Node Pools](/docs/nodes).
A node pool groups nodes by name within a namespace. Configure a pool before connecting nodes to it. A node joins the pool selected by `RIVET_POOL`, which defaults to `default`. For example, create a `gpu` pool for Actors that need GPUs alongside a `default` pool for the rest. See [Node Pool Configuration](/docs/pool-configuration).
16 changes: 10 additions & 6 deletions vendor/docs/docs/content/authentication.mdx
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
---
title: "Authentication"
description: "The credentials the control plane accepts and where each one belongs."
description: "Choose credentials for a self-hosted control plane or Rivet Cloud."
skill: true
---

The control plane checks credentials before serving management requests or routing Actor traffic. Application-level authorization still belongs in your Actor. See [Permissions](/actors/docs/permissions).
The control plane checks credentials before serving management requests or routing Actor traffic. Application-level authorization still belongs in your Actor. See [Authentication](/actors/docs/authentication).

## Credentials
## Self-hosted control plane

Your Rivet token is the `auth.admin_token` value in the control plane config. Your nodes and backend use it to connect, create namespaces, and manage node pools. Keep it server-side.
The configured `auth.admin_token` grants every operation in every namespace. Keep it in your backend or secret store. Use it to create namespaces and issue [scoped JWTs](/docs/jwt) for applications.

For browser clients, issue [scoped JWTs](/docs/jwt). A JWT grants access to one namespace and an explicit set of resources and operations, so give each client only the permissions it needs.
A JWT grants access to one namespace and an explicit set of resources and operations. Give browser clients only the permissions they need, and keep tokens that can register nodes or manage pools on your backend.

Pass credentials in `Authorization: Bearer <token>`, or use an [endpoint URL](/docs/connect) such as `https://production:<token>@rivet.mycompany.com`.
Pass credentials in `Authorization: Bearer <token>`, or use an [endpoint URL](/docs/endpoints) such as `https://production:<token>@rivet.mycompany.com`.

## Rivet Cloud

Get your namespace endpoints and credentials from the [Rivet Cloud dashboard](https://dashboard.rivet.dev). Keep secret credentials on your backend. See [Rivet Cloud](/docs/deploy/cloud/) for managed deployment and token settings.
24 changes: 20 additions & 4 deletions vendor/docs/docs/content/benchmarking.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ These measurements come from the October 25, 2026 Rivet Actors benchmark. Use th
| **Software** | Debian 13, Node.js 24, release builds |
| **Object storage** | Azure Blob Storage in the same region, with locally redundant storage (LRS) |

Requests use plain HTTP inside the region over reused connections, with one request in flight. Create and wake requests have 20 ms gaps. Each row reports all successful samples from its condition, with nearest-rank percentiles and no trimming. All conditions below completed without errors.
Requests use plain HTTP inside the region over reused connections, with one request in flight. Create and wake requests have 20 ms gaps. The warm-read condition has 1,000 warmups; inserts run without gaps. Each row reports all successful samples from its condition, with nearest-rank percentiles and no trimming. All conditions below completed without errors.

## Results

Expand All @@ -29,26 +29,38 @@ Milliseconds, p50 / p99.
| **Wake** | 1,200 | 4.37 | 6.80 | Client send to full ping reply from a Rivet Actor asleep for at least 10 seconds. |
| **Durable insert** | 1,200 | 0.69 | 0.80 | One 128-byte SQLite row inserted in autocommit mode. Timed around the query on the node, including the write to a majority of disks across zones. |
| **Warm read** | 1,200 | 0.059 | 0.080 | A primary-key read of one 128-byte row from SQLite's memory cache, timed around the query. |
| **Random read after sleep, 50 MB database** | 1,200 | 1.33 | 100.1 | First random point read after at least 13 seconds asleep. Timed around the query, with two network page fetches. |
| **Random read after sleep, 1 GB database** | 1,024 | 41.8 | 168.4 | The same condition with three network page fetches. |

The query interval includes the SQLite call, transport, and materializing the result on the node. It excludes the client's HTTP round trip. Full HTTP p50 is 0.59 ms for a warm read, 17.8 ms for the 50 MB read after sleep, and 61.6 ms for the 1 GB read after sleep, including wake time.

The node reaches the control plane in its own zone for reads; durable writes require a cross-zone acknowledgement. Sleeping does not guarantee a cold object-storage read. This setup tests replicated writes across zones, but its LRS bucket does not test object-storage availability during a zone failure.

## Reproducing it

<Steps>

<Step title="Deploy the control plane">

Run three control plane instances on VMs in three zones of one region, each on raw local NVMe, with a bucket in the same region. Follow the [VM](/docs/deploy/self-host/control-plane/vm/) guide. Kubernetes does not give pods raw NVMe, so it cannot reproduce these numbers.
Run three control plane instances in three zones of one region, each on local NVMe, with a bucket in the same region. Follow the [Kubernetes](/docs/deploy/self-host/control-plane/kubernetes/) or [VM](/docs/deploy/self-host/control-plane/vm/) guide.

</Step>

<Step title="Deploy one node">

Run one node in the first zone, with a Rivet Actor that has a `ping` action and a SQLite table, and a load generator on the same machine. See [Deploy nodes](/docs/deploy/self-host/nodes/).
Run one node in the first zone, with a Rivet Actor that has a `ping` action and a SQLite table, and a load generator on the same machine. See [Deploy nodes](/docs/deploy/self-host/workers/).

</Step>

<Step title="Warm up">

For the warm-read condition, seed one 128-byte row and run 1,000 reads before recording samples. Keep startup and warmup measurements separate from measured requests.

</Step>

<Step title="Measure each condition">

Use the sample counts and pacing above, with a new key for each create. For wakes, explicitly sleep each Actor and wait at least 10 seconds.
Use the sample counts and pacing above, with a new key for each create. For wakes, explicitly sleep each Actor and wait at least 10 seconds. For random reads, seed 128-byte rows into databases of the stated sizes, sleep the Actors for at least 13 seconds, and verify each measured query fetches pages over the network. Record HTTP and query intervals separately.

</Step>

Expand All @@ -59,3 +71,7 @@ Sort the successful samples and take nearest-rank percentiles. Report the sample
</Step>

</Steps>

## Under load

These latency results use one request at a time. Concurrent create workloads that overlapped mass teardown produced timeouts. Unpaced 10 MiB writes produced errors, and a seven-minute write run slowed substantially. Measure sustained throughput and error rates for your workload separately from these latency conditions.
Loading
Loading