Skip to content

chore(db): reword stats-migration comment - #3395

Closed
ValentaTomas wants to merge 2 commits into
mainfrom
scrub-stats-migration-comment
Closed

ValentaTomas wants to merge 2 commits into
mainfrom
scrub-stats-migration-comment

Conversation

@ValentaTomas

Copy link
Copy Markdown
Member

Comment-only rewording of the 20260725100500 migration; no SQL statements change. Safe for already-migrated databases: goose tracks applied versions by number and does not checksum file content, so this neither re-runs nor invalidates the migration anywhere.

…#3393)

## Summary

`InvalidateUnstartedTemplateBuilds` runs 2–60s instead of milliseconds
because of a statistics blind spot, not scan cost:
`status_group='pending'` is ~0.006% of env_builds (measured 19,906 of
~333M rows), so the default ~30k-row statistics sample never catches it
— the value is absent from the column's MCV list and the planner
estimates ~11 rows. That drives the update's join from the pending-scan
side (~20k probes into env_build_assignments per execution,
fresh-session EXPLAIN attached below) instead of one selective `(env_id,
tag)` lookup. Every template-build registration then queues behind it on
the envs row lock — the current upsert convoy (20–70s waits).

Raising the per-column statistics target to 2000 (~600k-row sample)
makes rare-but-hot status groups visible and flips the join order; the
migration ANALYZEs immediately so the fix takes effect at promote rather
than at the next autoanalyze. Same per-table-settings precedent as the
autovacuum migrations.

Fresh-session plan today (store-resident columnar unit present and
unused — the misestimate, not columnar, is the bug):
`Index Scan using idx_env_builds_status_group (rows=11 estimated,
rows=19905 actual)` → nested-loop probing env_build_assignments ×19,905.

## Verification (post-draft)

- **Scratch replay**: full migration chain via `scripts/migrator.go` on
Postgres 16 applies cleanly; `goose down` (SET STATISTICS -1 + ANALYZE)
and re-up also clean.
- **Live read-only plan capture** (`PREPARE` + `EXPLAIN EXECUTE` ×7):
custom plans (executions 1–5) are the pending-driven nested loop — est
11 rows, cost 662, the convoy plan; the generic plan (execution 6+)
already flips to the assignments `(env_id, tag)` drive at cost 223.
Production's pooled prepared statements re-run custom plans on every
fresh connection, so busy templates keep landing on the bad side — this
migration corrects the one wrong number in that comparison.
- **Worst case bounded**: env_build_assignments is 323M rows; the
heaviest template has ~438k assignment rows and the largest single
`(env_id, tag)` group is ~219k. With truthful stats on both sides the
planner picks the cheaper of the two known join orders — ≈ status-quo
cost for that one template, ~1000× improvement for typical ones.
- **Lock envelope**: ALTER…SET STATISTICS and ANALYZE both take SHARE
UPDATE EXCLUSIVE (no read/DML blocking; queues behind a concurrent
autovacuum on the same table at worst), bounded by the 1h timeout and
rerunnable. Down is an instant revert.


## Postscript: the mechanism was observed live the same day

At 16:09Z a routine autoanalyze happened to catch 'pending' into the MCV
(frequency 0.0001) and the convoy vanished within the half-hour — rows
returned collapsed from 14–17M/s (steady since 08:00Z) to ~0.5M/s, with
no deploy, no columnar change, and the pending count unchanged. That is
the dice roll this migration removes: at 0.0001 the value is one bad
sample away from disappearing again, and every autoanalyze re-flips the
coin. Target 2000 makes the catch deterministic (~36 expected sample
hits).
@cursor

cursor Bot commented Jul 25, 2026

Copy link
Copy Markdown

PR Summary

Low Risk
Documentation-only change to migration comments with no runtime or schema impact.

