☁️ Cloud Resource Allocation Optimization

Minimizing Cost While Guaranteeing Performance

A Practical Deep Dive with Python, Linear Programming, and 3D Visualization


Cloud computing bills can spiral out of control fast. Allocate too few resources and your SLA burns. Over-provision and you’re bleeding money. This post walks through a concrete, solvable optimization problem — finding the sweet spot between cost and performance using Linear Programming (LP) and Mixed-Integer Programming (MIP) in Python.


🔍 The Problem Setup

Imagine you’re a cloud architect managing three workload types across four VM instance types on a hypothetical cloud provider:

Workloads:

  • 🌐 Web API Server (latency-sensitive)
  • 🧮 Batch Analytics Job (throughput-sensitive)
  • 🗄️ Database Cluster (memory-sensitive)

VM Instance Types:

Instance vCPU RAM (GB) Cost ($/hr)
t3.small 2 2 0.023
m5.large 2 8 0.096
c5.xlarge 4 8 0.170
r5.2xlarge 8 64 0.504

The Decision Variables:

Let $x_{ij}$ be the number of VM instance type $j$ allocated to workload $i$.

$$x_{ij} \in \mathbb{Z}_{\geq 0}, \quad i \in {1,2,3},\ j \in {1,2,3,4}$$

Objective — Minimize Total Hourly Cost:

$$\min \sum_{i=1}^{3} \sum_{j=1}^{4} c_j \cdot x_{ij}$$

where $c_j$ is the hourly cost of instance type $j$.

Constraints:

Each workload has minimum CPU, RAM, and performance score requirements:

Additionally, a total budget cap $B$:

$$\sum_{i=1}^{3} \sum_{j=1}^{4} c_j \cdot x_{ij} \leq B$$


🐍 Full Python Source Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
# ============================================================
# Cloud Resource Allocation Optimization
# Cost Minimization with Performance Constraints
# Requires: pip install pulp matplotlib numpy scipy
# ============================================================

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D
from itertools import product as iproduct
import warnings
warnings.filterwarnings('ignore')

try:
import pulp
except ImportError:
import subprocess, sys
subprocess.check_call([sys.executable, "-m", "pip", "install", "pulp", "-q"])
import pulp

# ─────────────────────────────────────────────
# 1. PROBLEM DATA
# ─────────────────────────────────────────────

# Instance types: [name, vCPU, RAM_GB, cost_per_hour, perf_score]
instances = [
{"name": "t3.small", "cpu": 2, "ram": 2, "cost": 0.023, "perf": 10},
{"name": "m5.large", "cpu": 2, "ram": 8, "cost": 0.096, "perf": 30},
{"name": "c5.xlarge", "cpu": 4, "ram": 8, "cost": 0.170, "perf": 55},
{"name": "r5.2xlarge", "cpu": 8, "ram": 64, "cost": 0.504, "perf": 100},
]

# Workloads: [name, min_cpu, min_ram, min_perf, max_instances]
workloads = [
{"name": "Web API", "min_cpu": 8, "min_ram": 16, "min_perf": 80, "max_inst": 10},
{"name": "Batch Job", "min_cpu": 16, "min_ram": 32, "min_perf": 120, "max_inst": 10},
{"name": "Database", "min_cpu": 8, "min_ram": 128,"min_perf": 150, "max_inst": 10},
]

BUDGET_CAP = 5.0 # $/hr total

n_w = len(workloads)
n_i = len(instances)

# ─────────────────────────────────────────────
# 2. BUILD THE MIP MODEL WITH PuLP
# ─────────────────────────────────────────────

prob = pulp.LpProblem("CloudResourceAllocation", pulp.LpMinimize)

# Decision variables: x[i][j] = # of instance type j for workload i
x = [[pulp.LpVariable(f"x_{i}_{j}", lowBound=0, cat="Integer")
for j in range(n_i)] for i in range(n_w)]

# Objective: minimize total cost
prob += pulp.lpSum(instances[j]["cost"] * x[i][j]
for i in range(n_w) for j in range(n_i)), "TotalCost"

