Skip to content
AquadactylAquadactyl
Panel

Aquadactyl performance improvements

Before-and-after measurements for batched server-list queries, API-key usage updates and initial route loading.

Measured 2026-10-15 using two Aquadactyl instances built from the same base commit a42ce0af63c5. The candidate contains an uncommitted performance patch, identified by file hashes in the raw report. These results describe that working patch, not a published release.

Homepage comparisons · Full samples · Performance patch · Measured runner

The earlier Pterodactyl comparison remains a separate measurement of its pinned revisions. This experiment compares Aquadactyl before and after the changes; it does not combine measurements from different runs into a new Pterodactyl ranking.

What changed

  • The client server list batches eggs, allocations, pending transfers, subusers and variable definitions/overrides for the current page. It reuses the already loaded parent for allocation transformation. Server-specific variable values are matched by server ID and variable ID, with distinct model copies for servers sharing an egg. Hidden variables, allocation-note restrictions and other permission checks remain in place.
  • API-key last-used timestamp saves validate the timestamp without rerunning unchanged key identity and foreign-key validation queries. Creation and changes to any other field retain full validation. Token lookup, expiry/revocation checks, authorization, rate limiting and timestamp persistence remain active.
  • The entry point loads the requested auth, server or dashboard shell before mounting App, displaying the existing spinner while the chunk loads. The first shell then renders without going through an initial lazy/Suspense reveal. Other route chunks remain lazy, and load failures still reach the existing error boundary.

The changes do not replace real API responses with a frontend cache or omit normal response fields to improve timings. The font files and their language coverage are unchanged.

Controlled setup

Both stacks use the same production Composer lockfile, PHP 8.5.11, four static FPM workers, OPcache settings, nginx gzip configuration, Redis driver and resource limits. Each panel has two CPU quota cores and 1 GiB memory. Each has a separate MariaDB and Redis instance with matching limits.

The dataset is one non-admin user with 50 owned suspended server records, one node, one egg and one allocation per server. No Wings or game servers are contacted by the measured endpoints. The original panel and Wings/Manta test stacks were not used by the tests.

The runtime methodology defines the same collection procedure used here: ten cold/warm browser samples per variant, three alternating-order API repetitions per endpoint/concurrency, and 30 idle plus 30 loaded memory samples per variant. Warm-ups are discarded. Browser, API and memory phases run separately. Server caches stay warm, including during cold-browser loads.

Browser timing

Browser cacheMeasurementBefore medianAfter medianBefore p95After p95
coldDashboard ready570.3 ms176.9 ms598.6 ms191.5 ms
coldFirst contentful paint418.0 ms112.0 ms424.0 ms128.0 ms
coldLargest contentful paint422.0 ms296.0 ms612.0 ms328.0 ms
coldWindow load event68.8 ms66.9 ms73.2 ms71.5 ms
coldHTML time to first byte18.1 ms16.9 ms24.4 ms19.8 ms
warmDashboard ready610.4 ms190.0 ms661.2 ms193.0 ms
warmFirst contentful paint422.0 ms112.0 ms436.0 ms120.0 ms
warmLargest contentful paint434.0 ms132.0 ms684.0 ms304.0 ms
warmWindow load event68.5 ms74.7 ms74.2 ms79.3 ms
warmHTML time to first byte16.3 ms15.5 ms21.9 ms18.8 ms

Dashboard ready is the custom milestone when all 50 server names exist in the DOM, observed by animation-frame polling. It is not a standardized Time to Interactive metric. FCP and LCP use browser performance entries; LCP is observed for two seconds after readiness. With ten samples, p95 is the maximum observed value. The same 1440×900 headless Chromium 156.0.8078.4 ran both variants without CPU or network throttling on localhost.

Authenticated API throughput

EndpointConcurrencyBeforeAfterBefore p95 latencyAfter p95 latency
account190.0 req/s103.8 req/s13.2 ms11.4 ms
account8260.0 req/s306.0 req/s58.8 ms56.1 ms
account16265.5 req/s313.5 req/s82.3 ms73.4 ms
servers17.6 req/s34.6 req/s143.8 ms32.7 ms
servers819.6 req/s85.5 req/s483.2 ms102.3 ms
servers1619.9 req/s82.8 req/s871.2 ms209.2 ms