Overview
This PR only rewrites the explanatory comment above the env_builds.status_group statistics change in an existing goose migration. The narrative now describes rarity in relative terms (“well under a hundredth of a percent,” measured in production) and frames the planner mistake as underestimation “by orders of magnitude,” with “tens of thousands” of index probes, instead of citing specific row counts and a ~11-row estimate. No SQL or migration behavior changes; applied databases are unaffected because goose keys off migration version, not file checksums.

Reviewed by Cursor Bugbot for commit 8682f56. Bugbot is set up for automated code reviews on this repo. Configure here.

@codecov

codecov Bot commented Jul 25, 2026

Copy link
Copy Markdown

❌ 6 Tests Failed:

Tests completed Failed Passed Skipped
3512 6 3506 9
View the top 3 failed test(s) by shortest run time
github.com/e2b-dev/infra/tests/integration/internal/tests/envd::TestListDir/depth_1_lists_root_directory
Stack Traces | 0.01s run time
=== RUN   TestListDir/depth_1_lists_root_directory
=== PAUSE TestListDir/depth_1_lists_root_directory
=== CONT  TestListDir/depth_1_lists_root_directory
    filesystem_test.go:96: 
        	Error Trace:	.../tests/envd/filesystem_test.go:96
        	Error:      	Received unexpected error:
        	            	unavailable: 502 Bad Gateway
        	Test:       	TestListDir/depth_1_lists_root_directory
--- FAIL: TestListDir/depth_1_lists_root_directory (0.01s)
github.com/e2b-dev/infra/tests/integration/internal/tests/envd::TestListDir/depth_2_lists_first_level_of_subdirectories_(in_this_case_the_root_directory)
Stack Traces | 0.01s run time
=== RUN   TestListDir/depth_2_lists_first_level_of_subdirectories_(in_this_case_the_root_directory)
=== PAUSE TestListDir/depth_2_lists_first_level_of_subdirectories_(in_this_case_the_root_directory)
=== CONT  TestListDir/depth_2_lists_first_level_of_subdirectories_(in_this_case_the_root_directory)
    filesystem_test.go:96: 
        	Error Trace:	.../tests/envd/filesystem_test.go:96
        	Error:      	Received unexpected error:
        	            	unavailable: 502 Bad Gateway
        	Test:       	TestListDir/depth_2_lists_first_level_of_subdirectories_(in_this_case_the_root_directory)
--- FAIL: TestListDir/depth_2_lists_first_level_of_subdirectories_(in_this_case_the_root_directory) (0.01s)
github.com/e2b-dev/infra/tests/integration/internal/tests/envd::TestListDir/depth_0_lists_only_root_directory
Stack Traces | 0.02s run time
=== RUN   TestListDir/depth_0_lists_only_root_directory
=== PAUSE TestListDir/depth_0_lists_only_root_directory
=== CONT  TestListDir/depth_0_lists_only_root_directory
    filesystem_test.go:96: 
        	Error Trace:	.../tests/envd/filesystem_test.go:96
        	Error:      	Received unexpected error:
        	            	unavailable: 502 Bad Gateway
        	Test:       	TestListDir/depth_0_lists_only_root_directory
--- FAIL: TestListDir/depth_0_lists_only_root_directory (0.02s)
github.com/e2b-dev/infra/tests/integration/internal/tests/envd::TestListDir
Stack Traces | 0.46s run time
=== RUN   TestListDir
=== PAUSE TestListDir
=== CONT  TestListDir
--- FAIL: TestListDir (0.46s)
github.com/e2b-dev/infra/tests/integration/internal/tests/proxies::TestMaskRequestHostAPIParameter
Stack Traces | 4.5s run time
=== RUN   TestMaskRequestHostAPIParameter
=== PAUSE TestMaskRequestHostAPIParameter
=== CONT  TestMaskRequestHostAPIParameter
    mask_request_host_test.go:44: Command [python3] output: event:{start:{pid:1114}}
