Repository navigation
Conversation
Add the two trace-claim pieces of live retune: - retune/live/window_cut.py cuts one retune window from the frontends' gzip request-trace segments on the shared RWX claim into the window file a DGDR Run replays, with the selection, censoring and de-duplication of retune.window.read_trace_window, bounded memory (window_too_large), corrupt-member handling (trace_corrupt) and an atomic output, in a spawn child killed at the deadline or on cancel. - retune/live/reconciler.py garbage-collects what live retune leaves on the claim and in the namespace (old frontend segments, window and temp files, and DGDR Requests beyond retune.dgdr.keep_requests), and is the SweepRegistry a sweep uses to protect its window and Requests. Both are libraries that only the live retune supervisor calls; nothing in this layer wires them into a Planner, which still rejects retune at startup. Third layer of the live retune stack split from #17562. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: hongkuanz <hongkuanz@nvidia.com>
Stacks this PR on #17736. The library layers of live retune now come from #17733 (CRN replay), #17734 (configuration and premises), #17735 (window cut and reconciler) and #17736 (DGDR executor), so this PR's diff against #17736 is the supervisor, state, actuator, metrics, the Planner wiring, Helm RBAC, docs and the end-to-end tests. #17734 also adds a replay check that rejects executor 'dgdr'. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: hongkuanz <hongkuanz@nvidia.com>
✅ Dynamo PR CI passed — run 41146051842 (attempt 1) on
|
jthomson04
left a comment
There was a problem hiding this comment.
Reviewed at 73c0e58. Two should-fix items inline (re-cut ordering and the scope of pass 1), plus nits. Nothing here blocks the event loop, and the atomic publish, partial-member handling and retention all check out.
|
|
||
| if selection.num_rows == 0: | ||
| return result(None) | ||
| if os.path.lexists(out_path): |
There was a problem hiding this comment.
Check out_path before pass 1. Today re-cutting an already published window scans every segment first, so a later leader adopting the window can fail it as trace_corrupt/window_too_large, or return counts that #17736 (dgdr.py:1399) writes into new Requests while they replay the unchanged file. Take the counts from the existing file or the adopted Requests' rows annotation.
| # ---------------------------------------------------------------- pass 1 | ||
|
|
||
|
|
||
| def _segment_files(root: str) -> list[tuple[str, bool]]: |
There was a problem hiding this comment.
Pass 1 decodes every retained segment, about 2T + 11 min with the defaults, so roughly half the parse is outside the window and counts against cut_budget_seconds. Corruption or a malformed row there can also fail windows it holds no rows for. Skip segments whose mtime is before start_ms minus a skew margin.
| elif view.dgd_uid != dgd_uid: | ||
| # The DGD was deleted and recreated under the same name. | ||
| reasons.setdefault(view.name, "other_dgd_uid") | ||
| states = {view.name: self._is_terminal(state, view) for view in ours} |
There was a problem hiding this comment.
should_continue is only asked before a delete; this comprehension still does a get_run (10 s timeout) per uncached Request after the budget has expired or leadership was lost, and the supervisor won't start the next round until it returns. Check it before these reads too.
|
|
||
|
|
||
| @dataclass(frozen=True) | ||
| class _PlainBlock: |
There was a problem hiding this comment.
Production never feeds this a plain JSONL file: the guide requires jsonl_gz, and the reconciler only collects .NNNNNN.jsonl.gz segments, so a plain .jsonl sink would be re-read in full on every cut and never collected. Drop _PlainBlock/_plain_blocks, or make the reconciler handle it.
| self._sink_reported = current | ||
| if missing: | ||
| logger.warning( | ||
| "retune reconciler: Ready frontends without a trace directory " |
There was a problem hiding this comment.
This warns every 15 s round while the condition holds, and the gauge already tracks it. Log on state change. Same for the missing-label warning at :682.
| pass | ||
| # The writer thread takes the GIL back after every zlib block; at the | ||
| # default 5 ms interval it would wait behind parsing (pass 2 ~30% slower). | ||
| sys.setswitchinterval(_CHILD_SWITCH_INTERVAL_S) |
There was a problem hiding this comment.
Is the ~30% pass-2 gain worth a writer thread, setswitchinterval and os.nice in the child? A synchronous GzipFile.write drops about 70 lines and a thread error path.
Note
Stacked PR, live retune (split from #17562). Merge order: #17483 → #17733 → #17734 → #17735 (this PR) → #17736 → #17562
This PR targets
hzhou/live-retune-2-config, the branch of #17734 (the PR below it), so its diff shows only this layer. #17562 was split into these layers for review. Together they carry #17562's original diff exactly, plus a 40-line replay check in #17734 that rejectsexecutor: dgdrin offline replay.Overview
This is the third layer of live retune (#17562). It adds the two pieces that work on the shared ReadWriteMany trace claim:
Both are libraries that only the live retune supervisor in #17562 calls. Nothing in this layer wires them into a Planner, and a live Planner still rejects
retuneat startup.Key Changes
About 2.3k lines of implementation and 2.3k lines of tests, all in new files.
planner/retune/live/window_cut.py: merges each frontend pod'srequest_endjsonl.gz segments into<windows_dir>/<dgd>/<window_end_ms>.jsonl.gz. Row selection, censoring, de-duplication androw_countsfollowretune.window.read_trace_window, and the rows are the same bytes.cut_budget_seconds, which is killed at the deadline or on cancel.trace_corrupt). An in-flight flush at the end of a writer's newest segment is counted but not held against either ratio.max_window_rowsbounds memory (window_too_large).planner/retune/live/reconciler.py: garbage-collects finished Requests and window files once no live pod still reads them, based on pod phase.keep_requestswindows. Non-terminal Requests past their window deadline, queued deletions and Requests of a recreated DGD are deleted.SweepRegistrythe DGDR sweep uses. In-flight windows are protected by ref-counted entries, so two sweeps of one window cannot drop each other's protection.unit/test_live_retune_window_cut.py(corrupt and truncated gzip members, zero-filled holes, ratio gates, golden output againstread_trace_window, the child process and its interruption) andunit/test_live_retune_reconciler.py(segment, window, temp file and Request rules, the deletion queue,should_continue, DGD recreation, and reference counting during GC).The only imports are
contracts,planner_config,retune.windowandoffline.trace_data, so no module of this layer imports a later layer.Validation
Ran on this layer (
aacfacb02b) and compared with the same suites at the stack base6e8087d359(#17483's head):test_live_retune_window_cut.pyandtest_live_retune_reconciler.pycomponents/src/dynamo/planner/tests,components/src/dynamo/replay/tests, no xdist)pre-commit run --fileson the 4 changed filesdynamo-pipeline.ymlcommand,MYPYPATH=components/src:lib/bindings/python/src mypy --explicit-package-bases) ondynamo/planneranddynamo/replayyaml/pandas/nvtxstubs and existingarg-typeerrors); none is new, and none is inretune/liveThe 59 failures come from this host. Its prebuilt bindings lack
conversation_affinity(#16854) and theais-forward-passfeature, and it has noetcdbinary. CI builds the bindings from source.This layer adds library code that nothing reachable calls until #17562. It needs no guard of its own. A live Planner still fails in
NativePlannerBase.__init__, and replay still rejectsexecutor: dgdrthrough #17734's replay check.Issues and Ongoing Work
Pull Request Checklist
/pr-checklistskill.🤖 Generated with Claude Code