🐛 Fixed slow posts and pages lists when analytics data was unavailable#29048
Conversation
ref https://linear.app/ghost/issue/ONC-1871/possible-performance-issues-early-on-1st-july - the posts/pages list route blocked rendering while it awaited visitor counts from the Tinybird-backed stats endpoint, so a slow or down analytics service delayed the whole screen (~30s+ per request: 10s timeout x 3 attempts server-side) even though the counts are optional - the analytics service already exposes counts as tracked state that the list rows read reactively, so the requests can run in the background and the columns populate whenever the data arrives
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
WalkthroughThe Changes
Sequence Diagram(s)sequenceDiagram
participant PostsRoute
participant PostAnalyticsService
participant VisitorTask
participant MemberTask
PostsRoute->>PostAnalyticsService: loadVisitorCounts(posts)
PostsRoute->>PostAnalyticsService: loadMemberCounts(posts)
PostAnalyticsService->>VisitorTask: _loadVisitorCounts(..., generation)
PostAnalyticsService->>MemberTask: _loadMemberCounts(..., generation)
PostAnalyticsService->>PostAnalyticsService: reset() increments _generation
VisitorTask-->>PostAnalyticsService: ignore stale results
MemberTask-->>PostAnalyticsService: ignore stale results
Possibly related PRs 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
| Command | Status | Duration | Result |
|---|---|---|---|
nx run @tryghost/admin:build |
✅ Succeeded | 2m 6s | View ↗ |
nx run ghost-admin:test |
✅ Succeeded | 2m 25s | View ↗ |
nx run ghost:build:assets |
✅ Succeeded | 2s | View ↗ |
nx run-many -t lint -p ghost-admin |
✅ Succeeded | 18s | View ↗ |
nx run ghost:build:tsc |
✅ Succeeded | 7s | View ↗ |
nx run-many --target=build --projects=tag:publi... |
✅ Succeeded | 1s | View ↗ |
💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗
☁️ Nx Cloud last updated this comment at 2026-07-02 14:08:42 UTC
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
ghost/admin/app/routes/posts.js (1)
16-35: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winOverlapping fetch logic with
_fetchAnalyticsForPosts.
_fetchAnalyticsForPosts(Lines 156-191, unchanged) already fetches visitor/member counts forpublishedAndSentInfinityModel.contentfromsetupController, duplicating whatafterInfinityModelnow does for every page (including the first). It's harmless today thanks to the_fetchedUuids/_fetchedMemberIdscaches short-circuiting the second call, but it's redundant code that could diverge over time.Consider whether
_fetchAnalyticsForPosts/its call site is still needed now thatafterInfinityModelcovers all pages non-blockingly.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@ghost/admin/app/routes/posts.js` around lines 16 - 35, The analytics fetch path is duplicated between afterInfinityModel and _fetchAnalyticsForPosts/setupController, causing redundant visitor/member count requests for the first page and risking divergence later. Consolidate the logic into a single place: either keep afterInfinityModel as the non-blocking source for all pages and remove the now-redundant _fetchAnalyticsForPosts call path, or have both delegate to one shared helper so the fetch behavior stays consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@ghost/admin/app/routes/posts.js`:
- Around line 16-35: The analytics reset flow in
afterInfinityModel/postAnalytics can leave in-flight
loadVisitorCounts/loadMemberCounts requests able to write stale counts back
after reset() clears the cache. Update the reset() path in the postAnalytics
logic to cancel or invalidate any pending _loadVisitorCounts/_loadMemberCounts
work, and add a guard so late responses are ignored and cannot repopulate
visitorCounts or memberCounts after filters change.
---
Nitpick comments:
In `@ghost/admin/app/routes/posts.js`:
- Around line 16-35: The analytics fetch path is duplicated between
afterInfinityModel and _fetchAnalyticsForPosts/setupController, causing
redundant visitor/member count requests for the first page and risking
divergence later. Consolidate the logic into a single place: either keep
afterInfinityModel as the non-blocking source for all pages and remove the
now-redundant _fetchAnalyticsForPosts call path, or have both delegate to one
shared helper so the fetch behavior stays consistent.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 7c4de0b5-306d-41e0-9c49-a6f54ea81cf3
📒 Files selected for processing (1)
ghost/admin/app/routes/posts.js
ref https://linear.app/ghost/issue/ONC-1871/possible-performance-issues-early-on-1st-july - loadVisitorCounts/loadMemberCounts run as plain (non-restartable) ember-concurrency tasks, so an in-flight request from a previous filter view isn't cancelled when reset() clears the cache for the new one - a late response could still merge its stale data back into visitorCounts/memberCounts after the view had moved on - became easier to hit once the list stopped blocking on analytics, since users can now change filters again while a slow request is still in flight - tag each load with the reset "generation" it started on and ignore the response if reset() has since moved the cache to a new generation

ref https://linear.app/ghost/issue/ONC-1871/possible-performance-issues-early-on-1st-july
Problem
During the Tinybird slowdown early on July 1, customers reported the admin posts/pages lists taking up to 60s to render (and the Analytics page not loading).
The lists were slow because
PostsWithAnalytics.afterInfinityModelawaited the visitor-count fetch before returning posts, and the route's model hook waits on that — so the whole screen sat on the loading spinner until the stats request finished. Server-side, eachstats/posts-visitor-countsrequest proxies to Tinybird with a 10s timeout and got's default 2 retries (timeouts/5xx are retried), so a hanging Tinybird held each request for ~30s before returning an empty 200. Filter changes reset the analytics cache and refetch, blocking again each time.Fix
Fire the visitor-count and member-count loads without awaiting them. The
post-analyticsservice exposes the counts as tracked state that the list-item components already read reactively (and they already handle null counts), so the list renders immediately and the count columns populate whenever the data arrives. An analytics outage now costs nothing but temporarily missing count badges.Testing
ember test --filter="Posts / Pages"— 39 passing (includes the analytics column visibility tests)ember test --filter="post-analytics"— 19 passingPossible follow-up (separate PR): cap retries / shorten the timeout on the interactive stats endpoints so they fail fast server-side too.