Skip to content

Commit d443186

Browse files
Rescue Infra provisioning from hidden errors
2 parents db4e62a + 73cb189 commit d443186

11 files changed

Lines changed: 988 additions & 78 deletions

docker-compose.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ services:
136136
- 5432:5432
137137
volumes:
138138
- ./container-volume/cb-tumblebug-container/meta_db/postgres:/var/lib/postgresql/data
139+
command: postgres -c max_connections=500
139140
environment:
140141
- POSTGRES_USER=tumblebug
141142
- POSTGRES_PASSWORD=tumblebug

docs/feature_guide/high-scale-provisioning-architecture.md

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,38 @@ flowchart TD
180180

181181
## 🎯 Intelligent Status Management
182182

183-
This state diagram shows the lifecycle of a Node status check. The system **intelligently skips CSP API calls** for Nodes in stable states (Terminated, Failed, Suspended), significantly reducing unnecessary API traffic and improving overall system responsiveness by utilizing cached statuses.
183+
Status tracking is handled by the **Node Status Agent** (`NodeStatusAgent`), a background daemon that continuously polls CSP Node statuses using a priority-aware scheduler and per-CSP rate limiters — completely decoupled from the API request path.
184+
185+
```mermaid
186+
flowchart LR
187+
subgraph "Background: NodeStatusAgent"
188+
TICK[1s tick] --> DISPATCH[Dispatch eligible\nentries from StatusStore]
189+
DISPATCH --> RATE[Per-CSP rate limiter]
190+
RATE --> SPIDER[Spider /vmstatus]
191+
SPIDER --> STORE[StatusStore update]
192+
end
193+
194+
subgraph "API request path (no CSP call)"
195+
REQ[GET /infra status] --> STORE
196+
STORE --> RESP[Fresh cached response]
197+
end
198+
199+
style TICK fill:#e3f2fd
200+
style STORE fill:#fff3e0
201+
style RESP fill:#4caf50
202+
```
203+
204+
**Poll priorities** drive how often each Node is re-checked:
205+
206+
| Priority | Interval | Assigned when |
207+
|---|---|---|
208+
| `PollUrgent` | ~5 s | Creating, Terminating, Rebooting, … |
209+
| `PollHigh` | ~15 s | Running with pending TargetAction |
210+
| `PollNormal` | ~5 min | Stable Running / Undefined |
211+
| `PollRecover` | ~10 min | Suspended |
212+
| `PollSkip` | never | Terminated, Failed (final states) |
213+
214+
The system also **intelligently skips CSP API calls** for Nodes in final states:
184215

185216
```mermaid
186217
stateDiagram-v2
@@ -209,6 +240,8 @@ stateDiagram-v2
209240
note right of ParallelProcess : CSP-aware rate<br/>limiting prevents<br/>API throttling
210241
```
211242