The account endpoint returns account details; the server-list endpoint returns all 50 server records and their normal relationships. Each 10-second closed-loop window uses persistent HTTP connections, with requests in flight drained before calculating throughput. API limits are set equally to 1,000,000/minute in these disposable stacks. Throughput and p95 columns are medians of three per-window values. All 47,531 measured responses passed status and JSON checks, with 0 errors.

The page-size/query regression test checks that SQL query count stays bounded as more servers are returned. The diagnostic query profile is collected separately from the timed HTTP tests and excludes application bootstrap queries; its CLI elapsed times are not used as browser or API throughput measurements.

Memory

StateBefore panelAfter panelBefore panel + DB + RedisAfter panel + DB + Redis
idle124.6 MiB124.8 MiB247.9 MiB247.8 MiB
loaded129.3 MiB127.4 MiB253.6 MiB251.0 MiB

Values are medians of 30 samples per state. Container working set is cgroup memory.current minus inactive_file. Loaded samples use the 50-server-list API at concurrency 16. The measured memory differences are modest; idle memory is essentially unchanged. Peak samples and raw usage/cache bytes are retained in the full report. This measures short-window steady-state use, not a memory leak or endurance test. Browser retained JavaScript heap after GC is also present in the report and is distinct from container memory and total browser RAM.

Validation and provenance

The panel checks passed: 1,508 frontend tests with 100% statement, branch, function and line coverage, 43 focused backend tests (286 assertions), TypeScript, frontend lint, scoped format checks, static analysis of the changed PHP files and the production Vite build. Backend regressions cover per-server variable overrides (including empty values), defaults, hidden values, restricted subusers, primary allocations, bounded query counts, API-key timestamp persistence and validation of key edits. The broader API/controller/startup tests passed alongside them.

The common PHP/OS image is sha256:63d23d0b2ce483142f09f4e4578c7a5736cd02a8ef3c4770dff3341e82f2dba1. Both source revisions and Composer lock hashes match. Each variant's image ID and frontend manifest hash, and all candidate file hashes, are in the report. Patch inventory SHA-256: 9c5822f36eb3c29e1ddedd396c1fca099636cfbc3a3fd1033fba2aeef8769bd0. The raw runner download is the exact source used for this measurement, identified by its SHA-256 in the report.

Reproduce

Create two separate checkouts at the base commit above. Leave the first clean and apply the performance changes to the second. Install locked dependencies and build frontend assets in each checkout. From the website repository:

git -C /path/to/patched-candidate apply /path/to/website/public/benchmarks/aquadactyl-optimization.patch
node scripts/prepare-optimization-benchmark.mjs /path/to/clean-baseline /path/to/patched-candidate

Use the temporary directory printed by that command as WORKDIR:

docker compose -f WORKDIR/compose.json up -d --build --wait --wait-timeout 240
node scripts/benchmark-runtime.mjs WORKDIR public/benchmarks/aquadactyl-optimization.json
node scripts/write-optimization-guide.mjs
node scripts/verify-optimization-benchmark.mjs
docker compose -f WORKDIR/compose.json down --volumes

Use a host with Docker and the same common aquadactyl:test runtime image available; install Playwright Chromium as described in the runtime guide. Preparation archives the committed base and overlays only the candidate's changed files; its manifest records their hashes. Tests use separate disposable volumes and random local-only credentials. Run the optional query profiler after the timed benchmark, not during it.

Limits

Single shared Windows/WSL2 workstation; existing unrelated game containers remain running. No CPU pinning, controlled network emulation, TLS, WAN latency, mobile device, endurance test or real-player load. This is a controlled local configuration comparison of pinned distributions, not their default deployment presets or proof of general production capacity.

These observations establish improvements for the tested configuration and data. They do not establish universal production throughput or game performance. Optional features, larger variable/subuser datasets, addons, deployment settings and WAN/mobile conditions may change the outcome.