Files

242 lines
8.7 KiB
Markdown

# Versions & Upgrades
> Source: `src/content/docs/actors/versions.mdx`
> Canonical URL: https://rivet.dev/docs/actors/versions
> Description: When you deploy new code, Rivet ensures actors are upgraded seamlessly without downtime.
---
## How Versions Work
Each runner has a **version number**. When you deploy new code with a new version, Rivet handles the transition automatically:
- **New actors go to the newest version**: When allocating actors, Rivet always prefers runners with the highest version number
- **Multiple versions can coexist**: Old actors continue running on old versions while new actors are created on the new version
- **Drain old actors**: When enabled, a runner connecting with a newer version number will gracefully stop old actors to be rescheduled to the new version
Versions are not configured by default. See [Registry Configuration](/docs/general/registry-configuration) to learn how to configure the runner version.
`RIVET_ENVOY_VERSION` is only needed when self-hosting or using a custom runner. Rivet Compute handles versioning automatically.
### Example Scenario
### Drain Enabled
When a new version is deployed, existing actors are gracefully stopped on the old runner and rescheduled onto the new version.
```mermaid
sequenceDiagram
participant R1 as Runner v1
participant R2 as Runner v2
Note over R1: Currently running
Note over R2: Deployed
R2->>R1: Drain old actors
R1->>R2: Reschedule actors
Note over R1: Shut down when all actors stopped
```
### Drain Disabled
When a new version is deployed, both versions coexist. New actors are created on the new version while existing actors continue running on the old version until.
```mermaid
sequenceDiagram
participant R1 as Runner v1
participant R2 as Runner v2
Note over R1: Currently running
Note over R2: Deployed
Note over R1: Actor 1 sleeps from inactivity
Note over R2: Actor 1 wakes up when prompted
```
## Configuration
### Setting the Version
Configure the runner version using an environment variable or programmatically:
```bash {{"title": "Environment Variable"}}
RIVET_ENVOY_VERSION=2
```
The version **must** be set at build time, not at runtime. Do not use `Date.now()` or similar runtime values in your registry setup code. This would assign a different version every time the server starts, causing actors to be drained and rescheduled on every restart instead of only on new deployments.
### Example Configurations
We recommend injecting a build-time value that increments with every deployment. Here are concrete examples for common setups:
### Dockerfile
Generate the version at build time and bake it into the image as an environment variable:
```bash @nocheck
docker build --build-arg RIVET_ENVOY_VERSION=$(date +%s) .
```
```dockerfile @nocheck
FROM node:20-slim
ARG RIVET_ENVOY_VERSION
ENV RIVET_ENVOY_VERSION=$RIVET_ENVOY_VERSION
WORKDIR /app
COPY . .
RUN npm install && npm run build
CMD ["node", "dist/server.js"]
```
All containers from this image will share the same version.
### Next.js
Set the version in `next.config.ts`. Next.js evaluates this file once at build time and inlines the value into the bundle:
```typescript @nocheck
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
env: {
RIVET_ENVOY_VERSION: String(Math.floor(Date.now() / 1000)),
},
};
export default nextConfig;
```
### Vite
Use `define` in your Vite config. This is evaluated once at build time and inlined into the bundle:
```typescript @nocheck
import { defineConfig } from "vite";
export default defineConfig({
define: {
"process.env.RIVET_ENVOY_VERSION": JSON.stringify(
String(Math.floor(Date.now() / 1000))
),
},
});
```
### CI/CD
Set the version from your CI pipeline:
```yaml @nocheck
# GitHub Actions
env:
RIVET_ENVOY_VERSION: ${{ github.run_number }}
```
```bash @nocheck
# Railway / Render / generic CI
export RIVET_ENVOY_VERSION=$(date +%s)
```
```bash @nocheck
# Git commit count
export RIVET_ENVOY_VERSION=$(git rev-list --count HEAD)
```
### Build Script
Generate a version file during your build step and import it:
```json @nocheck
{
"scripts": {
"build": "echo \"export const BUILD_VERSION = $(date +%s);\" > src/build-version.ts && tsc"
}
}
```
```typescript @nocheck
import { actor, setup } from "rivetkit";
import { BUILD_VERSION } from "./build-version";
const myActor = actor({ state: {}, actions: {} });
const registry = setup({
use: { myActor },
envoy: {
version: BUILD_VERSION,
},
});
```
### Drain on Version Upgrade
The `drainOnVersionUpgrade` option controls whether old actors are stopped when a new version is deployed. This is configured in the Rivet dashboard under your runner configuration. See [Pool Configuration](/docs/general/pool-configuration) for the full set of pool options, including how to rate-limit actor eviction during the drain.
| Value | Behavior |
|-------|----------|
| `false` | Old actors continue running. New actors go to new version. Versions coexist. |
| `true` (default) | Old actors receive stop signal and have 30m to finish gracefully. |
## Upgrading Actor State
When you deploy a new version, existing actors may need to handle schema changes in their persisted data.
### SQLite (recommended for complex schemas)
**Drizzle (recommended)**
Use [Drizzle](/docs/actors/sqlite-drizzle) for typed schemas with generated migrations. Drizzle generates versioned `.sql` migration files from your TypeScript schema and applies them in order automatically. This is the recommended approach when your schema evolves frequently.
**Raw SQL**
For actors using [raw SQLite](/docs/actors/sqlite), migrations run automatically via the `onMigrate` hook on every actor start. RivetKit wraps the hook in a SQLite savepoint, so the migration is fully atomic. Use SQLite's `user_version` pragma to track which migrations have run:
### In-memory state (`c.state`)
If you use `c.state` for persistence, you are responsible for handling schema changes yourself. If you add, remove, or rename fields between versions, your code must handle the old shape gracefully.
**Manual defaults in `onWake`**
Apply defaults for missing fields:
**Zod schema coercion**
Use [Zod](https://zod.dev/) to parse persisted state on wake. Zod's `.default()` fills in missing fields automatically, so old actor state is coerced to the current schema:
For anything beyond simple defaults, consider moving to [SQLite](/docs/actors/sqlite) where you get proper migration tooling.
## Advanced
### How Version Upgrade Detection Works
When `drainOnVersionUpgrade` is enabled, Rivet uses two mechanisms to detect version changes:
- **New runner connection**: When a runner connects with a newer version number, the engine immediately drains all older runners with the same name. This is the primary mechanism for [runner mode](/docs/general/runtime-modes) deployments.
- **Metadata polling** (serverless only): In [serverless mode](/docs/general/runtime-modes), runners periodically poll the engine to check for newer versions and self-drain if one is found. This ensures old runners drain even if no new requests trigger a runner connection.
### SIGTERM Handling
When a runner process receives SIGTERM, it gracefully stops all actors before exiting:
- Each actor's `onSleep` hook is called, giving it time to save state
- Actors are rescheduled to other available runners
- The runner waits up to **30 minutes** for all actors to finish stopping
- If the process is force-killed before actors finish (e.g. SIGKILL), actors are rescheduled with a crash backoff penalty instead of a clean handoff
Actors have a maximum of 30 minutes to clean up during shutdown. Ensure your platform's drain grace period is at most 30 minutes.
### Shutdown Timeouts
Several timeouts control how long each part of the shutdown process can take:
| Timeout | Default | Description | Configuration |
|---------|---------|-------------|---------------|
| `actor_stop_threshold` | 30m | Engine-side limit on how long each actor has to stop before being marked lost | [Engine config](/docs/self-hosting/configuration) (`pegboard.actor_stop_threshold`) |
| `sleepGracePeriod` | 15s | Total graceful sleep budget for `onSleep`, `waitUntil`, `keepAwake`, and async raw WebSocket handlers | [Actor options](/docs/actors/lifecycle#options) |
| `runner_lost_threshold` | 15s | Fallback detection if the runner dies without graceful shutdown | [Engine config](/docs/self-hosting/configuration) (`pegboard.runner_lost_threshold`) |
Rivet has a max shutdown grace period of 30 minutes that cannot be configured.
## Related
- [Runtime Modes](/docs/general/runtime-modes): Serverless vs runner deployment modes
- [Lifecycle](/docs/actors/lifecycle): Actor lifecycle hooks including `onSleep`
_Source doc path: /docs/actors/versions_