Stop fetching full quiz attempt review just to read the grade - #118
Merged
Conversation
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 moodle:competency (every 10 min)
and moodle:sendexamemails (every 5 min), 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.
Also fixes a second, independent call site in SendAcademyRatingExamEmails
(the Basic ATC exam branch) that hit the same endpoint directly for the
same reason.
Verified in Docker (compose.dev.yml) against a fixture 'moodle' database:
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>
3 tasks
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
VATUSAMoodle::getQuizAttempts()called Moodle'smod_quiz_get_attempt_reviewweb 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.moodle:competency(every 10 min) andmoodle:sendexamemails(every 5 min), this amounted to ~33,000 calls/day and ~2 TB/month of outbound traffic fromacademy.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.gradeis justquiz_rescale_grade(sumgrades, quiz)=round(sumgrades / quiz.sumgrades * quiz.grade). Bothsumgrades(per attempt, inmdl_quiz_attempts) andquiz.sumgrades/quiz.grade(per quiz, inmdl_quiz) already live in the Moodle database this class already queries viaDB::connection('moodle')for other fields — no need to go over HTTP at all.getQuizMeta()helper (memoized per quiz id for the life of the instance) plus a single batchedwhereInquery for attemptsumgrades, replacing the per-attempt review call ingetQuizAttempts().SendAcademyRatingExamEmails.php(the Basic ATC exam branch) that hitmod_quiz_get_attempt_reviewdirectly for the same reason, using the same DB-based computation.[]on missing/unresolvable data.CLAUDE.mdnote aboutcompose.dev.yml's Redis not working out of the box (config/database.phphardcodesscheme: tls, incompatible with the plain dev Redis container) — found while setting up local verification for this fix.Test plan
compose.dev.yml) against a fixturemoodledatabase (mdl_quiz/mdl_quiz_attemptsrows) with the realmod_quiz_get_user_attemptscall stubbed (the only thing that stub allows — any other web-service call, e.g. the old review call, would throw): 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../vendor/bin/phpunit— 18/18 passing, both natively and inside the Docker container.mod_quiz_get_attempt_reviewcall volume inacademy.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