Case studies / Open source

Making Discourse’s admin active-users endpoint 66.5% faster

An open source Rails performance fix that replaced per-user two-factor authentication checks in Discourse’s admin active-users endpoint with two batched queries.

Discourse admin active-users endpoint results at a glance: 67% lower mean server duration, 90% less SQL time, 94% fewer SQL events, 56% fewer allocated objects, and 150 per-user 2FA existence checks eliminated.

At a glance

ProblemTwo per-user existence checks while serializing each active user’s 2FA status
ScopeGET /admin/users/list/active.json in Discourse’s Rails application
Result66.5% lower mean server duration and 93.7% fewer SQL events for 75 active users
DeliveryRegression-tested open source change merged upstream

The problem

Discourse’s admin active-users endpoint reports whether each user has two-factor authentication enabled. During serialization, the checks for an enabled TOTP method and an enabled security key each issued an existence query for every user.

With 75 active users and no configured 2FA records, the endpoint performed 75 TOTP existence queries and 75 security-key existence queries. Those repeated checks accounted for nearly all of the request’s 159 SQL events and produced 148 duplicate query fingerprints.

What I changed

I moved the work from per-user database checks to association-aware lookups:

  • the admin user query now preloads both enabled TOTP records and enabled security keys;
  • totp_enabled? uses the loaded TOTP association;
  • security-key and passkey checks search the loaded security-key collection by factor type; and
  • when security keys have not been preloaded, the shared model method retains an exists? database fallback.

Putting the association-aware behavior in SecondFactorManager, rather than special-casing only the serializer, also lets other callers benefit whenever they have preloaded the same data. Existing site-setting checks and the distinction between second-factor security keys and first-factor passkeys remain intact.

Measured result

I benchmarked five runs locally in production mode with 75 active users and empty 2FA data.

MetricBefore meanAfter meanChange
Server duration146.49 ms49.11 ms−66.5%
SQL duration56.40 ms5.71 ms−89.9%
Allocated objects61,29227,292−55.5%
SQL events15910−93.7%
Duplicate query fingerprints1480−100%
User second-factor queries761−98.7%
Security-key queries751−98.7%
TOTP existence queries750−100%
Security-key existence queries750−100%

The timing figures came from a local environment, but the deterministic query counts capture the direct improvement: 150 per-user existence checks became two association preload queries.

Validation

The request spec creates three representative users: one with TOTP, one with a second-factor security key, and one with a passkey. It confirms that the first two are reported as having 2FA enabled, preserves the existing response behavior for the passkey user, and asserts that the endpoint performs exactly one query for TOTP records and one for security keys.

The change was reviewed, approved, and merged into Discourse upstream.

View the merged Discourse pull request →