Skip to content

Commit c1fc63c

Browse files
authored
Merge pull request #73 from cbullinger/docsp-46228-auto-scaling
DOCSP-46228: Add Scaling example script to Go SDK project
2 parents a037eeb + b47e905 commit c1fc63c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+3212
-384
lines changed

generated-usage-examples/copier-test/level-2/level-3/test-3.json

Lines changed: 0 additions & 5 deletions
This file was deleted.

generated-usage-examples/copier-test/test.txt

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"MONGODB_ATLAS_BASE_URL": "https://cloud-dev.mongodb.com",
3+
"ATLAS_ORG_ID": "32b6e34b3d91647abb20e7b8",
4+
"ATLAS_PROJECT_ID": "5e2211c17a3e5a48f5497de3",
5+
"ATLAS_PROJECT_NAME": "Customer Portal - Dev",
6+
"ATLAS_PROCESS_ID": "CustomerPortalDev-shard-00-00.ajlj3.mongodb.net:27017",
7+
"programmatic_scaling": {
8+
"target_tier": "M20",
9+
"pre_scale_event": true,
10+
"cpu_threshold": 75.0,
11+
"cpu_period_minutes": 60,
12+
"dry_run": true
13+
}
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"MONGODB_ATLAS_BASE_URL": "https://cloud.mongodb.com",
3+
"ATLAS_ORG_ID": "32b6e34b3d91647abb20e7b8",
4+
"ATLAS_PROJECT_ID": "5e2211c17a3e5a48f5497de3",
5+
"ATLAS_PROJECT_NAME": "Customer Portal - Prod",
6+
"ATLAS_PROCESS_ID": "CustomerPortalProd-shard-00-00.ajlj3.mongodb.net:27017",
7+
"programmatic_scaling": {
8+
"target_tier": "M50",
9+
"pre_scale_event": true,
10+
"cpu_threshold": 75.0,
11+
"cpu_period_minutes": 60,
12+
"dry_run": true
13+
}
14+
}

