Repository navigation
[core][gcs] Add a bounded TableName label to GCS Store count and latency metrics - #65583
Conversation
There was a problem hiding this comment.
Code Review
This pull request enhances GCS storage metrics by adding a TableName label to both operation count and latency metrics, allowing breakdowns by specific GCS tables. It introduces a normalization helper to map table names to a closed domain, preventing unbounded label cardinality, and adds a configuration flag to toggle these metrics on the external Redis backend. The review feedback points out that since clock_.NowUnixNanos() is non-monotonic, the latency calculation could occasionally yield negative values, and suggests guarding against this by using std::max with zero.
2181c67 to
c7e643b
Compare
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 c7e643b. Configure here.
c7e643b to
74ca549
Compare
74ca549 to
f259409
Compare
f259409 to
b2c1744
Compare
|
This pull request has been automatically marked as stale because it has not had You can always ask for help on our discussion forum or Ray's public slack channel. If you'd like to keep this open, just leave any comment, and the stale label will be removed. |
…m RST to MyST (#66673) ## Why are these changes needed? This PR converts the remaining prose pages under `ray-observability/`, `ray-more-libs/`, `ray-references/`, and `ray-air/` from reStructuredText to MyST Markdown. It continues the RST-to-MyST conversion that already landed for `ray-core/` (#66430), `data/` (#66375), and the doctest-wired pages (#66461). The conversion is format only: the goal is for each page's rendered HTML to match `/en/master`. There are five commits: 1. **Conversion.** This commit converts 25 pages, `git rm`s the `.rst` sources, and adds one `doc/BUILD.bazel` exclude, which commit 5 reverts. Prose keeps its source line wrapping, so the diff lines up with the `.rst` line by line. 2. **Soft-wrap.** This commit reflows whitespace only, to one line per paragraph and list item. `verify.py` from `ray-soft-wrap` confirms that the non-whitespace bytes and the rendered HTML don't change on all 25 files. 3. **Render fixes.** This commit fixes six regressions that the render diff against `/en/master` caught, even though the build was green. In `modin`, two bare-word RST link references (`Modin_`, `documentation_`) rendered as literal text. In `getting-started`, a bold URL was autolinked. In `system-metrics`, a URL in a table cell lost its link. In `dask-on-ray`, `_not_` rendered as italics. In `glossary`, the inline `.. TODO: RLlib` text, which `/en/master` shows, had become a comment. The commit also adds compat anchors to five headings with apostrophes (see below). 4. **Pseudo-lists.** This commit restores three pseudo-lists that the soft-wrap merged into one line. 5. **Review feedback.** This commit removes the glossary's placeholder TODO entries, reverts the `doc/BUILD.bazel` change so the PR stays docs-only, and fixes defects that the review flagged in the `.rst` sources: a stray definition list in `dask-on-ray`, an unconverted link in `system-metrics`, an extra parenthesis in `debug-failures`, typos in `debug-hangs` and `optimize-performance`, a grammar slip in `ray-debugging`, and em-dash pseudo-list items in `general-debugging`, which are now a bulleted list. ### Scope - `ray-air/`: `getting-started` and `deployment`, converted together. `getting-started` includes `deployment`, and the include now points at `deployment.md`. - `ray-observability/`: 14 pages, including `user-guides/debug-apps/` and `reference/system-metrics`. - `ray-more-libs/`: 9 pages. - `ray-references/`: `glossary` and `faq`. **Left as RST:** `ray-observability/reference/api.rst` (`autosummary`) and `ray-observability/reference/cli.rst` (`click`). They're API reference pages, so they stay RST along with the other `*/api/` reference trees. ### Construct notes for reviewers - **Glossary.** The glossary is a native MyST `{glossary}` directive, not RST wrapped in `{eval-rst}`. Sphinx's `glossary` directive parses each definition with the host parser, so the definitions are Markdown now. The glossary's placeholder `.. TODO:` entries are removed. Missing terms can be identified separately. - **Glossary doctest.** Nothing ran the glossary's `.. doctest::` block as `.rst`: `pytest-sphinx` doesn't execute `doctest` sections, and `--doctest-glob='*.md'` doesn't match `.rst`. As `.md`, the stock doctest parser would start running its five `>>>` prompts, and the `[[0],[1],...,[3],[4]]` output can't pass without `ELLIPSIS`. Each prompt now carries `# doctest: +SKIP`, which keeps coverage the same as master without a `doc/BUILD.bazel` change. Sphinx's default `trim_doctest_flags` strips the flag from the rendered page, as in `data/working-with-tensors.md`. If you'd rather fix the expected output and start testing it, I can do that in a follow-up. - **FAQ.** `faq.md` includes `_includes/_help-links.md`, the Markdown copy of `_help.rst` that #66417 added. `_help.rst` stays, because `cluster/vms/user-guides/community/yarn.rst` and `tune/key-concepts.rst` still include it. - **Comparison table.** The grid table in `ray-collective-custom-backend` is now a native `{list-table}`. - **Line blocks.** The line blocks in `dask-on-ray`'s version table are now Markdown hard line breaks. This is an expected render difference: `<p>` with `<br>` instead of `div.line-block`. ### Dead links fixed on the way These links were broken on `/en/master` and fail `fail_on_warning` when they're transcribed into MyST: - `joblib` and `multiprocessing` linked to relative `actors.html` and `installation.html`, which resolve under `ray-more-libs/` and return 404. They now link to `{doc}` `/ray-core/actors` and `/ray-overview/installation`. - `ray-collective` linked to `#api-reference`, but the heading's anchor is `#api-references`. ### Open PRs that edit these pages Each of these PRs needs to move its change onto the `.md` file: #66363 (`ray-event-export`), #65693 and #65583 (`system-metrics`), and #64957 (`joblib`, `multiprocessing`). #65321 edits `reference/cli.rst`, which this PR doesn't convert. ## Related issue number None. ## Checks - [x] I've signed off every commit (`git commit -s`). - [x] Duplicate check: no open PR converts these pages. I searched open PRs for MyST, observability, glossary, and more-libs, and for open PRs that edit these files. - **Doctest parity:** `doctest_parity.py` (the `pytest-sphinx` parser, run against `upstream/master`) reports the same sections and examples on all 25 pages, before and after the soft-wrap: 12 examples before, 12 after. - **Local doctest:** `pytest --doctest-modules --doctest-glob='*.md' -c NO_PYTEST_CONFIG -p doctest_pytest_plugin` on the 24 converted pages in the main target, with Ray nightly `bc900c2` on Python 3.10: 5 passed. Those are the 5 files with examples. The glossary, now in the target too, reports 1 skipped: the stock parser finds 5 examples and all 5 carry `+SKIP`. - **Static checks:** frontmatter parses, fences balance, every label is preserved, and there's no leftover RST outside fences or directives. - **RtD preview:** green under `fail_on_warning` on all three commits. - **Render diff** (`render_diff.py`, preview against `/en/master`, filtering the two class-only differences every MyST page shows): 11 of 25 pages are identical. Every difference on the other 14 is one of these: - RST comments now emit `<!-- ... -->` HTML comments, which readers don't see. Pages: `ray-air/*`, `ray-more-libs/index`, `ray-collective`, `getting-started`, `general-debugging`, `glossary`. - The `dask-on-ray` version table: Markdown hard line breaks (`<p>` with `<br>`) replace RST line blocks (`div.line-block`). - The `dask-on-ray` "Why use Dask on Ray?" item keeps its definition-list structure. MyST adds a `myst` class to the `<dl>`, and the class has no styling. - Same-page fragment and `{doc}` links render as `reference internal` instead of `reference external`. Pages: `ray-collective`, `joblib`, `multiprocessing`. This list also includes the dead links fixed above. - Headings with an apostrophe get a new anchor, because MyST drops the apostrophe where docutils turned it into a hyphen. For example, `#what-s-the-out-of-memory-error` becomes `#whats-the-out-of-memory-error`. Five of these headings had no label, so each gets a compat target with the old slug, which keeps existing links working. The sixth, in `optimize-performance`, already carries the `dashboard-cprofile` label. This PR was written with AI assistance (Claude Code). I reviewed the changes and ran the checks above. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Douglas Strodtman <douglas@anyscale.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…m RST to MyST (ray-project#66673) ## Why are these changes needed? This PR converts the remaining prose pages under `ray-observability/`, `ray-more-libs/`, `ray-references/`, and `ray-air/` from reStructuredText to MyST Markdown. It continues the RST-to-MyST conversion that already landed for `ray-core/` (ray-project#66430), `data/` (ray-project#66375), and the doctest-wired pages (ray-project#66461). The conversion is format only: the goal is for each page's rendered HTML to match `/en/master`. There are five commits: 1. **Conversion.** This commit converts 25 pages, `git rm`s the `.rst` sources, and adds one `doc/BUILD.bazel` exclude, which commit 5 reverts. Prose keeps its source line wrapping, so the diff lines up with the `.rst` line by line. 2. **Soft-wrap.** This commit reflows whitespace only, to one line per paragraph and list item. `verify.py` from `ray-soft-wrap` confirms that the non-whitespace bytes and the rendered HTML don't change on all 25 files. 3. **Render fixes.** This commit fixes six regressions that the render diff against `/en/master` caught, even though the build was green. In `modin`, two bare-word RST link references (`Modin_`, `documentation_`) rendered as literal text. In `getting-started`, a bold URL was autolinked. In `system-metrics`, a URL in a table cell lost its link. In `dask-on-ray`, `_not_` rendered as italics. In `glossary`, the inline `.. TODO: RLlib` text, which `/en/master` shows, had become a comment. The commit also adds compat anchors to five headings with apostrophes (see below). 4. **Pseudo-lists.** This commit restores three pseudo-lists that the soft-wrap merged into one line. 5. **Review feedback.** This commit removes the glossary's placeholder TODO entries, reverts the `doc/BUILD.bazel` change so the PR stays docs-only, and fixes defects that the review flagged in the `.rst` sources: a stray definition list in `dask-on-ray`, an unconverted link in `system-metrics`, an extra parenthesis in `debug-failures`, typos in `debug-hangs` and `optimize-performance`, a grammar slip in `ray-debugging`, and em-dash pseudo-list items in `general-debugging`, which are now a bulleted list. ### Scope - `ray-air/`: `getting-started` and `deployment`, converted together. `getting-started` includes `deployment`, and the include now points at `deployment.md`. - `ray-observability/`: 14 pages, including `user-guides/debug-apps/` and `reference/system-metrics`. - `ray-more-libs/`: 9 pages. - `ray-references/`: `glossary` and `faq`. **Left as RST:** `ray-observability/reference/api.rst` (`autosummary`) and `ray-observability/reference/cli.rst` (`click`). They're API reference pages, so they stay RST along with the other `*/api/` reference trees. ### Construct notes for reviewers - **Glossary.** The glossary is a native MyST `{glossary}` directive, not RST wrapped in `{eval-rst}`. Sphinx's `glossary` directive parses each definition with the host parser, so the definitions are Markdown now. The glossary's placeholder `.. TODO:` entries are removed. Missing terms can be identified separately. - **Glossary doctest.** Nothing ran the glossary's `.. doctest::` block as `.rst`: `pytest-sphinx` doesn't execute `doctest` sections, and `--doctest-glob='*.md'` doesn't match `.rst`. As `.md`, the stock doctest parser would start running its five `>>>` prompts, and the `[[0],[1],...,[3],[4]]` output can't pass without `ELLIPSIS`. Each prompt now carries `# doctest: +SKIP`, which keeps coverage the same as master without a `doc/BUILD.bazel` change. Sphinx's default `trim_doctest_flags` strips the flag from the rendered page, as in `data/working-with-tensors.md`. If you'd rather fix the expected output and start testing it, I can do that in a follow-up. - **FAQ.** `faq.md` includes `_includes/_help-links.md`, the Markdown copy of `_help.rst` that ray-project#66417 added. `_help.rst` stays, because `cluster/vms/user-guides/community/yarn.rst` and `tune/key-concepts.rst` still include it. - **Comparison table.** The grid table in `ray-collective-custom-backend` is now a native `{list-table}`. - **Line blocks.** The line blocks in `dask-on-ray`'s version table are now Markdown hard line breaks. This is an expected render difference: `<p>` with `<br>` instead of `div.line-block`. ### Dead links fixed on the way These links were broken on `/en/master` and fail `fail_on_warning` when they're transcribed into MyST: - `joblib` and `multiprocessing` linked to relative `actors.html` and `installation.html`, which resolve under `ray-more-libs/` and return 404. They now link to `{doc}` `/ray-core/actors` and `/ray-overview/installation`. - `ray-collective` linked to `#api-reference`, but the heading's anchor is `#api-references`. ### Open PRs that edit these pages Each of these PRs needs to move its change onto the `.md` file: ray-project#66363 (`ray-event-export`), ray-project#65693 and ray-project#65583 (`system-metrics`), and ray-project#64957 (`joblib`, `multiprocessing`). ray-project#65321 edits `reference/cli.rst`, which this PR doesn't convert. ## Related issue number None. ## Checks - [x] I've signed off every commit (`git commit -s`). - [x] Duplicate check: no open PR converts these pages. I searched open PRs for MyST, observability, glossary, and more-libs, and for open PRs that edit these files. - **Doctest parity:** `doctest_parity.py` (the `pytest-sphinx` parser, run against `upstream/master`) reports the same sections and examples on all 25 pages, before and after the soft-wrap: 12 examples before, 12 after. - **Local doctest:** `pytest --doctest-modules --doctest-glob='*.md' -c NO_PYTEST_CONFIG -p doctest_pytest_plugin` on the 24 converted pages in the main target, with Ray nightly `bc900c2` on Python 3.10: 5 passed. Those are the 5 files with examples. The glossary, now in the target too, reports 1 skipped: the stock parser finds 5 examples and all 5 carry `+SKIP`. - **Static checks:** frontmatter parses, fences balance, every label is preserved, and there's no leftover RST outside fences or directives. - **RtD preview:** green under `fail_on_warning` on all three commits. - **Render diff** (`render_diff.py`, preview against `/en/master`, filtering the two class-only differences every MyST page shows): 11 of 25 pages are identical. Every difference on the other 14 is one of these: - RST comments now emit `<!-- ... -->` HTML comments, which readers don't see. Pages: `ray-air/*`, `ray-more-libs/index`, `ray-collective`, `getting-started`, `general-debugging`, `glossary`. - The `dask-on-ray` version table: Markdown hard line breaks (`<p>` with `<br>`) replace RST line blocks (`div.line-block`). - The `dask-on-ray` "Why use Dask on Ray?" item keeps its definition-list structure. MyST adds a `myst` class to the `<dl>`, and the class has no styling. - Same-page fragment and `{doc}` links render as `reference internal` instead of `reference external`. Pages: `ray-collective`, `joblib`, `multiprocessing`. This list also includes the dead links fixed above. - Headings with an apostrophe get a new anchor, because MyST drops the apostrophe where docutils turned it into a hyphen. For example, `#what-s-the-out-of-memory-error` becomes `#whats-the-out-of-memory-error`. Five of these headings had no label, so each gets a compat target with the old slug, which keeps existing links working. The sixth, in `optimize-performance`, already carries the `dashboard-cprofile` label. This PR was written with AI assistance (Claude Code). I reviewed the changes and ran the checks above. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Douglas Strodtman <douglas@anyscale.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
Hi @zzchun . Thanks for the PR and the detailed writeup. I went through the changes and it seems like this PR is trying to solve two issues:
I think this can be split into two PRs. It would be much easier to review them separately. Also, I see some optimizations around string_view and string, which seem quite complex. Did you do any benchmarking to see what are the benefits to use string_view over string in this case? |
|
@karticam Thanks for taking the time to go through it ! Splitting: Agreed, the two changes are independent. I'll keep this PR scoped to the
|
…ring Address review on ray-project#65583: - Move the Redis-backend wrapping (MaybeObserve, the gcs_redis_storage_metrics_enabled flag, and the shared_ptr delegate change) out of this PR into a separate one. - NormalizeTableNameLabel now returns a std::string and the latency callbacks capture it by value. The string_view-into-static-storage design saved at most one SSO copy per operation, which is not measurable next to the storage round trip, so drop it along with the lifetime/SSO comments and the tests that only existed to support it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: zzchun <zzchun8@gmail.com>
b2c1744 to
0600ecf
Compare
…ring Address review on ray-project#65583: - Move the Redis-backend wrapping (MaybeObserve, the gcs_redis_storage_metrics_enabled flag, and the shared_ptr delegate change) out of this PR into a separate one. - NormalizeTableNameLabel now returns a std::string and the latency callbacks capture it by value. The string_view-into-static-storage design saved at most one SSO copy per operation, which is not measurable next to the storage round trip, so drop it along with the lifetime/SSO comments and the tests that only existed to support it. Signed-off-by: zzchun <zzchun8@gmail.com>
0600ecf to
3549453
Compare
|
@karticam I've addressed your comments. Please take another look when you have time, thanks! Splitting: Done. This PR now only adds the
I also rebased onto master to resolve a conflict (the metrics doc moved to |
RedisStoreClient is the only StoreClient that GcsServer never wraps in ObservableStoreClient, so gcs_storage_operation_count and gcs_storage_operation_latency_ms are not exported at all when the GCS uses external Redis. Route both REDIS_PERSIST construction sites (cluster tables and internal KV) through a new gcs::MaybeObserve helper, gated by gcs_redis_storage_metrics_enabled (default true) so the new Record calls can be rolled back. ObservableStoreClient and StoreClientInternalKV now hold their delegate as a shared_ptr, because GcsServer keeps a concrete RedisStoreClient pointer for the periodic health check; health-check PINGs keep bypassing the wrapper. Split out of ray-project#65583 at review. Signed-off-by: zzchun <zzchun8@gmail.com>
|
These changes LGTM besides a few nits. Also wanted to make sure that the string_view v/s string optimizations were only for the table name strings right (and they are all pretty small right) |
…ncy metrics gcs_storage_operation_count and gcs_storage_operation_latency_ms were labeled by operation only, so a slow GetAll on the actor table and the same operation on the job table were indistinguishable. ObservableStoreClient now also records the table name it already receives. TableName is normalized through the rpc::TablePrefix enum: StoreClient accepts an arbitrary string, so this bounds the label domain by construction instead of by convention, and returns a view into static storage that the latency observer can capture without copying. Unknown names collapse to OTHER. AsyncGetNextJobID, previously uninstrumented, reports JobCounter. The tag is declared in the metric factories too -- tag_keys is the allowlist on both metric stacks, so recording without declaring is a no-op. RedisStoreClient was the only StoreClient never wrapped in ObservableStoreClient, so neither metric existed on the external-Redis backend. Both REDIS_PERSIST branches now go through gcs::MaybeObserve, gated by gcs_redis_storage_metrics_enabled (default true). ObservableStoreClient and StoreClientInternalKV hold their delegate as a shared_ptr for this, because AsyncCheckHealth is not on the StoreClient interface and GcsServer must keep a concrete pointer for the health check; call sites pass unique_ptr rvalues and convert implicitly. Series identity changes: one series per Operation becomes up to seven, and sum by (Operation) (...) reproduces the previous series. On Redis the change is purely additive. Both metrics were undocumented; system-metrics.rst now covers them. Signed-off-by: zzchun <zzchun8@gmail.com>
…ring Address review on ray-project#65583: - Move the Redis-backend wrapping (MaybeObserve, the gcs_redis_storage_metrics_enabled flag, and the shared_ptr delegate change) out of this PR into a separate one. - NormalizeTableNameLabel now returns a std::string and the latency callbacks capture it by value. The string_view-into-static-storage design saved at most one SSO copy per operation, which is not measurable next to the storage round trip, so drop it along with the lifetime/SSO comments and the tests that only existed to support it. Signed-off-by: zzchun <zzchun8@gmail.com>
Signed-off-by: zzchun <zzchun8@gmail.com>
3549453 to
176eb4e
Compare
|
@karticam Thanks for the review! Rebased onto the latest master and updated the copyright year to 2026 in all three new files (176eb4e). On |
|
Thanks for the PR. LGTM |
dstrodtman
left a comment
There was a problem hiding this comment.
Docs-side style and grammar pass from Douglas Strodtman (Ray docs team), drafted with Claude Code assistance. I've read and stand behind the comment.
Scope is prose and docs conventions on system-metrics.md only. The metric semantics, label domain, and backend behavior are for the GCS maintainers, and I didn't change any of them. The latency row reads cleanly as written. The docs side has no objection to merging.
Signed-off-by: Douglas Strodtman <douglas@anyscale.com>

Description
gcs_storage_operation_countandgcs_storage_operation_latency_msare labeled byOperationonly, so a slowGetAllon the actor table can't be told apart from one on the job table without correlating logs.ObservableStoreClientalready receives atable_nameon every call. This PR:TableNamelabel on both metrics, forPut,Get,GetAll,MultiGet,Delete,BatchDelete,GetKeysandExists;GetNextJobID, the one storage operation the wrapper used to skip;system-metrics.md.Exporting these metrics on the external-Redis backend, where they don't exist today, was split out of this PR at review into #66882.
Related issues
Closes #65582.
Additional information
Label cardinality
StoreClientaccepts any string as a table name (the shared test base passes"test_table"), so the label is bounded explicitly:NormalizeTableNameLabelkeeps names from therpc::TablePrefixenum and maps anything else toOTHER.In practice the GCS produces seven values:
JOB,ACTOR,ACTOR_TASK_SPEC,PLACEMENT_GROUP,NODE,WORKERSandKV. The hard upper bound is 161Operation×TableNamecombinations per GCS process (8 table operations × 20 values, plusGetNextJobID×JobCounter), independent of cluster size or actor count.No user-controlled string reaches a label: the internal KV puts the user's namespace in the key (
MakeKeyinstore_client_kv.cc), not in the table name.NoUserDataInLabelscovers this.GetNextJobIDIt addresses a counter key rather than a table, so it reports the
JobCountersentinel. It can't simply leaveTableNameunset:tag_keysis the tag allowlist on both metric stacks, and a declared tag that a call site doesn't record is exported as an empty string, so it would show up asTableName="".Latency clock
Latency used to be the difference of two
NowUnixNanos()readings, which is wall time, so an NTP step could record a negative or inflated value. Every recording site is rewritten here anyway, so they now useSteadyNow(), whichClockInterfacedocuments as the clock for durations.Compatibility / release note
This changes series identity: each
Operationnow has up to seven series instead of one, andGetNextJobIDis a newOperationvalue. Label matchers are unaffected. A dashboard or alert that assumed one series per operation can aggregate the new label away:Use
without (TableName)rather thanby (Operation), becausebyalso dropsSessionName,Componentand the other global labels. Aggregations sensitive to series count (avg,max,count) still change.Not a duplicate
Searching open PRs for
"TableName gcs_storage_operation","observable store client","gcs_storage_operation redis"and"MaybeObserve"finds no other PR labeling these metrics. #66847 edits the sameRecordcall sites to move recording onto a separate io_context. It changes where recording runs, not what is recorded, so the two compose and whichever lands second rebases.Testing
AI assistance (Claude Code) was used to prepare this change. I reviewed every changed line and ran the tests above locally.