accounts: reduce initial load time further

When generating the cache and calling userSummary per user, previously
the DB was queried for an invite with the ReferrerJellyfinID set to the
user's ID. This was fast enough on my test system (~5000 users in
~1.5s), while testing cross-compilation, I found it ran extremely slow
on an arm64 build (running through QEMU admittedly), doing ~5000 in
~18s. Instead, a map of IDs to invites/referrals is generated once and
queried instead. Initial load now takes ~80ms on my system, and 0.95s
through QEMU, and 0.68s on a rockpro64 SBC.
This commit is contained in:
Harvey Tindall
2025-12-10 16:44:21 +00:00
parent 420a22970d
commit a08a0fd3e6
4 changed files with 36 additions and 5 deletions

View File

@@ -902,9 +902,23 @@ func (app *appContext) AdminPasswordReset(gc *gin.Context) {
respondBool(204, true, gc)
}
// getActiveReferrals returns a map of jellyfin user IDs to their active referral "invite" code, if they have one.
// It does not check if the user still exists, simply finding invites with the ReferrerJellyfinID field set.
func (app *appContext) getActiveReferrals() map[string]string {
out := map[string]string{}
for _, inv := range app.storage.GetInvites() {
if inv.ReferrerJellyfinID == "" {
continue
}
out[inv.ReferrerJellyfinID] = inv.Code
}
return out
}
// userSummary generates a respUser for to be displayed to the user, or sorted/filtered.
// also, consider it a source of which data fields/struct modifications need to trigger a cache invalidation.
func (app *appContext) userSummary(jfUser mediabrowser.User) respUser {
// referralCache can be passed to avoid querying the db each time this is called. It can be generated with app.getActiveReferrals().
func (app *appContext) userSummary(jfUser mediabrowser.User, referralCache *map[string]string) respUser {
adminOnly := app.config.Section("ui").Key("admin_only").MustBool(true)
allowAll := app.config.Section("ui").Key("allow_all").MustBool(false)
referralsEnabled := app.config.Section("user_page").Key("referrals").MustBool(false)
@@ -944,10 +958,17 @@ func (app *appContext) userSummary(jfUser mediabrowser.User) respUser {
}
// FIXME: Send referral data
referrerInv := Invite{}
// FIXME: This is veeery slow when running an arm64 binary through qemu
if referralsEnabled {
// 1. Directly attached invite.
err := app.storage.db.FindOne(&referrerInv, badgerhold.Where("ReferrerJellyfinID").Eq(jfUser.ID))
if err == nil {
found := false
if referralCache != nil {
_, found = (*referralCache)[jfUser.ID]
} else {
err := app.storage.db.FindOne(&referrerInv, badgerhold.Where("IsReferral").Eq(true).And("ReferrerJellyfinID").Eq(jfUser.ID))
found = err == nil
}
if found {
user.ReferralsEnabled = true
// 2. Referrals via profile template. Shallow check, doesn't look for the thing in the database.
} else if email, ok := app.storage.GetEmailsKey(jfUser.ID); ok && email.ReferralTemplateKey != "" {

View File

@@ -328,6 +328,9 @@ const (
// webhooks.go
WebhookRequest = "Webhook request send to \"%s\" (%d): %v"
// usercache.go
CacheRefreshCompleted = "Usercache refreshed, %d in %.2fs (%f.2u/sec)"
)
const (

View File

@@ -8,6 +8,8 @@ import (
"strings"
"sync"
"time"
lm "github.com/hrfee/jfa-go/logmessages"
)
const (
@@ -85,10 +87,12 @@ func (c *UserCache) MaybeSync(app *appContext) error {
status <- err
return
}
startTime := time.Now()
cache := make([]respUser, len(users))
labels := map[string]bool{}
referralCache := app.getActiveReferrals()
for i, jfUser := range users {
cache[i] = app.userSummary(jfUser)
cache[i] = app.userSummary(jfUser, &referralCache)
if cache[i].Label != "" {
labels[cache[i].Label] = true
}
@@ -101,6 +105,9 @@ func (c *UserCache) MaybeSync(app *appContext) error {
for label, _ := range labels {
labelSlice = append(labelSlice, label)
}
elapsed := time.Since(startTime).Seconds()
usersPerSec := float64(len(users)) / elapsed
app.debug.Printf(lm.CacheRefreshCompleted, len(users), elapsed, usersPerSec)
c.Cache = cache
c.Ref = ref
c.Sorted = false

View File

@@ -141,7 +141,7 @@ func (app *appContext) NewUserPostVerification(p NewUserParams) (out NewUserData
webhookURIs := app.config.Section("webhooks").Key("created").StringsWithShadows("|")
if len(webhookURIs) != 0 {
summary := app.userSummary(out.User)
summary := app.userSummary(out.User, nil)
for _, uri := range webhookURIs {
pendingTasks.Add(1)
go func() {