Fix split-topology result retrieval#2278
Conversation
Greptile SummaryThis PR replaces the racy load-balanced ClusterIP result-fetch with a shared-filesystem result store on the gateway, fixing silent result loss in multi-worker split-topology deployments.
|
| Filename | Overview |
|---|---|
| nemo_retriever/src/nemo_retriever/service/services/worker_result_store.py | New module implementing shared-filesystem result storage with immutable generations; clean error handling via ResultStoreTemporarilyUnavailable; TTL sweep logic is correct. |
| nemo_retriever/src/nemo_retriever/service/routers/ingest.py | Gateway callback handler wired to pull-and-store before mark_completed; status routes updated to read from shared store; blocking reads correctly wrapped with asyncio.to_thread; one httpx client created per pull request (no pooling). |
| nemo_retriever/src/nemo_retriever/service/services/job_tracker.py | Eviction now runs before job existence checks; consume_result_data replaced by idempotent get_result_data; changes look correct. |
| nemo_retriever/helm/templates/deployment.yaml | Gateway gets RESULTS_DIR + PVC mount; workers get only TTL_SECONDS; conditional logic is correctly scoped by role. |
| nemo_retriever/helm/values.yaml | ttlSeconds added; comment block updated with RWX/RWO guidance; pre-existing 'use local/host-path storage' comment is now potentially misleading for multi-node operators. |
| nemo_retriever/tests/test_service_worker_callback.py | New tests cover callback omits result_data, idempotent reads, filesystem concurrency, TTL sweep, and validate_result_store; good coverage of the happy and concurrent-write scenarios. |
Sequence Diagram
%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Client
participant Gateway
participant Worker
participant SharedFS as Shared PVC (gateway only)
participant Tracker as Job Tracker (gateway memory)
Client->>Gateway: "POST /v1/ingest/job/{id}/document"
Gateway->>Tracker: register_document + mark_processing
Gateway->>Worker: forward upload (X-Gateway-Callback-Url: pod-IP)
Note over Worker: Pipeline processes document
Worker->>Worker: store_result_data(id, rows) in-memory
Worker->>Gateway: "POST /v1/internal/job-callback {result_worker_ip}"
Gateway->>Gateway: get_result_data(id) returns None
Gateway->>Worker: "GET /v1/internal/document-result/{id} direct pod IP"
Worker-->>Gateway: "{result_data: [...]}"
Gateway->>SharedFS: store_result_data(id, rows) as UUID.json
Gateway->>Tracker: "mark_completed(id, result_rows=N)"
Gateway-->>Worker: 200 OK
Client->>Gateway: "GET /v1/ingest/status/{id}"
Gateway->>Tracker: get_result_data(id) returns None
Gateway->>SharedFS: _get_from_filesystem(id) returns rows
Gateway-->>Client: "200 {result_data: [...]}"
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant Client
participant Gateway
participant Worker
participant SharedFS as Shared PVC (gateway only)
participant Tracker as Job Tracker (gateway memory)
Client->>Gateway: "POST /v1/ingest/job/{id}/document"
Gateway->>Tracker: register_document + mark_processing
Gateway->>Worker: forward upload (X-Gateway-Callback-Url: pod-IP)
Note over Worker: Pipeline processes document
Worker->>Worker: store_result_data(id, rows) in-memory
Worker->>Gateway: "POST /v1/internal/job-callback {result_worker_ip}"
Gateway->>Gateway: get_result_data(id) returns None
Gateway->>Worker: "GET /v1/internal/document-result/{id} direct pod IP"
Worker-->>Gateway: "{result_data: [...]}"
Gateway->>SharedFS: store_result_data(id, rows) as UUID.json
Gateway->>Tracker: "mark_completed(id, result_rows=N)"
Gateway-->>Worker: 200 OK
Client->>Gateway: "GET /v1/ingest/status/{id}"
Gateway->>Tracker: get_result_data(id) returns None
Gateway->>SharedFS: _get_from_filesystem(id) returns rows
Gateway-->>Client: "200 {result_data: [...]}"
Reviews (12): Last reviewed commit: "Offload shared result reads" | Re-trigger Greptile
Description
Fix silent retained-result loss in split-topology deployments with multiple worker replicas (NVBug 6348464).
Previously, completed rows remained in worker-local memory while the gateway retrieved them through a load-balanced worker Service. If that request reached a replica that did not process the document, it returned 404 and clients could receive incomplete result data even though processing completed successfully.
What changed
Deployment impact
In split mode, only the gateway mounts the
retriever-resultsPVC. Realtime and batch workers keep pending handoffs in local memory, so they remain independently schedulable across nodes.The default
ReadWriteOnceaccess mode supports the current single-gateway topology, including multi-node worker deployments. Multiple gateway replicas require shared job-state and result-store coordination beyond the current topology contract. Deployments without a configured filesystem result store retain the in-memory fallback.Validation
git diff --checkpassed.Checklist