@platformatic/runtime 3.9.0 → 3.10.0
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 +32 -1
- package/lib/config.js +15 -9
- package/lib/runtime.js +144 -24
- package/lib/scaling-algorithm.js +28 -3
- package/lib/worker/symbols.js +1 -0
- package/package.json +15 -15
- package/schema.json +180 -13
package/config.d.ts
CHANGED
|
@@ -31,10 +31,39 @@ export type PlatformaticRuntimeConfig = {
|
|
|
31
31
|
maxHeapTotal?: number | string;
|
|
32
32
|
maxYoungGeneration?: number | string;
|
|
33
33
|
};
|
|
34
|
-
preload?: string | string[];
|
|
35
34
|
dependencies?: string[];
|
|
36
35
|
arguments?: string[];
|
|
36
|
+
env?: {
|
|
37
|
+
[k: string]: string;
|
|
38
|
+
};
|
|
39
|
+
envfile?: string;
|
|
40
|
+
sourceMaps?: boolean;
|
|
41
|
+
packageManager?: "npm" | "pnpm" | "yarn";
|
|
42
|
+
preload?: string | string[];
|
|
37
43
|
nodeOptions?: string;
|
|
44
|
+
permissions?: {
|
|
45
|
+
fs?: {
|
|
46
|
+
read?: string[];
|
|
47
|
+
write?: string[];
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
telemetry?: {
|
|
51
|
+
/**
|
|
52
|
+
* An array of instrumentations loaded if telemetry is enabled
|
|
53
|
+
*/
|
|
54
|
+
instrumentations?: (
|
|
55
|
+
| string
|
|
56
|
+
| {
|
|
57
|
+
package: string;
|
|
58
|
+
exportName?: string;
|
|
59
|
+
options?: {
|
|
60
|
+
[k: string]: unknown;
|
|
61
|
+
};
|
|
62
|
+
[k: string]: unknown;
|
|
63
|
+
}
|
|
64
|
+
)[];
|
|
65
|
+
[k: string]: unknown;
|
|
66
|
+
};
|
|
38
67
|
};
|
|
39
68
|
};
|
|
40
69
|
};
|
|
@@ -255,6 +284,7 @@ export type PlatformaticRuntimeConfig = {
|
|
|
255
284
|
};
|
|
256
285
|
};
|
|
257
286
|
plugins?: string[];
|
|
287
|
+
timeout?: number | string;
|
|
258
288
|
};
|
|
259
289
|
telemetry?: {
|
|
260
290
|
enabled?: boolean | string;
|
|
@@ -343,6 +373,7 @@ export type PlatformaticRuntimeConfig = {
|
|
|
343
373
|
timeWindowSec?: number;
|
|
344
374
|
cooldownSec?: number;
|
|
345
375
|
scaleIntervalSec?: number;
|
|
376
|
+
gracePeriod?: number;
|
|
346
377
|
applications?: {
|
|
347
378
|
[k: string]: {
|
|
348
379
|
minWorkers?: number;
|
package/lib/config.js
CHANGED
|
@@ -45,7 +45,7 @@ function raiseInvalidWorkersError (location, received, hint) {
|
|
|
45
45
|
throw new InvalidArgumentError(`${location} workers must be a positive integer; received "${received}"${extra}`)
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
export function
|
|
48
|
+
export function pprofCapturePreloadPath () {
|
|
49
49
|
const require = createRequire(import.meta.url)
|
|
50
50
|
|
|
51
51
|
let pprofCapturePath
|
|
@@ -55,6 +55,12 @@ export function autoDetectPprofCapture (config) {
|
|
|
55
55
|
// No-op
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
return pprofCapturePath
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function autoDetectPprofCapture (config) {
|
|
62
|
+
const pprofCapturePath = pprofCapturePreloadPath()
|
|
63
|
+
|
|
58
64
|
// Add to preload if not already present
|
|
59
65
|
if (!config.preload) {
|
|
60
66
|
config.preload = []
|
|
@@ -192,14 +198,7 @@ export async function transform (config, _, context) {
|
|
|
192
198
|
config = join(entryPath, configFilename)
|
|
193
199
|
}
|
|
194
200
|
|
|
195
|
-
const application = {
|
|
196
|
-
id,
|
|
197
|
-
config,
|
|
198
|
-
path: entryPath,
|
|
199
|
-
useHttp: !!mapping.useHttp,
|
|
200
|
-
health: mapping.health,
|
|
201
|
-
dependencies: mapping.dependencies
|
|
202
|
-
}
|
|
201
|
+
const application = { id, config, path: entryPath, ...mapping }
|
|
203
202
|
const existingApplicationId = applications.findIndex(application => application.id === id)
|
|
204
203
|
|
|
205
204
|
if (existingApplicationId !== -1) {
|
|
@@ -336,6 +335,13 @@ export async function transform (config, _, context) {
|
|
|
336
335
|
// like adding other applications.
|
|
337
336
|
}
|
|
338
337
|
|
|
338
|
+
if (config.metrics === true) {
|
|
339
|
+
config.metrics = {
|
|
340
|
+
enabled: true,
|
|
341
|
+
timeout: 1000
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
339
345
|
config.applications = applications
|
|
340
346
|
config.web = undefined
|
|
341
347
|
config.services = undefined
|
package/lib/runtime.js
CHANGED
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
kTimeout,
|
|
10
10
|
parseMemorySize
|
|
11
11
|
} from '@platformatic/foundation'
|
|
12
|
-
import os from 'node:os'
|
|
13
12
|
import { ITC } from '@platformatic/itc'
|
|
14
13
|
import fastify from 'fastify'
|
|
15
14
|
import { EventEmitter, once } from 'node:events'
|
|
@@ -17,13 +16,15 @@ import { existsSync } from 'node:fs'
|
|
|
17
16
|
import { readFile } from 'node:fs/promises'
|
|
18
17
|
import { STATUS_CODES } from 'node:http'
|
|
19
18
|
import { createRequire } from 'node:module'
|
|
20
|
-
import
|
|
19
|
+
import os from 'node:os'
|
|
20
|
+
import { dirname, isAbsolute, join } from 'node:path'
|
|
21
21
|
import { setImmediate as immediate, setTimeout as sleep } from 'node:timers/promises'
|
|
22
22
|
import { pathToFileURL } from 'node:url'
|
|
23
23
|
import { Worker } from 'node:worker_threads'
|
|
24
24
|
import SonicBoom from 'sonic-boom'
|
|
25
25
|
import { Agent, request, interceptors as undiciInterceptors } from 'undici'
|
|
26
26
|
import { createThreadInterceptor } from 'undici-thread-interceptor'
|
|
27
|
+
import { pprofCapturePreloadPath } from './config.js'
|
|
27
28
|
import {
|
|
28
29
|
ApplicationAlreadyStartedError,
|
|
29
30
|
ApplicationNotFoundError,
|
|
@@ -40,12 +41,12 @@ import {
|
|
|
40
41
|
import { abstractLogger, createLogger } from './logger.js'
|
|
41
42
|
import { startManagementApi } from './management-api.js'
|
|
42
43
|
import { startPrometheusServer } from './prom-server.js'
|
|
44
|
+
import ScalingAlgorithm from './scaling-algorithm.js'
|
|
43
45
|
import { startScheduler } from './scheduler.js'
|
|
44
46
|
import { createSharedStore } from './shared-http-cache.js'
|
|
45
47
|
import { version } from './version.js'
|
|
46
48
|
import { sendViaITC, waitEventFromITC } from './worker/itc.js'
|
|
47
49
|
import { RoundRobinMap } from './worker/round-robin-map.js'
|
|
48
|
-
import ScalingAlgorithm from './scaling-algorithm.js'
|
|
49
50
|
import {
|
|
50
51
|
kApplicationId,
|
|
51
52
|
kConfig,
|
|
@@ -57,7 +58,8 @@ import {
|
|
|
57
58
|
kStderrMarker,
|
|
58
59
|
kWorkerId,
|
|
59
60
|
kWorkersBroadcast,
|
|
60
|
-
kWorkerStatus
|
|
61
|
+
kWorkerStatus,
|
|
62
|
+
kWorkerStartTime
|
|
61
63
|
} from './worker/symbols.js'
|
|
62
64
|
|
|
63
65
|
const kWorkerFile = join(import.meta.dirname, 'worker/main.js')
|
|
@@ -277,7 +279,7 @@ export class Runtime extends EventEmitter {
|
|
|
277
279
|
}
|
|
278
280
|
|
|
279
281
|
if (this.#config.verticalScaler?.enabled) {
|
|
280
|
-
this.#setupVerticalScaler()
|
|
282
|
+
await this.#setupVerticalScaler()
|
|
281
283
|
}
|
|
282
284
|
|
|
283
285
|
this.#showUrl()
|
|
@@ -891,8 +893,12 @@ export class Runtime extends EventEmitter {
|
|
|
891
893
|
continue
|
|
892
894
|
}
|
|
893
895
|
|
|
894
|
-
const applicationMetrics = await
|
|
895
|
-
|
|
896
|
+
const applicationMetrics = await executeWithTimeout(
|
|
897
|
+
sendViaITC(worker, 'getMetrics', format),
|
|
898
|
+
this.#config.metrics?.timeout ?? 10000
|
|
899
|
+
)
|
|
900
|
+
|
|
901
|
+
if (applicationMetrics && applicationMetrics !== kTimeout) {
|
|
896
902
|
if (metrics === null) {
|
|
897
903
|
metrics = format === 'json' ? [] : ''
|
|
898
904
|
}
|
|
@@ -1315,6 +1321,17 @@ export class Runtime extends EventEmitter {
|
|
|
1315
1321
|
execArgv.push('--enable-source-maps')
|
|
1316
1322
|
}
|
|
1317
1323
|
|
|
1324
|
+
if (applicationConfig.permissions?.fs) {
|
|
1325
|
+
execArgv.push(...this.#setupPermissions(applicationConfig))
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
let preload = config.preload
|
|
1329
|
+
if (execArgv.includes('--permission')) {
|
|
1330
|
+
// Remove wattpm-pprof-capture from preload since it is not supported
|
|
1331
|
+
const pprofCapturePath = pprofCapturePreloadPath()
|
|
1332
|
+
preload = preload.filter(p => p !== pprofCapturePath)
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1318
1335
|
const workerEnv = structuredClone(this.#env)
|
|
1319
1336
|
|
|
1320
1337
|
if (applicationConfig.nodeOptions?.trim().length > 0) {
|
|
@@ -1337,7 +1354,10 @@ export class Runtime extends EventEmitter {
|
|
|
1337
1354
|
|
|
1338
1355
|
const worker = new Worker(kWorkerFile, {
|
|
1339
1356
|
workerData: {
|
|
1340
|
-
config
|
|
1357
|
+
config: {
|
|
1358
|
+
...config,
|
|
1359
|
+
preload
|
|
1360
|
+
},
|
|
1341
1361
|
applicationConfig: {
|
|
1342
1362
|
...applicationConfig,
|
|
1343
1363
|
isProduction: this.#isProduction,
|
|
@@ -1642,6 +1662,8 @@ export class Runtime extends EventEmitter {
|
|
|
1642
1662
|
}
|
|
1643
1663
|
|
|
1644
1664
|
worker[kWorkerStatus] = 'started'
|
|
1665
|
+
worker[kWorkerStartTime] = Date.now()
|
|
1666
|
+
|
|
1645
1667
|
this.emitAndNotify('application:worker:started', eventPayload)
|
|
1646
1668
|
this.#broadcastWorkers()
|
|
1647
1669
|
|
|
@@ -2441,9 +2463,14 @@ export class Runtime extends EventEmitter {
|
|
|
2441
2463
|
}
|
|
2442
2464
|
}
|
|
2443
2465
|
|
|
2444
|
-
#setupVerticalScaler () {
|
|
2445
|
-
const
|
|
2446
|
-
if (
|
|
2466
|
+
async #setupVerticalScaler () {
|
|
2467
|
+
const fixedWorkersCount = this.#config.workers
|
|
2468
|
+
if (fixedWorkersCount !== undefined) {
|
|
2469
|
+
this.logger.warn(
|
|
2470
|
+
`Vertical scaler disabled because the "workers" configuration is set to ${fixedWorkersCount}`
|
|
2471
|
+
)
|
|
2472
|
+
return
|
|
2473
|
+
}
|
|
2447
2474
|
|
|
2448
2475
|
const scalerConfig = this.#config.verticalScaler
|
|
2449
2476
|
|
|
@@ -2456,6 +2483,7 @@ export class Runtime extends EventEmitter {
|
|
|
2456
2483
|
scalerConfig.minELUDiff ??= 0.2
|
|
2457
2484
|
scalerConfig.scaleIntervalSec ??= 60
|
|
2458
2485
|
scalerConfig.timeWindowSec ??= 60
|
|
2486
|
+
scalerConfig.gracePeriod ??= 30 * 1000
|
|
2459
2487
|
scalerConfig.applications ??= {}
|
|
2460
2488
|
|
|
2461
2489
|
const maxTotalWorkers = scalerConfig.maxTotalWorkers
|
|
@@ -2468,9 +2496,18 @@ export class Runtime extends EventEmitter {
|
|
|
2468
2496
|
const scaleIntervalSec = scalerConfig.scaleIntervalSec
|
|
2469
2497
|
const timeWindowSec = scalerConfig.timeWindowSec
|
|
2470
2498
|
const applicationsConfigs = scalerConfig.applications
|
|
2499
|
+
const gracePeriod = scalerConfig.gracePeriod
|
|
2500
|
+
const healthCheckInterval = 1000
|
|
2501
|
+
|
|
2502
|
+
const initialResourcesUpdates = []
|
|
2471
2503
|
|
|
2472
2504
|
for (const application of this.#config.applications) {
|
|
2473
2505
|
if (application.entrypoint && !features.node.reusePort) {
|
|
2506
|
+
this.logger.warn(
|
|
2507
|
+
`The "${application.id}" application cannot be scaled because it is an entrypoint` +
|
|
2508
|
+
' and the "reusePort" feature is not available in your OS.'
|
|
2509
|
+
)
|
|
2510
|
+
|
|
2474
2511
|
applicationsConfigs[application.id] = {
|
|
2475
2512
|
minWorkers: 1,
|
|
2476
2513
|
maxWorkers: 1
|
|
@@ -2478,6 +2515,10 @@ export class Runtime extends EventEmitter {
|
|
|
2478
2515
|
continue
|
|
2479
2516
|
}
|
|
2480
2517
|
if (application.workers !== undefined) {
|
|
2518
|
+
this.logger.warn(
|
|
2519
|
+
`The "${application.id}" application cannot be scaled because` +
|
|
2520
|
+
` it has a fixed number of workers (${application.workers}).`
|
|
2521
|
+
)
|
|
2481
2522
|
applicationsConfigs[application.id] = {
|
|
2482
2523
|
minWorkers: application.workers,
|
|
2483
2524
|
maxWorkers: application.workers
|
|
@@ -2488,12 +2529,22 @@ export class Runtime extends EventEmitter {
|
|
|
2488
2529
|
applicationsConfigs[application.id] ??= {}
|
|
2489
2530
|
applicationsConfigs[application.id].minWorkers ??= minWorkers
|
|
2490
2531
|
applicationsConfigs[application.id].maxWorkers ??= maxWorkers
|
|
2532
|
+
|
|
2533
|
+
const appMinWorkers = applicationsConfigs[application.id].minWorkers
|
|
2534
|
+
if (appMinWorkers > 1) {
|
|
2535
|
+
initialResourcesUpdates.push({
|
|
2536
|
+
application: application.id,
|
|
2537
|
+
workers: minWorkers
|
|
2538
|
+
})
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
|
|
2542
|
+
if (initialResourcesUpdates.length > 0) {
|
|
2543
|
+
await this.updateApplicationsResources(initialResourcesUpdates)
|
|
2491
2544
|
}
|
|
2492
2545
|
|
|
2493
2546
|
for (const applicationId in applicationsConfigs) {
|
|
2494
|
-
const application = this.#config.applications.find(
|
|
2495
|
-
app => app.id === applicationId
|
|
2496
|
-
)
|
|
2547
|
+
const application = this.#config.applications.find(app => app.id === applicationId)
|
|
2497
2548
|
if (!application) {
|
|
2498
2549
|
delete applicationsConfigs[applicationId]
|
|
2499
2550
|
|
|
@@ -2512,18 +2563,43 @@ export class Runtime extends EventEmitter {
|
|
|
2512
2563
|
applications: applicationsConfigs
|
|
2513
2564
|
})
|
|
2514
2565
|
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
}
|
|
2566
|
+
const healthCheckTimeout = setTimeout(async () => {
|
|
2567
|
+
let shouldCheckForScaling = false
|
|
2568
|
+
|
|
2569
|
+
const now = Date.now()
|
|
2520
2570
|
|
|
2521
|
-
|
|
2571
|
+
for (const worker of this.#workers.values()) {
|
|
2572
|
+
if (
|
|
2573
|
+
worker[kWorkerStatus] !== 'started' ||
|
|
2574
|
+
worker[kWorkerStartTime] + gracePeriod > now
|
|
2575
|
+
) {
|
|
2576
|
+
continue
|
|
2577
|
+
}
|
|
2578
|
+
|
|
2579
|
+
try {
|
|
2580
|
+
const health = await this.#getHealth(worker)
|
|
2581
|
+
if (!health) continue
|
|
2582
|
+
|
|
2583
|
+
scalingAlgorithm.addWorkerHealthInfo({
|
|
2584
|
+
workerId: worker[kId],
|
|
2585
|
+
applicationId: worker[kApplicationId],
|
|
2586
|
+
elu: health.elu
|
|
2587
|
+
})
|
|
2522
2588
|
|
|
2523
|
-
|
|
2589
|
+
if (health.elu > scaleUpELU) {
|
|
2590
|
+
shouldCheckForScaling = true
|
|
2591
|
+
}
|
|
2592
|
+
} catch (err) {
|
|
2593
|
+
this.logger.error({ err }, 'Failed to get health for worker')
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
|
|
2597
|
+
if (shouldCheckForScaling) {
|
|
2524
2598
|
await checkForScaling()
|
|
2525
2599
|
}
|
|
2526
|
-
|
|
2600
|
+
|
|
2601
|
+
healthCheckTimeout.refresh()
|
|
2602
|
+
}, healthCheckInterval).unref()
|
|
2527
2603
|
|
|
2528
2604
|
let isScaling = false
|
|
2529
2605
|
let lastScaling = 0
|
|
@@ -2548,16 +2624,16 @@ export class Runtime extends EventEmitter {
|
|
|
2548
2624
|
const recommendations = scalingAlgorithm.getRecommendations(appsWorkersInfo)
|
|
2549
2625
|
if (recommendations.length > 0) {
|
|
2550
2626
|
await applyRecommendations(recommendations)
|
|
2627
|
+
lastScaling = Date.now()
|
|
2551
2628
|
}
|
|
2552
2629
|
} catch (err) {
|
|
2553
2630
|
this.logger.error({ err }, 'Failed to scale applications')
|
|
2554
2631
|
} finally {
|
|
2555
2632
|
isScaling = false
|
|
2556
|
-
lastScaling = Date.now()
|
|
2557
2633
|
}
|
|
2558
2634
|
}
|
|
2559
2635
|
|
|
2560
|
-
const applyRecommendations = async
|
|
2636
|
+
const applyRecommendations = async recommendations => {
|
|
2561
2637
|
const resourcesUpdates = []
|
|
2562
2638
|
for (const recommendation of recommendations) {
|
|
2563
2639
|
const { applicationId, workersCount, direction } = recommendation
|
|
@@ -2574,4 +2650,48 @@ export class Runtime extends EventEmitter {
|
|
|
2574
2650
|
// Interval for periodic scaling checks
|
|
2575
2651
|
setInterval(checkForScaling, scaleIntervalSec * 1000).unref()
|
|
2576
2652
|
}
|
|
2653
|
+
|
|
2654
|
+
#setupPermissions (applicationConfig) {
|
|
2655
|
+
const argv = []
|
|
2656
|
+
const allows = new Set()
|
|
2657
|
+
const { read, write } = applicationConfig.permissions.fs
|
|
2658
|
+
|
|
2659
|
+
if (read?.length) {
|
|
2660
|
+
for (const p of read) {
|
|
2661
|
+
allows.add(`--allow-fs-read=${isAbsolute(p) ? p : join(applicationConfig.path, p)}`)
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
|
|
2665
|
+
if (write?.length) {
|
|
2666
|
+
for (const p of write) {
|
|
2667
|
+
allows.add(`--allow-fs-write=${isAbsolute(p) ? p : join(applicationConfig.path, p)}`)
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
|
|
2671
|
+
if (allows.size === 0) {
|
|
2672
|
+
return argv
|
|
2673
|
+
}
|
|
2674
|
+
|
|
2675
|
+
// We need to allow read access to the node_modules folder both at the runtime level and at the application level
|
|
2676
|
+
allows.add(`--allow-fs-read=${join(this.#root, 'node_modules', '*')}`)
|
|
2677
|
+
allows.add(`--allow-fs-read=${join(applicationConfig.path, 'node_modules', '*')}`)
|
|
2678
|
+
|
|
2679
|
+
// Since we can't really predict how dependencies are installed (symlinks, pnpm store, and so forth), we also
|
|
2680
|
+
// add any node_modules folder found in the ancestors of the current file
|
|
2681
|
+
let lastPath = import.meta.dirname
|
|
2682
|
+
let currentPath = import.meta.dirname
|
|
2683
|
+
|
|
2684
|
+
do {
|
|
2685
|
+
lastPath = currentPath
|
|
2686
|
+
const nodeModules = join(currentPath, 'node_modules')
|
|
2687
|
+
if (existsSync(nodeModules)) {
|
|
2688
|
+
allows.add(`--allow-fs-read=${join(nodeModules, '*')}`)
|
|
2689
|
+
}
|
|
2690
|
+
|
|
2691
|
+
currentPath = dirname(currentPath)
|
|
2692
|
+
} while (lastPath !== currentPath)
|
|
2693
|
+
|
|
2694
|
+
argv.push('--permission', ...allows)
|
|
2695
|
+
return argv
|
|
2696
|
+
}
|
|
2577
2697
|
}
|
package/lib/scaling-algorithm.js
CHANGED
|
@@ -19,9 +19,7 @@ class ScalingAlgorithm {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
addWorkerHealthInfo (healthInfo) {
|
|
22
|
-
const workerId = healthInfo
|
|
23
|
-
const applicationId = healthInfo.application
|
|
24
|
-
const elu = healthInfo.currentHealth.elu
|
|
22
|
+
const { workerId, applicationId, elu } = healthInfo
|
|
25
23
|
const timestamp = Date.now()
|
|
26
24
|
|
|
27
25
|
if (!this.#appsELUs[applicationId]) {
|
|
@@ -59,6 +57,27 @@ class ScalingAlgorithm {
|
|
|
59
57
|
|
|
60
58
|
for (const { applicationId, elu, workersCount } of appsInfo) {
|
|
61
59
|
const appMinWorkers = this.#appsConfigs[applicationId]?.minWorkers ?? 1
|
|
60
|
+
const appMaxWorkers = this.#appsConfigs[applicationId]?.maxWorkers ?? this.#maxTotalWorkers
|
|
61
|
+
|
|
62
|
+
if (workersCount < appMinWorkers) {
|
|
63
|
+
recommendations.push({
|
|
64
|
+
applicationId,
|
|
65
|
+
workersCount: appMinWorkers,
|
|
66
|
+
direction: 'up'
|
|
67
|
+
})
|
|
68
|
+
totalWorkersCount += appMinWorkers - workersCount
|
|
69
|
+
continue
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (workersCount > appMaxWorkers) {
|
|
73
|
+
recommendations.push({
|
|
74
|
+
applicationId,
|
|
75
|
+
workersCount: appMaxWorkers,
|
|
76
|
+
direction: 'down'
|
|
77
|
+
})
|
|
78
|
+
totalWorkersCount -= workersCount - appMaxWorkers
|
|
79
|
+
continue
|
|
80
|
+
}
|
|
62
81
|
|
|
63
82
|
if (elu < this.#scaleDownELU && workersCount > appMinWorkers) {
|
|
64
83
|
recommendations.push({
|
|
@@ -75,6 +94,12 @@ class ScalingAlgorithm {
|
|
|
75
94
|
|
|
76
95
|
const { applicationId, workersCount } = scaleUpCandidate
|
|
77
96
|
|
|
97
|
+
const isScaled = recommendations.some(
|
|
98
|
+
r => r.applicationId === applicationId &&
|
|
99
|
+
r.direction === 'up'
|
|
100
|
+
)
|
|
101
|
+
if (isScaled) continue
|
|
102
|
+
|
|
78
103
|
const appMaxWorkers = this.#appsConfigs[applicationId]?.maxWorkers ?? this.#maxTotalWorkers
|
|
79
104
|
if (workersCount >= appMaxWorkers) continue
|
|
80
105
|
|
package/lib/worker/symbols.js
CHANGED
|
@@ -6,6 +6,7 @@ export const kWorkerId = Symbol.for('plt.runtime.worker.id')
|
|
|
6
6
|
export const kITC = Symbol.for('plt.runtime.itc')
|
|
7
7
|
export const kHealthCheckTimer = Symbol.for('plt.runtime.worker.healthCheckTimer')
|
|
8
8
|
export const kWorkerStatus = Symbol('plt.runtime.worker.status')
|
|
9
|
+
export const kWorkerStartTime = Symbol.for('plt.runtime.worker.startTime')
|
|
9
10
|
export const kInterceptors = Symbol.for('plt.runtime.worker.interceptors')
|
|
10
11
|
export const kLastELU = Symbol.for('plt.runtime.worker.lastELU')
|
|
11
12
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/runtime",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.10.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -35,14 +35,14 @@
|
|
|
35
35
|
"typescript": "^5.5.4",
|
|
36
36
|
"undici-oidc-interceptor": "^0.5.0",
|
|
37
37
|
"why-is-node-running": "^2.2.2",
|
|
38
|
-
"@platformatic/composer": "3.
|
|
39
|
-
"@platformatic/db": "3.
|
|
40
|
-
"@platformatic/
|
|
41
|
-
"@platformatic/
|
|
42
|
-
"@platformatic/
|
|
43
|
-
"@platformatic/sql-
|
|
44
|
-
"@platformatic/
|
|
45
|
-
"@platformatic/wattpm-pprof-capture": "3.
|
|
38
|
+
"@platformatic/composer": "3.10.0",
|
|
39
|
+
"@platformatic/db": "3.10.0",
|
|
40
|
+
"@platformatic/node": "3.10.0",
|
|
41
|
+
"@platformatic/gateway": "3.10.0",
|
|
42
|
+
"@platformatic/service": "3.10.0",
|
|
43
|
+
"@platformatic/sql-graphql": "3.10.0",
|
|
44
|
+
"@platformatic/sql-mapper": "3.10.0",
|
|
45
|
+
"@platformatic/wattpm-pprof-capture": "3.10.0"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@fastify/accepts": "^5.0.0",
|
|
@@ -72,12 +72,12 @@
|
|
|
72
72
|
"undici": "^7.0.0",
|
|
73
73
|
"undici-thread-interceptor": "^0.14.0",
|
|
74
74
|
"ws": "^8.16.0",
|
|
75
|
-
"@platformatic/
|
|
76
|
-
"@platformatic/
|
|
77
|
-
"@platformatic/itc": "3.
|
|
78
|
-
"@platformatic/
|
|
79
|
-
"@platformatic/metrics": "3.
|
|
80
|
-
"@platformatic/telemetry": "3.
|
|
75
|
+
"@platformatic/basic": "3.10.0",
|
|
76
|
+
"@platformatic/foundation": "3.10.0",
|
|
77
|
+
"@platformatic/itc": "3.10.0",
|
|
78
|
+
"@platformatic/generators": "3.10.0",
|
|
79
|
+
"@platformatic/metrics": "3.10.0",
|
|
80
|
+
"@platformatic/telemetry": "3.10.0"
|
|
81
81
|
},
|
|
82
82
|
"engines": {
|
|
83
83
|
"node": ">=22.19.0"
|
package/schema.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$id": "https://schemas.platformatic.dev/@platformatic/runtime/3.
|
|
2
|
+
"$id": "https://schemas.platformatic.dev/@platformatic/runtime/3.10.0.json",
|
|
3
3
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
4
4
|
"title": "Platformatic Runtime Config",
|
|
5
5
|
"type": "object",
|
|
@@ -171,6 +171,40 @@
|
|
|
171
171
|
},
|
|
172
172
|
"additionalProperties": false
|
|
173
173
|
},
|
|
174
|
+
"dependencies": {
|
|
175
|
+
"type": "array",
|
|
176
|
+
"items": {
|
|
177
|
+
"type": "string"
|
|
178
|
+
},
|
|
179
|
+
"default": []
|
|
180
|
+
},
|
|
181
|
+
"arguments": {
|
|
182
|
+
"type": "array",
|
|
183
|
+
"items": {
|
|
184
|
+
"type": "string"
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
"env": {
|
|
188
|
+
"type": "object",
|
|
189
|
+
"additionalProperties": {
|
|
190
|
+
"type": "string"
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
"envfile": {
|
|
194
|
+
"type": "string"
|
|
195
|
+
},
|
|
196
|
+
"sourceMaps": {
|
|
197
|
+
"type": "boolean",
|
|
198
|
+
"default": false
|
|
199
|
+
},
|
|
200
|
+
"packageManager": {
|
|
201
|
+
"type": "string",
|
|
202
|
+
"enum": [
|
|
203
|
+
"npm",
|
|
204
|
+
"pnpm",
|
|
205
|
+
"yarn"
|
|
206
|
+
]
|
|
207
|
+
},
|
|
174
208
|
"preload": {
|
|
175
209
|
"anyOf": [
|
|
176
210
|
{
|
|
@@ -186,20 +220,66 @@
|
|
|
186
220
|
}
|
|
187
221
|
]
|
|
188
222
|
},
|
|
189
|
-
"dependencies": {
|
|
190
|
-
"type": "array",
|
|
191
|
-
"items": {
|
|
192
|
-
"type": "string"
|
|
193
|
-
}
|
|
194
|
-
},
|
|
195
|
-
"arguments": {
|
|
196
|
-
"type": "array",
|
|
197
|
-
"items": {
|
|
198
|
-
"type": "string"
|
|
199
|
-
}
|
|
200
|
-
},
|
|
201
223
|
"nodeOptions": {
|
|
202
224
|
"type": "string"
|
|
225
|
+
},
|
|
226
|
+
"permissions": {
|
|
227
|
+
"type": "object",
|
|
228
|
+
"properties": {
|
|
229
|
+
"fs": {
|
|
230
|
+
"type": "object",
|
|
231
|
+
"properties": {
|
|
232
|
+
"read": {
|
|
233
|
+
"type": "array",
|
|
234
|
+
"items": {
|
|
235
|
+
"type": "string"
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
"write": {
|
|
239
|
+
"type": "array",
|
|
240
|
+
"items": {
|
|
241
|
+
"type": "string"
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
"additionalProperties": false
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
"additionalProperties": false
|
|
249
|
+
},
|
|
250
|
+
"telemetry": {
|
|
251
|
+
"type": "object",
|
|
252
|
+
"properties": {
|
|
253
|
+
"instrumentations": {
|
|
254
|
+
"type": "array",
|
|
255
|
+
"description": "An array of instrumentations loaded if telemetry is enabled",
|
|
256
|
+
"items": {
|
|
257
|
+
"oneOf": [
|
|
258
|
+
{
|
|
259
|
+
"type": "string"
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
"type": "object",
|
|
263
|
+
"properties": {
|
|
264
|
+
"package": {
|
|
265
|
+
"type": "string"
|
|
266
|
+
},
|
|
267
|
+
"exportName": {
|
|
268
|
+
"type": "string"
|
|
269
|
+
},
|
|
270
|
+
"options": {
|
|
271
|
+
"type": "object",
|
|
272
|
+
"additionalProperties": true
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
"required": [
|
|
276
|
+
"package"
|
|
277
|
+
]
|
|
278
|
+
}
|
|
279
|
+
]
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
203
283
|
}
|
|
204
284
|
}
|
|
205
285
|
}
|
|
@@ -405,6 +485,30 @@
|
|
|
405
485
|
"nodeOptions": {
|
|
406
486
|
"type": "string"
|
|
407
487
|
},
|
|
488
|
+
"permissions": {
|
|
489
|
+
"type": "object",
|
|
490
|
+
"properties": {
|
|
491
|
+
"fs": {
|
|
492
|
+
"type": "object",
|
|
493
|
+
"properties": {
|
|
494
|
+
"read": {
|
|
495
|
+
"type": "array",
|
|
496
|
+
"items": {
|
|
497
|
+
"type": "string"
|
|
498
|
+
}
|
|
499
|
+
},
|
|
500
|
+
"write": {
|
|
501
|
+
"type": "array",
|
|
502
|
+
"items": {
|
|
503
|
+
"type": "string"
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
},
|
|
507
|
+
"additionalProperties": false
|
|
508
|
+
}
|
|
509
|
+
},
|
|
510
|
+
"additionalProperties": false
|
|
511
|
+
},
|
|
408
512
|
"telemetry": {
|
|
409
513
|
"type": "object",
|
|
410
514
|
"properties": {
|
|
@@ -641,6 +745,30 @@
|
|
|
641
745
|
"nodeOptions": {
|
|
642
746
|
"type": "string"
|
|
643
747
|
},
|
|
748
|
+
"permissions": {
|
|
749
|
+
"type": "object",
|
|
750
|
+
"properties": {
|
|
751
|
+
"fs": {
|
|
752
|
+
"type": "object",
|
|
753
|
+
"properties": {
|
|
754
|
+
"read": {
|
|
755
|
+
"type": "array",
|
|
756
|
+
"items": {
|
|
757
|
+
"type": "string"
|
|
758
|
+
}
|
|
759
|
+
},
|
|
760
|
+
"write": {
|
|
761
|
+
"type": "array",
|
|
762
|
+
"items": {
|
|
763
|
+
"type": "string"
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
},
|
|
767
|
+
"additionalProperties": false
|
|
768
|
+
}
|
|
769
|
+
},
|
|
770
|
+
"additionalProperties": false
|
|
771
|
+
},
|
|
644
772
|
"telemetry": {
|
|
645
773
|
"type": "object",
|
|
646
774
|
"properties": {
|
|
@@ -877,6 +1005,30 @@
|
|
|
877
1005
|
"nodeOptions": {
|
|
878
1006
|
"type": "string"
|
|
879
1007
|
},
|
|
1008
|
+
"permissions": {
|
|
1009
|
+
"type": "object",
|
|
1010
|
+
"properties": {
|
|
1011
|
+
"fs": {
|
|
1012
|
+
"type": "object",
|
|
1013
|
+
"properties": {
|
|
1014
|
+
"read": {
|
|
1015
|
+
"type": "array",
|
|
1016
|
+
"items": {
|
|
1017
|
+
"type": "string"
|
|
1018
|
+
}
|
|
1019
|
+
},
|
|
1020
|
+
"write": {
|
|
1021
|
+
"type": "array",
|
|
1022
|
+
"items": {
|
|
1023
|
+
"type": "string"
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
},
|
|
1027
|
+
"additionalProperties": false
|
|
1028
|
+
}
|
|
1029
|
+
},
|
|
1030
|
+
"additionalProperties": false
|
|
1031
|
+
},
|
|
880
1032
|
"telemetry": {
|
|
881
1033
|
"type": "object",
|
|
882
1034
|
"properties": {
|
|
@@ -1691,6 +1843,17 @@
|
|
|
1691
1843
|
}
|
|
1692
1844
|
]
|
|
1693
1845
|
}
|
|
1846
|
+
},
|
|
1847
|
+
"timeout": {
|
|
1848
|
+
"anyOf": [
|
|
1849
|
+
{
|
|
1850
|
+
"type": "integer"
|
|
1851
|
+
},
|
|
1852
|
+
{
|
|
1853
|
+
"type": "string"
|
|
1854
|
+
}
|
|
1855
|
+
],
|
|
1856
|
+
"default": 10000
|
|
1694
1857
|
}
|
|
1695
1858
|
},
|
|
1696
1859
|
"additionalProperties": false
|
|
@@ -1873,6 +2036,10 @@
|
|
|
1873
2036
|
"type": "number",
|
|
1874
2037
|
"minimum": 0
|
|
1875
2038
|
},
|
|
2039
|
+
"gracePeriod": {
|
|
2040
|
+
"type": "number",
|
|
2041
|
+
"minimum": 0
|
|
2042
|
+
},
|
|
1876
2043
|
"applications": {
|
|
1877
2044
|
"type": "object",
|
|
1878
2045
|
"additionalProperties": {
|