# Constraints per workload
for i, wl in enumerate(workloads):
# CPU requirement
prob += (pulp.lpSum(instances[j]["cpu"] * x[i][j] for j in range(n_i))
>= wl["min_cpu"], f"CPU_{i}")
# RAM requirement
prob += (pulp.lpSum(instances[j]["ram"] * x[i][j] for j in range(n_i))
>= wl["min_ram"], f"RAM_{i}")
# Performance requirement
prob += (pulp.lpSum(instances[j]["perf"] * x[i][j] for j in range(n_i))
>= wl["min_perf"], f"PERF_{i}")
# Max instances per workload
prob += (pulp.lpSum(x[i][j] for j in range(n_i))
<= wl["max_inst"], f"MAXINST_{i}")

# Global budget cap
prob += (pulp.lpSum(instances[j]["cost"] * x[i][j]
for i in range(n_w) for j in range(n_i))
<= BUDGET_CAP, "Budget")

# ─────────────────────────────────────────────
# 3. SOLVE
# ─────────────────────────────────────────────

solver = pulp.PULP_CBC_CMD(msg=0)
prob.solve(solver)

print("=" * 55)
print(" OPTIMIZATION STATUS:", pulp.LpStatus[prob.status])
print("=" * 55)

# Extract solution
sol = np.array([[int(pulp.value(x[i][j])) for j in range(n_i)]
for i in range(n_w)])

total_cost = sum(instances[j]["cost"] * sol[i][j]
for i in range(n_w) for j in range(n_i))

print(f"\n Optimal Total Cost: ${total_cost:.4f}/hr")
print(f" Budget Cap: ${BUDGET_CAP:.4f}/hr\n")

for i, wl in enumerate(workloads):
alloc_cpu = sum(instances[j]["cpu"] * sol[i][j] for j in range(n_i))
alloc_ram = sum(instances[j]["ram"] * sol[i][j] for j in range(n_i))
alloc_perf = sum(instances[j]["perf"] * sol[i][j] for j in range(n_i))
wl_cost = sum(instances[j]["cost"] * sol[i][j] for j in range(n_i))
print(f" [{wl['name']}]")
for j in range(n_i):
if sol[i][j] > 0:
print(f" {instances[j]['name']:12s} x{sol[i][j]} "
f"(${instances[j]['cost']*sol[i][j]:.3f}/hr)")
print(f" → CPU: {alloc_cpu} vCPU (min {wl['min_cpu']})")
print(f" → RAM: {alloc_ram} GB (min {wl['min_ram']})")
print(f" → Perf: {alloc_perf} (min {wl['min_perf']})")
print(f" → Cost: ${wl_cost:.4f}/hr\n")

# ─────────────────────────────────────────────
# 4. PARETO FRONTIER: Cost vs Performance Trade-off
# ─────────────────────────────────────────────

pareto_costs = []
pareto_perfs = []
perf_targets = np.linspace(50, 200, 30)

for perf_scale in perf_targets:
p2 = pulp.LpProblem("Pareto", pulp.LpMinimize)
y = [[pulp.LpVariable(f"y_{i}_{j}_{int(perf_scale)}", lowBound=0, cat="Integer")
for j in range(n_i)] for i in range(n_w)]

p2 += pulp.lpSum(instances[j]["cost"] * y[i][j]
for i in range(n_w) for j in range(n_i))

for i, wl in enumerate(workloads):
p2 += (pulp.lpSum(instances[j]["cpu"] * y[i][j] for j in range(n_i))
>= wl["min_cpu"])
p2 += (pulp.lpSum(instances[j]["ram"] * y[i][j] for j in range(n_i))
>= wl["min_ram"])
scaled_perf = wl["min_perf"] * (perf_scale / 100.0)
p2 += (pulp.lpSum(instances[j]["perf"] * y[i][j] for j in range(n_i))
>= scaled_perf)
p2 += (pulp.lpSum(y[i][j] for j in range(n_i)) <= wl["max_inst"])

p2.solve(pulp.PULP_CBC_CMD(msg=0))

if pulp.LpStatus[p2.status] == "Optimal":
c = sum(instances[j]["cost"] * pulp.value(y[i][j])
for i in range(n_w) for j in range(n_i))
pf = sum(instances[j]["perf"] * pulp.value(y[i][j])
for i in range(n_w) for j in range(n_i))
pareto_costs.append(c)
pareto_perfs.append(pf)

# ─────────────────────────────────────────────
# 5. SENSITIVITY: Budget Cap Sweep
# ─────────────────────────────────────────────

budget_range = np.linspace(1.0, 8.0, 25)
budget_costs = []
budget_perfs = []
budget_feasible = []

