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
1 change: 0 additions & 1 deletion vendor/actors/docs/AGENTS.md

This file was deleted.

125 changes: 0 additions & 125 deletions vendor/actors/docs/CLAUDE.md

This file was deleted.

6 changes: 6 additions & 0 deletions vendor/actors/docs/content/docs/limits.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ These limits affect actions that do not use `.connect()` and [low-level HTTP req
| Max response body size | — | 20 MiB | Maximum size of HTTP response bodies. |
| Request timeout | 60 seconds | — | Maximum time for an `onRequest` handler to complete. Defaults to `actionTimeout`; configure with `actionTimeout`. |

### Actions

| Name | Soft Limit | Hard Limit | Description |
|------|------------|------------|-------------|
| Max actions per actor | 128 | None | Maximum number of action handlers defined on one actor. Nested action groups count each leaf handler. Configurable via `maxActions`. |

### Networking

| Name | Soft Limit | Hard Limit | Description |
Expand Down
232 changes: 232 additions & 0 deletions vendor/actors/docs/content/docs/sqlite-profiling.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
---
title: "SQLite Profiling"
description: "Profile SQLite statements and transactions in Rivet Actors using bounded metrics and sampled diagnostics."
skill: true
---

Profiling helps you find slow queries, transaction contention, and unnecessary storage activity.

## Logging slow queries

RivetKit logs slow or failed SQLite statements and transactions. Logs include the fingerprint, outcome, timing breakdown, and storage activity; statement logs also include rows and bytes, while transaction logs include the statement count.

Search actor logs for `sampled SQLite operation profile` for statements or `sampled SQLite transaction profile` for transactions.

## Identify operations

### Transaction names

Transaction names provide a stable identity for profiling transactions. Use a short, static name to correlate metrics.

Pass `{ name: "complete-order" }` as the options argument to `db.transaction()`.

Without a name, RivetKit falls back to a fingerprint of the transaction's statement sequence. Branches and different loop counts can therefore produce separate fingerprints.

### Statement fingerprints

RivetKit hashes each SQL statement exactly as provided. The fingerprint groups metrics without putting SQL text in a Prometheus label.

For example, repeated `SELECT * FROM orders WHERE id = ?` calls share one fingerprint regardless of the bound ID.

### Find the SQL for a fingerprint

RivetKit logs the SQL statement or transaction name for each tracked fingerprint.

For example, suppose a Prometheus result contains `fingerprint="select-a1b2c3d4e5f60718"`:

1. Copy the fingerprint: `select-a1b2c3d4e5f60718`.
2. Search the actor logs for `sqlite fingerprint catalog` and `select-a1b2c3d4e5f60718`.
3. Read `identity` from the matching log line:

```text
sqlite fingerprint catalog fingerprint="select-a1b2c3d4e5f60718" identity="SELECT value FROM items WHERE id = ?"
```

## Query metrics

Collect [Prometheus metrics from each worker](/actors/self-host/workers/prometheus-metrics/) to use the queries below.

### Slowest statements and transactions

Find the statements and transactions with the highest 95th-percentile latency.

```promql
histogram_quantile(
0.95,
sum by (le, actor_name, type, fingerprint) (
rate(rivet_rivetkit_sqlite_duration_seconds_bucket[5m])
)
)
```

### Slowest latency phases

Break down slow operations to see whether they spend time waiting, executing SQL, or accessing storage.

- `transaction_wait`: waiting for another transaction on the actor to finish.
- `worker_wait`: waiting for earlier SQLite work on the actor to finish.
- `storage`: loading or saving SQLite data.
- `local_work`: executing SQL and preparing results, excluding storage time.
- `application_time`: time the transaction stays open between SQL calls.
- `commit`: saving changes at the end of a transaction.

```promql
histogram_quantile(
0.95,
sum by (le, actor_name, type, fingerprint, phase) (
rate(rivet_rivetkit_sqlite_phase_duration_seconds_bucket[5m])
)
)
```

### Non-success outcomes

Find statements and transactions that fail, roll back, expire, or lose their connection.

```promql
sum by (actor_name, type, fingerprint, outcome) (
rate(rivet_rivetkit_sqlite_outcome_total{outcome!="success"}[5m])
)
```

### Transaction contention

See whether transactions are waiting for other transactions on the same actor.

```promql
max by (actor_name) (
max_over_time(rivet_rivetkit_sqlite_coordinator_queue_depth[5m])
)
```

### Native worker saturation

See whether SQLite operations are backing up on an actor. A sustained queue means work is arriving faster than SQLite can finish it, while `worker_inflight` shows how often SQLite is busy.

```promql
max by (actor_name) (
max_over_time(rivet_rivetkit_sqlite_worker_queue_depth[5m])
)
```

```promql
avg by (actor_name) (
avg_over_time(rivet_rivetkit_sqlite_worker_inflight[5m])
)
```

### Transactions with the most statements

Find transactions that execute many SQL statements before finishing. Large counts can identify loops or oversized units of work; use a static transaction name to keep its fingerprint stable.

```promql
histogram_quantile(
0.95,
sum by (le, actor_name, fingerprint) (
rate(rivet_rivetkit_sqlite_transaction_statement_count_bucket[5m])
)
)
```

### Average storage round trips per operation

See how many times each operation contacts storage on average. High counts can indicate a missing index, a large scan, or ineffective prefetching.

```promql
sum by (actor_name, type, fingerprint) (
rate(rivet_rivetkit_sqlite_get_pages_round_trips_sum[5m])
)
/
sum by (actor_name, type, fingerprint) (
rate(rivet_rivetkit_sqlite_get_pages_round_trips_count[5m])
)
```

### Pages per physical storage request

See how many pages each storage request asks for and returns. Compare `response_present` with `demand_requested` to find response amplification; `overflow_expansion_extra` shows overflow-chain reads, and `prefetch_requested` shows speculative reads.

```promql
sum by (actor_name, request_ordinal, page_kind) (
rate(rivet_rivetkit_sqlite_get_pages_pages_sum[5m])
)
/
sum by (actor_name, request_ordinal, page_kind) (
rate(rivet_rivetkit_sqlite_get_pages_pages_count[5m])
)
```

### Large storage responses

Find storage requests that return unusually large amounts of SQLite data.

```promql
histogram_quantile(
0.95,
sum by (le, actor_name, request_ordinal) (
rate(rivet_rivetkit_sqlite_get_pages_response_bytes_bucket[5m])
)
)
```

### Missing response pages

Find storage requests that could not return every requested page.

```promql
sum by (actor_name, request_ordinal) (
rate(rivet_rivetkit_sqlite_get_pages_missing_pages_total[5m])
)
```

### SQLite page usage by kind

Break down the pages used for each kind of SQLite activity. High page counts can indicate a missing index or a large scan.

```promql
sum by (actor_name, type, page_kind) (
rate(rivet_rivetkit_sqlite_local_pages_total[5m])
)
```

### SQLite data volume by kind

Compare bytes used by query parameters, results, storage reads, and writes.

```promql
sum by (actor_name, type, byte_kind) (
rate(rivet_rivetkit_sqlite_local_bytes_total[5m])
)
```

## Configure profiling

Profiling is enabled by default and most applications do not need to configure it. The entire profiling configuration surface is experimental and subject to change without notice. Set `profiling.slowOperationThresholdMs` or `profiling.baselineSampleRate` on the database provider when needed.

Increase fingerprint limits only when `other` is hiding frequently repeated operations. Prometheus series remain allocated for the life of the process after admission.

## Troubleshooting

### Most results are `other`

Fast statements initially appear under `other`, while overflow metrics show when a fingerprint limit was reached. Increase limits only for useful operations that repeat regularly.

### Too many fingerprints

Statement fingerprints use the exact query text. Keep formatting and query structure static, and pass dynamic values as bindings instead of constructing SQL strings.

### Transactions are hard to identify

Unnamed transactions are grouped by their statement sequence, which can vary across branches. Add a static `name` to each important transaction.

### Storage activity is high

Use the storage queries above to compare page counts, response bytes, and round trips by actor name. Large scans or missing indexes are common causes.

### Diagnostics are missing

Diagnostic events are sampled and bounded. Check `rivet_rivetkit_sqlite_event_dropped_total` for rate limiting or backpressure; aggregate Prometheus metrics continue reporting when events are dropped.

### No profiling metrics appear

Confirm profiling was not disabled in the database provider. Profiling currently applies to native actor-local SQLite, not remote or wasm SQLite.
Loading
Loading