feat(self-host): auto-clean metadata-registry on detach (gh-8749) - #10351
Conversation
WalkthroughThis PR updates metadata artifact handling to use endpoint-scoped identity plus owner-scoped registration and cleanup. ChangesOwner-scoped metadata artifact registration
🎯 3 (Moderate) | ⏱️ ~25 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Comment |
Address graham-code-review feedback on PR #10351: - Drop the secondary `owners` map; store `Owner = (instance_id, lora_slug)` inline with each entry value. One lock, one source of truth, no nested-write-lock hazard, no two-map sync risk. - `register` takes `&Owner` (one clone inside, not per-file). - Panic on collision: re-registering the same (slug, suffix, filename) with a different owner is a programming error (two attaches of the same model+suffix in one process would let detach-#1 wipe files detach-#2 still needs). Same-owner re-register is fine and just updates the path. - Doc + local var naming aligned on `instance_id` to match `local_model.rs`'s existing usage (the value populates `DiscoveryInstance::Model.instance_id`). - Tests: collision panic + same-owner update path coverage. Signed-off-by: nnshah1 <neelays@nvidia.com>
`MetadataArtifactRegistry` accumulated entries on every register but had no caller-reachable cleanup path: `clear_self_hosted_artifacts` needed a `LocalModel` handle, and the static `detach_from_endpoint` helper that the Python `unregister_model` binding calls doesn't have one. Long-running workers leaked entries on every LoRA detach or model reload — the documented blocker on flipping `DYN_SELF_HOST_METADATA` default-on. Fix: tag each registration with `Owner = (connection_id, lora_slug)`, the pair `detach_from_endpoint` already has. The registry keeps a secondary `owners -> (slug, suffix)` map; `unregister_for_owner` reads it and calls the existing `unregister` so there is no duplicated cleanup logic. HTTP `get` is unchanged — no new locks on the read path. The previous `attached_self_host_suffix` field and the never-called `clear_self_hosted_artifacts` method are deleted. Tests: existing 2 metadata_registry tests updated to the new `register` signature; new `unregister_for_owner_clears_only_that_owner` asserts LoRA detach leaves the base intact and is idempotent. Signed-off-by: nnshah1 <neelays@nvidia.com>
Address graham-code-review feedback on PR #10351: - Drop the secondary `owners` map; store `Owner = (instance_id, lora_slug)` inline with each entry value. One lock, one source of truth, no nested-write-lock hazard, no two-map sync risk. - `register` takes `&Owner` (one clone inside, not per-file). - Panic on collision: re-registering the same (slug, suffix, filename) with a different owner is a programming error (two attaches of the same model+suffix in one process would let detach-#1 wipe files detach-#2 still needs). Same-owner re-register is fine and just updates the path. - Doc + local var naming aligned on `instance_id` to match `local_model.rs`'s existing usage (the value populates `DiscoveryInstance::Model.instance_id`). - Tests: collision panic + same-owner update path coverage. Signed-off-by: nnshah1 <neelays@nvidia.com>
…ucceeds If discovery.unregister failed transiently, the model card stayed in discovery while the local metadata was already gone — frontend would fetch /v1/metadata URLs that 404'd. Defer the registry cleanup until after discovery.unregister returns Ok. Capture the owner key before the move into DiscoveryInstance. Signed-off-by: nnshah1 <neelays@nvidia.com>
7f5874b to
64c25a0
Compare
This comment has been minimized.
This comment has been minimized.
|
@coderabbitai full review @devin-ai-integration please do a full code review |
|
✅ Action performedFull review finished. |
|
@coderabbitai review (Earlier full-review request acked at 23:14 but no findings posted; re-issuing as a plain review.) |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "=== metadata_registry.rs ==="
cat -n lib/runtime/src/metadata_registry.rs
echo ""
echo "=== local_model.rs ==="
cat -n lib/llm/src/local_model.rsLength of output: 44271 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "=== Callers of MetadataArtifactRegistry methods ==="
rg -n "metadata_artifacts\(\)\." lib/ --type rust -A 1
echo ""
echo "=== Callers of registry.unregister (non-owner) ==="
rg -n '\.unregister\b' lib/ --type rust -C 2
echo ""
echo "=== clear_self_hosted_artifacts references (should be zero) ==="
rg -n "clear_self_hosted_artifacts" lib/ --type rust
echo ""
echo "=== attached_self_host_suffix references (should be zero) ==="
rg -n "attached_self_host_suffix" lib/ --type rust
echo ""
echo "=== Any other callers of move_to_self_host ==="
rg -n "move_to_self_host" lib/ --type rust -C 3
echo ""
echo "=== _instance usage in attach (check drop semantics) ==="
rg -n "_instance" lib/llm/src/local_model.rsLength of output: 6746
|
| Severity | Finding | File |
|---|---|---|
| 🔴 | MetadataArtifactRegistry::unregister is dead production code — remove or annotate |
metadata_registry.rs |
register panics on collision — consider Result<()> for graceful handling |
metadata_registry.rs |
|
get allocates 3 Strings per call |
metadata_registry.rs |
|
extra_files.extend accumulates on repeated attach (pre-existing) |
local_model.rs |
|
| ✅ | Ownership model design, detach ordering, test coverage | both |
…abbit feedback) - Remove MetadataArtifactRegistry::unregister (slug+suffix variant) and its test. Post-PR, the only production caller is unregister_for_owner which filters by Owner stored in the value — the slug+suffix method had no remaining callers and risked drifting out of sync with ownership semantics. - Switch Key from (String, String, String) tuple to a single \0-separated String. One allocation per lookup instead of three. The HTTP route handler (system_status_server) is the only get() caller and already has owned String parts from the URL parse; this just removes the duplicate work. - Trim the collision-panic message per review style (no AI-scaffolded prose, just the colliding owners). Signed-off-by: nnshah1 <neelays@nvidia.com>
|
@coderabbitai re:
If a real topology surfaces this collision (disagg co-located on one DRT, hot reload), we'd convert to a |
|
Tip For best results, initiate chat on the files or code changes.
Withdrawing the ✏️ Learnings added
|
…_self_host signature) - metadata_registry: revert key to (String, String, String) tuple per GuanLuo - local_model: move_to_self_host takes &DistributedRuntime directly Signed-off-by: nnshah1 <neelays@nvidia.com>
Per graham-code-review pass on 534bdcb: the 6-tuple key is now constructed by hand in two places (`register` and `get`), 7 lines each. A `make_key` helper compresses both call sites to one line, makes the key shape a single source of truth, and removes the visual ceremony that pushed the user to suggest it. Tradeoff revisited from the earlier 3-tuple revert: at 3 fields the helper was indirection; at 6 fields the per-call construction is genuinely noise. `#[allow(clippy::too_many_arguments)]` retained on the helper itself since the field set is the contract. Verified: cargo clippy clean, 5 metadata_registry tests pass, fmt OK. Signed-off-by: nnshah1 <neelays@nvidia.com>
Empty commit to nudge the DCO app to re-check; the previous check appears stuck in 'not started' on `55b3fef371` despite all required signoffs being present. Signed-off-by: nnshah1 <neelays@nvidia.com>
Capstone of the gh-8749 stack: workers now advertise their MDC files over the system_status_server by default instead of requiring shared storage. Behavior: - Unset env -> ON (new default) - 0/false/no/off (case-insensitive) -> OFF (opt out) - Otherwise -> ON Hard-fail (anyhow::bail!) when self_host_metadata is on but DYN_SYSTEM_PORT is unset, so misconfigurations surface at first registration instead of producing WARN spam plus silently degraded hf:// MDC advertisement. The k8s operator already sets DYN_SYSTEM_PORT=9090 (deploy/operator/internal/dynamo/component_worker.go) so operator-managed deployments are unaffected. Bare / non-operator upgrades either set DYN_SYSTEM_PORT or set DYN_SELF_HOST_METADATA=0. All prerequisites in main: #8855 worker HTTP hosting on system_status_server #9057 frontend MDC verify-and-cache pipeline #9610 harvest non-weight siblings into slug_dir #9707 worker advertises non-typed metadata siblings via extra_files #10037 native preprocessor consumes resolved local_dir #10599 register_model uses engine's runai-pulled local dir on object-storage URIs #10351 auto-clean MetadataArtifactRegistry on detach Signed-off-by: nnshah1 <neelays@nvidia.com>
Capstone of the gh-8749 stack: workers advertise their MDC files over the system_status_server by default instead of requiring shared storage. Behavior: - Unset env -> ON (new default) - 0/false/no/off (case-insensitive, trimmed) -> OFF (opt out) - Otherwise -> ON Hard-fail (anyhow::bail!) when self_host_metadata is on but DYN_SYSTEM_PORT is unset — surfaces misconfigurations at first registration instead of WARN spam plus silently degraded hf:// MDC advertisement. The k8s operator sets DYN_SYSTEM_PORT=9090 (see deploy/operator/internal/dynamo/component_worker.go), so operator- managed deployments are unaffected. Bare / non-operator upgrades either set DYN_SYSTEM_PORT or set DYN_SELF_HOST_METADATA=0. All prerequisites are in main: #8855 worker HTTP hosting on system_status_server #9057 frontend MDC verify-and-cache pipeline #9610 harvest non-weight siblings into slug_dir #9707 worker advertises non-typed metadata siblings via extra_files #10037 native preprocessor consumes resolved local_dir #10599 register_model uses engine's runai-pulled local dir on object-storage URIs #10351 auto-clean MetadataArtifactRegistry on detach Signed-off-by: nnshah1 <neelays@nvidia.com>
Capstone of the gh-8749 stack: workers advertise their MDC files over the system_status_server by default instead of requiring shared storage. Behavior: - Unset env -> ON (new default) - 0/false/no/off (case-insensitive, trimmed) -> OFF (opt out) - Otherwise -> ON Hard-fail (anyhow::bail!) when self_host_metadata is on but DYN_SYSTEM_PORT is unset — surfaces misconfigurations at first registration instead of WARN spam plus silently degraded hf:// MDC advertisement. The k8s operator sets DYN_SYSTEM_PORT=9090 (see deploy/operator/internal/dynamo/component_worker.go), so operator- managed deployments are unaffected. Bare / non-operator upgrades either set DYN_SYSTEM_PORT or set DYN_SELF_HOST_METADATA=0. All prerequisites are in main: #8855 worker HTTP hosting on system_status_server #9057 frontend MDC verify-and-cache pipeline #9610 harvest non-weight siblings into slug_dir #9707 worker advertises non-typed metadata siblings via extra_files #10037 native preprocessor consumes resolved local_dir #10599 register_model uses engine's runai-pulled local dir on object-storage URIs #10351 auto-clean MetadataArtifactRegistry on detach Signed-off-by: nnshah1 <neelays@nvidia.com>
Capstone of the gh-8749 stack: workers advertise their MDC files over the system_status_server by default instead of requiring shared storage. Behavior: - Unset env -> ON (new default) - 0/false/no/off (case-insensitive, trimmed) -> OFF (opt out) - Otherwise -> ON Hard-fail (anyhow::bail!) when self_host_metadata is on but DYN_SYSTEM_PORT is unset — surfaces misconfigurations at first registration instead of WARN spam plus silently degraded hf:// MDC advertisement. The k8s operator sets DYN_SYSTEM_PORT=9090 (see deploy/operator/internal/dynamo/component_worker.go), so operator- managed deployments are unaffected. Bare / non-operator upgrades either set DYN_SYSTEM_PORT or set DYN_SELF_HOST_METADATA=0. All prerequisites are in main: #8855 worker HTTP hosting on system_status_server #9057 frontend MDC verify-and-cache pipeline #9610 harvest non-weight siblings into slug_dir #9707 worker advertises non-typed metadata siblings via extra_files #10037 native preprocessor consumes resolved local_dir #10599 register_model uses engine's runai-pulled local dir on object-storage URIs #10351 auto-clean MetadataArtifactRegistry on detach Signed-off-by: nnshah1 <neelays@nvidia.com>
Capstone of the gh-8749 stack: workers advertise their MDC files over the system_status_server by default instead of requiring shared storage. Behavior: - Unset env -> ON (new default) - 0/false/no/off (case-insensitive, trimmed) -> OFF (opt out) - Otherwise -> ON Hard-fail (anyhow::bail!) when self_host_metadata is on but DYN_SYSTEM_PORT is unset — surfaces misconfigurations at first registration instead of WARN spam plus silently degraded hf:// MDC advertisement. The k8s operator sets DYN_SYSTEM_PORT=9090 (see deploy/operator/internal/dynamo/component_worker.go), so operator- managed deployments are unaffected. Bare / non-operator upgrades either set DYN_SYSTEM_PORT or set DYN_SELF_HOST_METADATA=0. All prerequisites are in main: #8855 worker HTTP hosting on system_status_server #9057 frontend MDC verify-and-cache pipeline #9610 harvest non-weight siblings into slug_dir #9707 worker advertises non-typed metadata siblings via extra_files #10037 native preprocessor consumes resolved local_dir #10599 register_model uses engine's runai-pulled local dir on object-storage URIs #10351 auto-clean MetadataArtifactRegistry on detach Signed-off-by: nnshah1 <neelays@nvidia.com>
Overview
MetadataArtifactRegistrywas a write-only sink: everymove_to_self_hostadded entries viaregister, but nothing ever removed them on detach. The only cleanup method on the worker side (LocalModel::clear_self_hosted_artifacts) required aLocalModelhandle that the staticLocalModel::detach_from_endpointhelper — and through it, the Pythonunregister_modelbinding — doesn't have. Long-running workers leaked one entry per metadata file on every LoRA detach or model reload. This is the documented blocker on flippingDYN_SELF_HOST_METADATAdefault-on in the gh-8749 capstone.Fix
Tag each registration with
Owner = (connection_id, lora_slug)— the pairdetach_from_endpointalready has on hand without an API change to the Python side. The registry keeps a secondaryowners -> (slug, suffix)map populated byregister. Newunregister_for_owner(&owner)looks up that map and calls the existingunregisterso there is no duplicated cleanup logic. The HTTPgetread path is unchanged — no new locks on the hot path.Details
lib/runtime/src/metadata_registry.rs: addedpub type Owner = (u64, Option<String>), newownersmap field, newunregister_for_ownermethod,registersignature gains a leadingOwnerparameter (single caller in the workspace — no external breakage). Existingregister/unregister/getsemantics preserved.lib/llm/src/local_model.rs:move_to_self_hostnow takes&Endpointinstead of&DistributedRuntime(one extra line to derivedrt) and passes(drt.connection_id(), model_suffix.map(str::to_string))as the owner.detach_from_endpointcallsunregister_for_ownerbefore the discovery unregister — no-op when self-host was disabled or skipped. Deleted the never-calledclear_self_hosted_artifactsmethod and the supportingattached_self_host_suffix: Option<String>field that only tracked one suffix perLocalModelanyway.Test
metadata_registrytests updated to the newregistersignature (semantics unchanged).unregister_for_owner_clears_only_that_owner: registers one base + one LoRA from the same connection_id, detaches the LoRA viaunregister_for_owner, asserts the LoRA entries are gone and base entries remain. Second call is a no-op (idempotent).dynamo-llm+dynamo-runtimelib test suites, clippy with-D warnings, andcargo fmt --checkall green.Out of scope
unregister_modelAPI is unchanged — callers incomponents/src/dynamo/vllm/handlers.pydo not need to pass a model name.DYN_SELF_HOST_METADATA— still gated on this PR plus a multimodal positive e2e; tracked in the DEP (light): Worker self-hosted metadata files #8749 capstone.Related
slug_dir(PR4a)Summary by CodeRabbit
Summary
Refactor
Enhancements
Tests
DYN-2477