for B in budget_range:
p3 = pulp.LpProblem("BudgetSweep", pulp.LpMinimize)
z = [[pulp.LpVariable(f"z_{i}_{j}_{B:.2f}", lowBound=0, cat="Integer")
for j in range(n_i)] for i in range(n_w)]

p3 += pulp.lpSum(instances[j]["cost"] * z[i][j]
for i in range(n_w) for j in range(n_i))

for i, wl in enumerate(workloads):
p3 += (pulp.lpSum(instances[j]["cpu"] * z[i][j] for j in range(n_i))
>= wl["min_cpu"])
p3 += (pulp.lpSum(instances[j]["ram"] * z[i][j] for j in range(n_i))
>= wl["min_ram"])
p3 += (pulp.lpSum(instances[j]["perf"] * z[i][j] for j in range(n_i))
>= wl["min_perf"])
p3 += (pulp.lpSum(z[i][j] for j in range(n_i)) <= wl["max_inst"])

p3 += (pulp.lpSum(instances[j]["cost"] * z[i][j]
for i in range(n_w) for j in range(n_i)) <= B)

p3.solve(pulp.PULP_CBC_CMD(msg=0))
feasible = (pulp.LpStatus[p3.status] == "Optimal")
budget_feasible.append(feasible)

if feasible:
c = pulp.value(p3.objective)
pf = sum(instances[j]["perf"] * pulp.value(z[i][j])
for i in range(n_w) for j in range(n_i))
budget_costs.append(c)
budget_perfs.append(pf)
else:
budget_costs.append(np.nan)
budget_perfs.append(np.nan)

# ─────────────────────────────────────────────
# 6. 3D SURFACE: CPU × RAM → Cost
# ─────────────────────────────────────────────

cpu_vals = np.arange(4, 20, 2)
ram_vals = np.arange(8, 80, 8)
CPU_G, RAM_G = np.meshgrid(cpu_vals, ram_vals)
COST_G = np.full(CPU_G.shape, np.nan)

for ri, rv in enumerate(ram_vals):
for ci, cv in enumerate(cpu_vals):
p4 = pulp.LpProblem("Surface", pulp.LpMinimize)
w = [pulp.LpVariable(f"w_{j}_{ci}_{ri}", lowBound=0, cat="Integer")
for j in range(n_i)]
p4 += pulp.lpSum(instances[j]["cost"] * w[j] for j in range(n_i))
p4 += (pulp.lpSum(instances[j]["cpu"] * w[j] for j in range(n_i)) >= cv)
p4 += (pulp.lpSum(instances[j]["ram"] * w[j] for j in range(n_i)) >= rv)
p4 += (pulp.lpSum(instances[j]["perf"] * w[j] for j in range(n_i)) >= 30)
p4 += (pulp.lpSum(w[j] for j in range(n_i)) <= 8)
p4.solve(pulp.PULP_CBC_CMD(msg=0))
if pulp.LpStatus[p4.status] == "Optimal":
COST_G[ri, ci] = pulp.value(p4.objective)

# ─────────────────────────────────────────────
# 7. PLOTTING
# ─────────────────────────────────────────────

plt.style.use('dark_background')
fig = plt.figure(figsize=(20, 22))
fig.patch.set_facecolor('#0d1117')

colors_wl = ['#58a6ff', '#3fb950', '#f78166']
colors_inst = ['#e3b341', '#58a6ff', '#3fb950', '#f78166']
inst_names = [inst["name"] for inst in instances]
wl_names = [wl["name"] for wl in workloads]

# ── Plot 1: Allocation Stacked Bar ──
ax1 = fig.add_subplot(3, 3, 1)
ax1.set_facecolor('#161b22')
bar_width = 0.18
x_pos = np.arange(n_w)

for j in range(n_i):
vals = [sol[i][j] for i in range(n_w)]
ax1.bar(x_pos + j * bar_width, vals, bar_width,
label=inst_names[j], color=colors_inst[j], alpha=0.9,
edgecolor='#30363d')

ax1.set_xticks(x_pos + bar_width * 1.5)
ax1.set_xticklabels(wl_names, color='white', fontsize=9)
ax1.set_ylabel("# Instances", color='white')
ax1.set_title("① Optimal VM Allocation", color='white', fontweight='bold', pad=10)
ax1.legend(loc='upper right', fontsize=7, facecolor='#21262d', labelcolor='white')
ax1.tick_params(colors='white')
ax1.spines['bottom'].set_color('#30363d')
ax1.spines['left'].set_color('#30363d')
ax1.spines['top'].set_visible(False)
ax1.spines['right'].set_visible(False)