Executing command ls in sandbox iwm9fkk2h4m4oirc2zk40
    mask_request_host_test.go:68: Command [cat] output: event:{start:{pid:1116}}
    mask_request_host_test.go:68: Command [cat] output: event:{data:{stderr:"cat: /tmp/nc_output.txt: No such file or directory\n"}}
    mask_request_host_test.go:68: Command [cat] output: event:{end:{exit_code:1 exited:true status:"exit status 1" error:"exit status 1"}}
    mask_request_host_test.go:69: 
        	Error Trace:	.../tests/proxies/mask_request_host_test.go:69
        	Error:      	Received unexpected error:
        	            	command cat in sandbox is068csrydjodpn9s7u8x failed with exit code 1
        	Test:       	TestMaskRequestHostAPIParameter
--- FAIL: TestMaskRequestHostAPIParameter (4.50s)
github.com/e2b-dev/infra/tests/integration/internal/tests/api/templates::TestUpdateTemplateNotOwnedByTeam
Stack Traces | 146s run time
=== RUN   TestUpdateTemplateNotOwnedByTeam
=== PAUSE TestUpdateTemplateNotOwnedByTeam
=== CONT  TestUpdateTemplateNotOwnedByTeam
    template_update_test.go:205: Build failed: {<nil> An internal error occurred. Please try again or contact support with the build ID. <nil>}
--- FAIL: TestUpdateTemplateNotOwnedByTeam (146.37s)
View the full list of 1 ❄️ flaky test(s)
github.com/e2b-dev/infra/tests/integration/internal/tests/envd::TestListDir/depth_3_lists_all_directories_and_files

Flake rate in main: 30.77% (Passed 9 times, Failed 4 times)

Stack Traces | 0.01s run time
=== RUN   TestListDir/depth_3_lists_all_directories_and_files
=== PAUSE TestListDir/depth_3_lists_all_directories_and_files
=== CONT  TestListDir/depth_3_lists_all_directories_and_files
    filesystem_test.go:96: 
        	Error Trace:	.../tests/envd/filesystem_test.go:96
        	Error:      	Received unexpected error:
        	            	unavailable: 502 Bad Gateway
        	Test:       	TestListDir/depth_3_lists_all_directories_and_files
--- FAIL: TestListDir/depth_3_lists_all_directories_and_files (0.01s)

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LGTM — comment-only rewording, no SQL or behavior changes.

What was reviewed: confirmed the diff only touches the explanatory comment above the ALTER TABLE ... SET STATISTICS statement; the SQL statements themselves (statistics target, ANALYZE, timeout bounds, down-migration) are byte-identical to the previous version. Since goose tracks migrations by version number rather than file checksum, this is safe for already-applied databases.

Extended reasoning...

Overview

The PR touches a single file, packages/db/migrations/20260725100500_env_builds_status_group_statistics.sql, and only rewords the prose comment explaining why the migration raises statistics target on env_builds.status_group. Every SQL statement (SET statement_timeout, ALTER TABLE ... SET STATISTICS 2000, ANALYZE, the down migration) is unchanged character-for-character.

Security risks

None. No SQL logic changed, no new privileges, no data exposure. Comment wording changes have zero runtime effect.

Level of scrutiny

This warrants minimal scrutiny — it's a pure documentation/comment change inside a migration file. The main thing to verify is that no SQL statements were altered, which I confirmed by diffing the file content directly.

Other factors

No outstanding review comments to address (only an automated Cursor bugbot summary, which independently reached the same low-risk conclusion). Migration is already merged (present in main at commit 8682f56 per the git log), so this is retroactively confirming safety of an already-landed trivial change.

@ValentaTomas

Copy link
Copy Markdown
Member Author

Superseded: the comment rewording was folded into the tip commit directly (history rewritten in place).

@ValentaTomas
ValentaTomas deleted the scrub-stats-migration-comment branch July 25, 2026 22:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant