@platformatic/runtime 3.71.1-alpha.0 → 4.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config.d.ts +602 -673
- package/index.d.ts +22 -27
- package/index.js +290 -30
- package/lib/config.js +270 -367
- package/lib/errors.js +27 -13
- package/lib/generator.js +249 -127
- package/lib/logger.js +3 -3
- package/lib/management-api.js +22 -65
- package/lib/management-handlers.js +3 -5
- package/lib/memory.js +29 -0
- package/lib/policies.js +27 -0
- package/lib/predictive-scaling.js +910 -0
- package/lib/predictive-worker-scaler.js +378 -0
- package/lib/runtime.js +1081 -572
- package/lib/scheduler.js +6 -41
- package/lib/schema.js +77 -15
- package/lib/upgrade.js +16 -1
- package/lib/versions/v4.0.0.js +31 -0
- package/lib/worker/controller.js +89 -109
- package/lib/worker/interceptors.js +34 -34
- package/lib/worker/itc.js +248 -66
- package/lib/worker/main.js +50 -98
- package/lib/worker/messaging.js +42 -1
- package/lib/worker/round-robin-map.js +10 -3
- package/lib/worker/symbols.js +6 -2
- package/package.json +29 -20
- package/schema.json +252 -433
- package/lib/scaling-algorithm.js +0 -256
- package/lib/worker-scaler.js +0 -249
package/lib/scaling-algorithm.js
DELETED
|
@@ -1,256 +0,0 @@
|
|
|
1
|
-
export const scaleUpTimeWindow = 10_000
|
|
2
|
-
export const scaleDownTimeWindow = 60_000
|
|
3
|
-
|
|
4
|
-
export class ScalingAlgorithm {
|
|
5
|
-
#maxTotalWorkers
|
|
6
|
-
#appsMetrics
|
|
7
|
-
#appsConfigs
|
|
8
|
-
#scaleUpELU
|
|
9
|
-
#scaleDownELU
|
|
10
|
-
|
|
11
|
-
constructor (options = {}) {
|
|
12
|
-
this.#maxTotalWorkers = options.maxTotalWorkers ?? Infinity
|
|
13
|
-
this.#appsConfigs = options.applications ?? {}
|
|
14
|
-
this.#appsMetrics = {}
|
|
15
|
-
this.#scaleUpELU = options.scaleUpELU ?? 0.8
|
|
16
|
-
this.#scaleDownELU = options.scaleDownELU ?? 0.2
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
addApplication (id, config = {}) {
|
|
20
|
-
config.scaleUpELU ??= this.#scaleUpELU
|
|
21
|
-
config.scaleDownELU ??= this.#scaleDownELU
|
|
22
|
-
|
|
23
|
-
this.#appsConfigs[id] = config
|
|
24
|
-
this.#appsMetrics[id] ??= {}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
removeApplication (id) {
|
|
28
|
-
delete this.#appsConfigs[id]
|
|
29
|
-
delete this.#appsMetrics[id]
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
addWorkerHealthInfo (healthInfo) {
|
|
33
|
-
const { workerId, applicationId, elu, heapUsed } = healthInfo
|
|
34
|
-
const timestamp = Date.now()
|
|
35
|
-
|
|
36
|
-
if (!this.#appsMetrics[applicationId]) {
|
|
37
|
-
throw new Error(`Missing application "${applicationId}" in the scaling algorithm.`)
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (!this.#appsMetrics[applicationId][workerId]) {
|
|
41
|
-
this.#appsMetrics[applicationId][workerId] = []
|
|
42
|
-
}
|
|
43
|
-
this.#appsMetrics[applicationId][workerId].push({
|
|
44
|
-
elu,
|
|
45
|
-
timestamp,
|
|
46
|
-
heapUsed
|
|
47
|
-
})
|
|
48
|
-
this.#removeOutdatedAppELUs(applicationId)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
getRecommendations (appsWorkersInfo, options = {}) {
|
|
52
|
-
let totalWorkersCount = 0
|
|
53
|
-
let totalAvailableMemory = options.availableMemory ?? Infinity
|
|
54
|
-
|
|
55
|
-
const appsInfo = []
|
|
56
|
-
|
|
57
|
-
for (const applicationId in appsWorkersInfo) {
|
|
58
|
-
const workersCount = appsWorkersInfo[applicationId]
|
|
59
|
-
|
|
60
|
-
const { heapUsed } = this.#calculateAppAvgMetrics(applicationId)
|
|
61
|
-
|
|
62
|
-
appsInfo.push({
|
|
63
|
-
applicationId,
|
|
64
|
-
workersCount,
|
|
65
|
-
avgHeapUsed: heapUsed
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
totalWorkersCount += workersCount
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const recommendations = []
|
|
72
|
-
|
|
73
|
-
for (const { applicationId, workersCount, avgHeapUsed } of appsInfo) {
|
|
74
|
-
const appMinWorkers = this.#appsConfigs[applicationId]?.minWorkers ?? 1
|
|
75
|
-
const appMaxWorkers = this.#appsConfigs[applicationId]?.maxWorkers ?? this.#maxTotalWorkers
|
|
76
|
-
|
|
77
|
-
if (workersCount < appMinWorkers) {
|
|
78
|
-
recommendations.push({
|
|
79
|
-
applicationId,
|
|
80
|
-
workersCount: appMinWorkers,
|
|
81
|
-
direction: 'up'
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
const newWorkersCount = appMinWorkers - workersCount
|
|
85
|
-
totalWorkersCount += newWorkersCount
|
|
86
|
-
totalAvailableMemory -= newWorkersCount * avgHeapUsed
|
|
87
|
-
continue
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (workersCount > appMaxWorkers) {
|
|
91
|
-
recommendations.push({
|
|
92
|
-
applicationId,
|
|
93
|
-
workersCount: appMaxWorkers,
|
|
94
|
-
direction: 'down'
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
const removedWorkersCount = workersCount - appMaxWorkers
|
|
98
|
-
totalWorkersCount -= removedWorkersCount
|
|
99
|
-
totalAvailableMemory += removedWorkersCount * avgHeapUsed
|
|
100
|
-
continue
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (workersCount > appMinWorkers) {
|
|
104
|
-
const recommendation = this.#getApplicationScaleRecommendation(applicationId)
|
|
105
|
-
if (recommendation.recommendation === 'scaleDown') {
|
|
106
|
-
recommendations.push({
|
|
107
|
-
applicationId,
|
|
108
|
-
workersCount: workersCount - 1,
|
|
109
|
-
direction: 'down'
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
const removedWorkersCount = 1
|
|
113
|
-
totalWorkersCount -= removedWorkersCount
|
|
114
|
-
totalAvailableMemory += removedWorkersCount * avgHeapUsed
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (totalWorkersCount < this.#maxTotalWorkers) {
|
|
120
|
-
let scaleUpCandidate = null
|
|
121
|
-
|
|
122
|
-
for (const { applicationId, workersCount, avgHeapUsed } of appsInfo) {
|
|
123
|
-
const appMaxWorkers = this.#appsConfigs[applicationId]?.maxWorkers ?? this.#maxTotalWorkers
|
|
124
|
-
if (workersCount >= appMaxWorkers) continue
|
|
125
|
-
if (avgHeapUsed >= totalAvailableMemory) continue
|
|
126
|
-
|
|
127
|
-
const isScaled = recommendations.some(r => r.applicationId === applicationId)
|
|
128
|
-
if (isScaled) continue
|
|
129
|
-
|
|
130
|
-
const recommendation = this.#getApplicationScaleRecommendation(applicationId)
|
|
131
|
-
if (recommendation.recommendation !== 'scaleUp') continue
|
|
132
|
-
|
|
133
|
-
if (
|
|
134
|
-
!scaleUpCandidate ||
|
|
135
|
-
recommendation.scaleUpELU > scaleUpCandidate.scaleUpELU ||
|
|
136
|
-
(recommendation.scaleUpELU === scaleUpCandidate.scaleUpELU && workersCount < scaleUpCandidate.workersCount)
|
|
137
|
-
) {
|
|
138
|
-
scaleUpCandidate = {
|
|
139
|
-
applicationId,
|
|
140
|
-
workersCount,
|
|
141
|
-
heapUsed: recommendation.avgHeapUsage,
|
|
142
|
-
elu: recommendation.scaleUpELU
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if (scaleUpCandidate) {
|
|
148
|
-
recommendations.push({
|
|
149
|
-
applicationId: scaleUpCandidate.applicationId,
|
|
150
|
-
workersCount: scaleUpCandidate.workersCount + 1,
|
|
151
|
-
direction: 'up'
|
|
152
|
-
})
|
|
153
|
-
totalWorkersCount++
|
|
154
|
-
totalAvailableMemory -= scaleUpCandidate.heapUsed
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
return recommendations
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
#calculateAppAvgMetrics (applicationId, options = {}) {
|
|
162
|
-
this.#removeOutdatedAppELUs(applicationId)
|
|
163
|
-
|
|
164
|
-
const appMetrics = this.#appsMetrics[applicationId]
|
|
165
|
-
if (!appMetrics) return { elu: 0, heapUsed: 0 }
|
|
166
|
-
|
|
167
|
-
const defaultTimeWindow = this.#getMetricsTimeWindow()
|
|
168
|
-
const timeWindow = options.timeWindow ?? defaultTimeWindow
|
|
169
|
-
|
|
170
|
-
let eluSum = 0
|
|
171
|
-
let heapUsedSum = 0
|
|
172
|
-
let count = 0
|
|
173
|
-
|
|
174
|
-
const now = Date.now()
|
|
175
|
-
|
|
176
|
-
for (const workerId in appMetrics) {
|
|
177
|
-
const workerMetrics = appMetrics[workerId]
|
|
178
|
-
|
|
179
|
-
let workerELUSum = 0
|
|
180
|
-
let workerHeapUsedSum = 0
|
|
181
|
-
let metricCount = 0
|
|
182
|
-
|
|
183
|
-
for (const metric of workerMetrics) {
|
|
184
|
-
if (metric.timestamp < now - timeWindow) continue
|
|
185
|
-
workerELUSum += metric.elu
|
|
186
|
-
workerHeapUsedSum += metric.heapUsed
|
|
187
|
-
metricCount++
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
if (metricCount === 0) continue
|
|
191
|
-
|
|
192
|
-
eluSum += workerELUSum / metricCount
|
|
193
|
-
heapUsedSum += workerHeapUsedSum / metricCount
|
|
194
|
-
count++
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
const elu = Math.round((eluSum / count) * 100) / 100
|
|
198
|
-
const heapUsed = Math.round((heapUsedSum / count) * 100) / 100
|
|
199
|
-
return { elu, heapUsed }
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
#removeOutdatedAppELUs (applicationId) {
|
|
203
|
-
const appELUs = this.#appsMetrics[applicationId]
|
|
204
|
-
if (!appELUs) return
|
|
205
|
-
|
|
206
|
-
const now = Date.now()
|
|
207
|
-
const timeWindow = this.#getMetricsTimeWindow()
|
|
208
|
-
|
|
209
|
-
for (const workerId in appELUs) {
|
|
210
|
-
const workerELUs = appELUs[workerId]
|
|
211
|
-
|
|
212
|
-
let firstValidIndex = -1
|
|
213
|
-
for (let i = 0; i < workerELUs.length; i++) {
|
|
214
|
-
const timestamp = workerELUs[i].timestamp
|
|
215
|
-
if (timestamp >= now - timeWindow) {
|
|
216
|
-
firstValidIndex = i
|
|
217
|
-
break
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
if (firstValidIndex > 0) {
|
|
222
|
-
// Remove all outdated entries before the first valid one
|
|
223
|
-
workerELUs.splice(0, firstValidIndex)
|
|
224
|
-
} else if (firstValidIndex === -1) {
|
|
225
|
-
// All entries are outdated, clear the array
|
|
226
|
-
workerELUs.length = 0
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// If there are no more workerELUs, remove the workerId
|
|
230
|
-
if (workerELUs.length === 0) {
|
|
231
|
-
delete appELUs[workerId]
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
#getMetricsTimeWindow () {
|
|
237
|
-
return Math.max(scaleUpTimeWindow, scaleDownTimeWindow) * 1000
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
#getApplicationScaleRecommendation (applicationId) {
|
|
241
|
-
const { elu: scaleUpELU } = this.#calculateAppAvgMetrics(applicationId, { timeWindow: scaleUpTimeWindow })
|
|
242
|
-
const { elu: scaleDownELU } = this.#calculateAppAvgMetrics(applicationId, { timeWindow: scaleDownTimeWindow })
|
|
243
|
-
const { heapUsed: avgHeapUsage } = this.#calculateAppAvgMetrics(applicationId)
|
|
244
|
-
const config = this.#appsConfigs[applicationId]
|
|
245
|
-
|
|
246
|
-
let recommendation = null
|
|
247
|
-
if (scaleUpELU > config.scaleUpELU) {
|
|
248
|
-
recommendation = 'scaleUp'
|
|
249
|
-
}
|
|
250
|
-
if (scaleDownELU < config.scaleDownELU) {
|
|
251
|
-
recommendation = 'scaleDown'
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
return { recommendation, scaleUpELU, scaleDownELU, avgHeapUsage }
|
|
255
|
-
}
|
|
256
|
-
}
|
package/lib/worker-scaler.js
DELETED
|
@@ -1,249 +0,0 @@
|
|
|
1
|
-
import { features } from '@platformatic/foundation'
|
|
2
|
-
import { availableParallelism } from 'node:os'
|
|
3
|
-
import { getMemoryInfo } from './metrics.js'
|
|
4
|
-
import { ScalingAlgorithm } from './scaling-algorithm.js'
|
|
5
|
-
import { kApplicationId, kId, kLastWorkerScalerELU, kWorkerStartTime, kWorkerStatus } from './worker/symbols.js'
|
|
6
|
-
|
|
7
|
-
const healthCheckInterval = 1000
|
|
8
|
-
export const kOriginalWorkers = Symbol('plt.runtime.application.dynamicWorkersScalerOriginalWorkers')
|
|
9
|
-
|
|
10
|
-
const defaultCooldown = 20_000
|
|
11
|
-
const defaultGracePeriod = 30_000
|
|
12
|
-
const scaleIntervalPeriod = 60_000
|
|
13
|
-
|
|
14
|
-
export class DynamicWorkersScaler {
|
|
15
|
-
#status
|
|
16
|
-
#runtime
|
|
17
|
-
#algorithm
|
|
18
|
-
|
|
19
|
-
#maxTotalMemory
|
|
20
|
-
#maxTotalWorkers
|
|
21
|
-
#maxWorkers
|
|
22
|
-
#minWorkers
|
|
23
|
-
#scaleUpELU
|
|
24
|
-
#scaleDownELU
|
|
25
|
-
#cooldown
|
|
26
|
-
#gracePeriod
|
|
27
|
-
|
|
28
|
-
#initialUpdates
|
|
29
|
-
#memoryInfo
|
|
30
|
-
#healthCheckTimeout
|
|
31
|
-
#checkScalingInterval
|
|
32
|
-
#isScaling
|
|
33
|
-
#lastScaling
|
|
34
|
-
#appsConfigs
|
|
35
|
-
|
|
36
|
-
constructor (runtime, config) {
|
|
37
|
-
this.#runtime = runtime
|
|
38
|
-
|
|
39
|
-
this.#maxTotalMemory = config.maxMemory // This is defaulted in start()
|
|
40
|
-
this.#maxTotalWorkers = config.total ?? availableParallelism()
|
|
41
|
-
this.#maxWorkers = config.maximum ?? this.#maxTotalWorkers
|
|
42
|
-
this.#minWorkers = config.minimum ?? 1
|
|
43
|
-
this.#scaleUpELU = config.scaleUpELU ?? 0.8
|
|
44
|
-
this.#scaleDownELU = config.scaleDownELU ?? 0.2
|
|
45
|
-
this.#cooldown = config.cooldown ?? defaultCooldown
|
|
46
|
-
this.#gracePeriod = config.gracePeriod ?? defaultGracePeriod
|
|
47
|
-
|
|
48
|
-
this.#algorithm = new ScalingAlgorithm({
|
|
49
|
-
maxTotalWorkers: this.#maxTotalWorkers,
|
|
50
|
-
scaleUpELU: this.#scaleUpELU,
|
|
51
|
-
scaleDownELU: this.#scaleDownELU
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
this.#isScaling = false
|
|
55
|
-
this.#lastScaling = 0
|
|
56
|
-
this.#initialUpdates = []
|
|
57
|
-
this.#status = 'init'
|
|
58
|
-
this.#appsConfigs = {}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
getConfig () {
|
|
62
|
-
return {
|
|
63
|
-
maxTotalMemory: this.#maxTotalMemory,
|
|
64
|
-
maxTotalWorkers: this.#maxTotalWorkers,
|
|
65
|
-
maxWorkers: this.#maxWorkers,
|
|
66
|
-
minWorkers: this.#minWorkers,
|
|
67
|
-
cooldown: this.#cooldown,
|
|
68
|
-
gracePeriod: this.#gracePeriod
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
async start () {
|
|
73
|
-
this.#memoryInfo = await getMemoryInfo()
|
|
74
|
-
this.#maxTotalMemory ??= this.#memoryInfo.total * 0.9
|
|
75
|
-
|
|
76
|
-
this.#checkScalingInterval = setInterval(this.#checkScaling.bind(this), scaleIntervalPeriod)
|
|
77
|
-
this.#healthCheckTimeout = setTimeout(this.#chechHealth.bind(this), healthCheckInterval)
|
|
78
|
-
|
|
79
|
-
if (this.#initialUpdates.length > 0) {
|
|
80
|
-
await this.#runtime.updateApplicationsResources(this.#initialUpdates)
|
|
81
|
-
this.#initialUpdates = []
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
this.#status = 'started'
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
stop () {
|
|
88
|
-
clearTimeout(this.#healthCheckTimeout)
|
|
89
|
-
clearInterval(this.#checkScalingInterval)
|
|
90
|
-
this.#status = 'stopped'
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
async add (application) {
|
|
94
|
-
const config = {}
|
|
95
|
-
|
|
96
|
-
if (application.entrypoint && !features.node.reusePort) {
|
|
97
|
-
this.#runtime.logger.warn(
|
|
98
|
-
`The "${application.id}" application cannot be scaled because it is an entrypoint and the "reusePort" feature is not available in your OS.`
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
config.minWorkers = 1
|
|
102
|
-
config.maxWorkers = 1
|
|
103
|
-
} else if (application.workers.dynamic === false) {
|
|
104
|
-
this.#runtime.logger.warn(
|
|
105
|
-
`The "${application.id}" application cannot be scaled because it has a fixed number of workers (${application.workers.static}).`
|
|
106
|
-
)
|
|
107
|
-
|
|
108
|
-
config.minWorkers = application.workers.static
|
|
109
|
-
config.maxWorkers = application.workers.static
|
|
110
|
-
} else {
|
|
111
|
-
config.minWorkers = application.workers.minimum
|
|
112
|
-
config.maxWorkers = application.workers.maximum
|
|
113
|
-
config.scaleUpELU = application.workers.scaleUpELU
|
|
114
|
-
config.scaleDownELU = application.workers.scaleDownELU
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
config.minWorkers ??= this.#minWorkers
|
|
118
|
-
config.maxWorkers ??= this.#maxWorkers
|
|
119
|
-
config.scaleUpELU ??= this.#scaleUpELU
|
|
120
|
-
config.scaleDownELU ??= this.#scaleDownELU
|
|
121
|
-
|
|
122
|
-
if (config.minWorkers > 1) {
|
|
123
|
-
const update = { application: application.id, workers: config.minWorkers }
|
|
124
|
-
this.#initialUpdates.push(update)
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
this.#appsConfigs[application.id] = config
|
|
128
|
-
this.#algorithm.addApplication(application.id, config)
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
async applyPendingUpdate (applicationId) {
|
|
132
|
-
if (this.#status !== 'started') {
|
|
133
|
-
return
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const update = this.#initialUpdates.find(update => update.application === applicationId)
|
|
137
|
-
if (!update) {
|
|
138
|
-
return
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
await this.#runtime.updateApplicationsResources([update])
|
|
142
|
-
this.#initialUpdates = this.#initialUpdates.filter(pending => pending !== update)
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
remove (applicationId) {
|
|
146
|
-
delete this.#appsConfigs[applicationId]
|
|
147
|
-
this.#initialUpdates = this.#initialUpdates.filter(update => update.application !== applicationId)
|
|
148
|
-
this.#algorithm.removeApplication(applicationId)
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
async #chechHealth () {
|
|
152
|
-
let shouldCheckForScaling = false
|
|
153
|
-
|
|
154
|
-
const now = Date.now()
|
|
155
|
-
|
|
156
|
-
const workers = await this.#runtime.getWorkers(true)
|
|
157
|
-
|
|
158
|
-
for (const { raw: worker } of Object.values(workers)) {
|
|
159
|
-
if (worker[kWorkerStatus] !== 'started' || worker[kWorkerStartTime] + this.#gracePeriod > now) {
|
|
160
|
-
continue
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
try {
|
|
164
|
-
const health = await this.#runtime.getWorkerHealth(worker, { previousELU: worker[kLastWorkerScalerELU] })
|
|
165
|
-
|
|
166
|
-
if (!health) {
|
|
167
|
-
continue
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
worker[kLastWorkerScalerELU] = health.currentELU
|
|
171
|
-
|
|
172
|
-
const workerId = worker[kId]
|
|
173
|
-
const applicationId = worker[kApplicationId]
|
|
174
|
-
const scaleConfig = this.#appsConfigs[applicationId]
|
|
175
|
-
if (!scaleConfig) continue
|
|
176
|
-
|
|
177
|
-
this.#algorithm.addWorkerHealthInfo({
|
|
178
|
-
workerId,
|
|
179
|
-
applicationId,
|
|
180
|
-
elu: health.elu,
|
|
181
|
-
heapUsed: health.heapUsed,
|
|
182
|
-
heapTotal: health.heapTotal
|
|
183
|
-
})
|
|
184
|
-
|
|
185
|
-
if (health.elu > scaleConfig.scaleUpELU) {
|
|
186
|
-
shouldCheckForScaling = true
|
|
187
|
-
}
|
|
188
|
-
} catch (err) {
|
|
189
|
-
this.#runtime.logger.error({ err }, 'Failed to get health for worker')
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
if (shouldCheckForScaling) {
|
|
194
|
-
await this.#checkScaling()
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
this.#healthCheckTimeout.refresh()
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
async #checkScaling () {
|
|
201
|
-
const isInCooldown = Date.now() < this.#lastScaling + this.#cooldown
|
|
202
|
-
if (this.#isScaling || isInCooldown) {
|
|
203
|
-
return
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
this.#isScaling = true
|
|
207
|
-
|
|
208
|
-
try {
|
|
209
|
-
const workersInfo = await this.#runtime.getWorkers()
|
|
210
|
-
const mem = await getMemoryInfo({ scope: this.#memoryInfo.scope })
|
|
211
|
-
|
|
212
|
-
const appsWorkersInfo = {}
|
|
213
|
-
for (const worker of Object.values(workersInfo)) {
|
|
214
|
-
if (worker.status === 'exited') {
|
|
215
|
-
continue
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
const applicationId = worker.application
|
|
219
|
-
appsWorkersInfo[applicationId] ??= 0
|
|
220
|
-
appsWorkersInfo[applicationId]++
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const availableMemory = this.#maxTotalMemory - mem.used
|
|
224
|
-
const recommendations = this.#algorithm.getRecommendations(appsWorkersInfo, { availableMemory })
|
|
225
|
-
|
|
226
|
-
if (recommendations.length > 0) {
|
|
227
|
-
await this.#applyRecommendations(recommendations)
|
|
228
|
-
this.#lastScaling = Date.now()
|
|
229
|
-
}
|
|
230
|
-
} catch (err) {
|
|
231
|
-
this.#runtime.logger.error({ err }, 'Failed to scale applications')
|
|
232
|
-
} finally {
|
|
233
|
-
this.#isScaling = false
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
async #applyRecommendations (recommendations) {
|
|
238
|
-
const resourcesUpdates = []
|
|
239
|
-
|
|
240
|
-
for (const recommendation of recommendations) {
|
|
241
|
-
const { applicationId, workersCount, direction } = recommendation
|
|
242
|
-
this.#runtime.logger.info(`Scaling ${direction} the "${applicationId}" app to ${workersCount} workers`)
|
|
243
|
-
|
|
244
|
-
resourcesUpdates.push({ application: applicationId, workers: workersCount })
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
return this.#runtime.updateApplicationsResources(resourcesUpdates)
|
|
248
|
-
}
|
|
249
|
-
}
|