243+
> For the full NodeStatusAgent design (StatusStore, operation lock, startup scan, orphan rescue integration), see [Infra Resource Model and Lifecycle Management](./infra-resource-model-and-lifecycle-management.md#node-status-agent).
244+
212245
## 🔄 Advanced Caching & Memory Optimization
213246

214247
We utilize a **multi-layered caching strategy** for connection configurations and Node statuses. Combined with Go's **channel-based concurrency** and minimal mutex usage, this approach minimizes memory footprint and eliminates redundant network operations, ensuring high performance.
@@ -490,7 +523,7 @@ We have validated the architecture with large-scale provisioning tests. The foll
490523

491524
### Performance Improvements
492525
- **3-Level Rate Limiting**: Prevent API throttling with hierarchical control (CSP → Region → Node).
493-
- **Smart Status Caching**: Eliminate unnecessary CSP calls for stable Nodes (30-50% call reduction).
526+
- **Node Status Agent**: Background daemon decouples CSP polling from API requests; priority-aware scheduler (PollUrgent → PollSkip) cuts unnecessary calls by 30–50%.
494527
- **Parallel Processing**: Optimal performance with unlimited parallelization per CSP and limited parallelization per Region/Node.
495528

496529
### Reliability Enhancements

docs/feature_guide/infra-resource-model-and-lifecycle-management.md

Lines changed: 114 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -471,28 +471,128 @@ Each Infra and Node maintains two tracking fields:
471471

472472
When `TargetStatus == CurrentStatus`, the action is considered complete, and both fields are set to `None` (ActionComplete/StatusComplete).
473473

474-
### Smart Status Caching
474+
### Node Status Agent
475475

476-
To optimize performance, the system skips CSP API calls for Nodes in stable final states:
476+
The **Node Status Agent** (`NodeStatusAgent`) is a background daemon that continuously maintains up-to-date Node status information in memory, decoupling CSP polling from API request handling.
477+
478+
#### Why a Background Daemon?
479+
480+
Without a daemon, every `GET /infra/{id}` call would fan out to all CSP APIs in real time. For a 200-node multi-cloud Infra, this means 200 concurrent CSP calls per status request — easily triggering rate limits. Under the pull-on-demand model, a live test with 1,061 CSP status calls in 4 minutes was observed before the daemon was introduced.
481+
482+
#### Architecture Overview
483+
484+
```mermaid
485+
flowchart TD
486+
subgraph "NodeStatusAgent (background daemon)"
487+
TICK[Tick every 1 second] --> DISPATCH[dispatchEligible\nscans StatusStore]
488+
DISPATCH --> ELIGIBLE{NextPollAt\nreached?}
489+
ELIGIBLE -->|Yes| QUEUE[workerCh]
490+
ELIGIBLE -->|No| SKIP_NODE[skip]
491+
492+
QUEUE --> WORKER[worker goroutine pool]
493+
WORKER --> RATE[Per-CSP rate limiter]
494+
RATE --> FETCH[FetchNodeStatus\nvía Spider vmstatus]
495+
FETCH --> STORE[StatusStore update\n+ KV write-through]
496+
end
497+
498+
subgraph "API request path (fast)"
499+
API[GET /infra status] --> CACHE_READ[Read from StatusStore\nor KV]
500+
end
501+
502+
STORE -->|fresh data| CACHE_READ
503+
504+
style TICK fill:#e3f2fd
505+
style STORE fill:#fff3e0
506+
style CACHE_READ fill:#4caf50
507+
```
508+
509+
#### In-Memory StatusStore
510+
511+
The `StatusStore` is a singleton, thread-safe map keyed by `"nsId/infraId/nodeId"`. Each entry holds the most recent CSP-polled status, native status, public IP, and scheduling metadata (next poll time, priority). Status writes go through to the KV store atomically so data survives daemon restarts.
512+
513+
#### Poll Priorities
514+
515+
Each Node entry in the StatusStore carries a **poll priority** that controls how frequently the daemon re-checks the CSP:
516+
517+
| Priority | Interval | When assigned |
518+
|---|---|---|
519+
| `PollUrgent` | ~5 s | Transitional states: Creating, Terminating, Resuming, Rebooting, Suspending |
520+
| `PollHigh` | ~15 s | Running Nodes with a pending TargetAction |
521+
| `PollNormal` | ~5 min | Stable Running or Undefined nodes (no pending action) |
522+
| `PollRecover` | ~10 min | Suspended nodes (stable, but can be resumed) |
523+
| `PollSkip` | never | Terminated, Failed nodes (final state — no further CSP calls) |
524+
525+
> **Newly created Nodes start at `PollUrgent`** so their transition from Creating → Running is tracked at second-level granularity.
526+
527+
#### Operation Lock
528+
529+
During active lifecycle operations (Create, Terminate, Reboot, etc.), the daemon must not overwrite the status set by the operation itself. An **operation lock** prevents this:
530+
531+
```mermaid
532+
sequenceDiagram
533+
participant OPS as Lifecycle goroutine
534+
participant AGENT as NodeStatusAgent
535+
participant STORE as StatusStore
536+
537+
OPS->>STORE: AcquireLock(nodeId, "Creating")
538+
Note over STORE: OperationLockedAt = now<br/>TTL = 25 min
539+
540+
loop every dispatch tick
541+
AGENT->>STORE: check lock
542+
STORE-->>AGENT: locked → skip poll
543+
end
544+
545+
OPS->>STORE: SetStatus("Running")
546+
OPS->>STORE: ReleaseLock(nodeId)
547+
548+
AGENT->>STORE: check lock
549+
STORE-->>AGENT: unlocked → poll normally
550+
```
551+
552+
If the lock TTL expires while `TargetAction` is still set (e.g. the server crashed mid-operation), the daemon logs a warning, clears the lock, and promotes the Node to `PollUrgent` so the discrepancy is detected quickly. Running `action=reconcile` then corrects the state.
553+
554+
#### Startup Scan
555+
556+
On daemon start, `StartupScan` reads all Node records from the KV store and populates the StatusStore:
557+
558+
- Nodes in **transitional states** (`Creating`, `Terminating`, …) with a **pending `TargetAction`** are promoted to `PollUrgent` and a warning is logged — these likely indicate a server restart mid-operation.
559+
- Nodes in **stable states** (`Running`, `Suspended`, …) are spread across the first polling interval to avoid a thundering-herd burst on startup.
560+
- Nodes in **final states** (`Terminated`, `Failed`) are set to `PollSkip` — they require no further CSP calls.
561+
562+
#### Smart Status Skipping
563+
564+
Before making a CSP API call, `FetchNodeStatus` checks several conditions that allow it to skip the round-trip entirely:
477565

478566
```mermaid
479567
flowchart LR
480-
CHECK{Node Status?}
481-
482-
CHECK -->|Terminated| SKIP[Skip CSP Call]
483-
CHECK -->|Failed| SKIP
484-
CHECK -->|Suspended| SKIP
485-
CHECK -->|Running/Creating| FETCH[Fetch from CSP]
486-
487-
SKIP --> CACHE[Return Cached Status]
488-
FETCH --> UPDATE[Update Cache]
489-
UPDATE --> RETURN[Return Fresh Status]
490-
568+
CHECK{Skip CSP call?}
569+
570+
CHECK -->|Terminated\nwith no active action| SKIP[Return cached status]
571+
CHECK -->|Failed\nwith no active action| SKIP
572+
CHECK -->|Suspended\nwith no active action| SKIP
573+
CHECK -->|CspResourceName empty\nand TargetAction ≠ Create| SKIP
574+
CHECK -->|Otherwise| FETCH[Call Spider /vmstatus]
575+
576+
SKIP --> CACHE[Return stored status]
577+
FETCH --> UPDATE[Update StatusStore + KV]
578+
491579
style SKIP fill:#4caf50
492580
style FETCH fill:#2196f3
493581
```
494582

495-
This optimization reduces API calls by 30-50% for large Infras with many terminated or suspended Nodes.
583+
This skipping reduces CSP API calls by **30–50%** for large Infras with many terminated or suspended Nodes.
584+
585+
#### Relationship to Crash Recovery and Orphan Rescue
586+
587+
The NodeStatusAgent tracks status continuously but does **not** attempt to reconcile discrepancies on its own. When a Node ends up `Undefined` (e.g. Spider returned 500 during creation without providing VM identity), the agent simply caches and reports that status. Actual recovery is triggered explicitly:
588+
589+
| Mechanism | Who runs it | Purpose |
590+
|---|---|---|
591+
| `action=reconcile` | Operator | Re-queries Spider for all transient/Undefined Nodes; queries `/allvm` to find orphan VMs unrecorded in TB; absorbs matched orphans |
592+
| `action=refine` | Operator | Removes Failed/Undefined Node metadata from TB (no CSP call) |
593+
| `action=abort` | Operator | Force-terminates all non-final Nodes, runs orphan rescue, then sweeps with refine |
594+
595+
See [Crash Recovery](#crash-recovery-reconcile-and-abort) for the full flow.
496596

497597
### Rate Limiting for Control Operations
498598

0 commit comments

Comments
 (0)