# ── Plot 2: Cost Breakdown Pie ──
ax2 = fig.add_subplot(3, 3, 2)
ax2.set_facecolor('#161b22')
wl_costs = [sum(instances[j]["cost"] * sol[i][j] for j in range(n_i))
for i in range(n_w)]
wedges, texts, autotexts = ax2.pie(
wl_costs, labels=wl_names, colors=colors_wl,
autopct='%1.1f%%', startangle=140,
wedgeprops=dict(edgecolor='#0d1117', linewidth=2),
textprops=dict(color='white', fontsize=9))
for at in autotexts:
at.set_fontsize(8)
at.set_color('#0d1117')
ax2.set_title(f"② Cost Breakdown\n(Total ${total_cost:.3f}/hr)",
color='white', fontweight='bold', pad=10)

# ── Plot 3: Resource Utilization vs Minimum ──
ax3 = fig.add_subplot(3, 3, 3)
ax3.set_facecolor('#161b22')
metrics = ['CPU', 'RAM', 'Perf']
bar_w = 0.12
pos = np.arange(len(metrics))

for i, wl in enumerate(workloads):
alloc = [
sum(instances[j]["cpu"] * sol[i][j] for j in range(n_i)),
sum(instances[j]["ram"] * sol[i][j] for j in range(n_i)),
sum(instances[j]["perf"] * sol[i][j] for j in range(n_i)),
]
mins = [wl["min_cpu"], wl["min_ram"], wl["min_perf"]]
ratios = [a / m for a, m in zip(alloc, mins)]
ax3.bar(pos + i * bar_w, ratios, bar_w,
label=wl["name"], color=colors_wl[i], alpha=0.9,
edgecolor='#30363d')

ax3.axhline(1.0, color='#f78166', linewidth=1.5, linestyle='--', label='Minimum (1.0x)')
ax3.set_xticks(pos + bar_w)
ax3.set_xticklabels(metrics, color='white')
ax3.set_ylabel("Allocation / Minimum", color='white')
ax3.set_title("③ Resource Headroom Ratio", color='white', fontweight='bold', pad=10)
ax3.legend(fontsize=7, facecolor='#21262d', labelcolor='white')
ax3.tick_params(colors='white')
ax3.spines['bottom'].set_color('#30363d')
ax3.spines['left'].set_color('#30363d')
ax3.spines['top'].set_visible(False)
ax3.spines['right'].set_visible(False)

# ── Plot 4: Pareto Frontier ──
ax4 = fig.add_subplot(3, 3, 4)
ax4.set_facecolor('#161b22')
if pareto_costs:
sc = ax4.scatter(pareto_perfs, pareto_costs,
c=pareto_costs, cmap='plasma',
s=60, zorder=3, edgecolors='white', linewidths=0.4)
ax4.plot(pareto_perfs, pareto_costs, color='#58a6ff', linewidth=1.5,
alpha=0.6, zorder=2)
cbar = plt.colorbar(sc, ax=ax4)
cbar.ax.yaxis.set_tick_params(color='white')
cbar.set_label('Cost ($/hr)', color='white', fontsize=8)
plt.setp(plt.getp(cbar.ax.axes, 'yticklabels'), color='white')

ax4.scatter([sum(instances[j]["perf"] * sol[i][j]
for i in range(n_w) for j in range(n_i))],
[total_cost], color='#f78166', s=120, zorder=5,
marker='*', label='Optimal Solution')
ax4.set_xlabel("Total Performance Score", color='white')
ax4.set_ylabel("Total Cost ($/hr)", color='white')
ax4.set_title("④ Pareto Frontier\nCost vs Performance", color='white', fontweight='bold', pad=10)
ax4.legend(fontsize=8, facecolor='#21262d', labelcolor='white')
ax4.tick_params(colors='white')
ax4.spines['bottom'].set_color('#30363d')
ax4.spines['left'].set_color('#30363d')
ax4.spines['top'].set_visible(False)
ax4.spines['right'].set_visible(False)

