Setup R4 Neon Rehearsal - SekiroKenjii/Quizzivy GitHub Wiki
The release checklist of docs/plan/74-r4.md asks for these numbers from a branch of production.
v0.10.0 shipped without them, by the owner's decision of 2026-10-12 (docs/setup/deploy.md,
"Rolling out v0.10.0"); this is the run for the day it is done, or for the next release that
needs a production-sized measurement.
Rules:
- A branch of production, never the production endpoint.
- Connect as
quizzivy_migrateon the branch's endpoint. - Keep the connection string in the environment of the shell that runs this, never in a chat or a file.
- Delete the branch at the end.
- Everything below is read-only except the goose steps, which change the branch alone.
- Run it from a checkout of the release commit.
neon branches create --project-id "$NEON_PROJECT" --name r4-rehearsal-$(date +%Y%m%d) --parent <production branch>
export BRANCH_DSN='postgres://quizzivy_migrate:<password>@<the BRANCH endpoint host>/quizzivy?sslmode=require'
psql "$BRANCH_DSN" -Atc "SELECT current_database(); SELECT max(version_id) FROM goose_db_version" # the version production is at
mkdir -p rehearsalpsql "$BRANCH_DSN" -At > rehearsal/before-counts.txt <<'SQL'
-- questions with more than eight options (the cap applies to writes only)
SELECT 'questions_gt8', count(*) FROM (SELECT o.question_id FROM app.question_options o JOIN app.questions q ON q.id = o.question_id WHERE q.deleted_at IS NULL GROUP BY o.question_id HAVING count(*) > 8) x;
-- live join codes from before v0.8.0, which the API replaces at start-up
SELECT 'legacy_join_codes', count(*) FROM app.class_join_codes WHERE revoked_at IS NULL AND lookup_scheme = 1 AND expires_at > now();
-- what 00103 will rewrite, in its three heaviest columns, and the accepted answers it will fold
SELECT 'tests.title_nfd', count(*) FROM app.tests WHERE title IS NOT NFC NORMALIZED;
SELECT 'questions.prompt_nfd', count(*) FROM app.questions WHERE prompt IS NOT NFC NORMALIZED AND prompt_content IS NULL;
SELECT 'users.full_name_nfd', count(*) FROM app.users WHERE full_name IS NOT NFC NORMALIZED;
SELECT 'blank_answer_twins', count(*) - count(DISTINCT (blank_id, normalize(answer, NFC))) FROM app.question_blank_answers;
-- the teacher with the most attempts, for step 4
SELECT 'top_teacher', a.created_by, count(*) FROM app.attempts at JOIN app.assignments a ON a.id = at.assignment_id GROUP BY a.created_by ORDER BY 3 DESC LIMIT 1;
SQL
psql "$BRANCH_DSN" -v before=1 -f docs/setup/rehearsals/r4-legacy-role.sql > rehearsal/legacy-before.txt
psql "$BRANCH_DSN" -v before=1 -v scan=1 -f docs/setup/rehearsals/r4-ownership.sql > rehearsal/ownership-before.txt
grep -A6 "P1" rehearsal/ownership-before.txt # every line 0, or 00106 will stopgoose does not print a migration's NOTICEs, so run 00103's body once in a transaction that rolls back:
( echo 'BEGIN;'; sed -n '/+goose Up/,/+goose Down/p' migrations/00103_compose_text_nfc.sql | sed '/+goose/d'; echo 'ROLLBACK;' ) \
| psql -v ON_ERROR_STOP=1 "$BRANCH_DSN" 2>&1 | tee rehearsal/nfc-notices.txtExpect "F-37: folded N …" and "every listed column is composed".
/usr/bin/time -v goose -dir migrations postgres "$BRANCH_DSN" up 2>&1 | tee rehearsal/goose-up.txt
psql "$BRANCH_DSN" -f docs/setup/rehearsals/r4-legacy-role.sql > rehearsal/legacy-after.txt
psql "$BRANCH_DSN" -f docs/setup/rehearsals/r4-ownership.sql > rehearsal/ownership-after.txt
diff <(sed -n '/== A/,/== [BP]/p' rehearsal/legacy-before.txt) <(sed -n '/== A/,/== [BP]/p' rehearsal/legacy-after.txt) # empty
diff <(sed -n '/== A/,/== [BP]/p' rehearsal/ownership-before.txt) <(sed -n '/== A/,/== [BP]/p' rehearsal/ownership-after.txt) # empty
grep -i "must_be" rehearsal/legacy-after.txt rehearsal/ownership-after.txt # each prints what it saysThe migrations do not rotate join codes; the API does at start-up, so legacy_join_codes is the
count the deploy's log should show as legacy_join_codes_rotated.
getTeacherSummary and getDashboard for the teacher with the most attempts, on the migrated
schema. Take the statements from server/internal/modules/dashboard/repositories/{nav,home,submissions,today,activity}.go
and server/internal/shared/visibility/visibility.go, with the scope set to that teacher ($1 = false, $2 = the top_teacher id), the zone Asia/Ho_Chi_Minh and 14 days, and run each under
EXPLAIN (ANALYZE, BUFFERS). The plans should use the five indexes T-R4.19 names; a sequential
scan on attempts is a finding.
goose -dir migrations postgres "$BRANCH_DSN" down-to 79 && goose -dir migrations postgres "$BRANCH_DSN" upRecord in the release's pull request:
- the per-file timings from
goose-up.txt; -
questions_gt8,legacy_join_codesandblank_answer_twinsfrombefore-counts.txt; - the notices;
- the index names from the plans.
Then delete the branch:
neon branches delete --project-id "$NEON_PROJECT" r4-rehearsal-$(date +%Y%m%d)The seven-day legacy_admin_path count (T-R4.49 A) is not a database number; it comes from the
API's logs.
Generated from docs/setup/rehearsals/r4-neon-rehearsal.md. Edit that file in the repository; changes made on this page are overwritten.