Skip to content

feat(github): show table-size estimates in the plan comment - #1230

Draft
aparajon wants to merge 1 commit into
feat/table-size-capturefrom
feat/plan-table-size-estimates
Draft

feat(github): show table-size estimates in the plan comment#1230
aparajon wants to merge 1 commit into
feat/table-size-capturefrom
feat/plan-table-size-estimates

Conversation

@aparajon

@aparajon aparajon commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Why this matters

A plan shows the DDL but not the scale of what it touches: ADD INDEX or a varchar widening on a 12-row table and on a 120-million-row table read identically, so the operator judging "how long will this run, and should the instance be scaled first" has nothing to go on. The engines now capture per-table size estimates at plan time; this PR is the surface that puts them in front of the operator.

Top of the table-size estimates stack: #1236 formatters → #1237 cost-scaling detection → #1238 engine capture → this PR.

What it does

The plan comment renders the captured estimates as a 📊 Table sizes info section above the plan summary, below any lint warnings. The section is scoped to where size is the cost signal: a statement carries a size line unless the dialect's real parser proves it metadata-only (ddl.CostScalesWithTableSize). Index builds, column type changes and widenings, charset conversions, and constraint validations all get a line; plain column adds, drops, renames, and default changes render no section at all, so routine alters and table creations add zero noise. Whether a given alter ultimately runs instant is decided by the server at execution time, so the display errs toward showing the size when a copy is possible.

Two display properties worth calling out:

  • Estimates always render with ~. They come from statistics and are never exact.
  • Silence is explicit. A table facing a size-scaling statement with no estimate renders size estimate unavailable, never an omission — a failed probe must not read as a small table.

Full plan comments as rendered (from TEMPLATES.md):

MySQL plan — the alter adds an index, so products gets a size line

Schema Change Plan — Staging

Database: testapp | Type: MySQL | Schema Name: testapp

Requested by @jackjackbits at 2026-01-01 00:00:00 UTC · planned from abcdef1

CREATE TABLE `users` (
    `id` bigint unsigned NOT NULL AUTO_INCREMENT,
    `email` varchar(255) NOT NULL,
    `created_at` timestamp DEFAULT current_timestamp(),
    PRIMARY KEY(`id`),
    INDEX `idx_email`(`email`)
) ENGINE InnoDB,
  CHARSET utf8mb4,
  COLLATE utf8mb4_0900_ai_ci;

CREATE TABLE `orders` (
    `id` bigint unsigned NOT NULL AUTO_INCREMENT,
    `user_id` bigint NOT NULL,
    `total_cents` bigint NOT NULL,
    `status` varchar(50) NOT NULL DEFAULT 'pending',
    PRIMARY KEY(`id`),
    INDEX `idx_user_id`(`user_id`)
) ENGINE InnoDB,
  CHARSET utf8mb4,
  COLLATE utf8mb4_0900_ai_ci;

ALTER TABLE `products` ADD INDEX `idx_category_price`(`category`, `price`);

💡 Lint Warnings: 2 advisory findings

  • users: Column created_at uses TIMESTAMP which overflows on 2038-01-19. Consider using DATETIME instead.
  • products: Index idx_category on column category is redundant - covered by index idx_category_price on columns (category, price)

📊 Table sizes:

  • products: ~2.3M rows · ~1.1 GB

📋 Plan: 2 tables to create, 1 table to alter


▶️ To apply all schema changes from this PR, comment:

schemabot apply -e staging
Sharded Vitess plan — cross-shard totals with the largest single shard

Schema Change Plan — Staging

Database: commerce | Type: Vitess

Requested by @jackjackbits at 2026-01-01 00:00:00 UTC · planned from abcdef1

Keyspace: commerce

VSchema

--- a/commerce.json
+++ b/commerce.json
@@ -4,5 +4,8 @@
     "orders_seq": {
       "type": "sequence"
+    },
+    "address_seq": {
+      "type": "sequence"
     }
   }
 }
