Add pscale insights and pscale inspect commands for database diagnostics#1308
Open
nickvanw wants to merge 8 commits into
Open
Add pscale insights and pscale inspect commands for database diagnostics#1308nickvanw wants to merge 8 commits into
nickvanw wants to merge 8 commits into
Conversation
pscale insights surfaces PlanetScale's server-side analysis of production traffic: top queries by latency/rows-read/time (queries), failing queries (errors), resource anomalies (anomalies), and schema recommendations with ready-to-apply DDL (recommendations). The insights API endpoints were previously only reachable via the MCP server or raw pscale api calls; this adds a typed QueryInsights service to the internal SDK and wires the already-present-but-unused SchemaRecommendations service to a command. pscale inspect runs read-only, bounded diagnostic checks over a direct connection using the same ephemeral-credential model as pscale sql: table/index sizes, unused and redundant indexes, full-table scans, long-running queries, locks, bloat/fragmentation, vacuum stats, and replication state. Checks adapt per engine (information_schema/sys for MySQL/Vitess, pg_catalog/pg_stat for PostgreSQL); checks that don't apply to an engine point at the equivalent surface instead. pscale inspect all runs every applicable check over a single connection and prints a combined report. The two surfaces cross-reference each other in help text, human output, and JSON next_steps so agents discover both: insights for traffic-aware, historical analysis; inspect for live state and physical layout. Supporting changes: sqlquery gains a reusable Session (one connection, many queries) extracted from Execute; the printer exposes its resource writer for commands that render dynamic column sets.
Agents that discover pscale via --help (rather than a repo AGENTS.md) had no pointer to the embedded agent guide outside the root command's help and JSON error next_steps. Every command's help/usage output now ends with a footer pointing at pscale agent-guide, and the full guide is readable as a help topic via pscale help agents. --keyspace on sql and inspect now accepts vtgate shard targets, e.g. mykeyspace/-80 or mykeyspace/-80@replica. Shard-targeted connections bypass the query planner and send statements directly to that shard's MySQL instance, so inspect checks can read one specific shard's statistics on sharded databases (enumerate shards with SHOW VITESS_SHARDS; rows are keyspace/shard). Databases can have hundreds of shards, so nothing fans out automatically — one target per run. The DSN is now built with the driver's Config/FormatDSN so the slash and at-sign in shard targets survive; previously they produced an invalid-DSN error.
nickvanw
force-pushed
the
nick/inspect-insights
branch
from
July 23, 2026 01:35
03c5895 to
25cac7b
Compare
nickvanw
force-pushed
the
nick/inspect-insights
branch
from
July 23, 2026 15:19
7438a71 to
19ee257
Compare
table-sizes rolls partitions up into their root (a 100-partition table now reads as one entity, not 100 rows) and includes materialized views, which also consume storage. index-sizes includes the table each index belongs to; an index name alone is often ambiguous. long-running-queries excludes walsenders — replication connections stay 'active' forever and made the check permanently noisy on any database doing CDC — and bounds query text to 200 chars like the MySQL variant. locks now reports only the roots of blocking trees (via pg_blocking_pids) with a count of sessions stuck behind each, instead of dumping every granted lock; routine AccessShareLocks drowned out the signal. bloat covers index bloat in addition to table bloat and adds object size and a bloat percentage, so the waste column has context. vacuum-stats honors per-table autovacuum reloptions (including autovacuum_enabled=false, reported as 'disabled') instead of only the global settings, and tracks analyze alongside vacuum. replication-slots separates retained WAL (restart_lsn) from unconfirmed WAL (confirmed_flush_lsn) — they measure different failure modes — and adds wal_status and safe_wal_size, the headroom before max_slot_wal_keep_size invalidates the slot. New invalid-indexes check finds indexes left invalid by failed CREATE INDEX CONCURRENTLY builds: they consume space, are never used, and are easy to miss. All queries verified against live databases on both engines.
Index storage is itemized by the index-sizes check (and index bloat by the bloat check), so including indexes in table-sizes counted the same bytes twice across checks. pg_table_size reports the table's own storage: heap + TOAST + FSM + VM.
nickvanw
marked this pull request as ready for review
July 23, 2026 20:13
Bugbot flagged two gaps in human output: a skipped check printed only the catalog hint, whose embedded command lacks --org and fails when pasted, even though runCheck had already built fully-flagged NextSteps; and inspect all never showed the per-check follow-ups (e.g. the rowsReadPerReturned sort for seq-scans), only the generic footer. Skipped checks now print their next steps in both single-check and combined output, and combined-report sections show a compact one-line follow-up when a check found something. Empty sections stay quiet — the report footer already covers the generic pointers.
When a check errored (timeout, permission, SQL failure), the synthetic result carried the error text but dropped the check's next steps, so JSON consumers lost the cross-reference exactly when the live check couldn't answer — the case where the server-side alternative matters most.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 7d4f593. Configure here.
The check listed every user table ordered by seq_scan, including rows with zero scans, implying activity where none occurred — and the empty state could never appear. The MySQL variant already returns only tables that were actually scanned.
no-itsbackpack
approved these changes
Jul 23, 2026
dgraham
approved these changes
Jul 23, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
Adds two complementary read-only diagnostic surfaces to the CLI, for both MySQL/Vitess and PostgreSQL databases:
pscale insights— surfaces PlanetScale's server-side analysis of production traffic, which until now was only reachable through the MCP server or rawpscale apicalls:insights queries <db> <branch>— top queries ranked by any insights metric (--sort totalTime|count|p99Latency|rowsRead|rowsReadPerReturned|…, plus--period,--dir,--limit)insights errors <db> <branch>— failing query patterns with error messagesinsights anomalies <db> <branch>— detected resource anomaliesinsights recommendations <db>— schema recommendations with ready-to-apply DDLpscale inspect— live, point-in-time checks run over a direct connection using the same ephemeral-credential model aspscale sql(always read-only, always bounded):table-sizes,index-sizes,unused-indexes,redundant-indexes,invalid-indexes,seq-scans,long-running-queries,locks,outliers,calls,bloat,vacuum-stats,replication-slots,subscriptions— andinspect all, which runs every applicable check over a single connection and prints a combined report.All commands support
--format json(andcsvfor single checks) for scripting and agents. Every command's--helpends with a footer pointing agents atpscale agent-guide --format json, and the embedded guide is readable viapscale help agents.Why
Users (and increasingly, AI agents) diagnosing a slow or unhealthy database today have to bounce between the web app, the MCP server, and hand-written SQL through
pscale shell. The platform already computes rich, traffic-aware analysis — query stats, anomalies, schema recommendations — but none of it was exposed as first-class CLI commands, and there was no safe, canned way to ask the database itself the standard DBA questions (what's big, what's unused, what's locked, what's bloated).This gives both audiences a single obvious front door. The two surfaces deliberately cross-reference each other (help text, human output, and
next_stepsin JSON) so an agent that lands on either one discovers the other:insightsfor historical, traffic-aware analysis;inspectfor live state and physical layout. When a check isn't available for an engine — or needs a PostgreSQL extension that isn't installed (e.g.outliersneedspg_stat_statements) — the output points at theinsightscommand that answers the same question server-side with no setup.Changes
internal/planetscale/insights.go— newQueryInsightsservice (ListQueries/ListErrors/ListAnomalies) plusWithSort/WithPeriodlist options; the already-presentSchemaRecommendationsservice is now actually used by a commandinternal/cmd/insights/— the four insights subcommandsinternal/cmd/inspect/— check catalog (per-engine SQL with invariants enforced by tests: single statement, read-only,LIMIT-bounded, system/internal schemas filtered) and the command treeinternal/sqlquery/session.go— reusableSession(one connection, many queries) extracted fromExecute; connection setup for both engines refactored intoopenMySQL/openPostgreswith no behavior change topscale sqlinternal/printer— expose the resource writer so commands with dynamic column sets can render CSVAGENTS.md— documents both surfaces and when to use whichThe PostgreSQL query catalog was sharpened from review feedback:
table-sizesusespg_table_size(heap + TOAST + FSM + VM; indexes are itemized byindex-sizes, so counting them here would double-count), rolls partitions up into their root, and includes materialized viewsindex-sizesnames indexes astable.indexlong-running-queriesexcludes walsenders (replication connections are "active" forever and made the check permanently noisy) and bounds query textlocksreports only the roots of blocking trees viapg_blocking_pids()with a count of sessions stuck behind each, instead of dumping every granted lockbloatcovers index bloat as well as table bloat, with object size and a bloat percentagevacuum-statshonors per-table autovacuum reloptions (includingautovacuum_enabled=false, reported asdisabled) and tracks analyze alongside vacuumreplication-slotsseparates retained WAL from unconfirmed WAL and addswal_status/safe_wal_size(headroom beforemax_slot_wal_keep_sizeinvalidates the slot)invalid-indexesis a new check for indexes left invalid by failedCREATE INDEX CONCURRENTLYbuildsNotes for reviewers:
inspectstatistics reflect one shard's MySQL instance per run.--keyspaceaccepts vtgate shard targets (mykeyspace/-80,mykeyspace/-80@replica) to pin an exact shard — shard-targeted connections bypass the planner and read that shard's statistics directly. Enumerate shards withSHOW VITESS_SHARDS. Databases can have hundreds of shards, so nothing fans out automatically.--dbnameselects it, and the reader role can lack CONNECT on non-default databases — the error suggests--role admin.