# ── Plot 5: Budget Sensitivity ──
ax5 = fig.add_subplot(3, 3, 5)
ax5.set_facecolor('#161b22')
feasible_budgets = [budget_range[k] for k in range(len(budget_range)) if budget_feasible[k]]
feasible_costs = [budget_costs[k] for k in range(len(budget_range)) if budget_feasible[k]]
infeasible_b = [budget_range[k] for k in range(len(budget_range)) if not budget_feasible[k]]

if feasible_budgets:
ax5.plot(feasible_budgets, feasible_costs, color='#3fb950',
linewidth=2, marker='o', markersize=4, label='Feasible')
if infeasible_b:
for ib in infeasible_b:
ax5.axvline(ib, color='#f78166', alpha=0.3, linewidth=1)
ax5.axvline(infeasible_b[-1] if infeasible_b else 0,
color='#f78166', alpha=0.3, linewidth=1, label='Infeasible Budget')

ax5.axvline(BUDGET_CAP, color='#e3b341', linewidth=2,
linestyle='--', label=f'Our Budget ${BUDGET_CAP}')
ax5.set_xlabel("Budget Cap ($/hr)", color='white')
ax5.set_ylabel("Optimal Cost ($/hr)", color='white')
ax5.set_title("⑤ Budget Sensitivity Analysis", color='white', fontweight='bold', pad=10)
ax5.legend(fontsize=8, facecolor='#21262d', labelcolor='white')
ax5.tick_params(colors='white')
ax5.spines['bottom'].set_color('#30363d')
ax5.spines['left'].set_color('#30363d')
ax5.spines['top'].set_visible(False)
ax5.spines['right'].set_visible(False)

# ── Plot 6: Cost-Efficiency per Instance Type ──
ax6 = fig.add_subplot(3, 3, 6)
ax6.set_facecolor('#161b22')
eff_cpu = [inst["cpu"] / inst["cost"] for inst in instances]
eff_ram = [inst["ram"] / inst["cost"] for inst in instances]
eff_perf = [inst["perf"] / inst["cost"] for inst in instances]

x6 = np.arange(n_i)
w6 = 0.25
ax6.bar(x6 - w6, [e/max(eff_cpu) for e in eff_cpu], w6, label='CPU/Cost',
color='#58a6ff', alpha=0.9)
ax6.bar(x6, [e/max(eff_ram) for e in eff_ram], w6, label='RAM/Cost',
color='#3fb950', alpha=0.9)
ax6.bar(x6 + w6, [e/max(eff_perf) for e in eff_perf], w6, label='Perf/Cost',
color='#e3b341', alpha=0.9)
ax6.set_xticks(x6)
ax6.set_xticklabels(inst_names, color='white', fontsize=8, rotation=15)
ax6.set_ylabel("Normalized Efficiency", color='white')
ax6.set_title("⑥ Instance Cost-Efficiency", color='white', fontweight='bold', pad=10)
ax6.legend(fontsize=8, facecolor='#21262d', labelcolor='white')
ax6.tick_params(colors='white')
ax6.spines['bottom'].set_color('#30363d')
ax6.spines['left'].set_color('#30363d')
ax6.spines['top'].set_visible(False)
ax6.spines['right'].set_visible(False)

# ── Plot 7: 3D Surface — CPU × RAM → Minimum Cost ──
ax7 = fig.add_subplot(3, 3, 7, projection='3d')
ax7.set_facecolor('#161b22')
fig.patch.set_alpha(1)

mask = ~np.isnan(COST_G)
if mask.any():
surf = ax7.plot_surface(CPU_G, RAM_G, np.where(mask, COST_G, 0),
cmap='inferno', alpha=0.85,
linewidth=0, antialiased=True)
fig.colorbar(surf, ax=ax7, shrink=0.5, pad=0.1,
label='Min Cost ($/hr)').ax.yaxis.set_tick_params(color='white')

ax7.set_xlabel('vCPU Required', color='white', labelpad=8)
ax7.set_ylabel('RAM (GB) Required', color='white', labelpad=8)
ax7.set_zlabel('Min Cost ($/hr)', color='white', labelpad=8)
ax7.set_title("⑦ 3D Cost Surface\n(CPU × RAM → Min Cost)",
color='white', fontweight='bold', pad=15)
ax7.tick_params(colors='white')
ax7.xaxis.pane.fill = False
ax7.yaxis.pane.fill = False
ax7.zaxis.pane.fill = False
ax7.xaxis.pane.set_edgecolor('#30363d')
ax7.yaxis.pane.set_edgecolor('#30363d')
ax7.zaxis.pane.set_edgecolor('#30363d')
ax7.view_init(elev=28, azim=-55)