CREATE TABLE `address_seq` (
    `id` tinyint unsigned NOT NULL DEFAULT '0',
    `next_id` bigint unsigned,
    `cache` bigint unsigned,
    PRIMARY KEY(`id`)
) ENGINE InnoDB,
  CHARSET utf8mb4,
  COLLATE utf8mb4_0900_ai_ci,
  COMMENT 'vitess_sequence';

Keyspace: commerce_sharded

VSchema

--- a/commerce_sharded.json
+++ b/commerce_sharded.json
@@ -15,5 +15,16 @@
         }
       ]
     }
+    "addresses": {
+      "column_vindexes": [
+        {
+          "column": "customer_id",
+          "name": "hash"
+        }
+      ],
+      "auto_increment": {
+        "column": "id",
+        "sequence": "commerce.address_seq"
+      }
+    }
   }
 }
CREATE TABLE `addresses` (
    `id` bigint unsigned NOT NULL,
    `customer_id` bigint unsigned NOT NULL,
    `street` varchar(255) NOT NULL,
    `city` varchar(100) NOT NULL,
    PRIMARY KEY(`id`),
    INDEX `idx_customer_id`(`customer_id`)
) ENGINE InnoDB,
  CHARSET utf8mb4,
  COLLATE utf8mb4_0900_ai_ci;

ALTER TABLE `customers` ADD INDEX `idx_loyalty_tier`(`loyalty_tier`);

📊 Table sizes:

  • customers: ~48.2M rows · ~23.4 GB across 2 shards (largest shard ~24.6M rows)

📋 Plan: 2 tables to create, 1 table to alter, 2 vschema updates


▶️ To apply all schema changes from this PR, comment:

schemabot apply -e staging
Plain column add — metadata-only, so no size section at all

Schema Change Plan — Staging

Database: testapp | Type: MySQL | Schema Name: testapp

Requested by @jackjackbits at 2026-01-01 00:00:00 UTC · planned from abcdef1

ALTER TABLE `products` ADD COLUMN `discount_cents` bigint;

📋 Plan: 1 table to alter


▶️ To apply all schema changes from this PR, comment:

schemabot apply -e staging

How it moves us toward the northstar

These estimates are the substrate for honest copy progress under the driver cap: persisted with the plan, they can seed the progress denominator for shards whose waves have not started, turning #1191's "across N of M shards" disclosure into a whole-table bar that converges to engine-reported figures as waves dispatch. This PR completes the plan surface; plan persistence and apply-side seeding follow as their own slice.

Opened by Claude (Fable 5).

Copilot AI lite review requested due to automatic review settings September 1, 2026 00:33

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds “table size (row estimate)” context to schema change plan rendering by plumbing best-effort, display-only per-table row estimates from engines through storage/proto/API into the plan comment templates.

Changes:

  • Extend plan data models (engine → storage/proto/API → webhook templates) with EstimatedRows, ShardCount, and LargestShardRows.
  • Implement best-effort row-estimate probing for Spirit (MySQL information_schema) and PlanetScale/Vitess (per-shard probe + all-or-nothing aggregation).
  • Render a new 📊 Table sizes (approximate) section in plan comments and update previews/templates + tests accordingly.

Reviewed changes