generated-usage-examples/go/atlas-sdk-go/main.snippet.archive-collections.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ func main() {
4747

4848
fmt.Printf("\nFound %d clusters to analyze\n", len(clusters.GetResults()))
4949

50-
// Connect to each cluster and analyze collections for archiving
5150
failedArchives := 0
5251
skippedCandidates := 0
5352
totalCandidates := 0

generated-usage-examples/go/atlas-sdk-go/main.snippet.get-linked-orgs.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ import (
66
"fmt"
77
"log"
88

9-
"github.com/joho/godotenv"
10-
"go.mongodb.org/atlas-sdk/v20250219001/admin"
11-
129
"atlas-sdk-go/internal/auth"
1310
"atlas-sdk-go/internal/billing"
1411
"atlas-sdk-go/internal/config"
12+
13+
"github.com/joho/godotenv"
14+
"go.mongodb.org/atlas-sdk/v20250219001/admin"
1515
)
1616

1717
func main() {
@@ -55,4 +55,3 @@ func main() {
5555
fmt.Printf(" %d. %v\n", i+1, org)
5656
}
5757
}
58-

generated-usage-examples/go/atlas-sdk-go/main.snippet.get-metrics-prod.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,10 @@ import (
99

1010
"atlas-sdk-go/internal/auth"
1111
"atlas-sdk-go/internal/config"
12+
"atlas-sdk-go/internal/metrics"
1213

1314
"github.com/joho/godotenv"
1415
"go.mongodb.org/atlas-sdk/v20250219001/admin"
15-
16-
"atlas-sdk-go/internal/metrics"
1716
)
1817

1918
func main() {

generated-usage-examples/go/atlas-sdk-go/main.snippet.line-items.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@ import (
66
"fmt"
77
"log"
88

9-
"github.com/joho/godotenv"
10-
"go.mongodb.org/atlas-sdk/v20250219001/admin"
11-
129
"atlas-sdk-go/internal/auth"
1310
"atlas-sdk-go/internal/billing"
1411
"atlas-sdk-go/internal/config"
1512
"atlas-sdk-go/internal/data/export"
1613
"atlas-sdk-go/internal/fileutils"
14+
15+
"github.com/joho/godotenv"
16+
"go.mongodb.org/atlas-sdk/v20250219001/admin"
1717
)
1818

1919
func main() {
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
// See entire project at https://github.com/mongodb/atlas-architecture-go-sdk
2+
package main
3+
4+
import (
5+
"context"
6+
"fmt"
7+
"log"
8+
"strings"
9+
"time"
10+
11+
"atlas-sdk-go/internal/auth"
12+
"atlas-sdk-go/internal/clusterutils"
13+
"atlas-sdk-go/internal/config"
14+
"atlas-sdk-go/internal/scale"
15+
16+
"github.com/joho/godotenv"
17+
)
18+
19+
func main() {
20+
envFile := ".env.production"
21+
if err := godotenv.Load(envFile); err != nil {
22+
log.Printf("Warning: could not load %s file: %v", envFile, err)
23+
}
24+
25+
secrets, cfg, err := config.LoadAllFromEnv()
26+
if err != nil {
27+
log.Fatalf("Failed to load configuration: %v", err)
28+
}
29+
30+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Minute)
31+
defer cancel()
32+
client, err := auth.NewClient(ctx, cfg, secrets)
33+
if err != nil {
34+
log.Fatalf("Failed to initialize authentication client: %v", err)
35+
}
36+
37+
projectID := cfg.ProjectID
38+
if projectID == "" {
39+
log.Fatal("Failed to find Project ID in configuration")
40+
}
41+
42+
procDetails, err := clusterutils.ListClusterProcessDetails(ctx, client, projectID)
43+
if err != nil {
44+
log.Printf("Warning: unable to map detailed processes to clusters: %v", err)
45+
}
46+
47+
// Based on the configuration settings, perform the following programmatic scaling:
48+
// - Pre-scale ahead of a known traffic spike (e.g. planned bulk inserts)
49+
// - Reactive scale when sustained compute utilization exceeds a threshold
50+
//
51+
// NOTE: Prefer Atlas built-in auto-scaling for gradual growth. Use programmatic scaling for exceptional events or custom logic.
52+
scaling := scale.LoadScalingConfig(cfg)
53+
fmt.Printf("Starting scaling analysis for project: %s\n", projectID)
54+
fmt.Printf("Configuration - Target tier: %s, Pre-scale: %v, CPU threshold: %.1f%%, Period: %d min, Dry run: %v\n",
55+
scaling.TargetTier, scaling.PreScale, scaling.CPUThreshold, scaling.PeriodMinutes, scaling.DryRun)
56+
57+
clusterList, _, err := client.ClustersApi.ListClusters(ctx, projectID).Execute()
58+
if err != nil {
59+
log.Fatalf("Failed to list clusters: %v", err)
60+
}
61+
62+
clusters := clusterList.GetResults()
63+
fmt.Printf("\nFound %d clusters to analyze for scaling\n", len(clusters))
64+
65+
// Track scaling operations across all clusters
66+
scalingCandidates := 0
67+
successfulScales := 0
68+
failedScales := 0
69+
skippedClusters := 0
70+
71+
for _, cluster := range clusters {
72+
clusterName := cluster.GetName()
73+
fmt.Printf("\n=== Analyzing cluster: %s ===\n", clusterName)
74+
75+
// Skip clusters that are not in IDLE state
76+
if cluster.HasStateName() && cluster.GetStateName() != "IDLE" {
77+
fmt.Printf("- Skipping cluster %s: not in IDLE state (current: %s)\n", clusterName, cluster.GetStateName())
78+
skippedClusters++
79+
continue
80+
}
81+
82+
currentTier, err := scale.ExtractInstanceSize(&cluster)
83+
if err != nil {
84+
fmt.Printf("- Skipping cluster %s: failed to extract current tier: %v\n", clusterName, err)
85+
skippedClusters++
86+
continue
87+
}
88+
fmt.Printf("- Current tier: %s, Target tier: %s\n", currentTier, scaling.TargetTier)
89+
90+
// Skip if already at target tier
91+
if strings.EqualFold(currentTier, scaling.TargetTier) {
92+
fmt.Printf("- No action needed: cluster already at target tier %s\n", scaling.TargetTier)
93+
continue
94+
}
95+
96+
// Shared tier handling: skip reactive CPU (metrics unavailable) unless pre-scale
97+
if scale.IsSharedTier(currentTier) && !scaling.PreScale {
98+
fmt.Printf("- Shared tier (%s): reactive CPU metrics unavailable; skipping (enable PreScale to force scale)\n", currentTier)
99+
continue
100+
}
101+
102+
// Gather process info for dedicated tiers
103+
var processID string
104+
var primaryID string
105+
var processIDs []string
106+
if procs, ok := procDetails[clusterName]; ok && len(procs) > 0 {
107+
for _, p := range procs {
108+
processIDs = append(processIDs, p.ID)
109+
}
110+
if pid, okp := clusterutils.GetPrimaryProcessID(procs); okp {
111+
primaryID = pid
112+
}
113+
processID = processIDs[0]
114+
}
115+
if len(processIDs) > 0 && !scale.IsSharedTier(currentTier) {
116+
fmt.Printf("- Found %d processes (primary=%s)\n", len(processIDs), primaryID)
117+
} else if processID != "" {
118+
fmt.Printf("- Using process ID: %s for metrics\n", processID)
119+
}
120+
121+
// Evaluate scaling decision based on configuration and metrics
122+
var shouldScale bool
123+
var reason string
124+
if !scale.IsSharedTier(currentTier) && len(processIDs) > 0 { // dedicated tier with multiple processes
125+
shouldScale, reason = scale.EvaluateDecisionAggregated(ctx, client, projectID, clusterName, processIDs, primaryID, scaling)
126+
} else if !scale.IsSharedTier(currentTier) && processID != "" { // fallback if no aggregation possible
127+
shouldScale, reason = scale.EvaluateDecisionForProcess(ctx, client, projectID, clusterName, processID, scaling)
128+
} else if !scale.IsSharedTier(currentTier) { // dedicated tier but no process info
129+
shouldScale, reason = scale.EvaluateDecision(ctx, client, projectID, clusterName, scaling)
130+
} else { // shared tier (M0/M2/M5)
131+
shouldScale = scaling.PreScale
132+
if shouldScale {
133+
reason = "pre-scale event flag set (shared tier)"
134+
} else {
135+
reason = "shared tier without pre-scale"
136+
}
137+
}
138+
if !shouldScale {
139+
fmt.Printf("- Conditions not met: %s\n", reason)
140+
continue
141+
}
142+
143+
scalingCandidates++
144+
fmt.Printf("- Scaling decision: proceed -> %s\n", reason)
145+
146+
if scaling.DryRun {
147+
fmt.Printf("- DRY_RUN=true: would scale cluster %s from %s to %s\n",
148+
clusterName, currentTier, scaling.TargetTier)
149+
successfulScales++
150+
continue
151+
}
152+
153+
if err := scale.ExecuteClusterScaling(ctx, client, projectID, clusterName, &cluster, scaling.TargetTier); err != nil {
154+
fmt.Printf("- ERROR: Failed to scale cluster %s: %v\n", clusterName, err)
155+
failedScales++
156+
continue
157+
}
158+
fmt.Printf("- Successfully initiated scaling for cluster %s from %s to %s\n",
159+
clusterName, currentTier, scaling.TargetTier)
160+
successfulScales++
161+
}
162+
163+
fmt.Printf("\n=== Scaling Operation Summary ===\n")
164+
fmt.Printf("Total clusters analyzed: %d\n", len(clusters))
165+
fmt.Printf("Scaling candidates identified: %d\n", scalingCandidates)
166+
fmt.Printf("Successful scaling operations: %d\n", successfulScales)
167+
fmt.Printf("Failed scaling operations: %d\n", failedScales)
168+
fmt.Printf("Skipped clusters: %d\n", skippedClusters)
169+
170+
if failedScales > 0 {
171+
fmt.Printf("WARNING: %d of %d scaling operations failed\n", failedScales, scalingCandidates)
172+
}
173+
174+
if successfulScales > 0 && !scaling.DryRun {
175+
fmt.Println("\nAtlas will perform rolling resizes with zero-downtime semantics.")
176+
fmt.Println("Monitor status in the Atlas UI or poll cluster states until STATE_NAME becomes IDLE.")
177+
}
178+
fmt.Println("Scaling analysis and operations completed.")
179+
}
180+

generated-usage-examples/go/atlas-sdk-go/project-copy/.env.example

Lines changed: 0 additions & 4 deletions
This file was deleted.

0 commit comments

Comments
 (0)