Load test results in moodle 45 (feb 2026)

Versión 5 (Emilio Penna, Jueves, 19 de Marzo de 2026 12:38:25 -0300)

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