# ── Plot 8: 3D Scatter — Budget × Perf → Cost ──
ax8 = fig.add_subplot(3, 3, 8, projection='3d')
ax8.set_facecolor('#161b22')

valid_idx = [k for k in range(len(budget_range))
if budget_feasible[k] and not np.isnan(budget_perfs[k])]
if valid_idx:
bx = [budget_range[k] for k in valid_idx]
py = [budget_perfs[k] for k in valid_idx]
cz = [budget_costs[k] for k in valid_idx]
sc8 = ax8.scatter(bx, py, cz, c=cz, cmap='cool',
s=60, edgecolors='white', linewidths=0.3)
ax8.plot(bx, py, cz, color='#58a6ff', alpha=0.5, linewidth=1.5)
fig.colorbar(sc8, ax=ax8, shrink=0.5, pad=0.1,
label='Cost ($/hr)').ax.yaxis.set_tick_params(color='white')

ax8.set_xlabel('Budget Cap ($/hr)', color='white', labelpad=8)
ax8.set_ylabel('Total Perf Score', color='white', labelpad=8)
ax8.set_zlabel('Optimal Cost ($/hr)', color='white', labelpad=8)
ax8.set_title("⑧ 3D: Budget × Perf → Cost",
color='white', fontweight='bold', pad=15)
ax8.tick_params(colors='white')
ax8.xaxis.pane.fill = False
ax8.yaxis.pane.fill = False
ax8.zaxis.pane.fill = False
ax8.xaxis.pane.set_edgecolor('#30363d')
ax8.yaxis.pane.set_edgecolor('#30363d')
ax8.zaxis.pane.set_edgecolor('#30363d')
ax8.view_init(elev=25, azim=40)

# ── Plot 9: Heatmap — Workload × Instance Allocation ──
ax9 = fig.add_subplot(3, 3, 9)
ax9.set_facecolor('#161b22')
im = ax9.imshow(sol, cmap='YlOrRd', aspect='auto', interpolation='nearest')
plt.colorbar(im, ax=ax9, label='# Instances').ax.yaxis.set_tick_params(color='white')
ax9.set_xticks(range(n_i))
ax9.set_xticklabels(inst_names, color='white', fontsize=8, rotation=20)
ax9.set_yticks(range(n_w))
ax9.set_yticklabels(wl_names, color='white', fontsize=9)
ax9.set_title("⑨ Allocation Heatmap\n(Workload × Instance Type)",
color='white', fontweight='bold', pad=10)
for i in range(n_w):
for j in range(n_i):
ax9.text(j, i, str(sol[i][j]), ha='center', va='center',
fontsize=12, fontweight='bold',
color='black' if sol[i][j] > 0 else '#555')

plt.suptitle("Cloud Resource Allocation Optimization Dashboard",
color='white', fontsize=16, fontweight='bold', y=1.01)
plt.tight_layout(pad=2.5)
plt.savefig("cloud_optimization.png", dpi=150, bbox_inches='tight',
facecolor='#0d1117')
plt.show()
print("Plot saved to cloud_optimization.png")

🔬 Code Walkthrough

Section 1 — Problem Data

We define four VM instance types with realistic AWS-like specs and pricing. Each has a performance score — a synthetic metric representing compute throughput (useful for normalizing across CPU-bound and memory-bound workloads). The three workloads each carry hard lower bounds on CPU, RAM, and performance they need to function correctly.

Section 2 — Building the MIP Model

We use PuLP, Python’s LP/MIP modeling library with the CBC solver bundled inside it (no external solver installation needed). The decision variables $x_{ij}$ are declared as integers (cat="Integer") — you can’t rent half a VM. The objective is a simple dot product of cost vector and allocation matrix.

The constraints are straightforward linear inequalities. The key insight is that this is a Mixed-Integer Program because of the integrality requirement — the feasible region is a discrete lattice, not a continuous convex set.

Section 3 — Solving and Output

PULP_CBC_CMD(msg=0) suppresses solver verbosity. After solving, we decode the solution matrix and compute per-workload breakdowns, showing exactly which instance types were chosen and whether all constraints are satisfied.

Section 4 — Pareto Frontier

