242 lines
8.7 KiB
Markdown
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_
|