already exists.**
3. Enter the database password.
4. Expand **Connect via SSH jump host** if you need a tunnel, or **Advanced**
for TLS settings.
5. Select **Test connection**, then **Add target**. The **Turn on monitoring**
dialog opens next for that database.
```text
postgresql://readonly@db.example.com:5432/myapp
```
## Check a connection
The target list shows the engine, host, port, server version, and measured
round-trip latency. **Test** checks one target; **Check all** checks the list.
**Password stored** means RDST can use the saved keychain credential.
**Tunnel active** means its SSH tunnel is open.
Use a [read-only account](/readyset-ai/rdst/desktop/database-permissions) with
access to application tables and query statistics. RDST warns when it detects
write privileges. **How to create a read-only user** opens example grants for
an administrator to run.
Passwords are saved in the operating system's keychain or read from a named
environment variable. They're never written into `config.toml`.
## Start monitoring
After you add a database, from Targets or during your first launch, the
[Turn on monitoring](/readyset-ai/rdst/desktop/monitoring/turn-on) dialog opens
next in the same flow. RDST checks statistics collection and access before
taking its first readings.
# Private databases (/readyset-ai/rdst/desktop/connect/private)
An SSH jump host lets RDST reach a database in a private network. RDST opens a
local tunnel through the host and uses it for the database connection.
## Configuring a jump host
1. Open the target in **Settings**, or select **Add target** and open
**Manual setup**.
2. Enter the database's private endpoint, port, and database name. Use the
address the jump host can reach.
3. Expand **Connect via SSH jump host**.
4. Choose a saved host or **Enter manually**.
5. Enter the jump host address, SSH port, user, and private-key path.
6. Select **Test connection** to test the tunnel and database together.
RDST reads the private key to authenticate. It doesn't copy the key.
The target list shows **Tunnel active** while the tunnel is open.
## Reusing a jump host
Select the same saved jump host for each database it can reach. Editing that
host updates the targets that use it.
If one database fails while another works through the same host, check routing
and security rules between the host and the failing database.
## Monitoring through a tunnel
Continuous monitoring needs the SSH host and database to remain reachable.
RDST manages the tunnel while it collects readings. If the connection fails,
check the Inbox and the connection details.
[AWS discovery](/readyset-ai/rdst/desktop/connect/discover) imports endpoints;
you still need to configure the route to a private database.
## Other network paths
For VPN access, connect the VPN first. RDST uses your computer's routing.
If the database is reachable only inside its network, install the
[CLI](/readyset-ai/rdst/cli) on a host there. Run
[`rdst web`](/readyset-ai/rdst/cli/integrations/web#deployment-patterns) and
forward its local port over SSH. Keep `rdst web` running to collect monitoring
history.
# Supabase, Neon, and DigitalOcean (/readyset-ai/rdst/desktop/connect/providers)
Open **Settings**, select **Add target**, and choose your provider on the
**Integrations** tab.
| Provider | Sign-in |
| ------------ | ------------------------------------------------------------------------------------------------------------------------ |
| Neon | API key from the Neon console's account settings |
| Supabase | Browser authorization for your organization |
| DigitalOcean | Browser sign-in |
| AWS | Existing AWS CLI profile or IAM Identity Center login; see [Import from AWS](/readyset-ai/rdst/desktop/connect/discover) |
Select **Discover**, choose the databases to import, and select **Add**.
The app lists projects, branch endpoints, or managed clusters for the provider.
## After import
Test each connection. Enter missing database credentials when prompted.
RDST stores passwords in the operating system's keychain.
Check the provider's allowed-address settings. Where supported, RDST offers to
add your current address and records the entry it created. Otherwise, add the
address in the provider's console.
Supabase and Neon can provide direct and pooled endpoints. RDST prefers a pooled
endpoint where it's needed for IPv4 access. Edit the host if your network needs
the direct endpoint.
For a private endpoint, connect to its network or configure an
[SSH jump host](/readyset-ai/rdst/desktop/connect/private). Then
[turn on monitoring](/readyset-ai/rdst/desktop/monitoring/turn-on).
# Database permissions (/readyset-ai/rdst/desktop/database-permissions)
import { Callout } from 'fumadocs-ui/components/callout'
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
RDST Desktop should connect with a dedicated read-only database user. The
baseline account needs permission to connect, inspect the schema, and read the
application tables you want RDST to analyze. Monitoring activity from other
database users requires additional statistics privileges.
RDST Desktop doesn't run these `GRANT` statements for you. Ask a database
administrator to create the account, replace the example names, and limit it to
the databases and schemas you want RDST to inspect.
## Permissions by feature
| Feature | PostgreSQL | MySQL |
| ------------------------------------------- | ----------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| Connect and test credentials | `CONNECT` on the database | Permission to connect to the server |
| Schema, Analyze, Ask, and query execution | `USAGE` on each schema and `SELECT` on relevant tables and views | `SELECT` on the relevant database; `SHOW VIEW` when inspecting or explaining views |
| Realtime monitoring of this user's sessions | Baseline access | Baseline access |
| Realtime monitoring of all sessions | Membership in `pg_read_all_stats` | Global `PROCESS` |
| Historical slow queries | `pg_stat_statements` enabled, plus `pg_read_all_stats` to see other users' query text | Performance Schema enabled and `SELECT` on `performance_schema.*` |
| Health Check | Baseline access gives partial results; `pg_monitor` provides the most complete server-wide view | Baseline access gives partial results; `PROCESS`, Performance Schema access, and `REPLICATION CLIENT` provide more complete results |
Optional monitoring privileges expose query text and activity from other
database users. Without them, RDST Desktop can still connect and analyze
queries, but TOP and Health Check may show only RDST's sessions or incomplete
historical data. Some checks return partial or empty results instead of an
explicit permission error.
## Create a read-only account
Run this as an administrator. Replace `my_database`, `public`, and
`app_owner` with your database, schema, and table-owning role.
```sql
CREATE ROLE rdst_readonly
LOGIN
PASSWORD 'replace_with_a_strong_password'
NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION NOBYPASSRLS;
GRANT CONNECT ON DATABASE my_database TO rdst_readonly;
-- Run these while connected to my_database.
GRANT USAGE ON SCHEMA public TO rdst_readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO rdst_readonly;
-- Keep access when app_owner creates new tables.
ALTER DEFAULT PRIVILEGES FOR ROLE app_owner IN SCHEMA public
GRANT SELECT ON TABLES TO rdst_readonly;
```
Repeat the schema grants for every application schema RDST should inspect.
Repeat `ALTER DEFAULT PRIVILEGES` for each role that creates tables.
For complete workload monitoring, add:
```sql
GRANT pg_read_all_stats TO rdst_readonly;
```
For the most complete Health Check, grant `pg_monitor` instead. It includes
`pg_read_all_stats`, so you don't need to grant both.
```sql
GRANT pg_monitor TO rdst_readonly;
```
Historical query monitoring also requires `pg_stat_statements` to be enabled
by a database administrator:
```sql
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
```
PostgreSQL may require `pg_stat_statements` in `shared_preload_libraries`, a
server or managed-service setting rather than a user privilege.
Run this as an administrator. Replace `my_database` and the host pattern
with the narrowest values that match the RDST machine.
```sql
CREATE USER 'rdst_readonly'@'10.0.0.%'
IDENTIFIED BY 'replace_with_a_strong_password';
GRANT SELECT ON `my_database`.*
TO 'rdst_readonly'@'10.0.0.%';
```
If RDST must inspect or explain views, add:
```sql
GRANT SHOW VIEW ON `my_database`.*
TO 'rdst_readonly'@'10.0.0.%';
```
For complete realtime monitoring and Health Check, add:
```sql
GRANT PROCESS ON *.* TO 'rdst_readonly'@'10.0.0.%';
GRANT SELECT ON performance_schema.*
TO 'rdst_readonly'@'10.0.0.%';
GRANT REPLICATION CLIENT ON *.*
TO 'rdst_readonly'@'10.0.0.%';
```
Performance Schema statement instrumentation and the `statements_digest`
consumer must also be enabled. Granting `SELECT` can't enable collection.
If you explicitly use TOP's MySQL slow-log source, also grant
`SELECT ON mysql.slow_log` and configure `slow_query_log=ON` with
`log_output=TABLE`.
## Verify the account in Desktop
1. Open **Settings**.
2. Select **Add target**, or edit an existing target.
3. Enter the read-only account credentials.
4. Select **Test connection** and save the target.
After the connection succeeds, open **Database** and click **Turn on
monitoring**. Its [prerequisite checks](/readyset-ai/rdst/desktop/monitoring/turn-on#prerequisites)
confirm the account can read every user's statements, that the statistics
extension or Performance Schema is collecting, and that new statements show up.
Each failed check comes with the fix.
`EXPLAIN ANALYZE` executes the query while collecting its plan and timing.
Keep the account read-only. Choose **EXPLAIN only** in monitoring setup to
inspect plans without executing the query.
## Readyset caching uses different permissions
The grants above cover RDST Desktop diagnostics, analysis, and current shallow
cache deployment. A deep Readyset deployment additionally snapshots tables and
follows the replication stream, requiring broader upstream permissions and
server configuration.
* [PostgreSQL Readyset Cache permissions](/cache/connect/postgres)
* [MySQL Readyset Cache permissions](/cache/connect/mysql)
Use a separate deployment credential when possible. Don't add replication or
administrative privileges to a diagnostics-only RDST account unless you're
intentionally deploying deep caching.
## Why least privilege matters
Database permissions are the final enforcement boundary. Audit direct,
inherited, default, `PUBLIC`, and routine-execution privileges: a role with no
direct table-write grant may still inherit broader access or invoke a
security-definer routine with side effects.
On MySQL, incomplete Performance Schema access can make unavailable index
counters look like zeroes, potentially producing a false "unused index"
finding. Confirm permissions and collection settings before acting on that
recommendation.
# Demo (/readyset-ai/rdst/desktop/demo)
import { Callout } from 'fumadocs-ui/components/callout'
import { Step, Steps } from 'fumadocs-ui/components/steps'
The demo starts a local orders database, a QueryPilot router, and Readyset.
It runs traffic so you can compare throughput with caching on and off.
Docker is required.
This demo tests caching against sample data. To monitor your own database,
[connect it](/readyset-ai/rdst/desktop/connect) and
[turn on monitoring](/readyset-ai/rdst/desktop/monitoring/turn-on).
## What the Readyset Platform is
QueryPilot routes SQL traffic and selects queries to cache. Readyset serves the
cached results. The [QueryPilot documentation](/querypilot/what-is-querypilot)
describes the architecture.
## What RDST sets up for you
RDST creates and manages the local containers for the orders database, Readyset,
and QueryPilot.
The first run downloads container images. Later runs can reuse them.
The environment cleans itself up after an hour, and **Shut down demo** removes
it immediately. Nothing it creates touches any database you've configured.
## Running it
**Start the demo.** RDST pulls the images if it needs to, starts Postgres,
loads an orders dataset, and brings up Readyset and QueryPilot in front of
it.
**Start traffic.** A workload runs against the database through both paths,
directly to Postgres and through QueryPilot, so the two can be compared under
identical load.
**Turn QueryPilot on.** The Accelerator picks queries to cache according to
the policy you choose, creates the Readyset caches, and routes matching
traffic to them. The chart marks the moment it takes effect.
## Reading the chart
With traffic running and caching off, both paths carry the same load, because
every query reaches Postgres either way. QueryPilot is proxying, not yet
accelerating.
Switching QueryPilot on changes that. In the run above, throughput through the
platform went from matching Postgres to roughly 1.6k queries per second against a
19 qps baseline, and the green line shows the moment the caches came online.
**Caching policy** decides which queries the Accelerator picks. **Most frequent**
targets the queries that run most often, and **Most expensive** targets the ones
consuming the most database time. Switching between them resets the comparison
window and marks the chart, so the effect of each choice is visible separately.
**Show per-query detail** breaks the same numbers down by query, with hit counts
and average latency for each path.
Check per-query detail to see which queries account for the measured change.
## Moving on to your own database
1. [Connect your database](/readyset-ai/rdst/desktop/connect).
2. [Turn on monitoring](/readyset-ai/rdst/desktop/monitoring/turn-on).
3. Expand a query on the Database page and select **Benchmark** to compare it
with a [local Readyset cache](/readyset-ai/rdst/desktop/monitoring/database-page#benchmarks).
The demo's measurements describe its sample workload. Benchmark your own queries
before deciding whether caching helps your application.
# RDST Desktop (/readyset-ai/rdst/desktop)
import { Cards, Card } from 'fumadocs-ui/components/card'
RDST Desktop monitors PostgreSQL and MySQL from your computer. It records query
activity, checks for changes, and runs background analyses. Open the Inbox to
review findings or the Database page to inspect a query and its history.
Closing the window keeps RDST running in the system tray on Windows and Linux,
or in the menu bar on macOS. Your computer must be awake and connected to the
database to collect readings.
## The app's navigation
| Page | What you'll find |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Home** | Connection and monitoring status |
| **Database** | [Query charts and analyses](/readyset-ai/rdst/desktop/monitoring/database-page), plus the [Report tab](/readyset-ai/rdst/desktop/monitoring/nightly-report) |
| **Inbox** | [Findings and actions](/readyset-ai/rdst/desktop/monitoring/inbox) |
| **Agent activity** | [Analysis, AI calls, and diagnostic statements](/readyset-ai/rdst/desktop/monitoring/agent-activity) |
| **Ask** | [Questions about your data](/readyset-ai/rdst/desktop/ask) |
| **Try the demo** | [A local caching demo](/readyset-ai/rdst/desktop/demo) |
| **Settings** | [Connections, AI access, and local data](/readyset-ai/rdst/desktop/settings) |
## Requirements
* A PostgreSQL or MySQL database reachable from this computer.
* A [read-only account with statistics access](/readyset-ai/rdst/desktop/database-permissions).
* Query statistics enabled. [Turn on monitoring](/readyset-ai/rdst/desktop/monitoring/turn-on)
checks the prerequisites.
* A Readyset sign-in for AI monitoring. Local monitoring works without AI.
* Docker for the caching demo and benchmarks.
The [CLI](/readyset-ai/rdst/cli) shares RDST's local configuration and saved
queries under the same operating system user.
# Get started (/readyset-ai/rdst/desktop/install)
import { Step, Steps } from 'fumadocs-ui/components/steps'
## Desktop download
Choose your platform at [readyset.io/downloads](https://readyset.io/downloads).
The stable download routes open the downloads page:
| Platform | Download | Install |
| -------------------- | --------------------------------------------------------------------------------------- | --------------------------------------------------------- |
| macOS, Apple Silicon | [DMG](/docs/download/rdst/macos-arm64) | Open the disk image and drag RDST Desktop to Applications |
| Windows, x64 | [Installer](/docs/download/rdst/windows-x64) | Run the installer. It installs for your user |
| Linux, x64 | [AppImage](/docs/download/rdst/linux-appimage) | Mark it executable and run it |
| Linux, x64 | [Debian package](/docs/download/rdst/linux-deb) or [RPM](/docs/download/rdst/linux-rpm) | Install with your package manager |
For an AppImage, replace the filename with the one you downloaded:
```bash
chmod +x rdst-desktop.AppImage
./rdst-desktop.AppImage
```
## First launch
**Choose how RDST uses AI.** The first screen asks you to sign in to
Readyset. AI monitoring needs a sign-in, so pick **Sign up or sign in**.
**Add Anthropic key** below it covers deep analysis and reports, and doesn't
replace signing in. See [AI access](/readyset-ai/rdst/desktop/ai-credits).
**Connect a database.** Fill in the connection details, or paste a
connection URL into **Quick setup**, and select **Test & connect**. You can
also discover databases at AWS, Supabase, Neon or DigitalOcean. See
[Connect a database](/readyset-ai/rdst/desktop/connect).
**Let RDST see your queries.** RDST checks that it can read the database's
query statistics and shows the fix for anything missing.
**Select Turn on monitoring.** It opens the
[turn-on dialog](/readyset-ai/rdst/desktop/monitoring/turn-on) on the
database's Database page. Pick how deep analysis reads a query and turn it
on. The first readings arrive within seconds.
When your account's AI usage limit is reached, AI monitoring stops gracefully,
local monitoring keeps running, and the app asks you to contact us.
## Running in the tray
Closing the window hides it. RDST keeps monitoring from the system tray on
Windows and Linux, or the menu bar on macOS. Select the icon to reopen it.
**Start at login** is enabled on the first packaged launch where supported.
RDST starts in the background without opening a window. Clear **Start at login**
in the tray menu to disable it. Linux AppImage builds don't support this setting.
| Tray item | Action |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Open RDST | Show the window |
| Inbox | Open the [Inbox](/readyset-ai/rdst/desktop/monitoring/inbox) |
| Monitoring | Turn monitoring on or off for a database or all databases; see [tray controls](/readyset-ai/rdst/desktop/monitoring/pause-and-limits#control-monitoring-from-the-tray) |
| Start at login | Control startup with your computer |
| Notifications | Enable or disable system notifications |
| Send test notification | Check notification delivery |
| Open logs folder | Open [backend logs](/readyset-ai/rdst/desktop/monitoring/troubleshooting#the-backend-log) |
| Quit RDST | Stop the app and monitoring |
The icon shows whether RDST is running, needs attention, or has monitoring off. Hover it for details.
Monitoring can't collect while your computer is asleep or off. The chart keeps
that gap when collection resumes.
## Updates and upgrades
Published main releases download updates in the background. Use the app's
restart control or quit RDST from the tray to apply a downloaded update.
Preview and local builds need a manual reinstall.
On the first launch after an upgrade, RDST may migrate its data files. Leave
**Upgrading your data, this can take a minute** to finish. Failed startup offers
**Try again** and **Open logs**.
Update a separately installed CLI too. An older CLI may read saved queries but
fail to write data upgraded by the desktop app. See
[Data and retention](/readyset-ai/rdst/desktop/monitoring/data-and-retention).
## Running in a browser
Install the [CLI](/readyset-ai/rdst/cli/getting-started#install) and run
[`rdst web`](/readyset-ai/rdst/cli/integrations/web). It serves the same database,
Inbox, and report interface locally.
Keep `rdst web` running to collect readings. Closing a browser tab doesn't stop
the server, but browser notifications need an open tab. The browser interface
has no desktop tray or start-at-login controls.
# Actions (/readyset-ai/rdst/desktop/monitoring/actions)
import { Callout } from 'fumadocs-ui/components/callout'
Actions is where RDST tells you what to change. While monitoring runs, it keeps
a ranked list of changes for each database, built from what it measures: which
queries take the database's time, which indexes they read, how hard each table
is written to, and how the server is set up. Each action says what to change,
why it matters for this database, and the SQL or steps to do it.
RDST never changes your database. It doesn't run the SQL in an action. You
run it, through your own migration or change process, and RDST checks the
database afterwards to see that it happened.
## Kinds of change
| Kind | Where it comes from |
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Add an index** | The index plan. RDST reads the deep analyses of your busiest queries, works out which indexes would speed them up, and weighs each one against the writes it would cost on its table |
| **Drop an index** | The index plan. An index no query reads, or one another index already covers, still costs every write. RDST suggests dropping it only when it's confident or the table is written to heavily |
| **Rewrite a query** | Deep analysis, which tests the rewrites it suggests. One shows up when it ran at least 20% faster, or the planner estimates 20% less work, on a read that takes at least 2% of database time |
| **Server settings** | Database checks, such as connections near the limit, sorts spilling to disk, or a buffer that's too small for the measured cache hit ratio |
| **Maintenance** | Database checks, such as tables that need a vacuum, stale statistics, or transactions left open |
The index plan also lists the indexes to keep, and smaller adds that would help
a little but still cost every write. Index changes on PostgreSQL use
`CREATE INDEX CONCURRENTLY` and `DROP INDEX CONCURRENTLY`, so writes keep
flowing while they run. Each index change comes with the SQL to undo it.
A drop on a database with replicas carries a note to check them first: a
replica can read an index the primary doesn't.
## When actions update
Actions follow the database as it changes. You don't need to start anything.
* **First plan.** When you turn monitoring on, RDST deep analyzes the 10
busiest reads first. The first plan is ready once they're done, usually
within about 10 minutes. Until then, Actions shows how many of those
analyses have finished.
* **Index plan and rewrites.** About every 5 minutes, RDST rereads table,
index and setting statistics and rebuilds the plan from them and the latest
deep analyses. A deep analysis that finishes can add or change an action on
the next pass.
* **Settings and maintenance.** Database checks run when the database is added,
again once there are a couple of minutes of traffic to measure rates, and
then every 15 minutes. They don't use AI.
* **Index changes.** When RDST sees an index appear or disappear, the plan and
the actions follow on the next pass.
Each kind of change says when it was last checked and when it's checked next.
## How they're ranked
Every action gets a score for how much database time it should save. For an
index or a rewrite, that's the share of database time its queries take times
how much faster they'd get. Settings and maintenance findings are scored by how
serious the check rates them.
While AI monitoring is on, a short AI read of each database's open actions
adjusts the order by how urgent each one looks now, and can retire one it's
sure no longer applies.
Actions are grouped into tiers by score:
| Tier | What goes there |
| ------------ | -------------------------------------------------------------------- |
| **Do first** | The changes that save the most: up to three per database |
| **Then** | Changes that still save a meaningful part of what the best one saves |
| **Later** | Small wins |
{/* REDESIGN: tier names Do first / Then / Later land with CL 16395. Main shows High impact,
Worth doing and Small win. */}
## Why each change matters
Each new change gets a one-sentence explanation tied to this database's
evidence: the queries it touches, their share of database time, and the writes
it costs or saves. The index plan gets two or three sentences on why its
changes fit together.
RDST writes these with a short AI call, once per change. Changes found close
together share a call, and each database gets at most 3 a day, at least 15
minutes apart. A change found after that day's calls are used shows without its
explanation until the next day's first call. Nothing new means no call.
## What you can do with one
Open an action to see its steps, its SQL and the queries it affects. From
there:
| Choice | What it does |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Copy** the SQL | Copies the statements to run, and the undo statements where there are any |
| **Copy as migration** | Copies the change as a migration for Alembic, Prisma, Rails or Flyway |
| **I did this** | Tells RDST you made the change. It rereads the database's indexes and statistics right away. If the change doesn't show yet, it says so and checks again in a few minutes |
| **Set aside** | Puts it away until a date you pick, with a reason. It comes back on its own after that |
| **Dismiss** | Closes it, with a reason. It stays closed until you bring it back, whatever the database does |
| **Bring back** | Reopens something you set aside or dismissed |
{/* REDESIGN: Copy as migration, Set aside with a date and reason, and Dismiss with a reason
land with CL 16395. Main has Not now (one week) in place of Set aside. */}
A rewrite asks you to check that it returns the same rows before you ship it.
## After you make a change
RDST closes an action on its own when the fix shows up in the database: the new
index exists, the dropped index is gone, the setting moved the way the action
asked, the maintenance ran, or the old query stopped running in favor of the
rewrite. You don't need to press **I did this** for that to happen.
For an index or a rewrite, RDST then measures the queries it was meant to help,
before and after the change:
* **Verified** means they got faster, with the before and after latency.
* **Didn't help** means they didn't, and the change may be worth undoing.
Applied changes, with these results, stay in the action history. You can undo
an index change with its undo SQL and tell RDST with **I undid this**.
{/* REDESIGN: the Applied view and I undid this land with CL 16395. Main lists these under
Done recently. */}
An action also closes when its evidence fades, for example when the query it
was about stops carrying load. If the same problem comes back later, the action
reopens.
## Actions and the Inbox
The [Inbox](/readyset-ai/rdst/desktop/monitoring/inbox) holds things that
happened and need you, like a query that slowed down or a lost connection.
Actions holds the standing list of changes worth making. When new changes are
ready for a database, the Inbox gets one item that points to them.
{/* REDESIGN: the "changes ready" Inbox item lands with CLs 16375 and 16395. */}
## Without AI
Database checks and the index plan's measurements run without AI, so settings,
maintenance and drop-index actions keep coming with local monitoring. Changes
that come from deep analysis, the AI ranking and the written explanations need
AI monitoring. See [AI access](/readyset-ai/rdst/desktop/ai-credits).
# Agent activity (/readyset-ai/rdst/desktop/monitoring/agent-activity)
import { Callout } from 'fumadocs-ui/components/callout'
**Agent activity** in the sidebar is RDST's audit log. When RDST analyzes a
query, answers a question or checks your database, it's written here as it
happens, newest first.
## Read-only diagnostics
The page lists these diagnostic rules:
| Rule | What it means |
| ------------------------------- | ----------------------------------------------------------------------------------- |
| **Only reads** | Plans, statistics and `SELECT`s. Monitoring leaves data and schema changes to you |
| **EXPLAIN ANALYZE is bounded** | Read-only statements with execution timeouts. Open an entry to check its time limit |
| **No result rows go to the AI** | The AI sees SQL, plans, schema and sampled values. The log doesn't keep the values |
## What's listed
| Action | What it's |
| ------------------------- | ------------------------------------------------------------------------------------------------------- |
| **Deep analysis** | One query analyzed, with its key finding, like "suggested an index on events(event\_type, created\_at)" |
| **Skipped deep analysis** | A query RDST chose not to run, with the reason |
| **Attention checks** | The regular look for changes that need you |
| **Query classification** | The quick AI read of a new query |
| **Daily report** | The nightly report being written |
| **Ask** | A question you asked in [Ask](/readyset-ai/rdst/desktop/ask) |
| **Benchmark** | A comparison against a Readyset cache |
Routine work, like a day of attention checks, folds into one line per database
per day. Open it to see each hour, then each check.
Open any entry to see:
* **Statements**: each one RDST ran on your database, with literals shown as
`?`, what kind it was (EXPLAIN, EXPLAIN ANALYZE, a statistics read, a
parameter sampling read, a hypothetical index check), how long it took and
its time limit.
* **AI calls**: the model, what it was for, what kind of data it was sent,
and tokens.
The once-a-minute statistics read and the look at running sessions for parameter
values aren't listed one by one.
## Filter and export
Filter by **Time**, **Database** and **Action**. **Export CSV** downloads one
row per entry for the days you pick.
The log keeps 90 days.
Costs for AI calls through your Readyset account are what Readyset charged.
Costs for calls on your own Anthropic key are estimates from published prices.
# Data and retention (/readyset-ai/rdst/desktop/monitoring/data-and-retention)
import { Callout } from 'fumadocs-ui/components/callout'
## Where your data lives
Monitoring history stays on your computer, in RDST's data folder:
| Platform | Folder |
| --------------- | --------------------- |
| macOS and Linux | `~/.rdst` |
| Windows | `%USERPROFILE%\.rdst` |
**Settings**, then **Data & privacy**, shows the folder RDST is using.
The folder holds the readings, Inbox items, nightly reports, deep analyses,
the [Agent activity](/readyset-ai/rdst/desktop/monitoring/agent-activity) log,
and your saved queries. The [CLI](/readyset-ai/rdst/cli) uses the same folder,
so a query analyzed in one shows up in the other.
## How long things are kept
| Data | Kept for |
| --------------------------------------- | -------------------------------------------------------------------------------- |
| Per-minute readings | 48 hours for the top 1,000 queries by load; other tracked queries roll up sooner |
| Minute-level evidence for an Inbox item | As long as that Inbox item exists |
| 5-minute readings | 7 days |
| Hourly readings | 90 days |
| Daily readings | As long as monitoring has room |
| Sampled parameter values | 48 hours |
| Inbox and report event history | 30 days |
| Agent activity | 90 days |
RDST tracks the top 10,000 queries per database. The top 1,000 get full detail.
The remaining tracked queries keep 5-minute readings for 48 hours and hourly
readings for 30 days. Heavier new queries replace the lightest tracked ones;
traffic beyond the limit stays in database totals and the **Other** band.
Older readings roll up into coarser ones, so a 90-day chart still works after
the per-minute detail is gone.
## Disk use
Each monitored database has a local storage limit, 250 MB by default. When it
fills, RDST rolls up and removes the oldest minute-level readings first to make
room. Minute-level evidence for existing Inbox items stays available. The **Usage** panel in the
[monitoring settings](/readyset-ai/rdst/desktop/monitoring/pause-and-limits#monitoring-settings)
shows how much a database uses.
## Removing the data
Export any data you need before uninstalling RDST Desktop. Uninstalling removes
app data, including monitoring history. Updating the app keeps it. To keep the
app and clear the data, use **Remove all local data** in
[Settings](/readyset-ai/rdst/desktop/settings).
Database passwords are never in the data folder. They're in your operating
system's keychain. See [Settings](/readyset-ai/rdst/desktop/settings#password-handling).
# The Database page (/readyset-ai/rdst/desktop/monitoring/database-page)
import { Callout } from 'fumadocs-ui/components/callout'
The Database page shows one database: its live chart and query list, covered
here, and its [nightly reports](/readyset-ai/rdst/desktop/monitoring/nightly-report).
The changes worth making on it are in
[Actions](/readyset-ai/rdst/desktop/monitoring/actions).
The header under the database's name shows its monitoring state and how much
history RDST has. The green monitoring dot pulses while RDST collects readings,
with reduced motion when your computer requests it. The monitoring state
opens the [monitoring menu](/readyset-ai/rdst/desktop/monitoring/pause-and-limits#the-monitoring-menu),
where you can turn monitoring off or open the monitoring
settings.
Switching databases keeps you on the same page for the new database. From
Inbox or another page that isn't tied to one database, switching opens that
database's Database page.
## The chart
The chart stacks each query's share of the work, one colored band per query, so
the biggest bands are where your database spends its time. Switch what it plots:
| Metric | What it shows |
| -------------- | ------------------------------------ |
| **Queries/s** | Calls per second |
| **Latency** | Time per call |
| **Time spent** | Each query's recorded execution time |
* Hover a band to find its query in the list below. Click it to open that query.
* Drag across the chart to zoom into a stretch of time. **Reset zoom** goes back.
* When a query is open, the chart draws that query's own line with its name, so
you can always tell which one you're looking at.
A hatched band marks a gap in readings. **Monitoring paused: RDST couldn't reach
the database** links to the Inbox item for that outage. **No readings** marks a gap with no known cause.
For example, RDST or the computer may have been off.
## Health metrics
Under the chart, three tiles show the database as a whole. Click one to plot it.
| Tile | What it tells you |
| ------------------- | ---------------------------------------------------------------------------------------------------------- |
| **Connections** | Open connections against the server's maximum, and the peak |
| **Cache hit ratio** | The share of reads served from memory instead of disk. A drop means more reads go to disk, which is slower |
| **Rollbacks** | Transactions your app started and then cancelled, per minute, plus deadlocks |
## Time frames
Pick a preset above the query list: **5m**, **15m**, **1h**, **24h**, **7d**,
**30d**, **90d** or **All**. **Custom** takes any start and end date and time,
from 5 minutes up to all the history RDST has stored.
The chart, the list and every number on the page follow the time frame you
pick.
## The query list
The list ranks queries by their load in the time frame. Its heading says how
much of the total they cover, for example "These 10 queries are 97% of your
database load in the last 5 minutes".
RDST tracks at most **10,000 queries per database**. Heavier new queries
replace the lightest tracked queries. Traffic beyond that limit still counts
in the database totals and the chart's **Other** band. The top **1,000** queries
get full detail; the remaining tracked queries keep coarser history. See
[Data and retention](/readyset-ai/rdst/desktop/monitoring/data-and-retention).
Each row shows:
| Column | What it shows |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Score** | The deep analysis score out of 100. Lower is worse. While work is queued or running, it shows **Queued** with its place, **Analyzing** with the current step (sampling values, running EXPLAIN or asking the AI), or **Retrying**. Otherwise, it shows **No score** until the query is analyzed |
| **Query** | A name RDST gives it from what it does, like `COUNT on users and orders`, its status, and its SQL. Names are unique within a database; when two queries would share one, the later one gets a number, like `COUNT on events by props (3)` |
| **Calls/s** | Calls per second in the time frame |
| **Avg latency** | Mean time per call |
| **Share of time** | Its share of the database's recorded execution time |
Expand a row for the query's own charts, its deep analysis, its SQL and
classification, and its actions: **Analyze now** (or **View analysis progress**
while one is under way), **Benchmark**, which opens a
[benchmark](#benchmarks) with this query already picked, and **Send to Ask**.
**Analyze a query** above the list takes SQL you paste, runs a deep analysis on
it, and adds it to the list.
### Statuses
Every query gets one status, the first of these that applies, with a one-line
reason like "Traffic up 3.5x since yesterday 9:39 PM":
| Status | Meaning |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Needs attention** | It has an open [Act now](/readyset-ai/rdst/desktop/monitoring/inbox#act-now-watch-and-back-to-normal) item in the Inbox |
| **Watch** | It has an open Watch item |
| **Fix suggested** | Deep analysis found an index, a rewrite or a setting that would help |
| **Healthy** | Analyzed, with nothing to fix |
| **Not analyzed yet** | It hasn't had a deep analysis. The score column shows **Queued** with its place, **Analyzing** with the current step, or **Retrying** while work is under way |
| **Not analyzed** | RDST won't analyze it. Hover for the reason, for example "UPDATE changes data, so RDST doesn't run it" |
Needs attention and Watch come straight from the Inbox, so the list and the
Inbox always agree.
### Sort and filter
**Sort** orders the list by:
| Sort | Orders by |
| -------------------------------------------------- | ---------------------------------------------------- |
| **Impact** (default) | Database time in the time frame |
| **Biggest change** | The largest significant latency change |
| **Most frequent** | Calls per second |
| **Slowest** | Mean latency per call |
| **Score, lowest first** / **Score, highest first** | The deep analysis score. Queries without one go last |
Filters narrow it down:
* **Status**: any status above, or a classification such as **Missing index**,
**Broad scan**, **Join growth**, **Repeated work** or **Index bypassed**.
* **Table**: only queries that touch one table.
* **Search**: matches query text and table names. Press `/` to jump to it.
**Clear filters** resets them.
### Writes
`INSERT`, `UPDATE` and `DELETE` statements are hidden from the list by default.
**Show writes (N)** brings them back. They always count toward the chart and the
totals, and a heavy write can still raise an Inbox item. RDST never runs or
deep-analyzes them.
An Inbox item's link opens this page already set to that item's time frame
and filtered to the queries it's about. A banner at the top says which item
you came from, and **Show the last 24 hours** steps back out.
## Benchmarks
A benchmark runs one or more read queries over and over and measures latency
and throughput. The **Benchmarks** section at the bottom of the Database page
lists past runs. To start one:
1. Select **Benchmark** in the Benchmarks section, or on an expanded query,
which picks that query for you.
2. Under **Pick queries**, select the queries to measure. The list has this
database's monitored and saved queries, with a search box. Writes and schema
changes can't be picked. Queries with parameters take values, filled in with
values RDST observed where it can.
3. Choose **How long**:
* **Quick**: 30 seconds on one connection, with a short pause between runs.
Light load.
* **Standard**: 2 minutes on two connections, back to back, to see how much
throughput the query can reach. Higher load.
4. Choose **Where to run**:
* **Source only** measures your database.
* **Source plus Readyset** also runs the same queries at the same pace
through Readyset. RDST starts Readyset in a temporary container on this
computer first, which needs Docker and can add a few minutes. If Readyset
can't start, the benchmark still measures your database.
* **Readyset only** measures a Readyset deployment you've already set up for
this database: a target named after it with `-cache` on the end, like
`demoshop-cache`, which [rdst cache deploy](/readyset-ai/rdst/cli/cache/deploy)
adds. Queries Readyset hasn't cached still reach your database through it.
5. Select **Start benchmark**.
A benchmark runs against your live database and adds load to it while it runs.
Only reads run, and you can stop it at any time.
With Readyset in the run, RDST creates and warms a temporary cache for each
query, checks that the results match, and measures both sides. It drops the
temporary caches when the run finishes. The container can stay up for the next
run.
Use parameter values and a load that represent your application. Compare mean
and tail latency for each side. The result describes those queries, their
parameters, and the test workload.
The temporary Readyset uses shallow caching. It doesn't configure replication
or put Readyset in front of your application. See [Readyset Cache](/cache) for
a production deployment, or use
[rdst query cache-compare](/readyset-ai/rdst/cli/cache/compare) from the CLI.
# Deep analysis (/readyset-ai/rdst/desktop/monitoring/deep-analysis)
import { Callout } from 'fumadocs-ui/components/callout'
A deep analysis reads a query's execution plan and works out where its time
goes. When a change would help, it may suggest adding an index, rewriting the
query, or changing a database setting. It gives the query a score out of 100 and shows up in the query's
**Deep analysis** section on the [Database page](/readyset-ai/rdst/desktop/monitoring/database-page).
Background analysis runs up to 3 queries at a time per database, and up to 6
across all your databases. It saves the full analysis record, including plans,
findings, and suggested changes, and tests the rewrites it suggests. Tested
indexes and rewrites feed [Actions](/readyset-ai/rdst/desktop/monitoring/actions).
## How many run each day
Each database has two settings in the
[monitoring settings](/readyset-ai/rdst/desktop/monitoring/pause-and-limits#monitoring-settings):
* **Deeply analyze the top N queries per day** defaults to **10**. When
monitoring turns on, all of the top N get their first deep analysis right
away. You can pick 5, 10, 25, 50, 100, 200 or 500.
* **Analyze up to M new top queries per day** defaults to **25**. With
**Analyze new top queries** on, queries entering the top N for the first time
get analyzed automatically, up to this separate daily ceiling.
RDST re-ranks queries by their current load every few minutes. A query that
falls out of the top before its turn can give its place to a busier one.
When the day's ceiling is reached, the query isn't queued. RDST picks it up
on the next day if it's still in the top N. Daily ceilings reset at midnight
on your computer's clock. The Analyses list only contains work queued to run now.
## What gets analyzed
With **Deep analysis** on, RDST picks queries in this order:
* **Initial top queries get analyzed right away.** All of the top N are eligible
as soon as readings arrive. RDST aims to score the first page within about
10 minutes, with only a few analyses running at once to limit database load.
* **New top queries get analyzed automatically.** A query entering the top N for
the first time uses the newcomer setting while **Analyze new top queries** is on.
* **Top queries get analyzed again.** Each of the 15 busiest queries is eligible
for re-analysis once its last analysis is more than a day old. These runs
follow the daily ceiling, so scores keep up with your data and load.
RDST only analyzes reads. Writes, schema changes and session commands show
**Not analyzed**, with the reason on hover.
If a deep analysis gets an unusable answer from the AI, RDST retries once
right away and shows **Retrying**. Classifications get the same immediate retry.
If the AI service still doesn't answer, RDST tries again after 1, 5 and 30 minutes.
The row shows **Will retry at** with a time, then **Retrying** when the retry
is near. After that, the query waits for its normal turn.
## Analyze a query yourself
A query you pick always gets its turn first:
* **Analyze now** on a query row puts it at the front of the line, ahead of
every automatic analysis. It works with **Deep analysis** switched off and
after the day's automatic analyses are used. If the query's already waiting,
the row shows its place, like **Queued, 2nd**.
* **Analyze a query** above the list takes SQL you paste and analyzes it right
away.
You can deep analyze any query yourself, as many times as you like, from its
row or detail. Manual analysis always runs and never counts toward either
automatic daily ceiling. Raise the settings to have more queries analyzed
automatically.
## The values it uses
A query with parameters needs real values to run. RDST picks them in this
order and says which it used on the analysis:
| Analysis says | Where the values came from |
| ---------------------------------------------------- | ------------------------------------------------------------------------------ |
| Measured with values from live traffic | Values sampled from your app's own running queries |
| Measured with values taken from the table's own data | Real values read from the table, when none were sampled |
| Plan only | No real values were available, so RDST read the plan without running the query |
RDST never makes values up. With **EXPLAIN only** chosen, every analysis is plan
only. See [EXPLAIN ANALYZE or EXPLAIN only](/readyset-ai/rdst/desktop/monitoring/turn-on#explain-analyze-or-explain-only).
## The Analyses list
**Analyses** at the bottom of the sidebar lists the work that's queued or
running on every database:
* Deep analyses appear in one group per database that expands to each query.
They show **Analyzing now**, **Queued, 2nd** and so on, or **Retrying**.
* Reports appear while they're being written.
* Analyses you started appear while they're queued or running.
**Done today** stays collapsed at the bottom. Work that isn't queued isn't
listed. The score column on the Database page shows **Queued** with its place,
**Analyzing** with the current step, or **Retrying**.
Click a queued or analyzing item to open that query in the main list, on its
page, scrolled into view and highlighted. Your filters and sort stay as they
are. If a filter hides it, RDST offers **Show it**, which clears only the
filters needed to reveal it.
The **Deep analysis** group in a database's monitoring settings shows the same
list for that database.
## Read an analysis
Expand a query on the [Database page](/readyset-ai/rdst/desktop/monitoring/database-page)
and find **Deep analysis**. Start with its findings and score, then inspect the
plan and supporting measurements.
| Section | What to check |
| ------------------------- | ------------------------------------------------------------- |
| Execution plan | Operators that consume the most work |
| Estimates and actual rows | Differences that may point to stale statistics or a poor plan |
| Indexes | Suggested definitions and why they may help |
| Rewrites | Candidate SQL to review and test |
| Readyset assessment | Whether the query is supported for caching |
Measured timings appear only when RDST ran the query. Plan-only analysis uses
planner estimates. For parameterized SQL, the analysis labels where its values
came from.
Suggested changes need review and testing. RDST never applies them; the ones
worth making show up in [Actions](/readyset-ai/rdst/desktop/monitoring/actions). Use a
[benchmark](/readyset-ai/rdst/desktop/monitoring/database-page#benchmarks) to
measure a caching candidate, or [rdst analyze](/readyset-ai/rdst/cli/query-analysis/analyze)
for CLI analysis and follow-up questions.
Query execution has a timeout. Check the limit used by a run in
[Agent activity](/readyset-ai/rdst/desktop/monitoring/agent-activity).
# The Inbox (/readyset-ai/rdst/desktop/monitoring/inbox)
import { Callout } from 'fumadocs-ui/components/callout'
**Inbox** in the sidebar lists what RDST noticed on all your monitored
databases. The badge next to it counts unread items. **All databases** at the
top shows everything; pick a database there to see only its items. Opening an
item for another database switches the app to that database.
The Inbox holds things that happened and need you. The standing list of
changes worth making, like an index to add, lives in
[Actions](/readyset-ai/rdst/desktop/monitoring/actions). When new changes are
ready for a database, the Inbox gets one item that points to them.
{/* REDESIGN: the "changes ready" Inbox item lands with CLs 16375 and 16395. */}
## What raises an item
RDST raises an item when something changes in a way that matters for the
database. A consistently heavy query is covered in the
[nightly report](/readyset-ai/rdst/desktop/monitoring/nightly-report). The Inbox
focuses on changes and incidents.
| Item | When |
| ----------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Query latency increased** | A query that carries real load got slower than its own usual level |
| **Query plan may have changed** | A query's latency jumped in a way that points to a new plan |
| **Query traffic increased** | A query that carries real load is being called much more often |
| **New heavy query** | A query first seen in the last day is already carrying real load |
| **Query stopped running** | A busy query stopped. RDST skips queries that normally run in bursts |
| **Stuck query** | A query has been running for over a minute |
| Several queries at once | Several queries changed in the same burst, so they share one item named after the biggest. Smaller ones are listed as "N smaller queries also moved at the same time" |
| **Rollbacks or deadlocks increased**, **Cache hit ratio dropped**, connections near the maximum | A change across the whole database |
| **Can't reach the database** | RDST lost its connection. See [Notifications](/readyset-ai/rdst/desktop/monitoring/notifications#lost-connection) |
| **Sign in to resume alerts**, **Database password not set**, and similar | Monitoring needs something from you before it can carry on |
| **Daily report ready** | The nightly report has been written |
RDST considers queries near the top by database time, new heavy queries, and
changes that add substantial database work. Call count alone doesn't qualify a
cheap query for an alert. Smaller changes can appear in a report or as context
inside another item.
## Act now, Watch and back to normal
Items sit in sections by how urgent they are. Each item has a one-line reason
that says why, like "Act now: latency up 3.2x on a top-10 query today, still
happening".
| Section | What goes there |
| --------------------- | -------------------------------------------------------------------------------------------------------------- |
| **Act now** (red) | Hurting the database right now, like a lost connection or a big slowdown on a key query that's still happening |
| **Watch** (amber) | Worth a look today. Smaller changes and reports list last |
| **Recovered** (green) | Back to normal, or closed, in the last 24 hours |
RDST closes an item on its own once the measurements are back to normal for 3
checks in a row. The item turns green and names what recovered and when, like
"Latency back to normal at 11:25 PM". Something that's back to normal never
stays in Act now.
The tabs at the top filter to **Open**, **Act now** or **Watch**.
## Repeats
When the same burst happens again, RDST adds it to the existing item instead of
opening a new one. Expand the item to see each occurrence, newest first, with its
time and size.
A burst that repeats on a schedule, every hour or at the same time each day,
becomes normal. It's marked **Usual** and ranks Watch at most. It alerts again
only when an occurrence is bigger or lasts longer than usual, and the item says
so: "This happens every day around 2:00 AM, and this time it's bigger than
usual."
## Opening an item
Items start collapsed to one line. Click one to expand it and see the queries
involved, before and after numbers, and a small chart for each.
Each item links to the right place:
| Link | Opens |
| ------------------------- | ----------------------------------------------------------------------------------------------------------- |
| **Open query** | That query on the [Database page](/readyset-ai/rdst/desktop/monitoring/database-page), at the right section |
| **Open affected queries** | The Database page filtered to just the item's queries, set to the item's time frame |
| **Open this time frame** | The Database page zoomed to one occurrence, showing the tracked queries that ran then |
| **Check connection** | The connection details, for a lost connection or a missing password |
Some items also offer next steps as tiles, like **See when it stopped**, **See
what else ran then** or **Check its plan**. When the fix is outside RDST, the
item lists the steps, with SQL you can copy.
## Dismiss, snooze and mute
From an item's menu:
* **Dismiss** closes it. It stays silent for the rest of that episode.
* **Snooze** hides it for 1 hour, 1 day or 1 week.
* **Mute** stops that kind of alert for the query, the database or all
databases, for a day, a week, or until you unmute it.
Temporary mutes end at their set time. Alerts after that time follow your
notification settings again.
Muted alerts are still recorded. **Show muted** lists them, and
**Notification settings** holds the mute rules.
An item notifies once per episode. It notifies again only if the problem goes
away and comes back, or gets at least twice as bad as when you were last told.
# Continuous monitoring (/readyset-ai/rdst/desktop/monitoring)
import { Cards, Card } from 'fumadocs-ui/components/card'
import { Callout } from 'fumadocs-ui/components/callout'
Continuous monitoring keeps RDST running in the background, reading your
database's query statistics every minute. It learns what normal looks like for
each query, and when something changes in a way that matters, you hear about it.
Once monitoring is on, RDST:
* **Records** calls, latency and share of database time for up to 10,000
queries per database. Traffic beyond that still counts in the totals and
the **Other** band. The top 1,000 queries get full detail.
* **Spots changes** that matter: a busy query getting slower, a new heavy query,
a traffic spike, a query stuck for over a minute, connections near the limit.
* **Deep-analyzes** the heaviest queries in the background and tests the
indexes and rewrites it suggests.
* **Keeps a ranked list of [Actions](/readyset-ai/rdst/desktop/monitoring/actions)**:
the indexes to add or drop, the rewrites, the settings and the maintenance
worth doing, with the SQL to run. It checks that each change took effect and
measures whether it helped.
* **Files what it finds** in the [Inbox](/readyset-ai/rdst/desktop/monitoring/inbox),
most urgent first, and sends a
[system notification](/readyset-ai/rdst/desktop/monitoring/notifications)
for the things that need you now.
* **Writes a [nightly report](/readyset-ai/rdst/desktop/monitoring/nightly-report)**
on each database's day.
* **Logs analyses and AI work** in [Agent activity](/readyset-ai/rdst/desktop/monitoring/agent-activity),
with the diagnostic statements and AI calls for each action.
RDST never writes to your database. Monitoring uses read-only diagnostics,
and you apply any change yourself.
Monitoring runs inside the desktop app, which stays in your system tray (the
menu bar on macOS) after you close its window. See
[Get Started](/readyset-ai/rdst/desktop/install#running-in-the-tray).
## What you need
| Requirement | Notes |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| A connected database | PostgreSQL 13+ or MySQL 8.0+. See [Connect a database](/readyset-ai/rdst/desktop/connect) |
| Query statistics | `pg_stat_statements` on PostgreSQL, `performance_schema` on MySQL. RDST checks both when you turn monitoring on |
| A Readyset sign-in | AI monitoring needs it: change checks every few minutes, deep analysis and reports. An Anthropic key of your own covers deep analysis and reports, and doesn't replace signing in. See [AI access](/readyset-ai/rdst/desktop/ai-credits) |
Recording keeps working without AI. You still get the chart, the query list,
and locally measured alerts, including lost connections and stuck queries.
When your account's AI usage limit is reached, AI monitoring stops gracefully,
local monitoring keeps running, and the app asks you to contact us.
## Guides
# Nightly report (/readyset-ai/rdst/desktop/monitoring/nightly-report)
import { Callout } from 'fumadocs-ui/components/callout'
At the configured report time, RDST writes a report on each monitored
database's previous local day. Open it from the **Report** tab on the
[Database page](/readyset-ai/rdst/desktop/monitoring/database-page). A dot on
the tab means there's a new one, and the Inbox gets a **Daily report ready**
item.
## What's in it
The report is titled with the day it covers, like "Tuesday, September 29". A
section bar at the top jumps between its parts:
| Section | What it covers |
| ----------------- | ---------------------------------------------------------------------------------------------------------------- |
| **Health** | Database health: connections, cache hit ratio, rollbacks and other checks |
| **Numbers** | The day in numbers, compared with the day before when there's one |
| **Brief** | The day in brief: the top priorities, each with what it means for your app |
| **Actions** | Database actions in order, with SQL you can copy |
| **Chart** | The day's chart |
| **Spikes** | When load jumped, and which queries drove it |
| **What happened** | The day's alerts and events |
| **Queries** | Queries to look at, with their deep analysis |
| **All queries** | Every query, sortable by calls, latency, database time and change on the day before. **Download CSV** exports it |
Report metrics come from recorded readings and database checks. The written parts, like the brief, come
from AI working from those numbers.
To work through the changes themselves, open
[Actions](/readyset-ai/rdst/desktop/monitoring/actions), which tracks each one
until it's applied and measured.
A report doesn't wait indefinitely for queued analyses. When its analysis wait
ends, it uses the available findings and plans to finish.
## Other days
Use the day picker to open an earlier report. **Print / Save as PDF** in the
report's toolbar gives you a copy to share.
## Write one now
The report menu has **Run today's snapshot now**. It covers today so far. Progress shows the
history, analyses, health checks, and summary as they finish.
## Change the time or turn it off
In the [monitoring settings](/readyset-ai/rdst/desktop/monitoring/pause-and-limits#monitoring-settings),
**Nightly report** turns it off for a database, and **Report time** moves it to
another hour.
If RDST wasn't running at report time, it writes the latest day's report when
it next starts. A day with no readings gets no report.
# System notifications (/readyset-ai/rdst/desktop/monitoring/notifications)
import { Callout } from 'fumadocs-ui/components/callout'
import { Step, Steps } from 'fumadocs-ui/components/steps'
Every alert lands in the [Inbox](/readyset-ai/rdst/desktop/monitoring/inbox).
Eligible items can also send a Windows notification, macOS alert, or Linux
desktop notification. Delivery follows your event switches, daily notification
setting, and operating system permissions. Click a notification to open the item.
Notifications come from the desktop app, so they keep coming while its window is
closed, as long as RDST is running in the tray.
## What notifies
Open **Inbox**, then **Notification settings**. Under **Notify me when**, each
event has its own switch:
| Event | Default |
| ---------------------------------------------------------------------------------------------------------------- | ------- |
| **Database unreachable**: RDST can't connect, so monitoring is paused | On |
| **Monitoring resumed**: RDST reached the database again | Off |
| **Stuck query**: a query has been running for over a minute | On |
| **Query changes**: a query that carries real load slows down or changes plan, or a new heavy query shows up | On |
| **Database-wide changes**: connections near the maximum, a rollback or deadlock spike, or a cache hit ratio drop | On |
| **Monitoring needs you**: sign-in expired, the database password is missing, or setup is incomplete | On |
An event that's off still files its Inbox item. It just doesn't pop up.
## How many a day
**Notifications a day** controls pop-ups per database. Check or change its
value in Notification settings. Past that setting, changes still land in the
Inbox and in the nightly report.
A lost connection doesn't count toward the cap and is never held back by it.
## Lost connection
When RDST can't reach a monitored database, it:
* raises **Can't reach the database** in Act now and requests a notification;
* shows the tray icon's problem state, with the database named in its tooltip;
* marks the gap on the chart as **Monitoring paused: RDST couldn't reach the
database**.
A single blip doesn't count. RDST waits for 2 failed checks in a row, or 2
minutes since the first failure, before it raises the item. When the database
is back, the item closes on its own and says how long the gap was.
## Turn notifications on or off
Two switches control the same thing:
* **Notifications** in the tray menu.
* **System notifications** in **Notification settings**.
**Send test notification** in the tray menu shows a sample, so you can check
your system lets them through.
## Windows notification settings
Windows can block notifications without telling the app. RDST checks on launch
and every few minutes, and if they're blocked, it says so in the window and in
the tray menu (**Notifications are blocked by Windows... Fix**). To turn them
on:
Open **Settings**, then **System**, then **Notifications**. The tray item
opens this page for you.
Turn **Notifications** on.
Find **RDST Desktop** in the list of apps and turn it on.
Check **Do not disturb** is off, or that RDST is allowed through it.
On macOS, allow RDST in **System Settings**, **Notifications**. RDST uses
alerts, which stay on screen until you dismiss them, so a lost database isn't
missed.
In the browser version, `rdst web`, there's no tray. **Browser notifications**
in Notification settings pop up only while an RDST tab is open. Email and phone
alerts aren't available yet.
# Settings and monitoring limits (/readyset-ai/rdst/desktop/monitoring/pause-and-limits)
import { Step, Steps } from 'fumadocs-ui/components/steps'
## The monitoring menu
On the Database page, the monitoring state under the database's name (for
example **AI monitoring on**) opens a menu:
| Item | What it does |
| ----------------------- | ------------------------------------------------------------------------------------------------------- |
| **Turn off monitoring** | Stops reading this database, after you confirm |
| **Turn on monitoring** | Shown while monitoring is off. Opens the [turn-on dialog](/readyset-ai/rdst/desktop/monitoring/turn-on) |
| **Monitoring settings** | Opens the settings below |
## Monitoring settings
Each database has its own settings. Open them from the monitoring menu, or
with the gear button at the top right of the Database page. Changes apply right
away.
### What runs
| Setting | Default | What it does |
| --------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Monitor this database** | Off until you turn it on | Reads the database's query statistics every minute, tracking the top 10,000 queries. Switching it off asks you to confirm, like **Turn off monitoring** |
| **AI monitoring** | | Shows whether AI monitoring is on for this database and how many of your account's AI monitoring slots are used. See [the database limit](#the-database-limit) |
| **Deep analysis** | On | Analyzes new and top queries automatically. Off, only the analyses you start run |
| **Measure real timings** | On | On, RDST runs read-only `SELECT`s with `EXPLAIN ANALYZE`, each with a time limit, and records real timings. Off, it reads plans with `EXPLAIN` and your queries never run. The turn-on dialog sets this first |
| **Analyze new top queries** | On | Analyzes a query automatically the first time it enters the top N |
### Automatic analysis
| Setting | Default | What it does |
| -------------------------------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Deeply analyze the top N queries per day** | Top 10 | When monitoring turns on, all of the top N get their first deep analysis right away. They're re-analyzed as the ranking changes. Choose 5, 10, 25, 50, 100, 200 or 500. See [how many run each day](/readyset-ai/rdst/desktop/monitoring/deep-analysis#how-many-run-each-day) |
| **Analyze up to M new top queries per day** | 25 | Analyzes queries entering the top N for the first time. Beyond this daily ceiling, a query isn't queued; RDST picks it up the next day if it's still in the top N. This setting applies while **Analyze new top queries** is on |
| **Change checks** | Every 5 min | How often AI monitoring checks for new and changed queries: every 5 minutes, every 15 minutes, or hourly. Checking less often uses less AI, and changes take longer to reach the Inbox |
| **Pushes a day** | 3 | How many [system notifications](/readyset-ai/rdst/desktop/monitoring/notifications) this database can send a day: none, 1, 3, 5 or 10. Past the limit, alerts still land in the Inbox and the nightly report. A lost connection doesn't count toward it |
| **Nightly report** | On | Writes a [report](/readyset-ai/rdst/desktop/monitoring/nightly-report) on this database once a day |
| **Report time** | 12 AM | The hour the nightly report is written, on your computer's clock |
You can deep analyze any query yourself, as many times as you like, from its
row or detail. Manual analysis always runs and never counts toward either
automatic daily ceiling.
### Deep analysis and Usage
The **Deep analysis** group shows the queued and running work for this
database. It's the same list as
[Analyses](/readyset-ai/rdst/desktop/monitoring/deep-analysis#the-analyses-list)
in the sidebar.
**Usage** shows AI calls over the last 7 days and how much storage this
database's history uses. When your account's AI usage runs out, the app says
**You've run out of AI usage for now**. Local monitoring keeps running, and AI
resumes once your usage is raised. Use **Contact us** to ask for more.
## Control monitoring from the tray
Right-click the RDST icon in the system tray (click it in the macOS menu
bar).
Open **Monitoring** and pick **Turn monitoring off for** or **Turn
monitoring on for** the database. **Turn all monitoring off** and **Turn all
monitoring on** apply to every database.
Monitoring is either on or off for each database. When it's off, RDST stops
collecting readings. The history it already recorded stays, and the chart
shows the gap. The tray icon shows when monitoring is off for every database.
## Turn it off and on again
Pick **Turn off monitoring** in the monitoring menu, or switch off **Monitor
this database**, and confirm.
RDST stops reading the database. The charts keep the history recorded so far,
new changes go unnoticed until you turn it back on, and its AI monitoring slot
opens up for another database. **Settings**, then **Targets**, has the same switch for every
database.
To turn it back on, pick **Turn on monitoring** in the same menu. The
[turn-on dialog](/readyset-ai/rdst/desktop/monitoring/turn-on) runs its checks
again. A database with history picks up where it left off, and deep analysis
follows its automatic settings.
To stop RDST completely, pick **Quit RDST** from the tray menu.
## The database limit
RDST currently allows AI monitoring on up to 3 databases per account.
[Contact us for more](mailto:hello@readyset.io). Monitoring beyond that runs
locally without AI.
The app shows how many AI monitoring slots your account is using. The count
includes enabled databases even when they're unreachable, and it applies across
computers.
A database without an AI slot can use **Turn on local monitoring**. RDST records
it, draws its chart, lists its queries, and raises locally measured alerts.
Its AI change checks, automatic analyses, and written report summary are off.
The Database page labels this state as local monitoring.
To open up an AI slot, turn monitoring off for a database or remove its target.
Then select **Turn on AI monitoring** in the settings for the database you want
to use. If the app can't reach Readyset when you release a slot, the account
count can take a while to update.
Adding your own [Anthropic key](/readyset-ai/rdst/desktop/ai-credits#your-own-anthropic-key)
raises the limit to 10 databases. You still need to be signed in for AI
monitoring. [Contact us for more](mailto:hello@readyset.io).
Ask and individual Analyze calls don't claim an AI monitoring slot.
# Troubleshooting (/readyset-ai/rdst/desktop/monitoring/troubleshooting)
import { Callout } from 'fumadocs-ui/components/callout'
## The backend log
Most problems leave a trace in RDST's backend log. **Open logs folder** in the
tray menu opens it.
| Platform | Log file |
| -------- | ----------------------------------------- |
| Windows | `%APPDATA%\rdst-desktop\logs\backend.log` |
| macOS | `~/Library/Logs/rdst-desktop/backend.log` |
| Linux | `~/.config/rdst-desktop/logs/backend.log` |
The log rotates at 5 MB and keeps three older files. It can contain SQL and
error messages from your database, so check it before you share it.
## No notifications
1. Look at the tray menu. If it shows **Notifications are blocked by
Windows... Fix**, click it and follow
[Windows notification settings](/readyset-ai/rdst/desktop/monitoring/notifications#windows-notification-settings).
2. Check **Notifications** in the tray menu is ticked.
3. Pick **Send test notification** in the tray menu. If the test shows up,
notifications work.
4. Open **Inbox**, then **Notification settings**, and check the event is
switched on under **Notify me when**.
5. Check **Notifications a day** for that database. Past the cap, alerts wait in
the Inbox. A lost connection bypasses the daily cap. Its event switch and system notifications must still be on.
6. Check the alert isn't muted. **Show muted** in the Inbox lists muted alerts.
Notifications need RDST running. If you picked **Quit RDST**, nothing is
watching until you open it again.
## RDST can't connect
**Can't reach the database** in the Inbox means RDST's last checks failed.
* Click **Check connection** on the item to see the error and edit the host,
port, user or password.
* Check the database is up and reachable from this computer, including any VPN
or SSH tunnel it needs. A database inside WSL is reachable from Windows
through `localhost`.
* After a few failures in a row RDST checks less often, up to every 15 minutes.
Once a check succeeds, it starts reading again right away and closes the item.
* **Database password not set** means the password isn't in this computer's
keychain. Enter it again in [Settings](/readyset-ai/rdst/desktop/settings).
## No queries, or fewer than expected
* **Waiting for queries** means the database hasn't run anything since monitoring
started. Point your app or a test suite at it.
* **Writes are hidden.** `INSERT`, `UPDATE` and `DELETE` statements are hidden
from the list by default. Click **Show writes (N)** above the list.
* **Filters.** Check the status, table and search filters, and the time frame.
**Clear filters** resets them.
* **Only RDST's own queries.** The user RDST connects as can't read other users'
statistics. Grant `pg_read_all_stats` on PostgreSQL, or `SELECT` on
`performance_schema.*` on MySQL. When RDST notices, the Inbox item's
**Check setup** link runs the
[prerequisite checks](/readyset-ai/rdst/desktop/monitoring/turn-on#prerequisites)
again, with the fix for each.
* **SQL cut off.** Raise `track_activity_query_size` on PostgreSQL, or
`performance_schema_max_digest_length` on MySQL, to 4096 or more.
## Alerts or analyses stopped
* **Sign in to resume alerts** or **Sign in to continue deep analysis**: your
Readyset sign-in expired. Recording carries on; sign in again to restart the AI
checks.
* **Monitoring limit reached**: this database is past the
[database limit](/readyset-ai/rdst/desktop/monitoring/pause-and-limits#the-database-limit).
* When an [automatic daily ceiling](/readyset-ai/rdst/desktop/monitoring/deep-analysis#how-many-run-each-day)
is reached, additional queries aren't queued. They're picked up the next day
if they're still in the top N. You can analyze any query yourself at any time;
manual runs never count toward these ceilings.
* **You've run out of AI usage for now** means your account's AI usage limit
has been reached. Local monitoring keeps running. Use **Contact us** to ask
for more; AI resumes once your usage is raised. See
[AI access](/readyset-ai/rdst/desktop/ai-credits).
* RDST needs about a day of readings before it alerts on changes.
## RDST is slow to start after an update
An update can migrate RDST's data files on the first launch. The window says
**Upgrading your data, this can take a minute** while it runs. Leave it to
finish. If it fails, the window offers **Try again** and **Open logs**.
Still stuck? Use **Give feedback** in the sidebar, or ask in the RDST Discord,
and include the end of `backend.log`.
# Turn on monitoring (/readyset-ai/rdst/desktop/monitoring/turn-on)
import { Callout } from 'fumadocs-ui/components/callout'
import { Step, Steps } from 'fumadocs-ui/components/steps'
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
## Turn it on
**Open the Database page.** Pick the database in the selector at the top of
the sidebar, then click **Database**. If you haven't added one yet, see
[Connect a database](/readyset-ai/rdst/desktop/connect). After you add a
database, either from Targets or during your first launch, the
**Turn on monitoring** dialog opens next.
**Click Turn on monitoring.** It's on the Database page, and in the
monitoring menu under the database's name. RDST connects and runs its
prerequisite checks. Each one shows **Passed** or the fix for your provider, with the SQL
to copy. Fix a step and RDST checks again on its own.
**Choose how deep analysis reads a query.** See
[EXPLAIN ANALYZE or EXPLAIN only](/readyset-ai/rdst/desktop/monitoring/turn-on#explain-analyze-or-explain-only) below.
**Click Turn on monitoring** in the dialog. The first reading starts right
away.
If the database can't be reached, the dialog opens its connection details so you
can fix the host, port or password and test it right there.
If a required check fails, the button reads **Turn on anyway**. Monitoring turns
on, and RDST starts reading queries once the required checks pass.
A recommended check that isn't met says what's wrong and **Here's how to fix
it**, with the SQL and whether it needs a database restart. Monitoring works
without it.
When you're signed in, the dialog explains that turning on monitoring includes
AI checks through Readyset.
When AI isn't available, the monitoring switch area says **You won't get
automatic AI recommendations for this database.** It offers **Sign in** when
you're signed out, or **Manage databases** when your account's database slots
are all in use.
When you're signed out, the dialog's main action is **Sign in to turn on AI
monitoring**. It explains that local monitoring keeps charts and connection
alerts running. **Turn on local monitoring** lets you continue without signing
in. See
[AI access](/readyset-ai/rdst/desktop/ai-credits).
## Prerequisites
RDST reads the statistics your database already keeps. It needs those
statistics switched on, and permission to read them for every user, not just its
own sessions.
| Check | What it means |
| ------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| pg\_stat\_statements is loaded at server start | It's in `shared_preload_libraries`. Changing this needs a restart |
| pg\_stat\_statements is created in your database | `CREATE EXTENSION IF NOT EXISTS pg_stat_statements;` |
| Permission to read every user's statements and activity | The user is a member of `pg_read_all_stats`, or a superuser |
| RDST sees new statements as they run | RDST runs a tagged test statement on a second session and confirms the statistics picked it up |
| Long queries are captured in full (recommended) | `track_activity_query_size` is 4096 or more, so long SQL isn't cut off |
To grant the permission:
```sql
GRANT pg_read_all_stats TO rdst_readonly;
```
| Check | What it means |
| ----------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| performance\_schema is on | `performance_schema = ON`. Changing this needs a restart |
| Permission to read performance\_schema | `SELECT` on `performance_schema.*` |
| Statement statistics are being collected | The `global_instrumentation` and `statements_digest` consumers are on |
| RDST sees new statements as they run | RDST runs a tagged test statement on a second session and confirms the statistics picked it up |
| Recent statements are kept for analysis (recommended) | The statement history consumers are on, so deep analysis can use real parameter values |
| Long queries are captured in full (recommended) | `performance_schema_max_digest_length` is 4096 or more |
To grant the permission and turn the consumers on:
```sql
GRANT SELECT ON performance_schema.* TO 'rdst_readonly'@'%';
UPDATE performance_schema.setup_consumers SET ENABLED = 'YES'
WHERE NAME IN ('global_instrumentation', 'statements_digest');
```
The `UPDATE` resets when MySQL restarts. On RDS and Aurora, set the matching
`performance_schema_consumer_*` parameters in your DB parameter group to keep
it.
For the full set of grants, including a read-only account to start from, see
[Database permissions](/readyset-ai/rdst/desktop/database-permissions).
## EXPLAIN ANALYZE or EXPLAIN only
When RDST deep-analyzes a query, it asks the database for the query's plan. The
dialog asks how:
| Option | What happens |
| -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Run EXPLAIN ANALYZE on problem queries** (recommended) | RDST runs the read-only query with a time limit and records real timings. It adds a little load to the database |
| **EXPLAIN only** | RDST gets the plan without running the query. The statement isn't executed. Other diagnostic reads still run, and there are no measured query timings |
This applies to automatic analyses and the ones you start. You can change it
later with **Measure real timings** in the
[monitoring settings](/readyset-ai/rdst/desktop/monitoring/pause-and-limits#monitoring-settings).
RDST only runs `SELECT` statements. It never runs an `INSERT`, `UPDATE`,
`DELETE` or schema change, whichever option you pick. Every execution has a timeout, shown in
[Agent activity](/readyset-ai/rdst/desktop/monitoring/agent-activity).
**Include deep analysis** is on by default in the turn-on dialog. Turn it off
and the EXPLAIN ANALYZE and EXPLAIN only options are disabled, and no automatic
analyses run. Choosing EXPLAIN ANALYZE shows a short warning that confirms it
runs on problem queries. You can change **Deep analysis** later in the
[monitoring settings](/readyset-ai/rdst/desktop/monitoring/pause-and-limits#monitoring-settings).
## The first few minutes
On a database's first run, the Database page shows **This is your first run on
this database**, with what happens over the next few minutes:
| Step | What happens |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Found your queries** | The first reading lands right away. RDST reads every 10 seconds for the first 5 minutes, then once a minute, and the query list fills in |
| **First deep analyses** | All of the top N queries get their first deep analysis right away. N defaults to 10. Your first [Actions](/readyset-ai/rdst/desktop/monitoring/actions) follow once the 10 busiest reads are analyzed, usually within about 10 minutes. See [deep analysis](/readyset-ai/rdst/desktop/monitoring/deep-analysis#how-many-run-each-day) |
| **Nightly report** | Written at the report time set for this database, midnight by default |
Rows show **Classifying** until their quick AI read of the SQL and schema
arrives, then a classification such as **Missing index**. Rows waiting for a
deep analysis show their place, like **Queued, 1st**. **Hide** closes the
panel.
The panel only appears on a database's true first run. If you turn monitoring
off and on again, RDST picks up from the history it has, with no first-run
panel.
RDST needs about a day of readings before it alerts on changes, so it knows
what normal looks like for each query. A lost connection or a stuck query
alerts from the start.
### Waiting for queries
If nothing has run on the database yet, the page says **Waiting for queries**.
RDST shows what it sees, never simulated traffic, so send it some real work:
1. Start your app with this database as its connection, and click through the
pages that matter.
2. Or run your test suite or a load script against it.
3. Keep RDST running. The list updates as readings arrive; the window can be closed.
# Privacy and data handling (/readyset-ai/rdst/desktop/privacy)
import { Callout } from 'fumadocs-ui/components/callout'
RDST runs on your machine and connects directly to your database. Your
monitoring history, settings and reports stay on your machine, and RDST never
writes to your database. Connect it with a read-only database user: when the
user you add can write, RDST warns you and gives you the SQL to create a
read-only one.
This page covers what RDST keeps, what leaves your machine, and how to turn off
the parts you don't want.
## What RDST stores
RDST keeps its data on your own machine: the databases you've added, your
saved queries, monitoring history, reports and settings. None of it is copied
to Readyset.
**Remove all local data** in [Settings](/readyset-ai/rdst/desktop/settings)
deletes all of it.
## Database passwords
RDST saves database passwords in your operating system's secure credential
store: Keychain on macOS, Credential Manager on Windows and the Secret Service
on Linux. They're never written to disk in plain text and never leave your
machine.
On a system without a credential store, RDST keeps passwords in memory for the
session only and asks for them again next time.
## Your Readyset account
AI monitoring needs a Readyset sign-in. See [AI access](/readyset-ai/rdst/desktop/ai-credits).
Your account holds your sign-in, your AI usage, and a count of the databases
you monitor with AI. RDST counts each database by a fingerprint, a hash of its
engine, host, port and database name salted with your account, so Readyset can
count them without learning which databases they are.
Signed out, monitoring runs locally without AI and RDST doesn't contact your
account.
## What is sent to the AI
Only features that need reasoning call an AI model:
[continuous monitoring](/readyset-ai/rdst/desktop/monitoring) while AI
monitoring is on, [Analyze](/readyset-ai/rdst/desktop/monitoring/deep-analysis),
[Ask](/readyset-ai/rdst/desktop/ask), [schema discovery](/readyset-ai/rdst/desktop/schema),
and the written parts of reports. Everything else in the app works without an AI
call.
| Sent | Never sent |
| ----------------------------------------------------------------------------------------------------- | ---------------------------- |
| SQL text. Change checks and classification send it normalized, with literals replaced by placeholders | The rows your queries return |
| Execution plans from `EXPLAIN` and `EXPLAIN ANALYZE` | The contents of your tables |
| Schema metadata: table and column names and types, indexes, row estimates | Your database credentials |
| Measured numbers: calls, latency, share of database time, server settings | Your connection details |
| Descriptions you wrote in the semantic layer | |
Deep analysis runs the query on your own database with real values, sampled
from your app's running queries or read from the table, so the plan shows how
the query actually behaves. The analysis says which source it used. The
[Agent activity](/readyset-ai/rdst/desktop/monitoring/agent-activity) log
doesn't keep the values, and RDST deletes its local copy after 48 hours.
To stop automatic analyses from running queries with real values, turn off
**Deep analysis** in the
[monitoring settings](/readyset-ai/rdst/desktop/monitoring/pause-and-limits#monitoring-settings).
[Agent activity](/readyset-ai/rdst/desktop/monitoring/agent-activity) lists
every AI call with the model, what it was for, and the kinds of data it sent.
### Where requests go
| Work | Where it goes |
| ----------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| Change checks, query classification and ranking actions | Jev, Readyset's model for quick judgments, through your Readyset account. These calls carry normalized SQL with schema, index and timing evidence |
| Deep analysis, reports, Ask, with the included AI | Claude Haiku from Anthropic, through Readyset's AI service |
| Deep analysis, reports, Ask, with [your own Anthropic key](/readyset-ai/rdst/desktop/ai-credits#your-own-anthropic-key) | Straight from your computer to Anthropic's API. Readyset doesn't see these requests |
{/* TODO: hosted analysis runs on Claude Haiku from Anthropic once Faruk's change lands
(no Gerrit CL yet). Until then the hosted model is GLM 5.3 Flash through OpenRouter. Check
this row before publishing. */}
Under Anthropic's API terms, Anthropic doesn't train models on API data, and
it deletes API inputs and outputs within 30 days by default.
## What is sent to Readyset
Your database's rows and table contents are never sent to Readyset. With the
included AI, the requests described above pass through Readyset's AI service on
their way to the model, and Readyset records how much AI each account uses.
When you run a [comparison](/readyset-ai/rdst/desktop/comparisons) or the
[demo](/readyset-ai/rdst/desktop/demo), Readyset runs as a container **on your
machine**. It connects to your database locally to serve cached results, and the
data stays between those two processes. Nothing is transmitted to Readyset the
company, and the containers are removed when you are done with them.
## Usage telemetry
RDST reports usage telemetry so we can see which features are used and where
they fail. Signed out, it's anonymous. When you're signed in, it's linked to your
Readyset account and its email.
| Collected | Never collected |
| ------------------------------------------------------------------ | --------------------------------- |
| A random device identifier, and your account when you're signed in | IP-derived identity |
| RDST version, operating system, Python version | Query text |
| Which commands ran, and whether they succeeded | Table, column, or database names |
| Query **hashes**, so repeat analyses can be counted | Connection details or credentials |
| Cumulative counts of how often features are used | Anything your queries returned |
Queries appear only as hashes. The hash identifies that the same query was
analyzed twice; it cannot be turned back into the query.
### Turning it off
Set this environment variable before you start RDST:
```bash
export RDST_TELEMETRY=off
```
It turns telemetry off completely, and nothing else in RDST changes.
## Working in a restricted environment
For a database that nothing outside its network may reach, run RDST next to it.
Install the [CLI](/readyset-ai/rdst/cli) on a host inside the perimeter and use
[`rdst web`](/readyset-ai/rdst/cli/integrations/web) over SSH port forwarding,
which puts this same interface in your browser while the database connection
stays inside the network.
Signed out and without an LLM key, RDST makes no outbound requests other than
telemetry, which the setting above disables.
# Settings (/readyset-ai/rdst/desktop/settings)
Open **Settings** at the bottom of the sidebar. It has three tabs:
* **Targets** lists your databases, each with its connection check and a
**Monitoring** switch. **Add target** connects a new one by hand or imports
databases from a provider. See [Connect a database](/readyset-ai/rdst/desktop/connect).
* **AI** has your Readyset sign-in and your Anthropic key. See
[AI access](/readyset-ai/rdst/desktop/ai-credits).
* **Data & privacy** shows where RDST keeps its data, with **Remove all local
data**.
Each database's monitoring controls are on its Database page. The monitoring
state under its name opens the
[monitoring menu](/readyset-ai/rdst/desktop/monitoring/pause-and-limits#the-monitoring-menu),
and the gear button opens its
[monitoring settings](/readyset-ai/rdst/desktop/monitoring/pause-and-limits#monitoring-settings).
## Remove a database
Removing a database turns its monitoring off and cancels its queued analyses.
If you add a database with the same name again, monitoring starts off. Turn it
on when you're ready to collect readings.
## Password handling
Database passwords are stored in your operating system's keychain or read from
a named environment variable. `config.toml` stores the variable name, never its
value. Environment variables take precedence when set.
If you move RDST's data to another computer, enter the passwords and sign in
again. Copying the data directory doesn't copy keychain credentials.
## Local storage
The default data directory is `~/.rdst` on macOS and Linux, or
`%USERPROFILE%\.rdst` on Windows. `RDST_DATA_DIR` can select another directory.
The [CLI](/readyset-ai/rdst/cli) shares it under the same operating system user.
See [Data and retention](/readyset-ai/rdst/desktop/monitoring/data-and-retention)
for what monitoring keeps and for how long.
**Remove all local data** deletes RDST's data directory. Export anything you
need to keep first.
## Privacy
[Privacy and data handling](/readyset-ai/rdst/desktop/privacy) includes telemetry
controls. [Agent activity](/readyset-ai/rdst/desktop/monitoring/agent-activity)
shows background analyses, diagnostic statements, and AI calls.
# RDST (/readyset-ai/rdst)
import { Cards, Card } from 'fumadocs-ui/components/card'
RDST runs on your computer and monitors your PostgreSQL or MySQL databases.
It records query activity, checks for changes, and analyzes queries in the
background. The Inbox lists findings by urgency. A nightly report covers each
database's day.
You don't need a Readyset cache to use RDST.
## Desktop and command line
Use the desktop app for monitoring. Closing its window keeps it running in the
system tray or macOS menu bar. Monitoring stops when you quit RDST or your
computer sleeps.
Use the CLI for individual commands, scripts, and CI. It shares targets, saved
queries, and analyses with the desktop app under the same operating system
user. Keep both installations up to date so they can read and write the same
data files.
| Task | Guide |
| ------------------------------------- | --------------------------------------------------------------------- |
| Review a query's load and analysis | [Database page](/readyset-ai/rdst/desktop/monitoring/database-page) |
| Investigate a change | [Inbox](/readyset-ai/rdst/desktop/monitoring/inbox) |
| Read a database's daily health report | [Nightly report](/readyset-ai/rdst/desktop/monitoring/nightly-report) |
| Check SQL in a code change | [rdst scan](/readyset-ai/rdst/cli/query-analysis/scan) |
| Audit a fleet on a schedule | [rdst fleet audit](/readyset-ai/rdst/cli/fleet-and-audit/fleet-audit) |
| Connect Claude Code to RDST | [RDST MCP tools](/readyset-ai/rdst/cli/integrations/claude-code) |
Continuous monitoring is configured in the desktop or
[browser interface](/readyset-ai/rdst/cli/integrations/web). There isn't an
`rdst monitor` command. The MCP tools don't expose the monitoring Inbox or
nightly reports.
## Local and AI monitoring
Local monitoring records query statistics and keeps the charts running. AI
monitoring checks changes, analyzes queries, and writes report summaries.
Sign in to Readyset for AI monitoring. When your account's AI usage runs out,
the app says **You've run out of AI usage for now**.
When an AI usage limit is reached, AI monitoring stops gracefully. Local monitoring
keeps running, and the app asks you to contact us. See
[AI access](/readyset-ai/rdst/desktop/ai-credits).
## Readyset caching
Readyset is a SQL cache that runs alongside your database. RDST can
[benchmark a query with Readyset](/readyset-ai/rdst/desktop/monitoring/database-page#benchmarks)
or [deploy a cache from the CLI](/readyset-ai/rdst/cli/cache). These are optional
workflows. Monitoring doesn't require Docker or a Readyset deployment.
# Readyset Cloud (/readyset-cloud)
Readyset Cloud is a managed offering of Readyset. Readyset runs inside your own environment, right next to your application and database, while the Readyset team deploys, operates, and monitors it for you through a managed control plane.
Because Readyset stays inside your environment, your data never leaves your infrastructure. You get the performance benefits of Readyset's caching without taking on the operational burden of running it yourself.
## What you get
* **Deployed in your environment.** Readyset runs in your own VPC or Kubernetes cluster, alongside the database it caches. Your data and traffic stay within your network boundary.
* **Managed by the Readyset team.** Provisioning, upgrades, scaling, and health monitoring are handled for you by Readyset's managed control plane.
* **No operational burden.** You don't need to run, tune, or babysit Readyset infrastructure. The Readyset team keeps it healthy.
## Get started
Interested in Readyset Cloud? Get in touch with the Readyset team at [hello@readyset.io](mailto:hello@readyset.io) and we'll help you get set up in your environment.
# Authentication (/reference/authentication)
import { Callout } from 'fumadocs-ui/components/callout'
Clients authenticate to Readyset the same way they authenticate to your
upstream database: Readyset plays the server role in the client handshake and
verifies the supplied credentials before accepting the connection. Which
authentication methods are available depends on the client protocol,
PostgreSQL or MySQL.
## Allowed users
Readyset authenticates each connection against a set of allowed users.
[`--allowed-users`](/reference/config-options#--allowed-users) (env
`ALLOWED_USERS`) is a comma-separated list of `username:password` pairs; the
user from [`--upstream-db-url`](/reference/config-options#--upstream-db-url) is
accepted as well.
`--allowed-users` seeds the allowed-users set only on a deployment's
**first** startup. After that the set is persisted, so later edits to
`--allowed-users` are ignored. Manage users at runtime with
[`ALTER READYSET ADD/MODIFY/DROP USER`](/command-reference#alter-readyset-add-user)
instead. [User Management](/reference/user-management) covers the whole
lifecycle, from this bootstrap to runtime changes.
To inspect the users that can currently authenticate, read the
[`readyset.users`](/reference/system-views#readysetusers) view (passwords are
never exposed):
```sql
SELECT "user" FROM readyset.users;
```
To turn authentication off, start Readyset with
`--allow-unauthenticated-connections`. Clients then connect without
credentials, the allowed-users list is ignored, and Readyset uses the
`--upstream-db-url` credentials for its own upstream connections.
[Cache authorization](/reference/cache-authorization) does not apply in this
mode, because there are no client identities to authorize.
## PostgreSQL
Readyset supports two PostgreSQL authentication methods for client
connections. The
[`--postgres-authentication-method`](/reference/config-options#--postgres-authentication-method)
flag (env `POSTGRES_AUTHENTICATION_METHOD`) selects which one Readyset
requests during the connection handshake.
### scram-sha-256
The default. Readyset requests SASL authentication and verifies the client
with the SCRAM-SHA-256 challenge-response exchange, so the password is never
sent over the wire. This is the same method modern PostgreSQL servers use by
default, and it works with standard PostgreSQL clients and drivers without
extra configuration.
Over a TLS connection, Readyset also offers channel binding
(`SCRAM-SHA-256-PLUS`), which ties the authentication exchange to the TLS
session. Clients that support channel binding negotiate it automatically.
### cleartext
Readyset requests the password in cleartext and compares it directly. Because
the password crosses the connection unhashed, use this method only over a
[TLS](/reference/tls) connection or on a trusted network.
To select it:
```bash
readyset --postgres-authentication-method cleartext [other options]
```
To require every client to connect over TLS, set
[`--tls-mode`](/reference/config-options#--tls-mode) to `required`. Readyset
then rejects any non-TLS connection during the handshake.
## MySQL
Readyset supports two MySQL authentication plugins: `mysql_native_password`
and `caching_sha2_password`. The
[`--mysql-authentication-method`](/reference/config-options#--mysql-authentication-method)
flag controls which plugin Readyset advertises to clients during the MySQL
handshake.
### mysql\_native\_password
The legacy authentication plugin, used by default in MySQL 5.7 and available
in MySQL 8.0. It uses a challenge-response protocol based on SHA-1 hashing. No
additional infrastructure (TLS or RSA keys) is required for password exchange.
This was the default in earlier versions of Readyset and remains available for
environments that have not migrated to `caching_sha2_password`.
### caching\_sha2\_password
The default authentication plugin in Readyset (and in MySQL 8.0+). It is the
only built-in plugin in MySQL 9.x (which removed `mysql_native_password`). It
uses SHA-256 hashing and supports two authentication paths:
* **Fast auth (cache hit):** If Readyset has already verified a user's
password in a previous connection, it caches the password hash. Subsequent
connections for the same user skip the full exchange and authenticate using
only the cached hash. This is comparable in cost to `mysql_native_password`.
* **Full auth (cache miss):** On the first connection for a given user (or
after a restart, which clears the cache), the client must send the password
encrypted with the server's RSA public key or over a TLS connection. This
requires either TLS to be enabled or the client to obtain the RSA public key.
The password cache is in-memory and is cleared when Readyset restarts. The
first connection after a restart always performs full authentication.
### Configuration
Readyset defaults to `caching_sha2_password`. To use `mysql_native_password`
instead:
```bash
readyset --mysql-authentication-method mysql_native_password [other options]
```
Or using the environment variable:
```bash
MYSQL_AUTHENTICATION_METHOD=mysql_native_password readyset [other options]
```
### RSA key management
When `caching_sha2_password` is enabled, Readyset needs an RSA key pair for
full-auth password encryption on non-TLS connections.
#### Auto-generated keys
By default, Readyset generates a 2048-bit RSA key pair at startup and persists
it in the deployment subdirectory under
[`--storage-dir`](/reference/config-options#--storage-dir) (the storage
directory defaults to the current working directory) as two files:
* `caching_sha2_password_private_key.pem` (PKCS#1 PEM, mode `0600`)
* `caching_sha2_password_public_key.pem` (PKCS#8 PEM, mode `0644`)
On subsequent starts, Readyset loads the existing key from the private key file
so that the public key stays stable across restarts.
#### Supplying your own key
To use your own RSA key pair, place a PKCS#1 PEM-encoded private key at
`caching_sha2_password_private_key.pem` in the deployment subdirectory before
starting Readyset. Readyset loads this file at startup instead of generating a
new key, and derives the public key in memory.
#### Retrieving the public key
Clients that connect without TLS need the server's RSA public key to encrypt
their password. You can retrieve it with:
```sql
SHOW READYSET RSA PUBLIC KEY;
```
This returns a single row containing the PEM-encoded public key. Most MySQL
client libraries request this key automatically during the handshake (for
example, the `--get-server-public-key` option in the `mysql` CLI).
See the [command reference](/command-reference#show-readyset-rsa-public-key)
for details.
### Migrating from MySQL 8.x to 9.x
MySQL 9.x removed the `mysql_native_password` plugin entirely. Readyset
defaults to `caching_sha2_password`, so no configuration change is needed. If
you were previously overriding the authentication method to
`mysql_native_password`, remove that override:
1. Remove any
[`--mysql-authentication-method`](/reference/config-options#--mysql-authentication-method)
flag set to `mysql_native_password` (or the corresponding
`MYSQL_AUTHENTICATION_METHOD` environment variable).
2. Ensure your client drivers support `caching_sha2_password`. Most modern
MySQL connectors (MySQL Connector/J 8.0+, mysql2 for Node.js, Go `mysql`
driver, mysqlclient for Python) support it out of the box.
3. If you do not use TLS, verify that your client can retrieve the RSA public
key. Most drivers do this automatically, but some require an explicit option
(e.g., `--get-server-public-key` for the `mysql` CLI, or
`allowPublicKeyRetrieval=true` for JDBC).
If you use TLS between clients and Readyset, the RSA key exchange is not
needed. The password is sent securely over the encrypted channel, and full
auth completes without RSA.
### Performance considerations
* **Fast auth** is the common case in steady-state operation. Once a user has
authenticated at least once after startup, all subsequent connections for
that user use the cached hash. The performance is comparable to
`mysql_native_password`.
* **Full auth** occurs only on the first connection per user after a Readyset
restart (or if the cache is otherwise empty for that user). The RSA
encryption adds a small amount of overhead to this initial connection.
* The password cache is per-process and is not shared across Readyset restarts.
# Cache Authorization (/reference/cache-authorization)
import { Callout } from 'fumadocs-ui/components/callout'
Your database decides which users may read which tables, and it re-checks
those grants on every query. A [shallow cache](/concepts/shallow-caching) hit
is answered by Readyset without reaching your database, so without extra care
a cache would let a user keep reading a query after their access to the
underlying tables was revoked.
Cache authorization prevents that. Before Readyset serves a cache hit, it
confirms that the connected user is still allowed to run that query. Users who
are allowed are served from the cache as usual; users who are not are sent to
your database, exactly as if the query were uncached.
Cache authorization is on whenever [authentication](/reference/authentication)
is enabled. There is nothing to configure and no opt-out, and it adds no
latency to a cache hit.
## What a user sees
Whether a given user is served from a cache depends only on whether your
database grants that user access to the query:
* **Allowed** users get cache hits, at the same hit rate as before. A user's
identity never affects what is stored, so enabling cache authorization does
not shrink or partition the cache.
* **Users without access** are transparently routed to your database, which
applies its own permission check and returns the appropriate result or
error. They simply never get a cache hit for that query.
Because an unauthorized user is always sent to your database rather than
guessed at, cache authorization can never turn a permitted query into an
error or an error into data. Your database stays the single source of truth
for who may read what; Readyset only mirrors its decisions and never
reimplements grants, roles, or inheritance.
Readyset learns each user's access by asking your database, on its own
background connections, whether that user could run the cached query -- without
actually running it. These connections identify themselves as
`READYSET_ACL_POOLER` in your database's process list. No client connection is
ever used for this, and no check ever runs while a query is being served.
## Keeping up with privilege changes
Readyset re-reads each user's privileges from your database on a fixed
interval (default 5 minutes, set with
[`--cache-acl-refresh-interval-secs`](/reference/config-options#--cache-acl-refresh-interval-secs)).
This interval is the longest a user can keep being served cache hits after a
`GRANT` or `REVOKE` you issue directly on your database -- the same kind of
staleness window a cache already has for data via its TTL. A user who is
newly granted access converges just as quickly; until Readyset picks up the
change, their queries are simply proxied to your database.
Some changes take effect immediately, without waiting for the interval:
* **Creating a cache** authorizes every user against it in the background,
starting with its creator. Creating a cache is not itself a grant: the
creator is served from it only once their own privileges check out, the
same as everyone else.
* **[`ALTER READYSET ADD/MODIFY USER`](/command-reference#alter-readyset-add-user)**
re-checks that user; `DROP USER` forgets them. See
[User Management](/reference/user-management).
* **[`ALTER READYSET FLUSH PRIVILEGES`](/command-reference#alter-readyset-flush-privileges)**
re-checks everyone on demand. Run it right after a `GRANT` or `REVOKE` on
your database to apply the change without waiting for the next interval.
## Roles
### PostgreSQL
PostgreSQL evaluates privileges against a session's current role, and cache
authorization follows the same rule. A session that has switched roles with
`SET ROLE` is judged by that role's access, not the login user's. Readyset
does not decide role membership itself: a `SET ROLE` is forwarded to your
database on the session's own connection, and the session's identity changes
only if your database accepts it. Roles are authorized ahead of use, so
assuming a role does not cost a warm-up period.
### MySQL
On MySQL, a user is authorized under their default roles -- the same
privileges a fresh connection gets. Users whose access comes through roles are
fully supported as long as those roles are active by default (via
`SET DEFAULT ROLE` or `activate_all_roles_on_login=ON`).
A session that changes its active roles with `SET ROLE` mid-connection is not
served from the cache for the rest of that connection; its queries are proxied
to your database, which enforces privileges directly. Sessions that rely on
mid-connection role switching trade away caching on MySQL for now.
## Inspecting cache authorization
The [`readyset.cache_grants`](/reference/system-views#readysetcache_grants)
system view shows, for every user and cache, whether that user is currently
served from it and when Readyset last checked. Prometheus metrics under the
`readyset_cache_acl` prefix cover the same ground for dashboards, including how
often queries are routed to your database instead of served from a cache.
For a single connection,
[`EXPLAIN LAST STATEMENT`](/command-reference#explain-last-statement) reports
why its previous statement went to your database instead of a cache: a
`Readyset_reason` of `cache_acl_denied` or `cache_acl_unknown` means cache
authorization made the call.
## Limitations
* Cache authorization applies to [shallow caches](/concepts/shallow-caching)
on both MySQL and PostgreSQL. It does not apply under
[`--allow-unauthenticated-connections`](/reference/config-options), where
there are no user identities to authorize.
* It enforces table-level grants. Row-level security is handled separately by
Readyset's RLS-aware caching, which composes with cache authorization.
* [`SHOW CACHES`](/command-reference#show-caches) lists the query text of every
cache, including caches over tables a user cannot read.
# Advanced Configurations (/reference/config-options)
import { Callout } from 'fumadocs-ui/components/callout'
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
Readyset configuration options are set using command flags or environment variables.
For many common `readyset` options, you can set environment variables once instead of manually passing the flags each time you execute commands.
* To find out which flags support environment variables, see the documentation for each [configuration option](#configuration-options).
* To output the current configuration of Readyset and other environment variables, run `env`.
* When a command uses environment variables, the variable names are printed to the command's logs.
* To get a list of possible configuration options, run `readyset --help`.
Readyset prioritizes command flags, environment variables, and defaults as follows:
1. If a flag is set for a command, Readyset uses it.
2. If a flag is not set for a command, Readyset uses the corresponding environment variable.
3. If neither the flag nor environment variable is set, Readyset uses the default for the flag.
4. If there's no flag default, Readyset returns an error.
### Command flags
```
readyset --option value [FLAG OPTIONS]
```
### Environment variables
```
OPTION=value [ENV OPTIONS] readyset
```
## Configuration Options
#### `--address`, `-a`
The IP address/hostname and port that Readyset listens on.
This option accepts a list of addresses. Specify `--address` multiple times, or pass a comma-separated list via the `LISTEN_ADDRESS` environment variable, to bind several sockets at once (for example, to listen on both an IPv4 and an IPv6 interface).
If not set, Readyset follows the upstream database's own listening convention:
* **MySQL adapter:** `[::]:3307`, a dual-stack IPv6 wildcard socket, which accepts both IPv4 and IPv6 clients on Linux. Matches MySQL 8.0's `bind_address = '*'` default.
* **PostgreSQL adapter:** `127.0.0.1:5433` and `[::1]:5433`, separate IPv4 and IPv6 loopback sockets. Matches PostgreSQL's `listen_addresses = 'localhost'` default.
To listen only on IPv6, pass an IPv6 address explicitly. For example, use `--address '[::1]:5433'` for loopback, or `--address '[::]:5433'` for all interfaces. To listen on a specific pair of IPv4 and IPv6 interfaces, pass `--address` twice:
```
readyset --address 10.0.0.5:5433 --address '[2001:db8::1]:5433'
```
Or, via the environment:
```
LISTEN_ADDRESS='10.0.0.5:5433,[2001:db8::1]:5433' readyset
```
**Env variable:** `LISTEN_ADDRESS`
#### `--allowed-users`
Comma list of allowed `username:password` pairs to authenticate database connections with. If not set, the username and password in [`--upstream-db-url`](#--upstream-db-url) will be used. If `--allow-unauthenticated-connections` is passed, this will be ignored.
Naming a user twice starts the deployment mid-[password rotation](/reference/user-management#rotating-a-password-without-downtime). The first password is the current one and the second is the old password retained alongside it. Password rotations require MySQL mode.
**Env variable:** `ALLOWED_USERS`
`--allowed-users` seeds the allowed-users set only on Readyset's **first** startup for a deployment (together with the [`--upstream-db-url`](#--upstream-db-url) user). After that, the set is persisted and read from stored values, so later edits to `--allowed-users` are ignored; manage users at runtime with [`ALTER READYSET ADD/MODIFY/DROP USER`](/command-reference#alter-readyset-add-user) and inspect the current set via [`readyset.users`](/reference/system-views). See [User Management](/reference/user-management) for the full lifecycle.
#### `--auto-cache`
Enables Readyset's [automatic shallow caching](/concepts/shallow-caching/auto-caching) mode: every previously-unseen `SELECT` becomes a candidate for an automatically-created [shallow cache](/concepts/shallow-caching), with no `CREATE CACHE` statement or `/*rs+ ... */` hint required.
This flag is shorthand for setting [`--cache-mode`](#--cache-mode) to `shallow` and [`--query-caching`](#--query-caching) to `inrequestpath`. It is mutually exclusive with both of those flags; pass them explicitly only when you need finer-grained control than this convenience flag provides.
Bootstrap and introspection traffic (system-schema queries, session/user variables, non-deterministic functions like `NOW()` or `RAND()`) is filtered out by an [eligibility filter](/concepts/shallow-caching/auto-caching#eligibility-filter) and routed straight to the upstream database without creating a cache.
**Default:** off
**Env variable:** `AUTO_CACHE`
#### `--cdc-db-url`
The URL for snapshotting and replication from Readyset to the upstream database, or a list of URLs with the same syntax as [`--upstream-db-url`](#--upstream-db-url).
If omitted, --upstream-db-url will be used.
With a list, replication connects to the highest-weight server. When that server fails, replication moves to the next-highest-weight server. See [Failover](/reference/failover#automatic-failover-with-server-lists). Replication will use the new server until the next time the list changes or the next time the replication server fails.
By default, Readyset attempts to snapshot and replicate all tables in the
database specified in [`--cdc-db-url`](#--cdc-db-url). However, if the queries
you want to cache in Readyset access only a subset of tables in the database,
you can use the [`--replication-tables`](#--replication-tables) option to
narrow the scope accordingly. Filtering out tables that will not be used in
caches will speed up the snapshotting process.
**Env variable:** `CDC_DB_URL`
#### `--cache-acl-refresh-interval-secs`
How often, in seconds, [cache authorization](/reference/cache-authorization) re-reads each user's privileges from the upstream database. The interval bounds how long a user revoked on the upstream can keep being served cache hits; run [`ALTER READYSET FLUSH PRIVILEGES`](/command-reference#alter-readyset-flush-privileges) to re-check immediately instead of waiting for the next interval. Applies only when authentication is enabled.
**Default:** `300`
**Env variable:** `CACHE_ACL_REFRESH_INTERVAL_SECS`
#### `--cache-ddl-address`
Sets up a separate listener address dedicated to cache DDL statements (like `CREATE CACHE`, `DROP CACHE`, `DROP ALL CACHES`, and [hint-based cache creation](/concepts/shallow-caching/hint-based-caches)).
* **Not set (default):** Cache DDL statements are allowed from any connection on the main listening address.
* **Set:** Readyset opens a second listener on the specified address. Only connections arriving on that address are allowed to run cache DDL statements. Connections on the main [`--address`](#--address--a) listener will have cache DDL rejected.
This is an access control mechanism — it lets you expose the main proxy address to application traffic while restricting cache management operations to a separate, potentially internal-only, address.
**Env variable:** `CACHE_DDL_ADDRESS`
#### `--cache-mode`
Controls how `CREATE CACHE` (without an explicit `DEEP` or `SHALLOW` modifier) behaves.
**Possible values:**
* `deep`: Always attempt a streaming dataflow (deep) cache. Queries unsupported by the dataflow engine return an error.
* `shallow`: Always create a TTL-based shallow cache.
* `deep-then-shallow`: Attempt a deep cache first; if the query is unsupported by the dataflow engine, fall back to a shallow cache automatically.
**Default:** `shallow`
**Env variable:** `CACHE_MODE`
#### `--client-connect-timeout-ms`
How long, in milliseconds, a client has to complete the connection handshake and authenticate before Readyset closes the connection.
**Default:** `10000`
**Env variable:** `CLIENT_CONNECT_TIMEOUT_MS`
#### `--cleanup`
If supplied, clean up assets for the supplied deployment. If an upstream URL is supplied, also clean up various assets related to upstream (replication slot, etc.).
#### `--default-coalesce-ms`
Default coalescing window (in milliseconds) for shallow caches when no `COALESCE` clause is specified in `CREATE SHALLOW CACHE`. During this window, concurrent requests for the same query and parameter values are coalesced on a cache miss: only the first request goes to upstream, and the rest wait for its result.
Set to `0` to disable coalescing by default.
**Default:** `5000`
**Env variable:** `DEFAULT_COALESCE_MS`
#### `--default-ttl-ms`
Default TTL (in milliseconds) for shallow cache entries when no `POLICY TTL` clause is specified in `CREATE SHALLOW CACHE`. Each cache entry is considered stale after this duration since its last access.
**Default:** `10000`
**Env variable:** `DEFAULT_TTL_MS`
#### `--deployment-mode`
The operational mode for this Readyset process.
Possible values:
* `standalone`: Run an entire deployment of Readyset in this process (Adapter and Server).
* `embedded-readers`: Store cached query results with this Adapter instead of the Server. To use
this option, you must pass `--no-readers` when starting [`readyset-server`](readyset-server.md).
* `adapter`: Run this process as an Adapter with no locally cached data (requires a Server to be run
separately).
**Default:** `standalone`
**Env variable:** `DEPLOYMENT_MODE`
#### `--disable-create-publication`
Disable running `CREATE PUBLICATION` for PostgreSQL. If this flag is set a publication named `readyset` for all tables should be added manually on the primary server before streaming replication will start.
Contact support for instructions on how to manually add the publication.
**Env variable:** `DISABLE_CREATE_PUBLICATION`
#### `--disable-setup-ddl-replication`
Disable running DDL Streaming Replication setup for PostgreSQL. If this flag is set the DDL setup SQL queries will need to be manually run on the primary server before streaming replication will start.
Contact support for instructions on how to manually run the DDL setup SQL queries.
**Env variable:** `DISABLE_SETUP_DDL_REPLICATION`
#### `--disable-telemetry`
Don't sent anonymous [telemetry data](/reference/telemetry) to Readyset.
**Env variable:** `DISABLE_TELEMETRY`
#### `--disable-upstream-ssl-verification`
Disable verification of SSL certificates supplied by the upstream database (PostgreSQL only; ignored for MySQL). Ignored if `--upstream-db-url` is not passed.
If invalid certificates are trusted, any certificate for any site will be trusted for use, including expired certificates. This introduces significant vulnerabilities, and should only be used as a last resort.
**Env variable:** `DISABLE_UPSTREAM_SSL_VERIFICATION`
#### `--require-gtid`
Require GTID-based replication for MySQL. When set, Readyset uses Global Transaction Identifiers (GTIDs) instead of traditional binlog file/position tracking for replication. If the upstream MySQL server does not have `gtid_mode=ON`, replication fails rather than falling back to binlog file/position.
This flag is opt-in because many MySQL servers have `gtid_mode=ON` without requiring GTID-based replication. By defaulting to binlog file/position, existing deployments are unaffected and operators can choose when to switch.
When not set, Readyset uses binlog file/position regardless of the server's `gtid_mode` setting.
**Default:** `false`
**Env variable:** `REQUIRE_GTID`
#### `--max-gtid-rows-to-skip`
Maximum number of row events to skip during GTID crash recovery within a single transaction. When Readyset restarts after a crash mid-transaction, it replays from the pending GTID and skips row events that were already applied. If the pending event index exceeds this limit, replication fails assuming the persisted state is corrupt. Increase this value if the upstream database has transactions with more than 10,000 row events.
**Default:** `10000`
**Env variable:** `MAX_GTID_ROWS_TO_SKIP`
#### `--enable-rls`
Postgres [row-level security](/concepts/shallow-caching/row-level-security) awareness for shallow caching. On by default. Readyset reads your RLS policies from `pg_catalog` and partitions each shallow cache over an RLS-protected table by the session state its policies read, so a cache hit can only return rows the session was entitled to see.
Requires read access to the catalog relations listed under [Required privileges](/concepts/shallow-caching/row-level-security#required-privileges). If they are unreadable, Readyset refuses to start rather than cache RLS-protected tables with a shared key.
Has no effect on a MySQL upstream.
Setting this to
`false`
is dangerous. The RLS catalog is not consulted and every shallow cache is created with a shared, session-independent key. Enabling RLS on an already-cached table afterwards would serve one tenant's rows to another, with no later correction. Only set it to
`false`
for a Postgres deployment where no table has, or will ever gain, RLS and whose connection role cannot be granted catalog access.
**Default:** `true`
**Possible values:** `true`, `false`
**Env variable:** `RLS_ENABLED`
#### `--feature-materialization-persistence`
Allow fully materialized nodes to be persisted to disk. Fully materialized nodes are populated during startup and stored in memory by default. They can use significant amount of memory and are not candidate for evictions. This feature stores them on disk instead. Full materialized nodes are removed during shutdown.
This feature is experimental and should not be used in production. Contact support if you want to enable this feature.
**Default:** `false`
**Possible values:** `true`, `false`
**Env variable:** `FEATURE_MATERIALIZATION_PERSISTENCE`
#### `--feature-mixed-comparisons`
Enable support for mixing equality and range comparisons on query parameters.
This feature is experimental and should not be used in production. Contact support if you want to enable this feature.
**Default:** `false`
**Possible values:** `true`, `false`
**Env variable:** `FEATURE_MIXED_COMPARISONS`
#### `--feature-pagination`
Enable support for Pagination in dataflow.
This feature is experimental and should not be used in production. Contact support if you want to enable this feature.
**Default:** `false`
**Possible values:** `true`, `false`
**Env variable:** `FEATURE_PAGINATION`
#### `--feature-post-lookup`
Enable support for Post-Lookup (queries which do extra work after the lookup into the reader). If the Top K feature is disabled, Post-Lookup can still provide support for some queries supported by Top K, albeit with lower performance.
This feature is experimental and should not be used in production. Contact support if you want to enable this feature.
**Default:** `false`
**Possible values:** `true`, `false`
**Env variable:** `FEATURE_POST_LOOKUP`
#### `--feature-straddled-joins`
Enable support for straddled joins (joins with partial keys traced to both parents).
This feature is experimental and should not be used in production. Contact support if you want to enable this feature.
**Default:** `false`
**Possible values:** `true`, `false`
**Env variable:** `FEATURE_STRADDLED_JOINS`
#### `--feature-topk`
Enable support for Top K in dataflow.
This feature is experimental and should not be used in production. Contact support if you want to enable this feature.
**Default:** `false`
**Possible values:** `true`, `false`
**Env variable:** `FEATURE_TOPK`
#### `--help`, `-h`
Print help information.
#### `--ignore-ulimit-check`
Allow Readyset to start even if the file descriptor limit (`ulimit -n`) is below the minimum requirement. If set, Readyset still raises the soft limit to `min(our requirement, hard limit)`. It just doesn't treat `(our requirement > hard limit)` as a fatal error.
**Env variable:** `IGNORE_ULIMIT_CHECK`
#### `--listen-backlog`
The maximum number of client connections waiting to be accepted on each listen address. When a burst of clients connects at once, connections beyond this queue are dropped, and those clients stall until their connect timeout.
The kernel caps the value at `net.core.somaxconn`. Readyset logs the effective value at startup and warns when the cap applies.
**Default:** `4096`
**Env variable:** `LISTEN_BACKLOG`
#### `--log-dir`
Optional path to a directory where log files will be placed. Logs will be written to `readyset.log` within this directory. If set, logs will rollover based on the chosen `log_rotation` policy, which defaults to daily. Readyset must have write permissions.
**Env variable:** `LOG_DIR`
#### `--log-file`
Optional full log filename where logs will be appended. Incompatible with log rotation.
**Env variable:** `LOG_FILE`
#### `--log-format`
Format to use when emitting log events. See the [docs for the tracing library](https://docs.rs/tracing-subscriber/latest/tracing_subscriber/fmt/index.html#formatters) for details.
**Possible values:** `"compact"`, `"full"`, `"pretty"`, `"json"`
**Default:** `"full"`
**Env variable:** `LOG_FORMAT`
#### `--log-level`
The [severity level(s)](https://docs.rs/tracing-core/0.1.30/tracing_core/metadata/struct.Level.html#implementations) to include in Readyset logs. Messages at the specified and more severe levels are included. For example, when set to `INFO`, messages at the `INFO`, `WARN`, and `ERROR` levels are included.
Possible values, from most to least severe:
* `ERROR`: Used for hazardous situations that require special handling, where normal operation cannot proceed as expected.
* `WARN`: Used for potentially hazardous situations that may require special handling.
* `INFO`: Used for information messages that do not require action.
* `DEBUG`: Used for lower priority information.
* `TRACE`: Used for very low priority, often extremely verbose information.
The
`TRACE`
level is not available for
[official releases of Readyset](https://github.com/readysettech/readyset/)
, or for binaries built with the
`--release`
flag.
Also,
`--log-level`
can be set to a comma-separated list of directives for debugging during development. For the directive syntax, see the
[docs for the tracing library](https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html)
.
**Default:** `INFO`
**Env variable:** `LOG_LEVEL`
#### `--log-rotation`
Log Rotation policy to use if a log file is set. Defaults to daily. Does nothing if no log file is set.
**Possible values:** `"daily"`, `"hourly"`, `"minutely"`, `"never"`
**Default:** `"daily"`
**Env variable:** `LOG_ROTATION`
#### `--max-parallel-snapshot-tables`
The maximum number of relations that will be snapshotted in parallel from the upstream.
**Default:** Number of available cores - 1.
**Env variable:** `MAX_PARALLEL_SNAPSHOT_TABLES`
#### `--memory-limit`, `-m`
The amount of memory, in bytes, available to the Readyset Server.
This memory limit accounts for all memory use, including partial materializations (i.e., queries for which Readyset stores only part of the result set), full materializations (i.e., queries for which Readyset stores the entire result set), and other parts of the Readyset process (e.g., RocksDB).
Once memory usage surpasses this limit, Readyset starts evicting cache entries from partial materializations based on the [`--eviction-policy`](#--eviction-policy).
**Default:** 80% of the memory available to the process (the container memory limit, if one is set, otherwise the machine's total memory). Set to `0` for unlimited.
**Env variable:** `READYSET_MEMORY_LIMIT`
For production deployments, run the system under load, observe, and increase or decrease as needed.
#### `--metrics-address`
The IP address/hostname and port of the Prometheus endpoint for [Readyset metrics](http://docs/rustdoc/readyset_client/metrics/recorded/index.html).
This option is ignored unless [`--prometheus-metrics`](#--prometheus-metrics) is passed. It determines the Prometheus endpoint for the Readyset adapter; the [`--external-address`](readyset-server.md#--external-address) option for the `readyset-server` command determines the Prometheus endpoint for the Readyset server.
**Default:** `0.0.0.0:6034`
**Env variable:** `METRICS_ADDRESS`
#### `--mysql-authentication-method`
Authentication method to use for MySQL client connections. This controls which authentication plugin Readyset advertises during the MySQL handshake.
**Possible values:** `mysql_native_password`, `caching_sha2_password`
**Default:** `caching_sha2_password`
**Env variable:** `MYSQL_AUTHENTICATION_METHOD`
See [Authentication](/reference/authentication) for details on each method and migration guidance.
#### `--no-color`
Disable colors in all log output.
**Env variable:** `NO_COLOR`
#### `--postgres-authentication-method`
Authentication method to use for PostgreSQL clients.
**Possible values:** `cleartext`, `scram-sha-256`
**Default:** `scram-sha-256`
**Env variable:** `POSTGRES_AUTHENTICATION_METHOD`
#### `--query-caching`
Controls when Readyset migrates a query (compiles it into a cache) relative to the request that uses it.
**Possible values:**
* `explicit`: Caches are only created in response to an explicit `CREATE CACHE` statement or a `/*rs+ CREATE SHALLOW CACHE */` [hint](/concepts/shallow-caching/hint-based-caches). Queries that have not been explicitly cached are proxied to the upstream database. This is the default and the only non-experimental value.
* `inrequestpath`: Every previously-unseen `SELECT` becomes a candidate for cache creation as it is executed. Combined with [`--cache-mode`](#--cache-mode) `shallow`, this produces Readyset's [automatic shallow caching](/concepts/shallow-caching/auto-caching) mode; the [`--auto-cache`](#--auto-cache) flag is shorthand for that combination.
* `async`: Experimental.
Mutually exclusive with [`--auto-cache`](#--auto-cache).
**Default:** `explicit`
**Env variable:** `QUERY_CACHING`
#### `--readyset-identity-file`
The PKCS#12 identity file (certificate and key) used by Readyset for establishing TLS connections as the server. Readyset will not accept TLS connections if there is no identity file specified.
**Env variable:** `READYSET_IDENTITY_FILE`
#### `--readyset-identity-file-password`
Password for the PKCS#12 identity file used by Readyset for establishing TLS connections as the server. If not provided, Readyset will try using an empty string to unlock the identity file.
**Env variable:** `READYSET_IDENTITY_FILE_PASSWORD`
#### `--report-host`
Hostname to report when registering as a replica with the upstream database (MySQL only). If not set, no hostname will be reported
**Env variable:** `REPORT_HOST`
#### `--report-password`
Password to report when registering as a replica with the upstream database (MySQL only). If not set, no password will be reported
**Env variable:** `REPORT_PASSWORD`
#### `--report-port`
Port to report when registering as a replica with the upstream database (MySQL only). If not set, no port will be reported
**Env variable:** `REPORT_PORT`
#### `--report-user`
Username to report when registering as a replica with the upstream database (MySQL only). If not set, no username will be reported
**Env variable:** `REPORT_USER`
#### `--replication-server-id`
Server ID to use when registering as a replication follower with the upstream database.
This can be used to differentiate different Readyset deployments connected to the same upstream DB.
For Postgres, this ends up being a suffix of the replication slot or resnapshot replication slot, which have prefixes of `readyset_` and `readyset_resnapshot_`, respectively. Since a replication slot is limited by Postgres to a length of 63 bytes, this server id must be 43 bytes or fewer.
For MySQL, this must be parseable as a `u32` that is unique across the replication topology.
**Env variable:** `REPLICATION_SERVER_ID`
#### `--replication-tables`
By default, Readyset attempts to snapshot and replicate all tables in the database specified in [`--upstream-db-url`](#--upstream-db-url). However, if the queries you want to cache in Readyset access only a subset of tables in the database, you can use this option to limit the tables Readyset snapshots and replicates. Filtering out tables that will not be used in caches will speed up the snapshotting process.
This option accepts a comma-separated list of `
.` (specific table in a schema) or `.*` (all tables in a schema) for Postgres and `.` for MySQL.
Only tables specified in the list will be eligible to be used by caches.
**Env variable:** `REPLICATION_TABLES`
#### `--replication-tables-ignore`
By default, Readyset attempts to snapshot and replicate all tables in the database specified in [`--upstream-db-url`](#--upstream-db-url). However, if you know the queries you want to cache in Readyset won't access a subset of tables in the database, you can use this option to limit the tables Readyset snapshots and replicates. Filtering out tables that will not be used in caches will speed up the snapshotting process.
This option accepts a comma-separated list of `
.` (specific table in a schema) or `.*` (all tables in a schema) for Postgres and `.` for MySQL.
Tables specified in the list will not be eligible to be used by caches.
**Env variable:** `REPLICATION_TABLES_IGNORE`
#### `--replication-lag-interval`
Polling interval, in seconds, for the replication-lag reporter. Controls how often Readyset queries the upstream's current replication position and, if [`--replication-heartbeat`](#--replication-heartbeat) is enabled, writes a heartbeat timestamp to the upstream.
The value is surfaced through [`readyset.replication_status`](/reference/system-views#readysetreplication_status) and the Prometheus gauge `readyset_replicator.replication_lag`.
**Default:** `1`
**Possible values:** integer in the range `1`–`30`
**Env variable:** `REPLICATION_LAG_INTERVAL`
#### `--replication-heartbeat`
Enable pt-heartbeat-style time-based replication lag measurement. When enabled, Readyset maintains a small heartbeat table on the upstream and measures the round-trip time for a written timestamp to appear back through the replication stream. The result is exposed as `heartbeat_staleness_seconds` in [`readyset.replication_status`](/reference/system-views#readysetreplication_status) and as the Prometheus gauge `readyset_replicator.replication_staleness_seconds`.
Requires write access to the upstream so Readyset can create and update the heartbeat table (`readyset._heartbeat` on Postgres, `_readyset_heartbeat` on MySQL). If write access is not granted, a warning is logged, heartbeat is disabled, and byte/transaction lag reporting is unaffected.
The poll cadence is shared with [`--replication-lag-interval`](#--replication-lag-interval).
**Default:** `false`
**Possible values:** `true`, `false`
**Env variable:** `REPLICATION_HEARTBEAT`
#### `--rls-poll-interval-secs`
How often, in seconds, Readyset polls `pg_catalog` for changes to [row-level security](/concepts/shallow-caching/row-level-security) policies, RLS-enabled flags, and role attributes.
The interval bounds how long a shallow cache can keep serving under a stale view of your policies: a policy edited on the upstream takes effect in Readyset within one poll. This is independent of cache TTL, which bounds row staleness. Shorten it to tighten the policy-staleness window, at the cost of more catalog queries against the upstream; lengthen it to reduce that load.
Values outside `[1, 86400]` are clamped into range and a warning is logged with the effective value.
Applies only to a Postgres upstream with [`--enable-rls`](#--enable-rls) on. Monitor `readyset.rls.poll_age_seconds` against this value to detect a poller that has fallen behind.
**Default:** `60`
**Env variable:** `RLS_POLL_INTERVAL_SECS`
#### `--opportunistic-ryw-ms`
Opportunistic read-your-writes window (in milliseconds). Applies only *outside* transactions: after any write on a session, reads on that same session bypass every cache for this many milliseconds. In-transaction routing is unaffected and is governed by the per-cache transaction policy (`NEVER` (default), [`ALWAYS`](/command-reference#always), or [`UNTIL WRITE`](/command-reference#until-write)). See [Read-your-writes](/concepts/read-your-writes) for how the two layers fit together.
This is opportunistic, not a consistency guarantee. Once the window elapses, reads resume from the cache, and the cache may still hold a pre-write value (a TTL that has not yet expired, or a row Readyset has not yet refreshed). Use only when occasional stale reads after the window are acceptable. See
[Read-your-writes](/concepts/read-your-writes)
for the full picture and tradeoffs.
Pick a window long enough to cover the gap between a write completing on the upstream and Readyset's cache reflecting it. Too short and a freshly-written row may still be served stale; too long and reads that could safely hit the cache go upstream instead.
Unset (the default) disables the feature; the read path then never pays the clock comparison. A value of `0` also disables it.
**Default:** unset (disabled)
**Env variable:** `OPPORTUNISTIC_RYW_MS`
#### `--sampler-max-queries-per-second`
Specifies the maximum number of queries to sample per second.
**Default:** `100`
**Env variable:** `SAMPLER_MAX_QUERIES_PER_SECOND`
#### `--sampler-max-retries`
Specifies the maximum number of retries for a mismatched query.
**Default:** `6`
**Env variable:** `SAMPLER_MAX_RETRIES`
#### `--sampler-queue-capacity`
Specifies the maximum queue size for the background query sampler. Exceeding this size will cause the adapter to not add new queries to the sampler queue.
**Default:** `1024`
**Env variable:** `SAMPLER_QUEUE_CAPACITY`
#### `--sampler-retry-delay-ms`
Specifies the delay between retries for a mismatched query.
**Default:** `10000`
**Env variable:** `SAMPLER_RETRY_DELAY_MS`
#### `--sampler-sample-rate`
Specifies the sample rate for the background query sampler.
**Default:** `0.001`
**Env variable:** `SAMPLER_SAMPLE_RATE`
#### `--snapshot-query-comment`
A string to be included as a comment in snapshot queries for MySQL. The provided value is automatically enclosed within the delimiters `/*` and `*/`. Provide only the raw comment text without any comment markers.
**Default:** `""`
**Env variable:** `SNAPSHOT_QUERY_COMMENT`
#### `--shallow-adaptive-max-extra-load-percent`
Maximum extra refresh load an [adaptive](/concepts/shallow-caching#adaptive-refresh) shallow cache may send to the upstream database, as a percentage of the load required to refresh its current entries at the configured refresh interval. When shortened per-entry intervals reach this cap, intervals stop shrinking until aggregate load decreases.
**Default:** `100`
**Env variable:** `SHALLOW_ADAPTIVE_MAX_EXTRA_LOAD_PERCENT`
#### `--shallow-max-entry-bytes`
Maximum size, in bytes, of a single shallow cache entry's result set. Larger results are served from upstream without being cached. This is a backstop for pathological cases; [cost-aware admission](/concepts/shallow-caching#memory-and-eviction) normally decides which results are worth their memory.
**Default:** `1073741824` (1 GiB)
**Env variable:** `SHALLOW_MAX_ENTRY_BYTES`
#### `--shallow-memory-percent`
Percentage of [`--memory-limit`](#--memory-limit--m) to allocate to the shallow cache store, as a value from 0 to 100. Applies only when the memory limit is not `0`. When the store reaches this bound, [cost-aware admission](/concepts/shallow-caching#memory-and-eviction) decides which results are kept.
**Default:** Unset. The shallow store uses the full memory limit with [`--cache-mode`](#--cache-mode) `shallow`, and half of it with `deep` or `deep-then-shallow`.
**Env variable:** `SHALLOW_MEMORY_PERCENT`
#### `--shallow-refresh-workers`
Maximum number of concurrent background refresh queries allowed across all shallow caches. This value acts as a guardrail to prevent shallow cache refreshes from overwhelming your upstream database. It also determines the upper bound on the number of database connections used for refreshes.
Even with this limit in place, choose `REFRESH EVERY` intervals carefully on individual caches. High-cardinality caches with many distinct parameter values can generate significant query volume even at modest per-key refresh rates.
**Default:** `40`
**Env variable:** `SHALLOW_REFRESH_WORKERS`
#### `--ssl-root-cert`
Path to the PEM or DER root certificate that the upstream database connection will trust.
**Default:** System root store
**Env variable:** `SSL_ROOT_CERT`
#### `--storage-dir`
The path to the directory where Readyset stores replicated table data.
**Default:** Current working directory
**Env variable:** `STORAGE_DIR`
#### `--tls-mode`
Specifies the types of client connections permitted to connect to Readyset.
**Possible values:**
* `optional`: Client can use either plain or TLS connections
* `disabled`: TLS connections are disabled
* `required`: Only TLS connections are allowed
**Default:** `optional`
**Env variable:** `TLS_MODE`
#### `--upstream-connect-retries`
How many times replication retries a CDC server that failed before it moves to the next server in the [`--cdc-db-url`](#--cdc-db-url) list. A single server is retried forever.
**Default:** `3`
**Env variable:** `UPSTREAM_CONNECT_RETRIES`
#### `--upstream-connect-timeout`
Seconds to wait for a connection to an upstream server before Readyset treats the server as unreachable. The limit applies to every connection Readyset opens to an upstream.
**Default:** `5`
**Env variable:** `UPSTREAM_CONNECT_TIMEOUT`
#### `--upstream-db-url`
The URL of the upstream database, or a list of URLs for a group of equivalent servers. To pass a list, repeat the option or separate the URLs with commas. Each URL includes the username and password for Readyset to authenticate with, and the database to snapshot and replicate.
Each URL in a list can end with an optional `|` suffix, an integer from 1 to 100 that defaults to 50. This follows MySQL's asynchronous connection failover source lists.
```
--upstream-db-url 'mysql://user:pass@replica-a:3306/db|60,mysql://user:pass@replica-b:3306/db'
```
Lists are supported for MySQL only, because failover needs a replication position that means the same thing on every server. A PostgreSQL URL is always a single server. Every server in a list must use the same database type, database name, user, and password, and each `host:port` can appear only once.
Readyset spreads client sessions and its own upstream connections at random across the servers that are up. A server that stops answering is treated as down and is skipped until it answers again. To see the list, each server's health, and its connection count, run `SELECT * FROM readyset.upstream_servers`.
**Env variable:** `UPSTREAM_DB_URL`
A MySQL list with more than one server requires
`gtid_mode = ON`
on every server. All servers must also have the same
`lower_case_table_names`
, time zone, and server collation. At startup, Readyset connects to each server and rejects any server that fails these checks.
Readyset saves the server list on the
**first**
startup that passes
[verification](#--verify)
. After that, Readyset uses the saved list and ignores this option. To change the list, use
[`ALTER READYSET CHANGE UPSTREAM`](/command-reference#alter-readyset-change-upstream)
.
By default, Readyset attempts to snapshot and replicate all tables in the database specified in
[`--upstream-db-url`](#--upstream-db-url)
. However, if the queries you want to cache in Readyset access only a subset of tables in the database, you can use the
[`--replication-tables`](#--replication-tables)
option to narrow the scope accordingly. Filtering out tables that will not be used in caches will speed up the snapshotting process.
Special characters that may be interpreted as dividers of the connection string (e.g.
`@`
,
`:`
,
`/`
,
`#`
) must be
[percent-encoded](https://developer.mozilla.org/en-US/docs/Glossary/percent-encoding)
, including in passwords. For example,
`password#`
should be
`password%23`
. Encode a
`,`
inside a password as
`%2C`
, because commas separate the URLs in a list. A
`|`
inside a password doesn't need encoding, because Readyset reads the weight from the last
`|`
after the host.
On Readyset's **first** startup for a deployment, the user from `--upstream-db-url` is added to the allowed-users set so clients can authenticate to Readyset with those credentials. This seeding happens only once: the allowed-users set is then persisted and read from stored values. Changing `--upstream-db-url` later points Readyset at a different upstream but does **not** add the new URL's user to the allowed-users set. Manage users at runtime with [`ALTER READYSET ADD/MODIFY/DROP USER`](/command-reference#alter-readyset-add-user), as described in [User Management](/reference/user-management).
#### `--upstream-health-interval-secs`
How often, in seconds, Readyset checks that each upstream server is up. Health checks may be disabled by setting this value to `0`, in which case Readyset discovers that a server is down only when a client connection to it fails.
A check that gets no answer within [`--upstream-connect-timeout`](#--upstream-connect-timeout) counts as a failure. Readyset retries a server that is down by opening a new connection, first after 1 second, then waiting twice as long each time, up to 60 seconds. These connections use the program name `READYSET_HEALTH_MONITOR`, so you can find them in the upstream's process list.
**Default:** `10`
**Env variable:** `UPSTREAM_HEALTH_INTERVAL`
#### `--verify`
Run startup verification checks and exit, without starting the Readyset adapter.
* On success: prints a confirmation message to stdout and exits with code `0`.
* On failure: prints the error(s) to stderr and exits with a non-zero code.
Use this flag in CI/CD pipelines or deployment scripts to validate your Readyset configuration before the adapter goes live.
By default (when neither `--verify` nor [`--verify-skip`](#--verify-skip) is set), Readyset still runs the same checks at startup. A failure will stop the process, but output goes to the log rather than stdout/stderr.
Cannot be used together with [`--verify-skip`](#--verify-skip).
**Env variable:** `VERIFY`
#### `--verify-skip`
Skip all startup verification checks and proceed directly to starting the Readyset adapter.
Skipping verification means Readyset will not detect misconfigured upstream database settings (replication, permissions, disk space, etc.) before attempting to connect and snapshot. Use this option only when you have confirmed the configuration is correct or need to bypass a transient check failure.
Cannot be used together with [`--verify`](#--verify).
**Env variable:** `VERIFY_SKIP`
# Failover (/reference/failover)
import { Callout } from 'fumadocs-ui/components/callout'
When the upstream database fails and a replica is promoted to primary, Readyset can be redirected to the new source without a full restart. The failover procedure depends on the database type and the replication mode in use.
## Automatic failover with server lists
For MySQL with GTID replication, Readyset can fail over by itself when you give it more than one server. [`--upstream-db-url`](/reference/config-options#--upstream-db-url) and [`--cdc-db-url`](/reference/config-options#--cdc-db-url) accept a list of URLs. Repeat the option or separate the URLs with commas, and add an optional `|` suffix from 1 to 100 (default 50) to each one.
```
--cdc-db-url 'mysql://user:pass@replica-a:3306/db|60,mysql://user:pass@replica-b:3306/db|50,mysql://user:pass@replica-c:3306/db'
```
Replication connects to the highest-weight server. When that server fails, Readyset retries it up to [`--upstream-connect-retries`](/reference/config-options#--upstream-connect-retries) times, then moves to the next server by weight. GTIDs name the same position on every server, so no rows are lost or duplicated. Replication will use the new server until that server fails or you change the list.
Client connections are spread across the servers in the [`--upstream-db-url`](/reference/config-options#--upstream-db-url) list that are up.
You can edit the list while replication runs, for example from a tool that adds and removes replicas:
```sql
ALTER READYSET CHANGE CDC TO 'mysql://user:pass@replica-a:3306/db|60,mysql://user:pass@replica-d:3306/db';
```
Readyset checks new servers before it accepts the list. If replication's current server is still in the new list, replication keeps using it. If you removed it, Readyset restarts replication, using a server from the new list.
To see the list, each server's health, which server replication is on, and each server's connection count, run:
```sql
SELECT * FROM readyset.upstream_servers;
```
Every server in a MySQL list must have `gtid_mode=ON`, use the same database name, and have the same `lower_case_table_names`, time zone, and server collation. Readyset checks each server at startup and when it is added to the list.
Use the manual steps below with a single server, to move to a server that isn't in the list, or with PostgreSQL.
## MySQL
### GTID replication
When Readyset is replicating with [GTIDs](/reference/config-options#--require-gtid), failover is straightforward because GTIDs are global identifiers that are consistent across all servers in a replication topology. No position adjustment is needed. Point Readyset at the new primary:
```sql
ALTER READYSET CHANGE CDC TO 'mysql://:@new-primary:3306/db';
```
Replication restarts on the new server and resumes from the last GTID Readyset processed. If the new primary has the same GTID history, replication continues without a gap. It is not necessary to stop replication first.
If Readyset was originally started without [`--require-gtid`](/reference/config-options#--require-gtid) (using binlog file and position), you can switch to GTID replication during failover by setting a GTID position with [`ALTER READYSET SET REPLICATION POSITION`](/command-reference#alter-readyset-set-replication-position) before starting replication. The new primary must have `gtid_mode=ON`.
### Binlog file and position replication
Binlog file names and positions are local to each MySQL server. When failing over to a new primary, you must determine the correct starting position on the new server. This is the same process as failing over any MySQL replica.
1. Stop replication:
```sql
ALTER READYSET STOP REPLICATION;
```
2. Check the current replication position in Readyset:
```sql
SHOW READYSET STATUS;
```
The `Minimum Replication Offset` field shows the current binlog file and position (e.g., `binlog.000003:154`).
3. Determine the equivalent position on the new primary. This step is environment-specific and is the same procedure used when failing over any MySQL replica. Common approaches include:
* Using `SHOW BINARY LOG STATUS` on the new primary if the replica was in sync
* Consulting your replication management tool (e.g., Orchestrator, MHA)
* Comparing GTID sets if the servers have `gtid_mode=ON` (consider switching to GTID replication instead)
4. Set the replication position to the new primary's binlog coordinates:
```sql
ALTER READYSET SET REPLICATION POSITION 'binlog.000001:4587';
```
5. Point Readyset at the new primary:
```sql
ALTER READYSET CHANGE CDC TO 'mysql://:@new-primary:3306/db';
```
6. Resume replication:
```sql
ALTER READYSET START REPLICATION;
```
Setting an incorrect replication position can cause missed writes or replication errors. Verify that the position on the new primary corresponds to the same point in the data as the position reported by `SHOW READYSET STATUS`.
## PostgreSQL
PostgreSQL logical replication uses replication slots that are local to the primary server. When the primary fails, the slot is lost and cannot be recreated at the old position on a new server. Readyset handles this automatically by performing a full resnapshot. Point Readyset at the new primary:
```sql
ALTER READYSET CHANGE CDC TO 'postgresql://:@new-primary:5432/db';
```
Readyset restarts replication, detects that the replication slot does not exist on the new primary, creates a new slot, and performs a full resnapshot of all tables. Once the snapshot completes, replication resumes from the new slot's starting position.
PostgreSQL supports only a single server, so [automatic failover](#automatic-failover-with-server-lists) isn't available. LSNs and replication slots belong to one server, and no position carries over to the next one.
`SET REPLICATION POSITION` is not useful for PostgreSQL failover. LSN values are specific to each server's WAL stream and are not transferable between servers.
## Monitoring failover
Use [`SHOW READYSET STATUS`](/command-reference#show-readyset-status) to monitor the failover process:
* **Status**: Shows `Replication Stopped` after stopping, then transitions back to `Online` once replication catches up.
* **CDC Server**: The `host:port` of the upstream server that replication is connected to. It doesn't appear while replication is stopped. After a failover, it shows the new server.
* **Minimum Replication Offset** / **Maximum Replication Offset**: Show the current replication position. For MySQL GTID, this displays the GTID set. For binlog file and position, it displays the file name and offset.
With a server list, `SELECT * FROM readyset.upstream_servers` shows one row per server. Each row has the server's weight, its purpose (`cdc`, `proxy`, or `cdc, proxy`), whether replication is using it, its health, and its connection count.
# Profiling Queries (/reference/profiling-queries)
import { Callout } from 'fumadocs-ui/components/callout'
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
import { Cards, Card } from 'fumadocs-ui/components/card'
Before deciding to cache a query, it's worth investigating the performance impact each query has on your application.
One method for doing so:
## Enabling metrics
You can use the [pg\_stat\_statements extension](https://www.postgresql.org/docs/current/pgstatstatements.html) to retrieve detailed information about the queries running against your Postgres instance.
Connect to your database via the shell. Run the following command to see if pg\_stat\_statements is installed.
```sh copy
SELECT calls, query FROM pg_stat_statements LIMIT 1;
```
If an error is returned, enable pg\_stat\_statments with the following command:
```sh copy
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
```
In some environments, the pg_stat_statements extension may not be available. In that case, run
`ALTER SYSTEM SET shared_preload_libraries = 'pg_stat_statements';`
and restart your Postgres instance before re-running the
`CREATE EXTENSION`
command.
To enable Readyset metrics, start Readyset with the following options:
* [`--prometheus-metrics`](/using/cli/readyset/#--prometheus-metrics)
* [`--metrics-address`](/using/cli/readyset/#--metrics-address)
To include query-specific execution metrics, also pass:
* [`--query-log-mode=verbose`](/using/cli/readyset/#-query-log-mode)
You can access Readyset metrics at `/metrics`, where the metrics address is defined by the [`--metrics-address`](/using/cli/readyset/#--metrics-address) option (default: `0.0.0.0:6034/metrics`).
## Analyzing per-query metrics
Readyset can cache many `SELECT` queries.
To find `SELECT` queries with the highest latency, run:
```sql copy
SELECT query, calls, total_exec_time, mean_exec_time from pg_stat_statements WHERE query ILIKE '%SELECT%' order by mean_exec_time DESC;
```
Similarly, Readyset can be used to offload high-impact queries to improve throughput.
To find the most frequently-run `SELECT` queries, run:
```sql copy
SELECT query, calls, total_exec_time, mean_exec_time from pg_stat_statements WHERE query ILIKE '%SELECT%' order by calls DESC;
```
To find queries that cause the most total load on the database, run:
```sql copy
SELECT query, calls, total_exec_time, mean_exec_time from pg_stat_statements WHERE query ILIKE '%SELECT%' order by total_exec_time DESC;
```
Readyset metrics are formatted for easy integration with [Prometheus](https://prometheus.io/). However, the quickest way to examine per-query metrics is to use a simple metrics utility written in Python that queries the metrics endpoint and displays latencies for queries received by Readyset.
1. Download the metrics utility and its `requirements.txt`:
```sql copy
curl -L -O "https://readyset.io/quickstart/metrics.py"
curl -L -O "https://readyset.io/quickstart/requirements.txt"
```
2. Install dependencies for the utility:
```sh copy
pip3 install -r requirements.txt
```
3. Set the `METRICS_HOST` environment variable to the IP address/hostname portion of `--metrics-address`:
```sql copy
export METRICS_HOST=""
```
4. Run the script with
```sql copy
python3 metrics.py --host=${METRICS_HOST}
```
5. You can filter the output of this script to show only queries displayed in `SHOW PROXIED QUERIES` or `SHOW CACHES` by passing the `--filter-queries` flag and piping the output of those Readyset commands into the script like so:
```sql copy
PGPASSWORD=noria psql --host=127.0.0.1 --port=5433 --username=postgres --dbname=noria -c "SHOW CACHES" | python3 metrics.py --filter-queries
```
# View Query Metrics with the Database's Slow Query Log (/reference/profiling-queries/view-query-metrics-with-the-databases-slow-query-log)
import { Callout } from 'fumadocs-ui/components/callout'
After [connecting your app](/cache/connect/connect-an-application-via-an-orm) to Readyset, profile your app performance and identify queries to cache in Readyset. Generally, it's best to focus on frequent queries that are too slow and/or that are putting unwanted load on your upstream database.
### Enabling metrics
You can use the [pg\_stat\_statements extension](https://www.postgresql.org/docs/current/pgstatstatements.html) to retrieve detailed information about the queries running against your Postgres instance.
Connect to your database via the shell. Run the following command to see if pg\_stat\_statements is installed.
```sh copy
SELECT calls, query FROM pg_stat_statements LIMIT 1;
```
If an error is returned, enable pg\_stat\_statments with the following command:
```sh copy
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
```
In some environments, the pg\_stat\_statements extension may not be available. In that case, run `ALTER SYSTEM SET` shared\_preload\_libraries = 'pg\_stat\_statements'; and restart your Postgres instance before re-running the `CREATE EXTENSION` command.
### Identifying queries to cache
To find queries that put the highest load on the database, run:
```sh copy
SELECT query, calls, total_exec_time, mean_exec_time from pg_stat_statements WHERE query ILIKE '%SELECT%' order by total_exec_time DESC;
```
To find queries with the highest latency, run:
```sh copy
SELECT query, calls, total_exec_time, mean_exec_time from pg_stat_statements WHERE query ILIKE '%SELECT%' order by mean_exec_time DESC;
```
To find the most frequently issued queries, run:
```sh copy
SELECT query, calls, total_exec_time, mean_exec_time from pg_stat_statements WHERE query ILIKE '%SELECT%' order by calls DESC;
```
Once you've identified queries to cache, you can [cache them in Readyset](/cache/cache#1-creating-a-cache).
# Integrating with ProxySQL (/reference/proxysql)
This manual provides instructions for configuring ProxySQL alongside Readyset. Readyset has developed its own [ProxySQL Scheduler](https://github.com/readysettech/proxysql_scheduler) designed to integrate both tools.
This scheduler enables the identification of slow queries, assesses whether Readyset supports these queries, and automatically generates query rules to reroute these queries to Readyset, serving them directly from its cache.
The scheduler is also responsible for checking the health of a Readyset instance and adjusting it's status if necessary.
## Pre-requirements
This guide assumes that you already have ProxySQL installed, Readyset set up, and three MySQL servers in place (one writer and two readers).
We will be using 3 hostgroups:
| Hostgroup | Role |
| --------- | ----- |
| 10 | Write |
| 11 | Read |
| 12 | Cache |
For demo purposes, we will be using below list of servers and IPS:
| Server | Port | Hostgroup | Role |
| ------------- | ---- | --------- | -------- |
| 172.20.238.10 | 3306 | 10 | Source |
| 172.20.238.11 | 3306 | 11/12 | Replica1 |
| 172.20.238.12 | 3306 | 11/12 | Replica2 |
| 172.20.238.20 | 3306 | 12 | Readyset |
As a best practice, we will include the replicas in the cache hostgroup 12. This ensures that if Readyset becomes unavailable, the cache queries will be served by the Readers.
All commands will be executed in the admin interface of ProxySQL
```sql
mysql -u admin -padmin -P6032
```
## Configuring Users
We will configure our application user to connect by default to `hostgroup 10`
```sql
INSERT INTO mysql_users (username, password, default_hostgroup) VALUES ('app', 'app', 10);
LOAD MYSQL USERS TO RUNTIME; SAVE MYSQL USERS FROM RUNTIME; SAVE MYSQL USERS TO DISK;
```
## Configuring Servers
We will configure the server as follow:
```sql
INSERT INTO mysql_servers (hostgroup_id, hostname, port, weight, comment) VALUES
(10, '172.20.238.10', 3306, 1, 'Writer'),
(11, '172.20.238.11', 3306, 1, 'Read - Replica 1'),
(11, '172.20.238.12', 3306, 1, 'Read - Replica 2'),
(12, '172.20.238.20', 3306, 1000, 'Cache - Readyset'),
(12, '172.20.238.11', 3306, 1, 'Cache - Replica 1'),
(12, '172.20.238.12', 3306, 1, 'Cache - Replica 2');
LOAD MYSQL SERVERS TO RUNTIME; SAVE MYSQL SERVERS TO DISK;
```
Currently, Readyset does not support automatic failover; therefore, the configuration of mysql_replication_hostgroups has been omitted.
## Configuring Default Read / Write splitting
By default, we want to send all reads to `hostgroup 11`. The standard rules for splitting read and write operations are as follows::
```sql
INSERT INTO mysql_query_rules (username,destination_hostgroup,active,match_digest,apply) VALUES
('app',10,1,'^SELECT.*FOR UPDATE',1),
('app',11,1,'^SELECT ',0);
LOAD MYSQL QUERY RULES TO RUNTIME; SAVE MYSQL QUERY RULES TO DISK;
```
The last rule on
`^SELECT `
should be set with
`apply=0`
, allowing ProxySQL to evaluate additional rules that will be introduced through the scheduler.
## Configuring Scheduler
Download and install the latest release of [Readyset ProxySQL Scheduler](https://github.com/readysettech/proxysql_scheduler/releases).
Adjust `/etc/readyset_proxysql_scheduler.cnf` to reflect your credentials:
```
proxysql_user = 'admin'
proxysql_password = 'admin'
proxysql_host = '127.0.0.1'
proxysql_port = 6032
readyset_user = 'app'
readyset_password = 'app'
readyset_host = '172.20.238.20'
readyset_port = 3306
source_hostgroup = 11
readyset_hostgroup = 12
warmup_time = 60
lock_file = '/tmp/readyset_scheduler.lock'
```
We're setting the warmup\_time to 60 seconds. This configuration causes every new query identified by the scheduler to be mirrored to Readyset for this duration. It triggers the construction of the dataflow for the query, allowing Readyset to warm up the cache for this specific query entry.
Now, we need to proceed with adding a scheduler entry:
```sql
INSERT INTO scheduler (active, interval_ms, filename, arg1) VALUES (1, 10000, '/usr/bin/readyset_proxysql_scheduler', '--config=/etc/readyset_proxysql_scheduler.cnf');
LOAD SCHEDULER TO RUNTIME; SAVE SCHEDULER TO DISK;
```
That's everything required. Now, if we execute a sample query, it should activate the scheduler's query detection mechanism:
```sql
mysql> SELECT COUNT(*) FROM employees WHERE gender = 'F';
+----------+
| COUNT(*) |
+----------+
| 120051 |
+----------+
1 row in set (0,05 sec)
```
If we check the `proxysql.log` file, we should observe the following:
```
2024-04-04 17:42:37 [INFO] Readyset[98173]: Running readyset_scheduler
2024-04-04 17:42:37 [INFO] Readyset[98173]: Going to test query support for SELECT COUNT(*) FROM employees WHERE gender = ?
2024-04-04 17:42:37 [INFO] Readyset[98173]: Query is supported, adding it to proxysql and readyset
2024-04-04 17:42:37 [INFO] Readyset[98173]: Inserted warm-up rule
2024-04-04 17:42:37 [INFO] Received LOAD MYSQL QUERY RULES TO RUNTIME command
2024-04-04 17:42:37 [INFO] Computed checksum for 'LOAD MYSQL QUERY RULES TO RUNTIME' was '0x34AC063B91801340', with epoch '1712263357'
2024-04-04 17:42:37 [INFO] Received SAVE MYSQL QUERY RULES TO DISK command
2024-04-04 17:42:37 [INFO] Readyset[98173]: Finished readyset_scheduler
```
Running the same query while it is under the warm-up rule should yield the same 50ms runtime, as it is still been served by the read replicas while also warming up the Readyset cache. After the `warmup_time` has elapsed, the rule will automatically be migrated from `mirror_hostgroup` to `destination_hostgroup`, and from this point forward, Readyset will start receiving this query.
```
2024-04-04 17:43:47 [INFO] Readyset[98327]: Running readyset_scheduler
2024-04-04 17:43:47 [INFO] Readyset[98327]: Updated rule ID 13 from warmup to destination
2024-04-04 17:43:47 [INFO] Received LOAD MYSQL QUERY RULES TO RUNTIME command
2024-04-04 17:43:47 [INFO] Computed checksum for 'LOAD MYSQL QUERY RULES TO RUNTIME' was '0x3F6C96CD31D7261B', with epoch '1712263427'
2024-04-04 17:43:47 [INFO] Received SAVE MYSQL QUERY RULES TO DISK command
2024-04-04 17:43:47 [INFO] Readyset[98327]: Finished readyset_scheduler
```
Checking the query again should show that it completes in 0ms, indicating that the query is now being served from the Readyset cache:
```sql
mysql> SELECT COUNT(*) FROM employees WHERE gender = 'F';
+----------+
| COUNT(*) |
+----------+
| 120051 |
+----------+
1 row in set (0,00 sec)
```
If you add a new employee, the cache will be automatically updated by Readyset to reflect this change.
```sql
mysql> INSERT INTO employees VALUES (999999, '2000-01-01', 'Some', 'One', 'F',
'2020-01-01');
Query OK, 1 row affected (0,01 sec)
mysql> SELECT COUNT(*) FROM employees WHERE gender = 'F';
+----------+
| count(*) |
+----------+
| 120052 |
+----------+
1 row in set (0,00 sec)
```
The write operation mentioned above was redirected by ProxySQL to the source server, and the SELECT query was directed to Readyset, which again completed in 0ms while also bringing the updated cache entry.
Additionally, it's important to note that cache filters are parameterized, meaning that if we change the filter value, we will still be serving data from the cache:
```sql
mysql> SELECT COUNT(*) FROM employees WHERE gender = 'M';
+----------+
| count(*) |
+----------+
| 179973 |
+----------+
1 row in set (0,00 sec)
```
# QueryPilot Optional Configurations (/reference/querypilot-optional-configurations)
import { Callout } from 'fumadocs-ui/components/callout';
Provided artifacts and short recipes that do not belong to a single component page. The Router is the SQL proxy (binary `querypilot`). Everything here assumes the metrics/admin API is on (`[metrics] enabled = true`), which serves Prometheus on 9090 and the admin API on 9091 by default.
## Grafana dashboard
A ready-made Router overview dashboard (JSON) is available from your Readyset contact. Panels: Backend Health (`sqp_backend_health` by pool), Queries (query volume), Routing decisions (by pool), Pool connections, and Pool durations p99.
It expects a Prometheus datasource scraping the Router metrics port. A minimal scrape config:
```yaml
scrape_configs:
- job_name: querypilot
static_configs:
- targets: [":9090"]
```
Point the scrape target at the metrics port; 9091 is the admin API and does not serve `/metrics`. Import the dashboard either way:
* **Grafana UI:** Dashboards, New, Import, upload the JSON file, select your Prometheus datasource.
* **Provisioning:** copy the JSON into your Grafana provisioning dashboards directory alongside a dashboard provider definition.
## Prometheus alert rules
Load alert rules through a `rule_files` glob in your Prometheus config. Thresholds below are conservative defaults to tune against your SLOs:
```yaml
groups:
- name: querypilot-router
rules:
- alert: QueryPilotBackendUnhealthy
expr: sqp_backend_health == 0
for: 2m
labels: { severity: page }
annotations:
summary: "Backend {{ $labels.backend }} in pool {{ $labels.pool }} is unhealthy"
- alert: QueryPilotPoolSaturated
expr: sqp_pool_utilization_ratio > 0.9
for: 10m
labels: { severity: warning }
annotations:
summary: "Pool {{ $labels.pool }} above 90% utilization"
- alert: QueryPilotPoolTimeouts
expr: increase(sqp_pool_timeouts_total[5m]) > 0
for: 5m
labels: { severity: warning }
annotations:
summary: "Sessions timing out waiting for connections in pool {{ $labels.pool }}"
```
The full metric catalog is in the [metrics reference](/querypilot/reference/metrics).
## Annotated example configuration
A fuller `querypilot.toml` showing the optional knobs beyond the minimal configs in [Getting Started](/querypilot/getting-started) and [Deployment](/querypilot/deployment):
```toml
[general]
pool_mode = "transaction" # session | transaction | statement
worker_threads = 0 # 0 = one per CPU core
shutdown_timeout = "30s" # drain window on SIGTERM
[[listeners]]
protocol = "postgres"
bind = "0.0.0.0:5433"
# MySQL clients are served from a second listener (preview).
#[[listeners]]
#protocol = "mysql"
#bind = "0.0.0.0:6433"
[pools.primary]
role = "primary"
username = ""
password = ""
database = ""
min_connections = 2 # kept open when idle
max_connections = 20 # hard cap per pool
acquire_timeout = "30s" # max wait for a connection before erroring
idle_timeout = "10m" # prune idle connections after this
connect_timeout = "10s" # backend TCP/auth budget
# query_timeout = "60s" # per-statement deadline; unset = none
# raw_passthrough = true # vanilla PG-to-PG pools only; see the
# Router guide before enabling
backends = [
{ host = "127.0.0.1", port = 5432 }
]
[routing]
default_pool = "primary"
[stats]
max_entries = 10000 # distinct query fingerprints tracked
[metrics]
enabled = true
bind = "0.0.0.0:9090" # admin API always binds metrics port + 1
[logging]
level = "info" # trace | debug | info | warn | error
format = "pretty" # pretty | json
```
Key-by-key detail lives in the [Router configuration reference](/querypilot/reference/router-configuration). Longer walkthroughs are tutorials: [read/write splitting](/demos/querypilot/read-write-split), [Accelerator caching](/demos/querypilot/accelerator-caching), and [Guard policies](/demos/querypilot/guard-policies).
## How-to: add a pattern routing rule and test it
Steer one query shape to a specific pool. Pattern rules match by normalized query shape; three `type`s exist: `template` (a literal example query whose values are parameterized away), `regex` (over the query text), and `hash` (a fingerprint hash). Get the fingerprint of any query from the admin API first:
```bash
curl "http://localhost:9091/api/routing/test?sql=SELECT%20*%20FROM%20orders%20WHERE%20user_id%20=%2042"
# note "fingerprint_hash" and "normalized_sql" in the response
```
Add the rule to `querypilot.toml`:
```toml
[[routing.pattern_rules]]
name = "order_lookups_to_replica"
type = "template"
pattern = "SELECT * FROM orders WHERE user_id = 42"
pool = "replica"
priority = 10
```
Apply without a restart, then confirm the decision changed:
```bash
curl -X POST http://localhost:9091/api/config/reload
curl "http://localhost:9091/api/routing/test?sql=SELECT%20*%20FROM%20orders%20WHERE%20user_id%20=%207"
# "decision": "pattern", "target_pool": "replica"
```
Any literal value matches: the template is normalized, so `user_id = 7` hits the rule created from the `= 42` example. Rules can also be created at runtime by fingerprint via `POST /api/pattern-rules`; those are ephemeral and cleared on config reload, so put durable rules in the config file.
## How-to: tune pool sizing and timeouts
The knobs, per `[pools.]`: `min_connections` (kept open), `max_connections` (hard cap), `acquire_timeout` (how long a session waits for a free connection), `idle_timeout` (idle connections above the minimum are closed), `connect_timeout` (backend dial deadline), and optional `query_timeout`. A steady-service starting point:
```toml
[pools.primary]
min_connections = 2
max_connections = 10
acquire_timeout = "30s"
idle_timeout = "60s"
connect_timeout = "10s"
```
Verify under load: `sqp_pool_utilization_ratio` near 1.0 with growing `sqp_pool_queue_depth` and `sqp_pool_wait_duration_seconds` means the cap is too low, or backend capacity is the real limit. `sqp_pool_timeouts_total` and `sqp_pool_acquire_total{outcome="timeout"}` count requests that gave up after `acquire_timeout`. `sqp_pool_acquire_total{outcome="created"}` churning alongside a short `idle_timeout` means you are paying reconnect cost; raise `min_connections` or `idle_timeout`. Transaction pool mode (`pool_mode = "transaction"`, the default) lets one backend connection serve many clients, so size against concurrent transactions, not client count.
## How-to: connect from application frameworks
No driver changes are needed; point existing drivers at the Router and authenticate with the pool's credentials (see [Security](/querypilot/security) for the auth model).
**Go (pgx v5)** against a PostgreSQL listener:
```go
connStr := fmt.Sprintf("postgres://%s:%s@%s:%s/%s", user, pass, host, port, db)
conn, err := pgx.Connect(ctx, connStr)
```
pgx defaults to prepared statements and the binary protocol; both are supported through the Router, including numeric/decimal round-trips.
**Python (psycopg 3)** against a PostgreSQL listener:
```python
import psycopg
conn = psycopg.connect(host="proxy.internal", port=6432,
user="app", password="", dbname="mydb")
conn.autocommit = True # or manage transactions explicitly
```
**Python (PyMySQL)** against a MySQL listener:
```python
import pymysql
conn = pymysql.connect(host="proxy.internal", port=6433,
user="app", password="", database="mydb")
```
MySQL support is in preview. Two type quirks to normalize for: `BOOLEAN` columns come back as `TINYINT(1)` integers, and binary payloads may arrive as `bytes` or `bytearray` depending on path.
One cross-cutting caution: leave `raw_passthrough` off for any pool that is not vanilla same-protocol PostgreSQL. Cross-protocol pools (MySQL client to PostgreSQL backend) need row re-encoding.
{/* source: verified against code @ 2026-07-18 */}
# Snapshotting (/reference/snapshots)
import { Callout } from 'fumadocs-ui/components/callout'
[Deep caches](/concepts/deep-caching) are maintained from a snapshot of your database's tables plus a continuous replication stream. Snapshotting copies the data of every table into Readyset's [storage directory](/reference/config-options#--storage-dir). Deep caches require every table to finish the initial snapshot. [Shallow caches](/concepts/shallow-caching) do not depend on snapshotting.
## Monitoring snapshot progress
Check overall snapshotting progress by connecting a SQL shell to Readyset and running:
```sql
SHOW READYSET STATUS;
```
The row labeled `Status` shows `Snapshot In Progress` until every table finishes snapshotting, then `Online`:
```text
name | value
-------------------+---------
...
Status | Online
Replication Status | Running
...
```
For per-table detail, use [`SHOW READYSET TABLES`](/command-reference#show-readyset-tables):
```sql
SHOW READYSET TABLES;
```
```text
table | status | description
-----------------+--------------+------------
"public"."small" | Online |
"public"."big" | Snapshotting | 43.21%
"public"."huge" | Initializing |
(3 rows)
```
Tables waiting for their turn report `Initializing`. In-progress tables report `Snapshotting` with an estimated progress percentage in `description`, then `Compacting` while finalizing. `Online` means the table is fully snapshotted and replicating; restarts [do not re-snapshot](#restarting-during-snapshotting) online tables. Add `ALL` to also list upstream tables Readyset is not replicating, with the reason in `description`.
The Readyset log reports when each table's snapshot starts, with an estimated row count, and when it finishes, with the number of rows copied. While a table is snapshotting, the log reports its progress every 30 seconds.
## Tables Readyset does not replicate
Some upstream tables are never replicated at all. For example, on PostgreSQL, Readyset does not support tables with generated columns: PostgreSQL versions before 18 cannot send generated columns over logical replication, and Readyset publications does not yet support the `publish_generated_columns` option added in PostgreSQL 18.
Tables like these are refused when Readyset first sees them, during the initial snapshot or when a `CREATE TABLE` event arrives during replication. [`SHOW READYSET ALL TABLES`](/command-reference#show-readyset-tables) lists them as `Not replicated` and names the reason, for example `Tables with generated columns are not supported`. Queries against a non-replicated table fall through to the upstream database.
## Restarting during snapshotting
When Readyset restarts against an existing [storage directory](/reference/config-options#--storage-dir), snapshot progress is preserved **per table**:
* Tables whose snapshot completed before a clean shutdown are kept. Their data is read from the storage directory and they are not re-snapshotted; replication resumes from the position recorded for each table when its snapshot began.
* Tables that had not finished snapshotting start over **from the beginning of that table**. Rows already copied for that table are discarded.
* After an abrupt exit, such as a crash or `SIGKILL`, tables that finished snapshotting during that run may be snapshotted again.
If your deployment restarts routinely, avoid restarting while table snapshotting is in progress, for example during initial snapshotting or after changes to the ignored tables described in [Limiting which tables are snapshotted](#limiting-which-tables-are-snapshotted).
Restarting with an empty or different storage directory discards all previously snapshotted data and starts a full snapshot of all tables.
## Parallel snapshotting
Readyset snapshots multiple tables in parallel, up to [`--max-parallel-snapshot-tables`](/reference/config-options#--max-parallel-snapshot-tables) (default: number of available cores - 1). Each table being snapshotted takes its own connection to the upstream database. Tables are snapshotted independently, so overall snapshot time is at least as long as the slowest single table.
## Limiting which tables are snapshotted
By default, Readyset snapshots and replicates every table in the upstream database. To narrow the scope, include only the tables your deep caches need with [`--replication-tables`](/reference/config-options#--replication-tables), or exclude specific tables with [`--replication-tables-ignore`](/reference/config-options#--replication-tables-ignore). See [Config Options](/reference/config-options#--replication-tables) for the accepted formats and eligibility behavior.
To add previously excluded or omitted tables to a running deployment, use [`ALTER READYSET ADD TABLES`](/command-reference#alter-readyset-add-tables):
```sql
ALTER READYSET ADD TABLES db.table, db.table2;
```
Readyset snapshots the added tables; tables that already finished snapshotting are not re-snapshotted. While the added tables snapshot, `SHOW READYSET STATUS` reports `Snapshot In Progress` again, and existing deep caches keep serving results but do not receive upstream changes until snapshotting completes.
## When a table snapshot fails
If a table fails to snapshot, Readyset continues with the rest, and the deployment can still reach `Online`. Queries against the failed table fall through to the upstream database, so results stay correct but are not cached. Check [`SHOW READYSET ALL TABLES`](/command-reference#show-readyset-tables) to find it: on PostgreSQL it is listed as `Not replicated` with the error, and is not retried; on MySQL it is retried automatically but can stay `Initializing` until the underlying problem is fixed.
Once the problem is fixed, add the table back with [`ALTER READYSET ADD TABLES`](/command-reference#alter-readyset-add-tables):
```sql
ALTER READYSET ADD TABLES db.table;
```
Readyset re-snapshots the added table, and the deployment reports `Snapshot In Progress` until it finishes. Restarting Readyset also recovers the table: restarts preserve tables whose snapshot completed and start over from the beginning for the rest, as described in [Restarting during snapshotting](#restarting-during-snapshotting).
## Re-snapshotting a table
To re-copy a single table's data, use:
```sql
ALTER READYSET RESNAPSHOT TABLE db.table;
```
For behavior when the upstream database fails over to a new primary, including the full resnapshot PostgreSQL requires after a replication slot is lost, see [Failover](/reference/failover).
# System Views (/reference/system-views)
Readyset exposes read-only system views under the `readyset` schema, queryable
with plain `SELECT` statements. Use them to monitor a running deployment.
## `readyset.replication_status`
Reports how far behind Readyset is relative to the upstream database's current
replication position. A single row is returned.
```sql
SELECT * FROM readyset.replication_status;
```
### Columns
| Column | Type | Description |
| ----------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `replication_mode` | `text` | Replication protocol in use: `postgres`, `mysql_file`, or `mysql_gtid`. |
| `upstream_offset` | `text` | The upstream's current replication position. Format depends on `replication_mode`. |
| `replicator_offset` | `text` | Position of the most recent event Readyset has consumed from the stream. |
| `persisted_offset` | `text` | Persistence frontier: the furthest position any base table has durably recorded. |
| `consume_lag` | `bigint` | Bytes (Postgres, MySQL file-based) or transactions (MySQL GTID) between `upstream_offset` and `replicator_offset`. How far behind Readyset is in ingesting events. |
| `persist_lag` | `bigint` | Same units as `consume_lag`, between `upstream_offset` and `persisted_offset`. How far behind Readyset is in durably persisting events. |
| `heartbeat_staleness_seconds` | `double precision` | Wall-clock staleness in seconds. `NULL` when heartbeat is disabled. |
### Example output
```
replication_mode | upstream_offset | replicator_offset | persisted_offset | consume_lag | persist_lag | heartbeat_staleness_seconds
------------------+-----------------+-------------------+------------------+-------------+-------------+-----------------------------
postgres | 0/1A3F2C8 | 0/1A3F200 | 0/1A3F1B0 | 200 | 280 | 0.004
```
### Notes
* On a quiescent Postgres upstream, `consume_lag` and `persist_lag` report a
small non-zero value (\~288 bytes). This is a measurement artifact from the
WAL record that the position query itself generates, not real lag.
* When `replication_mode` is `mysql_gtid`, `consume_lag` and `persist_lag` are
counts of transactions, not bytes.
* `heartbeat_staleness_seconds` requires
[`--replication-heartbeat`](/reference/config-options#--replication-heartbeat)
to be enabled on the Readyset server, which in turn requires write access to
the upstream so Readyset can maintain a small heartbeat table
(`readyset._heartbeat` on Postgres, `_readyset_heartbeat` on MySQL). If
write access is not granted, Readyset logs a warning, disables heartbeat,
and the column reports `NULL`; the other columns are unaffected.
* The poll cadence is controlled by
[`--replication-lag-interval`](/reference/config-options#--replication-lag-interval)
(default `1` second).
### Required upstream privileges
* **Postgres**: none beyond what replication already requires.
* **MySQL**: `REPLICATION CLIENT` (already required for replication).
* **Heartbeat** (optional, either dialect): write access to the upstream.
## `readyset.users`
Lists the usernames currently allowed to authenticate with Readyset. Passwords
are never exposed. The list reflects runtime changes made with
[`ALTER READYSET ADD/MODIFY/DROP USER`](/command-reference#alter-readyset-add-user)
immediately.
```sql
SELECT * FROM readyset.users;
```
### Columns
| Column | Type | Description |
| ------------------ | ------ | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `user` | `text` | A username allowed to authenticate with Readyset. |
| `has_old_password` | `bool` | Whether the user has an old password retained by a [password rotation](/reference/user-management#rotating-a-password-without-downtime). |
### Notes
* `user` is a reserved word in PostgreSQL (it evaluates to `CURRENT_USER`), so
quote the column to select it explicitly: `SELECT "user" FROM readyset.users`.
On MySQL it may be used unquoted.
* The allowed-users set is seeded on Readyset's first startup from
[`--allowed-users`](/reference/config-options#--allowed-users) and the
[`--upstream-db-url`](/reference/config-options#--upstream-db-url) user, and is
managed at runtime with
[`ALTER READYSET ADD/MODIFY/DROP USER`](/command-reference#alter-readyset-add-user)
thereafter. See [User Management](/reference/user-management).
## `readyset.cache_grants`
Exposes the current [cache authorization](/reference/cache-authorization)
verdict for every combination of user and cache: whether that user is served
from that cache, or routed to the upstream database instead. One row per
pair, including pairs the background checks have not resolved yet.
```sql
SELECT * FROM readyset.cache_grants;
```
### Columns
| Column | Type | Description |
| ----------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `user` | `text` | The user (or assumed PostgreSQL role) the verdict applies to. |
| `cache` | `text` | The cache's query ID, as shown by `SHOW CACHES`. |
| `verdict` | `text` | `allowed` (served from the cache), `denied` (routed upstream, which rejects unauthorized access), or `unknown` (not checked yet; routed upstream, which authorizes directly). |
| `probed_at` | `text` | Timestamp of the check that produced this verdict; `NULL` for `unknown` pairs that have not been checked. |
### Notes
* `user` is a reserved word in PostgreSQL, so quote the column to select it
explicitly: `SELECT "user" FROM readyset.cache_grants`.
* `denied` and `unknown` are served identically (routed to the upstream);
they differ only in whether the background check has an answer yet.
* Verdicts converge within one
[`--cache-acl-refresh-interval-secs`](/reference/config-options#--cache-acl-refresh-interval-secs)
interval, or immediately on
[`ALTER READYSET FLUSH PRIVILEGES`](/command-reference#alter-readyset-flush-privileges).
# Telemetry (/reference/telemetry)
Readyset collects telemetry data to help us diagnose issues and understand how users interact with our product.
This telemetry data is anonymous by default. Users can opt into providing a persistent identifier, or disable telemetry completely.
Telemetry is reported using the [Segment](https://segment.com) customer data platform.
## Disabling telemetry
Telemetry can be disabled by running the Readyset installer with the `--disable-telemetry` option.
## Providing your own Segment source
By default, Readyset reports telemetry to an [HTTP Tracking API Source](https://segment.com/docs/connections/sources/catalog/libraries/server/http-api/) operated by Readyset, Inc. A different source can be specified by setting the `RS_SEGMENT_WRITE_KEY` environment variable to the write key corresponding to the source you wish to use.
## What telemetry data is collected?
Telemetry messages adhere to the [Segment Track API spec](https://segment.com/docs/connections/spec/track/). Each telemetry message contains a per-session anonymous UUID, an event name, a timestamp, and 0 or more properties. If the `RS_API_KEY` environment variable is set, its value will be used as the user ID.
The following OpenAPI schema describes all possible telemetry payloads reported by Readyset:
```yaml
components:
schemas:
TrackBody:
type: object
properties:
userId:
type: string
description: the value in the RS_API_KEY env var, if present
anonymousId:
type: string
description: a per-session UUID
event:
enum:
- adapter_start
- adapter_stop
- server_start
- server_stop
- installer_run
- deployment_started
- deployment_finished
- deployment_torn_down
- installer_finished
properties:
$ref: '#/components/schemas/Properties'
timestamp:
type: string
format: date-time
required:
- anonymousId
- event
- timestamp
Properties:
type: object
properties:
db_backend:
enum:
- mysql
- postgres
adapter_version:
type: string
server_version:
type: string
docker_version:
type: string
commit_id:
type: string
```
### Events
Below are brief descriptions of each possible event:
* **Server Start**: The Readyset server was launched
* **Server Stop**: The Readyset server was shut down
* **Adapter Start**: The Readyset adapter was launched
* **Adapter Stop**: The Readyset adapter was shut down
* **Installer Run**: The Readyset installer was launched
* **Deployment Started**: A deployment of Readyset was initiated by the installer
* **Deployment Finished**: The Readyset installer finished deploying a new instance of Readyset
* **Deployment Torn Down**: The Readyset installer finished tearing down a deployment
* **Installer Finished**: The Readyset installer exited successfully
# TLS Support (/reference/tls)
import { Callout } from 'fumadocs-ui/components/callout'
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
Readyset supports TLS encryption between itself and your upstream database out
of the box. To do this, Readyset must have access to the root certificate used
by your upstream database. Operating systems typically come with the root
certificates for some common certificate authorities already installed. If your
upstream database uses a different root certificate, then you must either
install that certificate (such that all applications on your system can use it
to verify certificates) or provide the root certificate file path directly to
Readyset via the `--ssl-root-cert` option.
The `--ssl-root-cert` option only supports a single certificate in the PEM or
DER format. It does not support bundles containing multiple certificates.
On macOS, providing the root certificate via the `--ssl-root-cert` option is
not supported, the Apple Security framework (used by Readyset to provide TLS
support) does not allow the use of untrusted certificates even when explicitly
provided.
Below are instructions for installing and trusting a new root certificate on supported platforms:
Readyset uses the system-provided OpenSSL package on Linux.
The following instructions are derived from [the Ubuntu docs](https://ubuntu.com/server/docs/security-trust-store).
To install a new certificate, first download the certificate, then run the following:
```sh
# Change the extension to .crt, and if necessary, convert from DER to PEM format
# Place the output file in the appropriate system directory
sudo openssl x509 -outform pem -in $INFILE -out /usr/local/share/ca-certificates/$OUTFILE.crt
# Install the certificate
sudo update-ca-certificates
```
Readyset uses the system-provided OpenSSL package on Linux.
The following instructions are derived from [the AWS docs](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/SSL-on-amazon-linux-2.html).
To install a new certificate, download the certificate, place it in `/etc/pki/tls/certs`,
and set the file ownership and permissions as follows:
```sh
sudo chown root:root $CERTIFICATE
sudo chmod 600 $CERTIFICATE
```
Readyset uses the Security framework on macOS.
Certificates must be trusted by the system to be used.
To install and trust a new certificate, do the following:
1. Download the certificate you wish to install
2. Open the Keychain Access application
3. From the left-side menu, under System Keychains, select System
4. Drag and drop the certificate into the keychain (or File > Import items...)
5. Open the newly installed certificate (or File > Get Info)
6. Set Trust > Secure Sockets Layer (SSL) to Always Trust and close the window (it prompts for authorization)
Below are download links for the root certificates for supported cloud database providers:
The [Amazon RDS global certificate bundle](https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem)
includes root certificates for every AWS region.
Alternatively, only download the [region-specific bundle(s)](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.SSL.html#UsingWithRDS.SSL.RegionCertificates)
you need.
The certificates used by Azure Database are probably already installed on your system.
Both [Azure Database for PostgreSQL Single Server](https://learn.microsoft.com/en-us/azure/postgresql/single-server/concepts-ssl-connection-security)
and [Azure Database for MySQL Single Server](https://learn.microsoft.com/en-us/azure/mysql/single-server/concepts-ssl-connection-security)
require the [DigiCert Global Root G2](https://cacerts.digicert.com/DigiCertGlobalRootG2.crt.pem)
certificate.
Both [Azure Database for PostgreSQL Flexible Server](https://learn.microsoft.com/en-us/azure/postgresql/flexible-server/how-to-connect-tls-ssl)
and [Azure Database for MySQL Flexible Server](https://learn.microsoft.com/en-us/azure/mysql/flexible-server/how-to-connect-tls-ssl)
require the [DigiCert Global Root CA](https://dl.cacerts.digicert.com/DigiCertGlobalRootCA.crt.pem)
certificate.
Google Cloud SQL creates a new root certificate per server instance. Follow [these instructions](https://cloud.google.com/sql/docs/mysql/configure-ssl-instance#server-certs)
to locate and download the certificate for your particular instance.
# User Management (/reference/user-management)
import { Callout } from 'fumadocs-ui/components/callout'
Readyset keeps its own set of users that are allowed to authenticate with it.
That set has two stages in its life: it is seeded from command-line flags the
very first time a deployment starts, and from then on you manage it with SQL
against a running Readyset, with no restart and no dropped connections.
## Bootstrap on the first startup
On a deployment's **first** startup, Readyset seeds the allowed-users set from
two places:
* The user in [`--upstream-db-url`](/reference/config-options#--upstream-db-url),
so the credentials Readyset already has can also be used to connect to it.
* [`--allowed-users`](/reference/config-options#--allowed-users) (env
`ALLOWED_USERS`), a comma-separated list of `username:password` pairs.
```bash
readyset --upstream-db-url postgresql://readyset:@db.internal/app \
--allowed-users 'alice:,bob:'
```
That start accepts `readyset`, `alice`, and `bob`.
Seeding happens **once**. Readyset then stores the set and reads it from
storage on every later start, so editing `--allowed-users` or pointing
`--upstream-db-url` at a different user has no effect on who may
authenticate. After the first startup, use the commands below.
## Managing users at runtime
Connect to Readyset as any allowed user and run:
```sql
ALTER READYSET ADD USER 'carol' PASSWORD 'carolpw';
ALTER READYSET MODIFY USER 'carol' PASSWORD 'rotatedpw';
ALTER READYSET DROP USER 'carol';
```
Each command is stored before it returns, so it survives restarts, and takes
effect on the next connection attempt: an added user can connect right away, a
rotated password is required immediately, and a dropped user is refused. A
dropped or rotated user's connections that are already open are not closed;
the change applies when they reconnect.
[`ADD USER`](/command-reference#alter-readyset-add-user) fails if the user
already exists;
[`MODIFY USER`](/command-reference#alter-readyset-modify-user) and
[`DROP USER`](/command-reference#alter-readyset-drop-user) fail if it does not.
The user derived from `--upstream-db-url` cannot be modified or dropped, since
that identity backs Readyset's own connection to your database. All three
commands require
[authentication](/reference/authentication) to be enabled, so they are
unavailable under `--allow-unauthenticated-connections`.
A user in Readyset also needs to exist in your database with the same
password. Readyset opens each session's connection to your database with
that session's own credentials, so `ALTER READYSET ADD USER` is a companion
to a `CREATE USER` upstream, not a replacement for it.
## Rotating a password without downtime
Changing a password with `MODIFY USER` normally requires new connections to
begin using the new password immediately. To allow the old password to remain
valid during a transitional period to the new password, perform:
```sql
ALTER READYSET MODIFY USER 'carol' PASSWORD 'rotatedpw' RETAIN CURRENT PASSWORD;
```
Carol can now authenticate with either password. Once all clients have moved to
the new one, retire the old password with:
```sql
ALTER READYSET MODIFY USER 'carol' DISCARD OLD PASSWORD;
```
A later `MODIFY USER ... RETAIN CURRENT PASSWORD` replaces any old retained
password with the current password, so only two passwords are ever valid at a
time. `MODIFY USER` without the clause changes the current password only and
leaves a retained one alone. `DISCARD OLD PASSWORD` is accepted even if there
is no old password.
To start a deployment already mid-rotation, name the user twice in
`--allowed-users`. The first password is the current one and the second is the
retained one:
```bash
readyset --allowed-users 'carol:newpassword,carol:oldpassword'
```
Password rotation is available with a MySQL upstream only, mirroring MySQL's
own dual-password support. Both clauses, and a repeated user in
`--allowed-users`, are rejected in PostgreSQL mode.
Rotate the upstream account first, using MySQL's own `ALTER USER ... RETAIN
CURRENT PASSWORD`, and only then run the Readyset statement. Readyset connects
to your database with whichever of the two passwords the client presented, and
checks [cache authorization](/reference/cache-authorization) with the current
one, so both passwords must already be valid upstream. When the rotation is
complete, discard the old password in Readyset before discarding it upstream.
## Listing the current users
The [`readyset.users`](/reference/system-views#readysetusers) system view lists
the usernames that may authenticate right now, including runtime changes.
Passwords are never exposed.
```sql
SELECT "user" FROM readyset.users;
```
`user` is a reserved word in PostgreSQL, so quote the column there; on MySQL
`SELECT user FROM readyset.users` works unquoted. The `has_old_password` column
reports whether the user has a password rotation in progress.
## How it fits with the rest of Readyset
**[Authentication](/reference/authentication)** verifies each connection
against this set, whichever method the client protocol negotiates. On MySQL,
`caching_sha2_password` keeps a per-user fast-auth digest; `MODIFY USER` and
`DROP USER` clear it as part of the change, so a rotated password cannot be
bypassed by a client that authenticated earlier. A retained password keeps a
digest of its own until it is discarded.
**[Cache authorization](/reference/cache-authorization)** decides which users
are served from a cache, by asking your database what each of them is allowed
to read. `ADD USER` and `MODIFY USER` schedule that check for the new user, so
they start getting cache hits as soon as their privileges are confirmed;
`DROP USER` forgets them. Until then their queries are simply proxied to your
database.
# QueryPilot (/troubleshooting/querypilot)
import { Callout } from 'fumadocs-ui/components/callout';
Symptom-first fixes for the failure modes operators actually hit. The Router is the SQL proxy (binary `querypilot`). Port conventions used throughout: Router wire 5433, metrics 9090, admin API 9091 (always metrics port + 1). Most "cannot reach it" reports trace back to probing the wrong port.
## Startup failures
| Symptom | Likely cause | Fix |
| --------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| Router exits with a TOML parse error naming a key | The Router's schema is strict; an unknown or misspelled key anywhere fails startup | Fix the key; run `querypilot --check -c ` before deploying |
| Router exits with `refusing to start: ... open proxy` | A listener binds a non-loopback address with no pool credentials configured | See below |
| Router exits with an address-in-use error on 9090 | Something else owns port 9090, most often a Prometheus server on the same host (its default port is also 9090) | Move `[metrics] bind` (the admin API follows to port + 1) or move Prometheus |
| Router exits with a port-overflow error | `metrics.bind` port is 65535, so the derived admin port (metrics + 1) cannot exist | Pick a lower metrics port |
| Admin API connection refused on 9091, container flaps unhealthy | `[metrics] enabled = false` (the default), or metrics moved so the admin port moved with it | Set `enabled = true`; remember the shipped healthcheck assumes 9090/9091 |
### The open-proxy refusal
With no pool credentials configured, the Router accepts any client credentials, so it refuses to put a listener on a non-loopback address. The error is explicit:
```text
refusing to start: no client authentication is configured but listener(s)
[...] bind a non-loopback address, which would expose an open proxy.
Set username/password on a pool to require auth, bind listeners to loopback,
or set `[general] allow_anonymous = true` to deliberately run without auth.
```
Fix one of three ways: set `username`/`password` on a pool (these double as the client credentials), bind listeners to `127.0.0.1`, or set [`allow_anonymous = true`](/querypilot/reference/router-configuration#allow_anonymous) to deliberately run without auth after reading the [security page](/querypilot/security).
### A config setting has no effect
Which schema are you in? The components treat mistakes differently:
* **Router (`querypilot.toml`): strict.** Unknown keys fail at startup. If the Router started, your key was at least spelled correctly; check you edited the file the Router actually loaded (`--config` flag, then `QUERYPILOT_CONFIG`, then `./querypilot.toml`) and see `GET /api/config/status` for the live config path.
## Connection and authentication failures
| Symptom | Likely cause | Fix |
| -------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| Credentials that work against the database fail via the Router | Clients must present the credentials of the first pool that defines them, not their database logins | Use the pool's `username`/`password`; check `sqp_auth_failures_total` for the reason |
| Unexpected credentials succeed | No pool defines credentials, so the Router accepts anything (loopback-only state) | Set credentials on a pool |
| Clients connect but every query fails with a backend error | Pool credentials or `database` wrong for the backend, or backend unreachable | Watch `sqp_backend_errors_total` and `sqp_backend_health`; test the pool's credentials directly against the backend |
| Connection refused on 5433 | Listener `bind` differs from the container convention, or the container publishes different ports | Check `[[listeners]]` in the loaded config |
## Routing surprises
| Symptom | Likely cause | Fix |
| --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------- |
| A query went to the wrong pool | A pattern rule with higher precedence (lower `priority` number), or the read/write split classified it differently than you expected | Dry-run it: `GET /api/routing/test?sql=...` returns `decision` and `target_pool` |
| Reads inside transactions hit the primary | Transaction pinning: statements inside an open transaction stay on the transaction's connection regardless of type | Expected; keep read-only work outside write transactions |
| A replica gets no traffic | Its measured replication lag exceeds its `max_replica_lag`, so it is skipped until it catches up | Watch `sqp_backend_health` per backend and fix upstream lag |
| A rule added via `POST /api/pattern-rules` vanished | Runtime rules are ephemeral and cleared on config reload | Put durable rules in `querypilot.toml` |
The dry-run endpoint answers most routing questions without sending traffic:
```bash
curl "http://localhost:9091/api/routing/test?sql=SELECT%20*%20FROM%20orders%20WHERE%20id%20=%201"
```
The response includes `decision` (`translation`, `pattern`, `regex`, `default`, or `pinned`), `target_pool`, `fingerprint_hash`, and `normalized_sql`. Under live traffic, `sqp_routing_decisions_total` split by `decision_type` and `pool` shows where queries actually go.
## Pool exhaustion
**Symptom:** queries stall then fail after roughly `acquire_timeout`; `sqp_pool_timeouts_total` and `sqp_pool_acquire_total{outcome="timeout"}` increment.
Diagnose with the pool gauges: `sqp_pool_utilization_ratio` pinned near 1.0 with growing `sqp_pool_queue_depth` and `sqp_pool_wait_duration_seconds` means every connection is busy and sessions are queueing. Fixes, in order of preference:
1. Confirm the backend is not the real bottleneck (slow queries hold connections; check `sqp_query_duration_seconds`).
2. Raise `max_connections` on the pool if the backend has headroom.
3. With transaction pooling (`pool_mode = "transaction"`, the default), size against concurrent transactions, not client count; look for applications holding transactions open (`sqp_transaction_idle_seconds`, `sqp_transactions_active`).
4. If connections churn (`sqp_pool_acquire_total{outcome="created"}` climbing alongside a short `idle_timeout`), raise `min_connections` or `idle_timeout`.
Also check pool status: a pool set to `offline_soft` or `shunned` via the admin API rejects all acquires (`GET /api/pools` shows current statuses).
## Reading logs
Turn up verbosity with the environment override, not `RUST_LOG`, which the binary does not read:
```bash
QUERYPILOT_LOG_LEVEL=debug querypilot
```
Precedence is `--log-level` flag, then the environment variable, then the config's `logging.level`. Set `format = "json"` under `[logging]` when shipping logs to a pipeline; `pretty` is for terminals. The Router reloads `logging.level` via `POST /api/config/reload` if you need debug logs without a restart. See [environment variables](/querypilot/reference/environment-variables).
{/* source: verified against code @ 2026-07-18 */}