[History Server] Poll Serve, placement group, and Ray Data endpoints by default - #5093
[History Server] Poll Serve, placement group, and Ray Data endpoints by default#5093Future-Outlier wants to merge 21 commits into
Conversation
…by default The head collector now polls these endpoints itself every RAY_COLLECTOR_POLL_INTERVAL (default 30s); RAY_COLLECTOR_ADDITIONAL_ENDPOINTS still adds more on top.
RayServiceReconciler.Recorder is events.EventRecorder, so record.NewFakeRecorder does not compile. Every other test in this file already uses events.
A periodic poll now stores an empty Serve response since that is the live truth, while the final shutdown poll still cannot erase a converged snapshot. Periodic polling stops on the shutdown signal instead of ShutdownChan so no tick can overwrite the final pass.
- Cap the shutdown wait on the periodic poller so a stuck storage write cannot consume the termination grace period, and skip periodic writes once shutdown starts so the final poll owns the store. - Drop the placement-group replay from the collector suite: testDeadClusterPlacementGroups already covers the same round trip. - Kill and reap the e2e kubectl port-forward on cleanup: a leaked forward keeps the port and the next test silently talks to a deleted namespace. Signed-off-by: Future-Outlier <eric901201@gmail.com>
|
cc @win5923 @machichima to take a look, tks |
machichima
left a comment
There was a problem hiding this comment.
Only went through part of the PR, will finish the review tomorrow!
- Match the sibling env blocks when parsing RAY_COLLECTOR_POLL_INTERVAL. - Merge the built-in and configured endpoints with slices.Concat. - Describe what PollAdditionalEndpointsPeriodically and pollAllEndpoints do, and keep the shutdown-ordering rationale at the call site. Signed-off-by: Future-Outlier <eric901201@gmail.com>
0805dee to
17626bc
Compare
Signed-off-by: Future-Outlier <eric901201@gmail.com>
machichima
left a comment
There was a problem hiding this comment.
Overall LGTM, just few comments
Signed-off-by: Future-Outlier <eric901201@gmail.com>
Signed-off-by: Future-Outlier <eric901201@gmail.com>
Signed-off-by: Future-Outlier <eric901201@gmail.com>
|
Just to confirm, I guess |
It can be less than 30s, I just want to keep the convention. |
Signed-off-by: Future-Outlier <eric901201@gmail.com>
Signed-off-by: Future-Outlier <eric901201@gmail.com>
|
CI note: the failed In both logs, shutdown closes |
Signed-off-by: Future-Outlier <eric901201@gmail.com>
Signed-off-by: Future-Outlier <eric901201@gmail.com>
| sessionName := path.Base(activeSessionDir) | ||
|
|
||
| // The head collector shares a pod with the dashboard; override for non-default ports. | ||
| dashboardAddress := "http://localhost:8265" |
There was a problem hiding this comment.
can we put this into a constant and/or default it via a flag as well?
There was a problem hiding this comment.
Addressed in 0e42467. http://localhost:8265 is now defaultDashboardAddress, and --dashboard-address uses that default. When both are set, RAY_DASHBOARD_ADDRESS intentionally wins, matching the neighboring collector env overrides; the help text and four precedence cases make this explicit.
There was a problem hiding this comment.
Can we improve this function name and the comments a bit?
There was a problem hiding this comment.
Addressed in 0e42467. I renamed the helper to missingSnapshotFallback and clarified its contract: when a dead session has no stored snapshot, known frontend paths receive a schema-valid empty payload, while unknown paths preserve the 404. Focused tests cover Serve, Ray Data, an unrelated path, a missing job ID, and a path with an extra segment such as /api/data/datasets/01000000/unknown.
| return data | ||
| default: | ||
| // Nothing is stored for a job that never used Ray Data; match the live dashboard. | ||
| if strings.HasPrefix(trimmed, "/api/data/datasets/") { |
There was a problem hiding this comment.
What's the reason for not including train endpoints?
There was a problem hiding this comment.
train endpoint is not in the ray's frontend now
There was a problem hiding this comment.
Built-in polling is scoped to endpoints consumed by the replayed Ray Dashboard UI, so we avoid storing snapshots the UI will not read. Train can still be configured through RAY_COLLECTOR_ADDITIONAL_ENDPOINTS; we can make it built-in if the supported frontend starts consuming it.
There was a problem hiding this comment.
Kept Train opt-in in 0e42467. The Ray 2.56 replayed frontend does not request this endpoint, while users can still add /api/train/v2/runs/v1 through RAY_COLLECTOR_ADDITIONAL_ENDPOINTS. I also aligned the stale RayJob example to show Train as optional rather than re-adding the now-built-in placement-group endpoint.
| // Post-process placement_groups response to add missing fields that the frontend requires. | ||
| // The collector may not store "bundles" and "stats", but the frontend crashes without them. | ||
| trimmedPath := strings.TrimRight(req.Request.URL.Path, "/") | ||
| if trimmedPath == "/api/v0/placement_groups" { |
There was a problem hiding this comment.
how come we don't need to do something similar for other endpoints (serve, data, etc)?
There was a problem hiding this comment.
Updated in 0e42467. The replay shim is now ensurePlacementGroupBundles and only backfills a missing bundles field: Ray 2.56 calls bundles.map() without a guard, while optional stats is guarded and remains untouched. Stored Serve and Ray Data payloads otherwise replay as-is, with schema fallbacks only when no snapshot exists. Tests verify the bundles-only change and byte-for-byte passthrough for an already complete payload.
| }) | ||
| return data | ||
| default: | ||
| // Nothing is stored for a job that never used Ray Data; match the live dashboard. |
There was a problem hiding this comment.
this comment is a bit confusing, how do we know at this point that a job never used Ray Data?
There was a problem hiding this comment.
Updated in 0e42467. The comment now states only that no snapshot was stored: Ray can evict finished dataset stats and then return the live empty {"datasets":[]} response; collection can also fail before a non-empty response is persisted. Therefore absence does not prove the job never used Ray Data. The fallback applies only to an exact /api/data/datasets/{jobID} path; tests keep /api/data/datasets/ and paths with an extra segment such as /api/data/datasets/01000000/unknown on the normal 404 path.
| // triggers "Serve not started" warning, matching live cluster behavior. | ||
| // Ref: https://github.com/ray-project/ray/blob/27d3d81d47/python/ray/dashboard/client/src/pages/serve/hook/useServeApplications.ts#L30 | ||
| // Ref: https://github.com/ray-project/ray/blob/b775a604ce/python/ray/dashboard/client/src/pages/serve/ServeDeploymentsListPage.tsx#L68 | ||
| case "/api/serve/applications": |
There was a problem hiding this comment.
I think in general we can improve the discovery of "default additional collector endpoints" somehow. Maybe a variable or constant that stores the defaults?
There was a problem hiding this comment.
yes we can do that, but in the future we will remove that, since ray will export those endpoint's event
There was a problem hiding this comment.
Addressed in 0e42467. The fixed built-in set is now named defaultPolledEndpoints and contains Serve applications and placement groups. Ray Data is also built in, but its per-job paths are discovered through jobsEndpoint and handled by pollDataDatasets, so it intentionally is not represented as a fixed URI in that slice. Existing tests distinguish the fixed defaults from configured endpoints and verify deduplication.
…-endpoints # Conflicts: # historyserver/pkg/collector/logcollector/runtime/logcollector/poll.go # historyserver/test/e2e/collector_test.go
Signed-off-by: Future-Outlier <eric901201@gmail.com>
Signed-off-by: Future-Outlier <eric901201@gmail.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
Reviewed by Cursor Bugbot for commit 0e42467. Configure here.
Signed-off-by: Future-Outlier <eric901201@gmail.com>
Signed-off-by: Future-Outlier <eric901201@gmail.com>
| flag.DurationVar(&pushInterval, "push-interval", time.Minute, "") | ||
| flag.StringVar(&ownerKind, "owner-kind", "", "") | ||
| flag.StringVar(&ownerName, "owner-name", "", "") | ||
| flag.StringVar(&dashboardAddress, "dashboard-address", defaultDashboardAddress, "Base URL of the Ray Dashboard; overridden by RAY_DASHBOARD_ADDRESS when set") |
| if val := os.Getenv("STORAGE_BACKEND_CONFIG_PATH"); val != "" { | ||
| storageBackendConfigPath = val | ||
| } | ||
| dashboardAddress = resolveDashboardAddress(dashboardAddress) |
There was a problem hiding this comment.
this function seems overkill just do this?
if value := os.Getenv("RAY_DASHBOARD_ADDRESS"); value != "" {
dashboardAddress = val
}
|
|
||
| import "testing" | ||
|
|
||
| func TestResolveDashboardAddress(t *testing.T) { |
There was a problem hiding this comment.
I personally don't think we need this test





Why are these changes needed?
The History Server rebuilds the UI from Ray events, but Serve applications, placement groups, and Ray Data datasets are not in the event stream yet. The head collector now polls these endpoints by default so replayed clusters show them:
/api/serve/applications//api/v0/placement_groups?detail=1&limit=10000/api/data/datasets/{job_id}(per job discovered via/api/jobs/)You can set the interval with
RAY_COLLECTOR_POLL_INTERVALif you want (default 30s).RAY_COLLECTOR_ADDITIONAL_ENDPOINTSstill adds more endpoints on top. Ray Train can follow once its frontend schema is settled.Screenshots
Dead clusters replayed through the History Server, each endpoint with and without data.
Serve applications:
Serve applications, cluster that never ran Serve:
Ray Data datasets from an illustrative larger manual workload with Prometheus (separate from the small E2E fixture), with a row expanded into its operators:
Placement groups; this job used no Ray Data, so no
Ray Data Overviewsection:Placement groups, job that created none:
Related issue number
Labels
doc-updates-requiredlabel.breaking-changelabel.Checks
Manual test instructions
cd historyserver && make test.cd historyserver && go test -count=1 ./test/e2e/ -run 'TestCollector$'— 8/8 passing.0without a Prometheus server (on live clusters too); the screenshot was taken with one installed.