Skip to content

Enhancing slow loads and some 503 errors on shared hosting servers#2045

Draft
Rvice wants to merge 11 commits intoAdmidio:masterfrom
Rvice:503_Issues_NetworkSolutions
Draft

Enhancing slow loads and some 503 errors on shared hosting servers#2045
Rvice wants to merge 11 commits intoAdmidio:masterfrom
Rvice:503_Issues_NetworkSolutions

Conversation

@Rvice
Copy link
Copy Markdown
Contributor

@Rvice Rvice commented May 5, 2026

https://www.admidio.org/forum/viewtopic.php?t=10366 - Started the hunt to improve loading times

My host (formerly iPage and now NetworkSolutions) is really slow and generates 503 errors. As a result, I went down this rabbit hole to figure out how to improve the experience.

Overview

This PR resolves severe profile-page latency and request contention by reducing PHP session lock time, removing unnecessary photo BLOB work, optimizing membership reload behavior, and improving UX during slow AJAX calls.

Problem

In production, profile-related requests were intermittently taking 30–90 seconds (especially first load), including:

  • modules/profile/profile.php
  • modules/profile/profile_function.php (reload_current_memberships, reload_former_memberships, reload_future_memberships)
  • modules/profile/profile_photo_show.php
  • Related overview/plugin image calls

Root Causes

  • Large usr_photo BLOBs were being fetched too eagerly.
  • Profile field loading included unnecessary coupling that amplified user-load cost.
  • PHP session write-lock was held across expensive operations.
  • Membership AJAX calls could stack/burst.
  • Legacy session payloads (photo/form data) caused first-load slowdowns.
  • Photo-serving paths performed work while still contending on session.

What Changed

1) Membership Reload Stabilization + UX

  • Added debounce + in-flight/pending guards for role membership reloads.
  • Deferred former/future membership loads until memberships UI is opened.
  • Added visible loading indicator and AJAX error placeholder for current/former/future sections.

Files:

  • modules/profile/profile.js

2) Profile Page Lock/Query Improvements

  • Added early session unlock in profile flow to reduce request serialization.
  • Replaced repetitive rights-query pattern with single-query aggregation logic.

Files:

  • modules/profile/profile.php

3) Profile Membership Endpoint Hardening

  • Improved reload endpoint handling as part of session/perf mitigation.
  • Added stale session cleanup hooks in profile reload path.

Files:

  • modules/profile/profile_function.php
  • modules/profile/roles_functions.php

4) Photo Endpoint Refactor

  • Reworked photo show flow to a single clean pass.
  • Captures needed session value first (for preview case), then releases session lock before image I/O.
  • Preserves ETag/304 short-circuit behavior.

Files:

  • modules/profile/profile_photo_show.php

5) Lazy usr_photo Loading

  • User reads avoid eager usr_photo fetch.
  • usr_photo is fetched only on demand.
  • Added serialization protection (__sleep / __wakeup) to prevent old BLOB payloads from persisting in session objects.

Files:

  • src/Users/Entity/User.php

6) Profile Field Data Read Optimization

  • Removed expensive read path coupling that unnecessarily touched user data context during profile field fetch.

Files:

  • src/ProfileFields/ValueObjects/ProfileFields.php

7) Session Object Cleanup Utility

  • Added session helper for clearing accumulated form objects (legacy session-bloat mitigation).

Files:

  • src/Session/Entity/Session.php

8) Photo Module Session Contention Reduction

  • Removed session album-object write behavior in photo show path.
  • Releases session lock before expensive image processing where safe.

Files:

  • modules/photos/photo_show.php

9) DB Indexes for Membership Date Filters

Added/updated indexes to support membership date-range filters efficiently:

  • (mem_usr_id, mem_begin)
  • (mem_usr_id, mem_end)

Files:

  • install/db_scripts/db.sql
  • install/db_scripts/update_5_1.xml

Results Observed

  • Significant reduction in repeat-load times (e.g., first-load slow, second-load fast pattern after session self-heal).
  • Reduced lock-chain behavior between profile AJAX/photo requests.
  • Improved perceived responsiveness due to loaders and reduced burst traffic.

Risk / Compatibility

  • Low-to-moderate risk, mostly in session/form handling and profile membership save path behavior.
  • Database migration adds indexes only (non-breaking schema change).
  • Existing bloated sessions may still show one slow first request until session is rewritten/cleared.

Rollout Notes

  • Deploy code + DB migration together.
  • Optional but recommended: one-time PHP session-file purge during maintenance window to remove legacy slow first-hit behavior for all users immediately.