We perform a parametric sweep over the minimum performance requirement (scaling from 50% to 200% of baseline). For each target, we re-solve the MIP independently. The resulting cloud of $(performance, cost)$ points traces the Pareto frontier — the set of solutions where you cannot improve performance without increasing cost.

Section 5 — Budget Sensitivity Analysis

We sweep the global budget cap from $1.00/hr to $8.00/hr. Below a certain threshold, the problem becomes infeasible (no valid allocation satisfies all constraints within budget). This reveals the minimum viable budget for your workload mix.

Section 6 — 3D Cost Surface

For a single generic workload, we evaluate the minimum cost over a grid of $(CPU_{min}, RAM_{min})$ pairs. This creates a 3D response surface showing how cost scales with resource requirements — essentially a continuous view of the piecewise-linear MIP objective landscape.


📊 Graph Explanations

① Optimal VM Allocation — Side-by-side bars showing which instance types were chosen per workload. r5.2xlarge should dominate for the Database workload due to its RAM density.

② Cost Breakdown Pie — Each workload’s share of the total bill. The Database workload typically carries the highest cost due to RAM requirements.

③ Resource Headroom Ratio — The ratio of allocated resource to minimum requirement. Values above 1.0 are headroom; exactly 1.0 would be a perfectly tight constraint. The solver may over-allocate slightly due to integer granularity.

④ Pareto Frontier — The key trade-off chart. Moving right on the x-axis means demanding higher performance, which drives cost upward on the y-axis. The red star marks our specific optimal solution.

⑤ Budget Sensitivity — The green line shows optimal cost as budget cap relaxes. Red verticals mark infeasible budgets. Notice the knee point where relaxing budget no longer improves the solution — this is your true minimum cost.

⑥ Instance Cost-Efficiency — Normalized efficiency ratios per instance type. t3.small wins on CPU/cost efficiency but loses on RAM. r5.2xlarge wins on RAM/cost despite its high absolute price — exactly why the solver picks it for memory-hungry workloads.

⑦ 3D Cost Surface (CPU × RAM → Min Cost) — The inferno-colored surface shows the minimum achievable cost over a grid of CPU and RAM requirements. Notice the step-function character — cost jumps at thresholds where you cross into the next instance tier. This visualizes the non-convexity introduced by integer constraints.

⑧ 3D Scatter (Budget × Perf → Cost) — Each point represents one budget scenario from our sweep. The curve shows that performance scales roughly logarithmically with budget — diminishing returns set in quickly beyond the knee point.

⑨ Allocation Heatmap — The matrix view of the solution. Each cell shows $x_{ij}$ — the number of instance type $j$ allocated to workload $i$. Zero cells are dark; non-zero cells light up. This is the most compact summary of the entire optimization output.


📌 Key Takeaways

The MIP formulation captures real-world cloud allocation decisions exactly:

  • Integer constraints prevent fractional VMs
  • Multiple resource dimensions (CPU, RAM, performance) reflect actual SLA requirements
  • The Pareto frontier makes the cost-performance trade-off quantitatively visible
  • Budget sensitivity identifies the minimum viable spend before constraint violations

The solver (CBC) handles problems of this size in milliseconds. For fleet-scale optimization (hundreds of workloads, dozens of instance families), you’d move to commercial solvers like Gurobi or CPLEX, or approximate with Lagrangian relaxation — but the model structure remains identical.


=======================================================
  OPTIMIZATION STATUS: Optimal
=======================================================

  Optimal Total Cost: $1.5680/hr
  Budget Cap:         $5.0000/hr

  [Web API]
    t3.small     x8  ($0.184/hr)
    → CPU: 16 vCPU (min 8)
    → RAM: 16 GB  (min 16)
    → Perf: 80   (min 80)
    → Cost: $0.1840/hr

  [Batch Job]
    t3.small     x8  ($0.184/hr)
    m5.large     x2  ($0.192/hr)
    → CPU: 20 vCPU (min 16)
    → RAM: 32 GB  (min 32)
    → Perf: 140   (min 120)
    → Cost: $0.3760/hr

  [Database]
    r5.2xlarge   x2  ($1.008/hr)
    → CPU: 16 vCPU (min 8)
    → RAM: 128 GB  (min 128)
    → Perf: 200   (min 150)
    → Cost: $1.0080/hr

Plot saved to cloud_optimization.png