Moodle Quiz Load Test Results — Moodle 4.5 (March 2026)

This page documents load test results for Moodle 4.5 quiz under concurrent exam conditions.
The goal is to share infrastructure sizing experience that may be useful for other institutions
running large-scale simultaneous online exams.

For the test procedure and files (JMeter script, quiz backup, users file), see:
How to run the tests (moodle 4.5)


Summary

We tested a Moodle 4.5 deployment with 2000 and 3000 simultaneous virtual users (VUs) completing
a 16-question quiz. The system handled 2000 VUs comfortably with no errors and good response times.
At 3000 VUs, the only point of stress was the synchronized exam start (startattempt spike);
all subsequent quiz navigation remained stable even at that load.

The main finding is that CPU is the actual bottleneck in this architecture, not RAM.
With PHP-FPM + OPcache + a separated database server, memory pressure was minimal even at peak load.
This challenges the common Moodle documentation recommendation that frames RAM as the primary
performance lever — that guidance appears to be a legacy of older mod_php architectures without OPcache.


Infrastructure

Component Details
Reverse proxy Apache 2.4 (MPM event), mod_shib, mod_remoteip
Web / app server Apache 2.4 + PHP-FPM 8.3, Debian 13, 12 vCPU / 12 GB RAM
Database server MySQL Community 8.4, Debian 13, 4 vCPU / 16 GB RAM
PHP acceleration OPcache enabled. Moodle Universal Cache (MUC): file cache (default)
PHP-FPM pool pm = dynamic, pm.max_children = 90
Virtualization VMware (VMs on shared physical hardware — introduces run-to-run variability)
Storage SAN/shared storage with SSD tier
Monitoring Custom shell script (CSV, 10s interval) + Percona PMM (DB) + nmon (DB server)

Test Quiz Characteristics

Parameter Value
Moodle version 4.5
Quiz pages 8 pages, 2 questions per page (16 questions total)
Question types 12 multiple choice, 2 true/false, 1 matching, 1 short answer (free text)
Images in questions 4 questions with small images (< 50 KB each)
Time limit None
Navigation Sequential (one page at a time)

Load Test Configuration

Parameter Value
Tool Apache JMeter 5.6.3
JMeter machine Dedicated, 32 GB RAM / 8 CPU, -Xms8g -Xmx8g -XX:+UseG1GC
Virtual users (VUs) 2000 / 2500 / 3000
Ramp-up 180 seconds
Think time Random per thread, uniform distribution 30–90 s
Exam start simulation Synchronizing Timer (all VUs held before startattempt, then released together)
Post-start timer Gaussian Random Timer, ? = 30 s, offset = 5 s
Approximate test duration ~30 minutes per run

The Synchronizing Timer models the real-world scenario where all students click "Start attempt"
within a short window at the beginning of a scheduled exam. This is the most demanding moment
for the infrastructure.


Results

2000 VUs — Stable, comfortable headroom

Two runs in identical conditions confirmed high reproducibility (TOTAL p95 differed by only 2 ms
between runs). Key figures:

Metric Value
TOTAL p95 ~408 ms
TOTAL p99 ~503 ms
startattempt p95 (controller) ~924 ms (avg of both runs)
Quiz pages p95 (pages 1–8) 410–490 ms
Errors < 0.002% (effectively zero)
CPU load1 peak (12 vCPU) ~7.5 (? 63% saturation)
PHP-FPM busy peak 10–20 workers / 90 configured
RAM peak ~1.6 GB used / 12 GB available
Swap 0 at all times

The startattempt spike lasted approximately 2 minutes, after which load dropped steadily.
All quiz navigation after exam start was unaffected.

3000 VUs — Acceptable, with a localized spike at exam start

Metric Value
TOTAL p95 423 ms
TOTAL p99 859 ms (driven by startattempt outliers)
startattempt p95 (controller) 2689 ms
Quiz pages p95 (pages 1–8) 399–483 ms
Errors 0.033% — all on startattempt endpoint
CPU load1 peak (12 vCPU) 22.4 (? 187% — queue forming)
PHP-FPM busy peak 77 workers / 90 configured
RAM peak ~2.2 GB used / 12 GB available
Swap 0 at all times

Pages 1–8 response times at 3000 VUs were virtually identical to the 2000 VU runs.
The system degraded only at the synchronized exam start, not during the exam itself.


Key Findings

CPU is the bottleneck, not RAM

With PHP-FPM + OPcache + a separated database server, the memory footprint per worker is low
(~60–70 MB private RSS; shared OPcache loaded once). At 2000 VUs peak, only ~260 MB of additional
RAM was consumed. CPU was consistently the limiting resource.

This is relevant because Moodle's official documentation historically emphasizes RAM as the
primary sizing parameter. That guidance reflects mod_php architectures without OPcache,
where each Apache process carries a full PHP interpreter in memory. With PHP-FPM and OPcache,
the architecture is fundamentally different.

The startattempt spike is the sole critical point

All quiz page navigation (pages 1–8, finish) remained stable across all load levels.
The only metric that degraded significantly with increased VUs was the synchronized exam start.
Any optimization that reduces the startattempt spike (staggered start, exam scheduling,
reduced synchronization) has disproportionate impact on perceived performance.

The database was not a bottleneck

At 2000 VUs, MySQL operated almost entirely from the InnoDB buffer pool (zero read I/O observed
via nmon). Write I/O peaked at ~6.5 MB/s during the startattempt spike (disk ~98% busy),
but iowait on the DB server remained below 4%, indicating writes did not block query execution.
DB server CPU peaked at 70% during the spike and recovered quickly.

The 16 GB RAM on the DB server allowed the working set to remain in the buffer pool throughout
the test — a key factor for this result.

Reproducibility and VMware variability

Running on shared VMware infrastructure introduces run-to-run variability. We ran at least two
identical runs at each load level to distinguish real behavior from hypervisor noise.
At 2000 VUs the results were highly consistent (TOTAL p95 ±2 ms). Some variability was observed
in the startattempt spike (p95 range: 775–1072 ms across two runs), which is expected given
its sensitivity to exact CPU availability at the moment of the synchronized burst.


Practical Sizing Reference

Based on these results, for this infrastructure profile:

Concurrent students Assessment
Up to ~1600 Comfortable headroom. CPU peak well under 70%, PHP-FPM pool far from saturation.
~2000 Supported with margin. Startattempt spike reaches ~65% CPU for ~2 minutes. No errors.
~2500 Under evaluation (results pending).
~3000 Functional but at the limit of the startattempt spike. 0.03% errors at exam start, CPU queuing during spike. All subsequent navigation unaffected.

Note: these figures assume a fully synchronized exam start (worst case).
Real exams where students enter over 2–5 minutes will perform significantly better at any load level.


Monitoring Setup

The following metrics were captured at 10-second intervals via a shell script during each run:

  • load average (1, 5, 15 min) — primary CPU saturation indicator
  • RAM used and swap
  • Apache processes
  • PHP-FPM: total workers, busy, idle
  • HTTPS connections (port 443)
  • MySQL TCP connections

The DB server was monitored independently with nmon (5-second interval), capturing CPU per core,
memory, disk I/O (read/write KB/s, %busy), and network throughput.
Percona PMM was used for MySQL-level metrics.


References and Resources

Results contributed by SECIU — Universidad de la República (Uruguay), March 2026.