Validation Checklist

  • Load profile page for previously slow users (first + second load).
  • Verify reload_current_memberships, reload_former_memberships, reload_future_memberships timing.
  • Verify profile photo endpoint timing and 304 behavior.
  • Verify membership edit/save flow (CSRF + date validation).
  • Confirm new indexes exist on production DB.
  • Smoke test overview page and random photo plugin image loading.
  • Change Db changes to a new version of 5.x (because the changes here go on 5.1 for testing)

Rvice added 11 commits May 5, 2026 12:29
…ofile role reload bursts that can drive slow responses and 503s.

What changed

Added guarded reloads (no overlapping requests per section) in profile.js
Current/former/future reloads now use a shared in-flight/pending mechanism.
If a reload is already running, a second call is queued once instead of starting another request immediately.
Added debounced “reload all memberships” helper in profile.js
New method scheduleRoleMembershipReloads() coalesces rapid trigger bursts into one reload cycle.
Deferred former/future loading until the role-memberships UI is opened in profile.js
New method initializeDeferredRoleMemberships() binds to:
shown.bs.tab on role-memberships tab
shown.bs.collapse on role-memberships accordion
This avoids loading all three sections immediately on profile page load.
Switched PHP inline JS to use the safer reload flow in profile.php
On save callback: replaced 3 immediate reload calls with one debounced call.
On initial page load: now loads current memberships immediately, and defers former/future via initialization hook.
On modal close: replaced 3 immediate reload calls with one debounced call.
Validation

No diagnostics reported after patch in:
profile.js
profile.php
What you should see

Fewer simultaneous requests to profile_function.php for memberships.
Faster first profile paint (former/future role blocks load on demand).
Lower chance of session-lock queueing and upstream timeouts that can surface as 503 after repeated navigation.
…e run (~31s) because it executed 13 separate SQL queries (one per permission column) — each doing a full-table scan without an index on mem_usr_id. Any parallel request from the same browser session (like the nav-bar photo) could not start until the lock was released.

Two changes applied to profile.php:

Session lock released early — after navigation and JS setup (which need the session), getCsrfToken() is force-called to ensure the token is persisted, then session_write_close() releases the lock before any heavy DB work. The nav-bar photo request now proceeds in parallel instead of queueing for 31 seconds.

13 queries → 1 query — the permission loop is replaced with a single SELECT that fetches rol_name plus all 13 permission flag columns for the user's active memberships in one round-trip. PHP then aggregates role names per permission. This is the fix for why profile.php itself was slow in the first place.
User.php — Lazy BLOB load

readData now builds a column list from columnsInfos, excluding usr_photo
usr_photo is only fetched when getValue('usr_photo') is first called
Added $photoLoaded flag so the extra query runs at most once per User instance
profile_photo_show.php — Clean order + removed duplicate block

Session data captured, then session_write_close() before ANY BLOB or file I/O
Removed the duplicated image-loading code from the earlier patch
BLOB fetch (getValue('usr_photo')) happens after session lock is released
Net effect: profile.php, profile_function.php membership endpoints, and any other script that loads a User will no longer pay the cost of transferring the BLOB at all. Only profile_photo_show.php fetches it (once, lazily, after releasing the session lock).

For existing large photos: they still take time to transfer from DB to PHP during profile_photo_show.php. If you want to eliminate that permanently, the long-term fix is to run a one-time migration that re-encodes those oversized photos through Image::scale(130, 170) and saves them back, or migrates them to filesystem storage.
Read + deserialized the huge session file (BLOB round-trips through PHP memory)
Ran the request
Wrote the still-huge session file back and released the lock
Added stale session payload cleanup in profile.php
Added stale session payload cleanup in profile_function.php
Added stale session payload cleanup in profile_photo_show.php only when new_photo is not requested
Previously-added serialization hardening remains active in User.php: __sleep and __wakeup strip usr_photo from session object state
Why this matters:

If ses_binary contains leftover uploaded image bytes from an abandoned photo edit flow, every request serializes/deserializes that blob under the PHP session lock.
That can make all three membership reload calls and unrelated photo calls queue and look like jquery.js timeout/long wait even when the target image itself is small.
The new cleanup makes these profile endpoints self-heal that condition immediately.
What to expect on first request:

The very first request in a bloated session can still be slow once (it must read the existing large session file).
After that, the session should be lean and the three profile_function reload calls should drop dramatically.
…e.js. This is probably the most important change even if everything else is ignored.
@Rvice Rvice marked this pull request as draft May 7, 2026 13:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant