Skip to content

[History Server] Poll Serve, placement group, and Ray Data endpoints by default - #5093

Open
Future-Outlier wants to merge 21 commits into
ray-project:masterfrom
Future-Outlier:collector-builtin-endpoints
Open

[History Server] Poll Serve, placement group, and Ray Data endpoints by default#5093
Future-Outlier wants to merge 21 commits into
ray-project:masterfrom
Future-Outlier:collector-builtin-endpoints

Conversation

@Future-Outlier

@Future-Outlier Future-Outlier commented Aug 5, 2026

Copy link
Copy Markdown
Member

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_INTERVAL if you want (default 30s). RAY_COLLECTOR_ADDITIONAL_ENDPOINTS still 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 with data

Serve applications, cluster that never ran Serve:

serve applications empty

Ray Data datasets from an illustrative larger manual workload with Prometheus (separate from the small E2E fixture), with a row expanded into its operators:

ray data datasets

Placement groups; this job used no Ray Data, so no Ray Data Overview section:

placement groups with data

Placement groups, job that created none:

placement groups empty

Related issue number

Labels

  • If this PR has user-facing changes that require documentation updates at release time, I have added the doc-updates-required label.
  • If this PR contains breaking changes, I have added the breaking-change label.

Checks

  • I've made sure the tests are passing.
  • Testing Strategy
    • Unit tests
    • Manual tests

Manual test instructions

  • Unit: cd historyserver && make test.
  • E2E on kind + MinIO: cd historyserver && go test -count=1 ./test/e2e/ -run 'TestCollector$' — 8/8 passing.
  • The Ray Data memory and CPU columns come from Prometheus queries in the dashboard, so they read 0 without a Prometheus server (on live clusters too); the screenshot was taken with one installed.

…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.
Comment thread historyserver/pkg/collector/logcollector/runtime/logcollector/collector.go Outdated
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.
Signed-off-by: Future-Outlier <eric901201@gmail.com>
Signed-off-by: Future-Outlier <eric901201@gmail.com>
@Future-Outlier
Future-Outlier marked this pull request as draft August 6, 2026 00:32
- 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>
@Future-Outlier
Future-Outlier marked this pull request as ready for review August 6, 2026 01:25
@Future-Outlier

Copy link
Copy Markdown
Member Author

cc @win5923 @machichima to take a look, tks

@machichima machichima left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Only went through part of the PR, will finish the review tomorrow!

Comment thread historyserver/cmd/collector/main.go
Comment thread historyserver/cmd/collector/main.go
Comment thread historyserver/pkg/collector/logcollector/runtime/logcollector/poll.go Outdated
Comment thread historyserver/pkg/collector/logcollector/runtime/logcollector/poll.go Outdated
Comment thread historyserver/pkg/collector/logcollector/runtime/logcollector/poll.go Outdated
- 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>
@Future-Outlier
Future-Outlier force-pushed the collector-builtin-endpoints branch from 0805dee to 17626bc Compare August 6, 2026 17:41
Signed-off-by: Future-Outlier <eric901201@gmail.com>

@machichima machichima left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Overall LGTM, just few comments

Comment thread historyserver/pkg/collector/logcollector/runtime/logcollector/poll.go Outdated
Comment thread historyserver/pkg/collector/logcollector/runtime/logcollector/poll.go Outdated
Comment thread historyserver/config/ray-data.yaml Outdated
Comment thread historyserver/config/rayservice.yaml
Comment thread historyserver/config/raycluster-azureblob.yaml Outdated
Comment thread historyserver/pkg/collector/logcollector/runtime/logcollector/collector.go Outdated
Signed-off-by: Future-Outlier <eric901201@gmail.com>
Signed-off-by: Future-Outlier <eric901201@gmail.com>
Signed-off-by: Future-Outlier <eric901201@gmail.com>
@machichima

Copy link
Copy Markdown
Collaborator

Just to confirm, I guess RAY_COLLECTOR_POLL_INTERVAL can only be 30s? I saw you change it back to 30s in: f4f0c84

@machichima machichima left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Overall LGTM! Tested with ray-data.yaml and rayservice.yaml on my side. Just two comments

Image Image Image

Comment thread historyserver/config/rayservice.yaml Outdated
Comment thread historyserver/config/rayservice.yaml Outdated
@Future-Outlier

Future-Outlier commented Aug 9, 2026

Copy link
Copy Markdown
Member Author

Just to confirm, I guess RAY_COLLECTOR_POLL_INTERVAL can only be 30s? I saw you change it back to 30s in: f4f0c84

It can be less than 30s, I just want to keep the convention.
update: I decided to remove this and increase TTL seconds

Signed-off-by: Future-Outlier <eric901201@gmail.com>
Signed-off-by: Future-Outlier <eric901201@gmail.com>
@Future-Outlier

Future-Outlier commented Aug 9, 2026

Copy link
Copy Markdown
Member Author

CI note: the failed Build historyserver attempts (previous, latest) both hit the existing flaky pkg/collector/eventcollector/TestShutdown_DrainsRetriedTasks. This PR does not modify that package.

In both logs, shutdown closes stopProducers before retryProcess registers its final attempt (Giving up ... during shutdown; keys: []). The previous failure passed on a same-HEAD rerun, and the latest race also reproduces locally with -count=500. No PR code change is needed.

Comment thread historyserver/config/ray-data.yaml Outdated
Comment thread historyserver/config/rayservice.yaml Outdated
Comment thread historyserver/pkg/collector/logcollector/runtime/logcollector/poll.go Outdated
Signed-off-by: Future-Outlier <eric901201@gmail.com>

@win5923 win5923 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thank you!

Comment thread historyserver/config/rayservice.yaml Outdated
Comment thread historyserver/config/ray-data.yaml Outdated
Signed-off-by: Future-Outlier <eric901201@gmail.com>
Comment thread historyserver/cmd/collector/main.go Outdated
sessionName := path.Base(activeSessionDir)

// The head collector shares a pod with the dashboard; override for non-default ports.
dashboardAddress := "http://localhost:8265"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

can we put this into a constant and/or default it via a flag as well?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we improve this function name and the comments a bit?

@Future-Outlier Future-Outlier Aug 10, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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/") {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What's the reason for not including train endpoints?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

train endpoint is not in the ray's frontend now

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

image

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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" {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

how come we don't need to do something similar for other endpoints (serve, data, etc)?

@Future-Outlier Future-Outlier Aug 10, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this comment is a bit confusing, how do we know at this point that a job never used Ray Data?

@Future-Outlier Future-Outlier Aug 10, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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":

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think in general we can improve the discovery of "default additional collector endpoints" somehow. Maybe a variable or constant that stores the defaults?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

yes we can do that, but in the future we will remove that, since ray will export those endpoint's event

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

Reviewed by Cursor Bugbot for commit 0e42467. Configure here.

Comment thread historyserver/pkg/historyserver/router.go
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")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: ray-dashboard-address

if val := os.Getenv("STORAGE_BACKEND_CONFIG_PATH"); val != "" {
storageBackendConfigPath = val
}
dashboardAddress = resolveDashboardAddress(dashboardAddress)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this function seems overkill just do this?

if value := os.Getenv("RAY_DASHBOARD_ADDRESS"); value != "" {
  dashboardAddress = val
}


import "testing"

func TestResolveDashboardAddress(t *testing.T) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I personally don't think we need this test

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.

5 participants