Skip to content

perf(user_status): skip heartbeats another tab already sent - #64137

Open
pringelmann wants to merge 1 commit into
masterfrom
perf/noid/user-status-heartbeat-load
Open

perf(user_status): skip heartbeats another tab already sent#64137
pringelmann wants to merge 1 commit into
masterfrom
perf/noid/user-status-heartbeat-load

Conversation

@pringelmann

@pringelmann pringelmann commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Every tab and every page load announced presence on its own, so the dashboard sent two heartbeats (it mounts two instances) and hopping between apps sent one per page.

They now share a lastHeartbeat timestamp in browser storage and send at most one announcement per four minutes, well inside the server's fifteen minute staleness threshold. A tab opened in the background waits until it's actually looked at.

The five minute interval is longer than the four minute window, so a single tab keeps its existing cadence and the gap to the server never grows. Coming back from away is never throttled.

Checklist

  • Code is properly formatted
  • Sign-off message is added to all commits
  • Tests (unit, integration, api and/or acceptance) are included
  • Screenshots before/after for front-end changes: no visual change
  • Documentation (manuals or wiki) has been updated or is not required
  • Backports requested where applicable (ex: critical bugfixes)
  • Labels added where applicable (ex: bug/enhancement, 3. to review, feature component)
  • Milestone added for target branch/version (ex: 32.x for stable32)

AI (if applicable)

  • The content of this PR was partly or fully generated using AI (tests, reviewed)

Signed-off-by: Peter Ringelmann <peter.ringelmann@nextcloud.com>
@pringelmann pringelmann added this to the Nextcloud 36 milestone Sep 9, 2026
@pringelmann pringelmann self-assigned this Sep 9, 2026
@pringelmann
pringelmann requested a review from a team as a code owner September 9, 2026 08:29
@pringelmann
pringelmann removed the request for review from a team September 9, 2026 08:29
@pringelmann

Copy link
Copy Markdown
Contributor Author

/backport to stable35

Comment on lines +32 to +41
const announce = (force = false) => {
// NaN (missing or unparseable) and a negative age (future timestamp)
// both fail this test, so both send
const age = Date.now() - Number.parseInt(browserStorage.getItem('lastHeartbeat') ?? '', 10)
if (!force && age >= 0 && age < HEARTBEAT_THROTTLE) {
return
}
browserStorage.setItem('lastHeartbeat', String(Date.now()))
beat(isAway)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Settings, reading and parsing browser storage seems to be a bit overkill here (if this is done sync it is quite slow as well).
Why not using BroadcastChannel instead?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not using BroadcastChannel instead?

BroadcastChannel only reaches tabs that are alive/active, and most of the win here is surviving a page load. Since navigations are full reloads, a fresh page can't learn that the previous page reported a heartbeat 10 seconds ago. A braodcast would only cover multi-tab, and we would need some kind of ownership election. (quite a lot more complex to implement than what I have added here)

if this is done sync it is quite slow as well

On the sync cost: agreed in general, but the expensive part of localStorage is the first access loading the storage area, and this is not the first code path that touches it on page load. Core and notifications both read/write it on every logged-in page. And this runs once per page load plus once per 5 minutes, so it's not a particularly hot code path.

Also worth pointing out that notifications already does the same thing btw: it throttles its cross-tab fetches with a lastUpdated timestamp in browser storage.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@susnux does the reasoning above work for you? Would like to get this in for today's RC, and it needs a second approval.

If you'd still rather have BroadcastChannel I'll happily do it as a follow-up.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No makes sense, I honestly was not aware it does not work with not-active tabs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants