Skip to content

Commit 9d68fa0

Browse files
committed
docs: updated documents to match v0.6.0
It's migration release for new features in v0.7.0
1 parent c339f50 commit 9d68fa0

4 files changed

Lines changed: 121 additions & 120 deletions

File tree

README.md

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ The PostgreSQL MCP server that doesn't need connection to the production.
88

99
## The problem
1010

11-
LLM/AI coding assistants are very good in writing code/SQL queries. But they are blind. THey don't know your schema, your indexes or your constraints. They might generate a migration that takes an `ACCESS EXCLUSIVE` lock on your busiest table and send your app down.
11+
LLM/AI coding assistants are very good in writing code/SQL queries. But they are blind. They don't know your schema, your indexes or your constraints. They might generate a migration that takes an `ACCESS EXCLUSIVE` lock on your busiest table and send your app down.
1212

1313
Some PostgreSQL MCP server ask you for the database connection. And to perform the administrative tasks you might need SUPERUSER permission. But that's like asking for problem.
1414

@@ -133,6 +133,20 @@ dryrun lint
133133

134134
All commands work offline from the schema file. Each project has its own `dryrun.toml` and `.dryrun/`, there is no global state. Add `.dryrun/` to your `.gitignore`.
135135

136+
Snapshots live in `~/.dryrun/history.db`, keyed by `(project_id, database_id)`. The MCP server reads from the history db first and falls back to `.dryrun/schema.json` for first-run or shared snapshots. After `dryrun snapshot take` it will switch to DB.
137+
138+
### Multi-node: capture activity from replicas
139+
140+
`snapshot take` runs against the primary and writes schema + planner stats. Activity counters (`idx_scan`, `n_dead_tup`, last vacuum) live on each replica, so capture them separately:
141+
142+
```sh
143+
dryrun --profile primary snapshot take
144+
dryrun --profile replica1 snapshot activity --from "$REPLICA1_URL" --label replica1
145+
dryrun --profile replica2 snapshot activity --from "$REPLICA2_URL" --label replica2
146+
```
147+
148+
The MCP `compare_nodes` tool then exposes per-node `idx_scan` so you can spot routing imbalances. See [docs/multi-node-stats.md](docs/multi-node-stats.md).
149+
136150
### Multiple databases per project
137151

138152
`dryrun snapshot take` keys snapshots by `(project_id, database_id)`. The defaults work — `project_id` is your folder name, `database_id` is the actual database name from `current_database()`:
@@ -167,9 +181,9 @@ dryrun --profile billing snapshot diff --latest
167181

168182
See [`docs/dryrun-toml.md`](docs/dryrun-toml.md) for all profile options.
169183

170-
Every DB-related command (`init`, `import`, `probe`, `dump-schema`, `lint`, `drift`, `stats apply`, all `snapshot` subcommands) accepts `--profile` and falls back to the resolved profile's `db_url` and `schema_file` when the corresponding CLI flag is not provider.
184+
Every DB-related command (`init`, `import`, `probe`, `dump-schema`, `lint`, `drift`, `stats apply`, all `snapshot` subcommands) accepts `--profile` and falls back to the resolved profile's `db_url` and `schema_file` when the corresponding CLI flag is not provided.
171185

172-
> **Note:** the MCP server is currently single-database. Using the default profile. Or the option is to run one `dryrun mcp-serve` process per database. Native multi-database support inside one MCP process is tracked in [#4](https://github.com/boringSQL/dryrun/issues/7).
186+
> **Note:** the MCP server is currently single-database. Using the default profile. Or the option is to run one `dryrun mcp-serve` process per database. Native multi-database support inside one MCP process is tracked in [#7](https://github.com/boringSQL/dryrun/issues/7).
173187
174188
## MCP server
175189

@@ -200,6 +214,12 @@ See the [Tutorial](TUTORIAL.md) for live database setup, SSE transport, and Clau
200214
- **[RegreSQL](https://github.com/boringsql/regresql)**, SQL regression testing and **`dryrun`**'s companion tool
201215

202216

217+
## Upgrading from 0.5.x
218+
219+
- `dump-schema --stats-only` is removed. Use `dryrun snapshot take` (primary) and `dryrun snapshot activity` (replicas).
220+
- Snapshot JSON no longer embeds `Table.stats`, `Column.stats`, `Index.stats`, or `node_stats`. Stats are read per-kind from the history db via `HistoryStore::get_annotated`.
221+
- `check_drift` is now schema-only. It no longer flaps when `reltuples` or `idx_scan` change.
222+
203223
## License
204224

205225
[BSD 2-Clause License](LICENSE)

TUTORIAL.md

Lines changed: 39 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ Privileges:
103103
dryrun init --db "$DATABASE_URL"
104104
```
105105

106-
Creates `.dryrun/schema.json` and `.dryrun/history.db`.
106+
Creates `dryrun.toml`, the `.dryrun/` directory, and `.dryrun/schema.json`. Snapshot history lives in `~/.dryrun/history.db` (shared across projects, keyed by `(project_id, database_id)`).
107107

108108
### 3. Snapshots and diffing
109109

@@ -149,55 +149,64 @@ All tools available including EXPLAIN ANALYZE (runs in rolled-back transactions,
149149

150150
## Part C: Multi-node workflow
151151

152-
For setups with one master and N replicas serving different query patterns. Stats (seq_scan, idx_scan, reltuples) differ per node. dryrun can aggregate them.
152+
For setups with one primary and N replicas serving different query patterns. Activity counters (`seq_scan`, `idx_scan`, `n_dead_tup`) differ per node and only live where the queries actually run, on the replicas. dryrun captures schema + planner stats from the primary and activity stats from each replica, then aggregates them.
153153

154-
### 1. Full dump from master
154+
In v0.6.0 a snapshot is split into three rows in `~/.dryrun/history.db`: `schema`, `planner_stats`, `activity_stats`. `snapshot take` writes the first two from the primary; `snapshot activity` writes one `activity_stats` row per replica, tagged with `--label`.
155+
156+
### 1. Schema + planner stats from the primary
155157

156158
```sh
157-
dryrun dump-schema --source "$MASTER_DB" --name "master" -o master.json
159+
dryrun --profile primary snapshot take
158160
```
159161

160-
### 2. Stats-only dumps from replicas
162+
Refuses to run on a standby. Writes `schema` (DDL) + `planner_stats` (`reltuples`, `relpages`, `pg_statistic`) to history.
161163

162-
No structural schema, just pg_stat_user_tables and pg_stat_user_indexes data:
164+
### 2. Activity stats from each replica
163165

164166
```sh
165-
dryrun dump-schema --source "$REPLICA1_DB" --stats-only --name "replica-1" -o r1-stats.json
166-
dryrun dump-schema --source "$REPLICA2_DB" --stats-only --name "replica-2" -o r2-stats.json
167-
dryrun dump-schema --source "$REPLICA3_DB" --stats-only --name "replica-3" -o r3-stats.json
167+
dryrun --profile replica1 snapshot activity --from "$REPLICA1_URL" --label replica1
168+
dryrun --profile replica2 snapshot activity --from "$REPLICA2_URL" --label replica2
169+
dryrun --profile replica3 snapshot activity --from "$REPLICA3_URL" --label replica3
168170
```
169171

170-
These are lightweight, good for nightly cron. Example cron entry:
172+
`--label` is required and identifies the node in `compare_nodes` and `detect`. `snapshot activity` refuses to run on the primary. Activity rows attach to the most recent `schema` row by `schema_ref_hash`; pass `--allow-orphan` to capture before a schema exists.
171173

172-
```sh
173-
# /etc/cron.d/dryrun-stats
174-
0 2 * * * app dryrun dump-schema --source "$REPLICA1_DB" --stats-only --name "replica-1" -o /data/dryrun/r1-stats.json
175-
```
174+
### 3. Define profiles for repeatable runs
176175

177-
### 3. Import with merged stats
176+
```toml
177+
# dryrun.toml
178+
[project]
179+
id = "myapp"
178180

179-
```sh
180-
dryrun import master.json --stats r1-stats.json r2-stats.json r3-stats.json
181+
[profiles.primary]
182+
db_url = "${PRIMARY_DATABASE_URL}"
183+
184+
[profiles.replica1]
185+
db_url = "${REPLICA1_DATABASE_URL}"
186+
187+
[profiles.replica2]
188+
db_url = "${REPLICA2_DATABASE_URL}"
181189
```
182190

183-
The resulting `.dryrun/schema.json` contains the full schema from master plus per-node stats from each replica. Consumers (suggest, validate, lint) automatically use aggregated values:
191+
### 4. Cron
184192

185-
- **reltuples**: max across nodes
186-
- **seq_scan / idx_scan**: sum across nodes (reveals which replicas are doing seq scans)
187-
- **table_size**: max across nodes
193+
Schema changes rarely; activity counters shift daily. Capture each on its own schedule:
188194

189-
### 4. Verify
195+
```sh
196+
# /etc/cron.d/dryrun-stats
197+
0 2 * * * app dryrun --profile primary snapshot take
198+
15 2 * * * app dryrun --profile replica1 snapshot activity --from "$REPLICA1_URL" --label replica1
199+
15 2 * * * app dryrun --profile replica2 snapshot activity --from "$REPLICA2_URL" --label replica2
200+
```
201+
202+
### 5. Verify
190203

191204
```sh
192-
cat .dryrun/schema.json | python3 -c "
193-
import sys, json
194-
d = json.load(sys.stdin)
195-
print(f'{len(d.get(\"node_stats\", []))} node stats attached')
196-
for ns in d.get('node_stats', []):
197-
print(f' {ns[\"source\"]}: {len(ns[\"table_stats\"])} tables, {len(ns[\"index_stats\"])} indexes')
198-
"
205+
dryrun snapshot list
199206
```
200207

208+
Each row prints its `kind` (`schema` / `planner_stats` / `activity_stats`), `node_label` for activity rows, and the `schema_ref_hash` linking activity to schema. The MCP `compare_nodes` tool then exposes per-node `idx_scan` for any table.
209+
201210
---
202211

203212
## Part D: MCP setup reference
@@ -285,6 +294,6 @@ GRANT pg_monitor TO your_readonly_user;
285294

286295
**"invalid schema JSON"** - The file must be a valid SchemaSnapshot. If you renamed fields or edited by hand, re-dump from the database.
287296

288-
**Multi-node stats not showing** - Verify `node_stats` array is present in `.dryrun/schema.json`. Each stats file must be a valid NodeStats JSON (from `--stats-only`).
297+
**Multi-node stats not showing** - Run `dryrun snapshot list` and confirm you see both `schema` rows (from `snapshot take` on the primary) and `activity_stats` rows (from `snapshot activity --label ...` on each replica) sharing the same `schema_ref_hash`. Activity captured before any schema exists needs `--allow-orphan` and won't reattach automatically.
289298

290299

docs/dryrun-toml.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ A profile is selected from:
6565

6666
CLI flags `--db` and `--schema-file` override the resolved profile's matching fields for that invocation; they don't bypass the profile, so `database_id` and `project_id` are still taken from it. `--profile billing --db $OTHER` connects to `$OTHER` but keys snapshots under billing's `database_id`.
6767

68-
Every DB command (`init`, `import`, `probe`, `dump-schema`, `lint`, `drift`, `stats apply`, all `snapshot` subcommands) accepts `--profile` and falls back to the resolved profile's `db_url` / `schema_file` when the corresponding CLI flag is omitted.
68+
Every DB command (`init`, `import`, `probe`, `dump-schema`, `lint`, `drift`, all `snapshot` subcommands) accepts `--profile` and falls back to the resolved profile's `db_url` / `schema_file` when the corresponding CLI flag is omitted.
6969

7070
Relative paths in `schema_file` are resolved from the project root (the directory containing `dryrun.toml`). Absolute paths work too.
7171

0 commit comments

Comments
 (0)