Skip to content

Stop fetching full quiz attempt review just to read the grade - #177

Merged
Celeo merged 1 commit into
masterfrom
fix/academy-quiz-grade-egress
Jul 30, 2026
Merged

Stop fetching full quiz attempt review just to read the grade#177
Celeo merged 1 commit into
masterfrom
fix/academy-quiz-grade-egress

Conversation

@Celeo

@Celeo Celeo commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Summary

  • VATUSAMoodle::getQuizAttempts() (duplicated identically in vatusa/api) called Moodle's mod_quiz_get_attempt_review web service once per attempt to read a single field (grade) — but that endpoint returns the entire rendered attempt review (every question, every response, ~2.17 MB) per call.
  • Driven by scheduled jobs in api (moodle:competency every 10 min, moodle:sendexamemails every 5 min) that share this class, this amounted to ~33,000 calls/day and ~2 TB/month of outbound traffic from academy.vatusa.net — ~98.6% of that droplet's total egress. This was surfaced while costing out an Azure migration: the bill for that traffic alone (~$193/mo on metered Azure egress) would exceed the entire nonprofit grant.
  • The review's grade is just quiz_rescale_grade(sumgrades, quiz) = round(sumgrades / quiz.sumgrades * quiz.grade). Both sumgrades (per attempt, in mdl_quiz_attempts) and quiz.sumgrades/quiz.grade (per quiz, in mdl_quiz) already live in the Moodle database this class already queries via DB::connection('moodle') for other fields — no need to go over HTTP at all.
  • This PR computes the grade from the DB instead: a new getQuizMeta() helper (memoized per quiz id for the life of the instance) plus a single batched whereIn query for attempt sumgrades, replacing the per-attempt review call in getQuizAttempts().
  • No behavior change for any consumer: same fields returned, same fail-closed [] on missing/unresolvable data.
  • Companion PR in vatusa/api, which also fixes a second, independent call site there and has a note on a Docker dev-environment quirk found along the way: Stop fetching full quiz attempt review just to read the grade api#118

Test plan

  • Verified in Docker against a fixture moodle database using api's identical class (this repo has no local Docker dev harness of its own — current's only compose file is the production build, per its CLAUDE.md): grades match the old review-derived formula exactly for both 1:1 and non-trivially-scaled quizzes, getQuizMeta() memoization confirmed, fail-closed behavior preserved for a missing attempt and an unknown quiz id. The change here is identical to the verified api change.
  • php -l syntax check passes. (current's Composer deps aren't installed in this environment, so phpunit couldn't be run locally here — api's identical suite passed 18/18.)
  • Post-deploy: confirm mod_quiz_get_attempt_review call volume in academy.vatusa.net's Apache access log drops to ~0, and outbound bandwidth drops toward the ~300 GB/month baseline of genuine site traffic.

🤖 Generated with Claude Code

VATUSAMoodle::getQuizAttempts() called Moodle's mod_quiz_get_attempt_review
web service once per attempt to read a single field (grade), but that
endpoint returns the entire rendered attempt review — every question, every
response, ~2.17 MB — per call. Driven by scheduled jobs in the api repo
(moodle:competency every 10 min, moodle:sendexamemails every 5 min) that
share this identical class, this amounted to ~33,000 calls/day and ~2
TB/month of outbound traffic from academy.vatusa.net, ~98.6% of that
droplet's total egress (see the migration cost analysis that surfaced this).

The review's grade is just quiz_rescale_grade(sumgrades, quiz) =
round(sumgrades / quiz.sumgrades * quiz.grade), and both sumgrades (per
attempt) and quiz.sumgrades/quiz.grade (per quiz) already live in the
Moodle database this class already queries via DB::connection('moodle')
for other fields. Compute the grade from there instead, batched per call
and memoized per quiz for the life of the instance — no behavior change
for any consumer, just no more multi-megabyte HTTP round-trips.

Mirrors the same fix landing in vatusa/api (VATUSAMoodle.php is duplicated
across both repos). Verified in Docker against a fixture 'moodle' database
using api's identical class: grades match the old review-derived formula
exactly for both 1:1 and scaled quizzes, fail-closed behavior on missing
data is preserved, and no calls to mod_quiz_get_attempt_review occur.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@Celeo
Celeo merged commit f4e3f1a into master Jul 30, 2026
@Celeo
Celeo deleted the fix/academy-quiz-grade-egress branch July 30, 2026 02:14
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.

1 participant