You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
GET /image_map/points is unpaginated: response size and serialization scale with gallery size #9637
GET /api/v1/image_map/points returns every accessible point. MAX_CLUSTERED_POINTS = 50_000 (projection.py) only disables clustering — it is not a cap on the response. There is no pagination, no limit, and no rate limit.
Measured on this PR's code:
points
body
full request
worst single event-loop stall
Σ stalls > 5 ms
20,000
1.8 MB
0.32 s
71 ms
0.13 s
50,000
4.6 MB
0.70 s
183 ms
0.36 s
120,000
11.0 MB
1.11 s
399 ms
1.07 s
500,000 (serialize only)
50 MB
—
—
1.57 s
Where the time goes
Clustering is correctly off the event loop via asyncio.to_thread. Two things are not:
Response serialization — jsonable_encoder/json.dumps over the point list runs on the loop.
Pre-thread DB reads — list_accessible_embedded_images + get_projection + scope_hash measure 13 ms @20k, 32 ms @50k, 82 ms @120k on the loop. GET /status does the same.
So the PR's "never blocks" claim holds for the UMAP fit and for clustering, but not for assembling and writing the response.
Suggested direction
Paginate, or add a limit with a documented default, or stream. Moving the pre-thread DB reads into the worker thread is a smaller independent win.
Context
Found during adversarial review of PR invoke-ai#37. Not a correctness or security bug — the access filtering is applied before clustering and verified correct — but it scales badly with gallery size and is trivially reachable by any authenticated user.
Activity
20k commentedon Oct 2, 2026
Thanks for letting me know 🙏