A deeper companion to the shareholder update — this page is for anyone who needs the real technical picture: the two Laravel applications, what Postgres and Redis are each responsible for, why a small Go service was added in mid-2026, and how to change any of it safely. Nothing simplified into diagrams that don't match the code.
"The backend" is actually two independent Laravel apps, on two separate servers, each with its own database. They don't share code at runtime — a WordPress site's login never touches the dashboard's database, and vice versa.
The core product. Every WordPress plugin install and every mobile app talks to this API for one thing: visual login. Owns sites, the login flow, membership/device records, plan enforcement, and the /super-admin operator panel.
The customer-facing account dashboard — where a site owner manages their own settings, seats, and billing. Its own database, its own login flow (built on the same visual-login package as a dependency, not a shared runtime).
They run on different physical servers today (Helsinki for the API, Nuremberg for the dashboard and a few smaller sites) — not a deliberate geographic strategy, just how the infrastructure grew. Both are single-server deployments: one box running the app, its database, and its cache, with nothing else competing for the same CPU cores.
Aura-Login-Backend runs on Laravel Octane with RoadRunner — not traditional PHP-FPM. The difference matters: PHP-FPM rebuilds the entire framework from scratch on every single request; Octane keeps a fixed pool of already-booted PHP worker processes alive between requests, so a request only pays for its own logic, not for re-bootstrapping Laravel every time. Currently tuned to 24 workers on a 4-vCPU box — that number was arrived at by measurement (12 → 24 → 32 workers, each tested under real load), not guessed.
Every durable fact lives here: sites, users, memberships, subscriptions, login attempts, and the live state of every in-progress visual login (visual_login_requests). If it needs to still be true after a restart, it's in Postgres.
A production query-profiling pass found two specific queries responsible for ~90% of the database's total query time — one was scanning every pending login system-wide instead of scoping to the site being logged into, the other was missing an index entirely. Fixing both cut total query time roughly13× with no infrastructure change.
Session storage, the application cache, rate-limit counters, and (as of this year) a lightweight pub/subchannel: whenever a login's status changes, a message is published so anything waiting on that specific login can react immediately instead of having to keep asking.
Nothing durable lives only in Redis — losing it would cost active sessions and in-flight rate-limit windows, not historical data.
One specific API call — check-status, the one a WordPress site polls while waiting for someone to confirm a login on their phone — behaves differently from everything else the API does: instead of answering instantly, it deliberatelyholds the connection open for up to ~10 seconds, waiting to see if the status changes, so the caller doesn't have to keep re-asking every couple of seconds.
That's a good trade for anyone calling it from far away — fewer expensive long-distance round trips for the same wait — but it exposes a real structural limit in Octane's worker model:every held request pins one of the 24 fixed workers for the entire hold. With no limit on how many logins can be waiting at once, a burst of concurrent logins could pin every worker in the pool simultaneously — and once that happens, theentire application stops responding to anything else: new logins, the admin panel, everything, until a hold times out.
Go's concurrency model doesn't have that constraint. A goroutine waiting on a timer or a channel costs kilobytes of memory, not an operating-system process — so a Go program can hold tens of thousands of connections open concurrently on hardware where a fixed-worker-pool server can only hold a few dozen. That's not a general "Go is faster" claim — it's a specific, structural fit between the tool and this one workload's shape.
check-status. Every other endpoint (starting a login, scanning, biometric confirmation, finalizing) stays in Laravel, unchanged.This was a deliberately narrow bet, not a rewrite. Everything security-sensitive — cryptographic site authentication, biometric signature verification, the actual login state machine — stays in Laravel. The one thing moved to Go was chosen specifically because it's read-mostly, idempotent, and low blast-radius if anything about the migration were ever wrong.
Live in production as of 2026-07-29, confirmed serving realcheck-status traffic on api.accessglow.com — not just deployed and benchmarked in isolation. Re-running the same realistic load test used throughout this section, at the same concurrency, before and after cutover:
A second, unrelated limit was found and fixed the same week: the application server's own container had a default 1,024 open-file ceiling — invisible under normal load, but a hard wall once concurrent logins approached that count, causing a sharp cliff in error rate rather than a gradual slowdown. Traced to the exact process (RoadRunner's own connection-handling process, not a PHP worker) by watching its live file-descriptor count climb into the 1,000s during a real test, then confirmed fixed by rerunning the identical test after raising the limit to 65,536 — 100% success at the same concurrency that previously failed outright. A reminder that "find the next real bottleneck, fix only that" keeps paying off even after the headline architecture change is done.
Every number in this system is a named, environment-driven config value — nothing load-bearing is hardcoded. The table below is the practical reference for what to change and where, without needing to read the source first.
| What | Where | Controls |
|---|---|---|
| RoadRunner worker count | Laravel API, Dockerfile | How many concurrent requests Octane can process at once. More workers ≠ free — each one costs memory and competes for the same CPU cores. |
AURA_VISUAL_LOGIN_LONGPOLL_SECONDS | Laravel API env | How long check-status holds a connection before giving up and answering with whatever it currently knows. |
AURA_VISUAL_LOGIN_LONGPOLL_SLOTS | Laravel API env | PHP path only — how many concurrent holds are allowed before falling back to an instant response, protecting the worker pool. Not used by the Go service, which has no such cap. |
| Per-site / per-IP rate limits | Laravel API, config/aura.php | Independent limits for site registration, login requests, status polling, and finalization — each keyed differently (some by site, some by IP) depending on who's actually making the call. |
DATABASE_URL / REDIS_ADDR | Go service env | Which Postgres/Redis instance it talks to. Always the same ones the Laravel API uses — this service has no data of its own. |
REDIS_CHANNEL_PREFIX | Go service env | Must exactly match Laravel's own computed Redis key prefix, or the Go service silently stops receiving wake-up messages and falls back to timing out on every call. The single most important value to get right when touching either side. |
| Postgres role for the Go service | deploy/postgres-role.sql, Go service repo | Defines exactly what it can read and write — read-only on the login table, narrow write access on two usage-tracking columns, nothing else. Changing its permissions is a deliberate, auditable SQL change, not a code change. |