Load test results in moodle 45 (feb 2026)
Versión 1 (Emilio Penna, Jueves, 19 de Marzo de 2026 10:01:11 -0300)
| 1 | 1 | h1. Load test results in moodle 45 (feb 2026) |
|
|---|---|---|---|
| 2 | 1 | ||
| 3 | 1 | h1. Moodle Quiz Load Test Results — Moodle 4.5 (March 2026) |
|
| 4 | 1 | ||
| 5 | 1 | This page documents load test results for Moodle 4.5 quiz under concurrent exam conditions. |
|
| 6 | 1 | The goal is to share infrastructure sizing experience that may be useful for other institutions |
|
| 7 | 1 | running large-scale simultaneous online exams. |
|
| 8 | 1 | ||
| 9 | 1 | For the test procedure and files (JMeter script, quiz backup, users file), see: |
|
| 10 | 1 | [[Load_tests_in_moodle_41_(update_2025)]] |
|
| 11 | 1 | ||
| 12 | 1 | --- |
|
| 13 | 1 | ||
| 14 | 1 | h2. Summary |
|
| 15 | 1 | ||
| 16 | 1 | We tested a Moodle 4.5 deployment with 2000 and 3000 simultaneous virtual users (VUs) completing |
|
| 17 | 1 | a 16-question quiz. The system handled 2000 VUs comfortably with no errors and good response times. |
|
| 18 | 1 | At 3000 VUs, the only point of stress was the synchronized exam start (startattempt spike); |
|
| 19 | 1 | all subsequent quiz navigation remained stable even at that load. |
|
| 20 | 1 | ||
| 21 | 1 | The main finding is that *CPU is the actual bottleneck* in this architecture, not RAM. |
|
| 22 | 1 | With PHP-FPM + OPcache + a separated database server, memory pressure was minimal even at peak load. |
|
| 23 | 1 | This challenges the common Moodle documentation recommendation that frames RAM as the primary |
|
| 24 | 1 | performance lever — that guidance appears to be a legacy of older mod_php architectures without OPcache. |
|
| 25 | 1 | ||
| 26 | 1 | We also tested Apache mod_cache on the reverse proxy. It reduced latency for static assets and |
|
| 27 | 1 | pre-exam navigation (10–35% improvement), but had a paradoxical effect on the startattempt spike: |
|
| 28 | 1 | pages load faster, so users arrive at the synchronization point more tightly clustered, |
|
| 29 | 1 | slightly increasing peak pressure at exam start. |
|
| 30 | 1 | ||
| 31 | 1 | --- |
|
| 32 | 1 | ||
| 33 | 1 | h2. Infrastructure |
|
| 34 | 1 | ||
| 35 | 1 | |_.Component|_.Details| |
|
| 36 | 1 | |Reverse proxy|Apache 2.4 (MPM event), mod_shib, mod_cache, mod_remoteip| |
|
| 37 | 1 | |Web / app server|Apache 2.4 + PHP-FPM 8.3, Debian 13, *12 vCPU / 12 GB RAM*| |
|
| 38 | 1 | |Database server|MySQL Community 8.4, Debian 13, *4 vCPU / 16 GB RAM*| |
|
| 39 | 1 | |PHP acceleration|OPcache enabled. Moodle Universal Cache (MUC): file cache (default)| |
|
| 40 | 1 | |PHP-FPM pool|pm = dynamic, pm.max_children = 90| |
|
| 41 | 1 | |Virtualization|VMware (VMs on shared physical hardware — introduces run-to-run variability)| |
|
| 42 | 1 | |Storage|SAN/shared storage with SSD tier| |
|
| 43 | 1 | |Monitoring|Custom shell script (CSV, 10s interval) + Percona PMM (DB) + nmon (DB server)| |
|
| 44 | 1 | ||
| 45 | 1 | --- |
|
| 46 | 1 | ||
| 47 | 1 | h2. Test Quiz Characteristics |
|
| 48 | 1 | ||
| 49 | 1 | |_.Parameter|_.Value| |
|
| 50 | 1 | |Moodle version|4.5| |
|
| 51 | 1 | |Quiz pages|8 pages, 2 questions per page (16 questions total)| |
|
| 52 | 1 | |Question types|12 multiple choice, 2 true/false, 1 matching, 1 short answer (free text)| |
|
| 53 | 1 | |Images in questions|4 questions with small images (< 50 KB each)| |
|
| 54 | 1 | |Time limit|None| |
|
| 55 | 1 | |Navigation|Sequential (one page at a time)| |
|
| 56 | 1 | ||
| 57 | 1 | --- |
|
| 58 | 1 | ||
| 59 | 1 | h2. Load Test Configuration |
|
| 60 | 1 | ||
| 61 | 1 | |_.Parameter|_.Value| |
|
| 62 | 1 | |Tool|Apache JMeter 5.6.3| |
|
| 63 | 1 | |JMeter machine|Dedicated, 32 GB RAM / 8 CPU, @-Xms8g -Xmx8g -XX:+UseG1GC@| |
|
| 64 | 1 | |Virtual users (VUs)|2000 / 2500 / 3000| |
|
| 65 | 1 | |Ramp-up|180 seconds| |
|
| 66 | 1 | |Think time|Random per thread, uniform distribution 30–90 s| |
|
| 67 | 1 | |Exam start simulation|Synchronizing Timer (all VUs held before startattempt, then released together)| |
|
| 68 | 1 | |Post-start timer|Gaussian Random Timer, σ = 30 s, offset = 5 s| |
|
| 69 | 1 | |Approximate test duration|~30 minutes per run| |
|
| 70 | 1 | ||
| 71 | 1 | The Synchronizing Timer models the real-world scenario where all students click "Start attempt" |
|
| 72 | 1 | within a short window at the beginning of a scheduled exam. This is the most demanding moment |
|
| 73 | 1 | for the infrastructure. |
|
| 74 | 1 | ||
| 75 | 1 | --- |
|
| 76 | 1 | ||
| 77 | 1 | h2. Results |
|
| 78 | 1 | ||
| 79 | 1 | h3. 2000 VUs — Stable, comfortable headroom |
|
| 80 | 1 | ||
| 81 | 1 | Two runs in identical conditions confirmed high reproducibility (TOTAL p95 differed by only 2 ms |
|
| 82 | 1 | between runs). Key figures: |
|
| 83 | 1 | ||
| 84 | 1 | |_.Metric|_.Value| |
|
| 85 | 1 | |TOTAL p95|~408 ms| |
|
| 86 | 1 | |TOTAL p99|~503 ms| |
|
| 87 | 1 | |startattempt p95 (controller)|~924 ms (avg of both runs)| |
|
| 88 | 1 | |Quiz pages p95 (pages 1–8)|410–490 ms| |
|
| 89 | 1 | |Errors|< 0.002% (effectively zero)| |
|
| 90 | 1 | |CPU load1 peak (12 vCPU)|~7.5 (≈ 63% saturation)| |
|
| 91 | 1 | |PHP-FPM busy peak|10–20 workers / 90 configured| |
|
| 92 | 1 | |RAM peak|~1.6 GB used / 12 GB available| |
|
| 93 | 1 | |Swap|0 at all times| |
|
| 94 | 1 | ||
| 95 | 1 | The startattempt spike lasted approximately 2 minutes, after which load dropped steadily. |
|
| 96 | 1 | All quiz navigation after exam start was unaffected. |
|
| 97 | 1 | ||
| 98 | 1 | h3. 3000 VUs — Acceptable, with a localized spike at exam start |
|
| 99 | 1 | ||
| 100 | 1 | |_.Metric|_.Value| |
|
| 101 | 1 | |TOTAL p95|423 ms| |
|
| 102 | 1 | |TOTAL p99|859 ms (driven by startattempt outliers)| |
|
| 103 | 1 | |startattempt p95 (controller)|2689 ms| |
|
| 104 | 1 | |Quiz pages p95 (pages 1–8)|399–483 ms| |
|
| 105 | 1 | |Errors|0.033% — all on startattempt endpoint| |
|
| 106 | 1 | |CPU load1 peak (12 vCPU)|22.4 (≈ 187% — queue forming)| |
|
| 107 | 1 | |PHP-FPM busy peak|77 workers / 90 configured| |
|
| 108 | 1 | |RAM peak|~2.2 GB used / 12 GB available| |
|
| 109 | 1 | |Swap|0 at all times| |
|
| 110 | 1 | ||
| 111 | 1 | Pages 1–8 response times at 3000 VUs were virtually identical to the 2000 VU runs. |
|
| 112 | 1 | The system degraded *only* at the synchronized exam start, not during the exam itself. |
|
| 113 | 1 | ||
| 114 | 1 | h3. Effect of mod_cache on the reverse proxy (2000 VUs) |
|
| 115 | 1 | ||
| 116 | 1 | Tested with a whitelist-based mod_cache configuration on the reverse proxy caching static assets, |
|
| 117 | 1 | requirejs bundles, and shared pluginfile.php resources. |
|
| 118 | 1 | ||
| 119 | 1 | |_.Endpoint|_.p95 without cache|_.p95 with cache|_.Delta| |
|
| 120 | 1 | |get / (homepage)|215 ms|140 ms|*-75 ms*| |
|
| 121 | 1 | |POST /login|272 ms|244 ms|-28 ms| |
|
| 122 | 1 | |course/view.php|139 ms|124 ms|-15 ms| |
|
| 123 | 1 | |Quiz pages (avg pages 1–8)|435 ms|415 ms|*-20 ms*| |
|
| 124 | 1 | |startattempt (controller)|775 ms|1023 ms|*+248 ms* ⚠| |
|
| 125 | 1 | |Errors|0.002%|0.000%|—| |
|
| 126 | 1 | ||
| 127 | 1 | The cache improved all static and navigation endpoints. The startattempt controller worsened |
|
| 128 | 1 | because mod_cache accelerated pre-exam page delivery, causing VUs to arrive at the |
|
| 129 | 1 | synchronization point more tightly clustered, amplifying the spike. |
|
| 130 | 1 | In real exams with natural student dispersion this effect would be attenuated. |
|
| 131 | 1 | ||
| 132 | 1 | --- |
|
| 133 | 1 | ||
| 134 | 1 | h2. Key Findings |
|
| 135 | 1 | ||
| 136 | 1 | h3. CPU is the bottleneck, not RAM |
|
| 137 | 1 | ||
| 138 | 1 | With PHP-FPM + OPcache + a separated database server, the memory footprint per worker is low |
|
| 139 | 1 | (~60–70 MB private RSS; shared OPcache loaded once). At 2000 VUs peak, only ~260 MB of additional |
|
| 140 | 1 | RAM was consumed. CPU was consistently the limiting resource. |
|
| 141 | 1 | ||
| 142 | 1 | This is relevant because Moodle's official documentation historically emphasizes RAM as the |
|
| 143 | 1 | primary sizing parameter. That guidance reflects mod_php architectures without OPcache, |
|
| 144 | 1 | where each Apache process carries a full PHP interpreter in memory. With PHP-FPM and OPcache, |
|
| 145 | 1 | the architecture is fundamentally different. |
|
| 146 | 1 | ||
| 147 | 1 | h3. The startattempt spike is the sole critical point |
|
| 148 | 1 | ||
| 149 | 1 | All quiz page navigation (pages 1–8, finish) remained stable across all load levels. |
|
| 150 | 1 | The only metric that degraded significantly with increased VUs was the synchronized exam start. |
|
| 151 | 1 | Any optimization that reduces the startattempt spike (staggered start, exam scheduling, |
|
| 152 | 1 | reduced synchronization) has disproportionate impact on perceived performance. |
|
| 153 | 1 | ||
| 154 | 1 | h3. The database was not a bottleneck |
|
| 155 | 1 | ||
| 156 | 1 | At 2000 VUs, MySQL operated almost entirely from the InnoDB buffer pool (zero read I/O observed |
|
| 157 | 1 | via nmon). Write I/O peaked at ~6.5 MB/s during the startattempt spike (disk ~98% busy), |
|
| 158 | 1 | but iowait on the DB server remained below 4%, indicating writes did not block query execution. |
|
| 159 | 1 | DB server CPU peaked at 70% during the spike and recovered quickly. |
|
| 160 | 1 | ||
| 161 | 1 | The 16 GB RAM on the DB server allowed the working set to remain in the buffer pool throughout |
|
| 162 | 1 | the test — a key factor for this result. |
|
| 163 | 1 | ||
| 164 | 1 | h3. Reproducibility and VMware variability |
|
| 165 | 1 | ||
| 166 | 1 | Running on shared VMware infrastructure introduces run-to-run variability. We ran at least two |
|
| 167 | 1 | identical runs at each load level to distinguish real behavior from hypervisor noise. |
|
| 168 | 1 | At 2000 VUs the results were highly consistent (TOTAL p95 ±2 ms). Some variability was observed |
|
| 169 | 1 | in the startattempt spike (p95 range: 775–1072 ms across two runs), which is expected given |
|
| 170 | 1 | its sensitivity to exact CPU availability at the moment of the synchronized burst. |
|
| 171 | 1 | ||
| 172 | 1 | --- |
|
| 173 | 1 | ||
| 174 | 1 | h2. Practical Sizing Reference |
|
| 175 | 1 | ||
| 176 | 1 | Based on these results, for this infrastructure profile: |
|
| 177 | 1 | ||
| 178 | 1 | |_.Concurrent students|_.Assessment| |
|
| 179 | 1 | |Up to ~1600|Comfortable headroom. CPU peak well under 70%, PHP-FPM pool far from saturation.| |
|
| 180 | 1 | |~2000|Supported with margin. Startattempt spike reaches ~65% CPU for ~2 minutes. No errors.| |
|
| 181 | 1 | |~2500|Under evaluation (results pending).| |
|
| 182 | 1 | |~3000|Functional but at the limit of the startattempt spike. 0.03% errors at exam start, CPU queuing during spike. All subsequent navigation unaffected.| |
|
| 183 | 1 | ||
| 184 | 1 | *Note:* these figures assume a fully synchronized exam start (worst case). |
|
| 185 | 1 | Real exams where students enter over 2–5 minutes will perform significantly better at any load level. |
|
| 186 | 1 | ||
| 187 | 1 | --- |
|
| 188 | 1 | ||
| 189 | 1 | h2. Monitoring Setup |
|
| 190 | 1 | ||
| 191 | 1 | The following metrics were captured at 10-second intervals via a shell script during each run: |
|
| 192 | 1 | ||
| 193 | 1 | * @load average@ (1, 5, 15 min) — primary CPU saturation indicator |
|
| 194 | 1 | * RAM used and swap |
|
| 195 | 1 | * Apache processes |
|
| 196 | 1 | * PHP-FPM: total workers, busy, idle |
|
| 197 | 1 | * HTTPS connections (port 443) |
|
| 198 | 1 | * MySQL TCP connections |
|
| 199 | 1 | ||
| 200 | 1 | The DB server was monitored independently with @nmon@ (5-second interval), capturing CPU per core, |
|
| 201 | 1 | memory, disk I/O (read/write KB/s, %busy), and network throughput. |
|
| 202 | 1 | Percona PMM was used for MySQL-level metrics. |
|
| 203 | 1 | ||
| 204 | 1 | --- |
|
| 205 | 1 | ||
| 206 | 1 | h2. References and Resources |
|
| 207 | 1 | ||
| 208 | 1 | * Test procedure and files (JMeter script, quiz backup, users CSV): [[Load_tests_in_moodle_41_(update_2025)]] |
|
| 209 | 1 | * Moodle documentation on performance: https://docs.moodle.org/en/Performance_recommendations |
|
| 210 | 1 | * PHP-FPM process manager configuration: https://www.php.net/manual/en/install.fpm.configuration.php |
|
| 211 | 1 | * Apache mod_cache documentation: https://httpd.apache.org/docs/2.4/mod/mod_cache.html |
|
| 212 | 1 | ||
| 213 | 1 | _Results contributed by SECIU — Universidad de la República (Uruguay), March 2026._ |
|
| 214 | 1 | _Infrastructure: eva-perf.seciu.edu.uy (test environment)._ |