Copilot reviewed 24 out of 25 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
TEMPLATES.md Updates rendered examples to include the new “Table sizes” section and updated plan summaries.
pkg/webhook/templates/table_sizes_test.go Adds focused template rendering tests for table size lines (sharded/unsharded/unavailable/omitted).
pkg/webhook/templates/preview.go Adds preview data for table sizes and a helper to create *int64 sample estimates.
pkg/webhook/templates/preview_sharded.go Adds sharded preview scenarios that include table size rendering (including “unavailable” behavior).
pkg/webhook/templates/plan.go Introduces TableSizes + rendering helpers (writeTableSizes, formatTableSize) in the plan template.
pkg/webhook/plan.go Threads size data from TableChangeResponse into template data and omits created tables from the size list.
pkg/webhook/plan_test.go Adds unit coverage ensuring buildPlanCommentData populates TableSizes and omits creates.
pkg/webhook/plan_integration_test.go Adds an integration test asserting plan comments include a row estimate for an altered existing table.
pkg/ui/format.go Adds FormatApproxRows for compact ~-prefixed row estimate formatting.
pkg/ui/format_test.go Adds unit tests for FormatApproxRows.
pkg/tern/tablechange_convert.go Plumbs size fields into storage/proto conversion for table changes.
pkg/tern/table_sizes.go Adds shard-level aggregation logic for row estimates (sum + largest shard + all-or-nothing presence).
pkg/tern/local_client.go Applies shard-aggregation results when building namespace-level views and stored plan data.
pkg/tern/local_client_shardplan_test.go Adds tests covering sharded aggregation behavior (complete, partial, pass-through).
pkg/storage/types.go Extends stored TableChange with size estimate fields.
pkg/proto/tern.proto Extends TableChange proto with optional row-estimate fields and shard count.
pkg/proto/ternv1/tern.pb.go Regenerates Go bindings for the proto additions.
pkg/engine/spirit/spirit.go Adds MySQL information_schema probing for per-table row estimates (best-effort, warn-only on failure).
pkg/engine/spirit/spirit_integration_test.go Adds integration coverage for Spirit plan carrying row estimates for existing tables.
pkg/engine/planetscale/table_sizes.go Adds PlanetScale/Vitess shard-count lookup + per-shard probing and aggregation.
pkg/engine/planetscale/table_sizes_test.go Adds unit tests for shard-count lookup and behavior without a vtgate DSN.
pkg/engine/planetscale/plan.go Wires shard-count lookup + size attachment into the PlanetScale plan path.
pkg/engine/engine.go Extends engine.TableChange with size estimate fields.
pkg/apitypes/apitypes.go Extends TableChangeResponse JSON shape with size estimate fields.
pkg/api/proto_helpers.go Plumbs size estimate fields from proto into API response types.
Files not reviewed (1)
  • pkg/proto/ternv1/tern.pb.go: Generated file

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread pkg/webhook/templates/preview.go
Comment thread pkg/webhook/plan.go Outdated
Comment thread pkg/webhook/plan_integration_test.go Outdated
Comment thread pkg/ui/format.go
@aparajon
aparajon force-pushed the feat/plan-table-size-estimates branch 5 times, most recently from da1a735 to 2bdf6f1 Compare September 1, 2026 11:18
@aparajon
aparajon changed the base branch from main to feat/table-size-capture September 1, 2026 11:20
@aparajon
aparajon force-pushed the feat/table-size-capture branch from 76d5d90 to 72f24bc Compare September 1, 2026 15:52
@aparajon
aparajon force-pushed the feat/plan-table-size-estimates branch from 2bdf6f1 to 1ad29b1 Compare September 1, 2026 15:52
@aparajon
aparajon force-pushed the feat/table-size-capture branch from 72f24bc to 71e2c80 Compare September 1, 2026 16:16
@aparajon
aparajon force-pushed the feat/plan-table-size-estimates branch from 1ad29b1 to dcbbc8b Compare September 1, 2026 16:16
@aparajon
aparajon force-pushed the feat/table-size-capture branch from 71e2c80 to f594eb9 Compare September 1, 2026 16:27
@aparajon
aparajon force-pushed the feat/plan-table-size-estimates branch from dcbbc8b to f59b115 Compare September 1, 2026 16:37
@aparajon
aparajon force-pushed the feat/table-size-capture branch from f594eb9 to 1a34552 Compare September 1, 2026 20:05
@aparajon
aparajon force-pushed the feat/plan-table-size-estimates branch from f59b115 to 4c449db Compare September 1, 2026 20:08
The plan comment renders a "Table sizes" info section above the plan
summary and below any lint warnings, scoped to statements that add an
index (detected with the dialect's real parser) since that is the
change class whose cost scales with the table. Cross-shard totals show
the largest single shard; a table gaining an index with no estimate
says so explicitly, and a plan with no index adds renders no section
at all.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants