feat(cli): servers and workers in cluster mode (#13601)
* feat(cli): servers and workers in cluster mode * allow percentage
This commit is contained in:
committed by
GitHub
parent
cbfe0a4e95
commit
9d8ed70130
6
.changeset/gorgeous-islands-double.md
Normal file
6
.changeset/gorgeous-islands-double.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@medusajs/cli": patch
|
||||
"@medusajs/medusa": patch
|
||||
---
|
||||
|
||||
feat(cli): servers and workers in cluster mode
|
||||
@@ -416,9 +416,19 @@ function buildLocalCommands(cli, isLocalProject) {
|
||||
: `Set port. Defaults to ${defaultPort}`,
|
||||
})
|
||||
.option(`cluster`, {
|
||||
type: `number`,
|
||||
type: `string`,
|
||||
describe:
|
||||
"Start the Node.js server in cluster mode. You can specify the number of cpus to use, which defaults to (env.CPUS)",
|
||||
"Start the Node.js server in cluster mode. Specify the number of CPUs to use or a percentage (e.g., 50%). Defaults to the number of available CPUs.",
|
||||
})
|
||||
.option("workers", {
|
||||
type: "string",
|
||||
default: "0",
|
||||
describe: "Number of worker processes in cluster mode or a percentage of cluster size (e.g., 25%).",
|
||||
})
|
||||
.option("servers", {
|
||||
type: "string",
|
||||
default: "0",
|
||||
describe: "Number of server processes in cluster mode or a percentage of cluster size (e.g., 25%).",
|
||||
}),
|
||||
handler: handlerP(
|
||||
getCommandHandler(`start`, (args, cmd) => {
|
||||
|
||||
@@ -26,6 +26,32 @@ const EVERY_SIXTH_HOUR = "0 */6 * * *"
|
||||
const CRON_SCHEDULE = EVERY_SIXTH_HOUR
|
||||
const INSTRUMENTATION_FILE = "instrumentation"
|
||||
|
||||
function parseValueOrPercentage(value: string, base: number): number {
|
||||
if (typeof value !== "string") {
|
||||
throw new Error(`Invalid value: ${value}. Must be a string.`)
|
||||
}
|
||||
|
||||
const trimmed = value.trim()
|
||||
if (trimmed.endsWith("%")) {
|
||||
const percent = parseFloat(trimmed.slice(0, -1))
|
||||
if (isNaN(percent)) {
|
||||
throw new Error(`Invalid percentage: ${value}`)
|
||||
}
|
||||
if (percent < 0 || percent > 100) {
|
||||
throw new Error(`Percentage must be between 0 and 100: ${value}`)
|
||||
}
|
||||
return Math.round((percent / 100) * base)
|
||||
} else {
|
||||
const num = parseInt(trimmed, 10)
|
||||
if (isNaN(num) || num < 0) {
|
||||
throw new Error(
|
||||
`Invalid number: ${value}. Must be a non-negative integer.`
|
||||
)
|
||||
}
|
||||
return num
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports the "instrumentation.js" file from the root of the
|
||||
* directory and invokes the register function. The existence
|
||||
@@ -142,9 +168,30 @@ async function start(args: {
|
||||
host?: string
|
||||
port?: number
|
||||
types?: boolean
|
||||
cluster?: number
|
||||
cluster?: string
|
||||
workers?: string
|
||||
servers?: string
|
||||
}) {
|
||||
const { port = 9000, host, directory, types } = args
|
||||
const {
|
||||
port = 9000,
|
||||
host,
|
||||
directory,
|
||||
types,
|
||||
cluster: clusterSize,
|
||||
workers,
|
||||
servers,
|
||||
} = args
|
||||
|
||||
const maxCpus = os.cpus().length
|
||||
const clusterSizeNum = clusterSize
|
||||
? parseValueOrPercentage(clusterSize, maxCpus)
|
||||
: maxCpus
|
||||
const serversCount = servers
|
||||
? parseValueOrPercentage(servers, clusterSizeNum)
|
||||
: 0
|
||||
const workersCount = workers
|
||||
? parseValueOrPercentage(workers, clusterSizeNum)
|
||||
: 0
|
||||
|
||||
async function internalStart(generateTypes: boolean) {
|
||||
track("CLI_START")
|
||||
@@ -261,12 +308,17 @@ async function start(args: {
|
||||
* cluster mode
|
||||
*/
|
||||
if ("cluster" in args) {
|
||||
const maxCpus = os.cpus().length
|
||||
const cpus = args.cluster ?? maxCpus
|
||||
const cpus = clusterSizeNum
|
||||
const numCPUs = Math.min(maxCpus, cpus)
|
||||
|
||||
if (serversCount + workersCount > numCPUs) {
|
||||
throw new Error(
|
||||
`Sum of servers (${serversCount}) and workers (${workersCount}) cannot exceed cluster size (${numCPUs})`
|
||||
)
|
||||
}
|
||||
|
||||
if (cluster.isPrimary) {
|
||||
let isShuttingDown = false
|
||||
const numCPUs = Math.min(maxCpus, cpus)
|
||||
const killMainProccess = () => process.exit(0)
|
||||
const gracefulShutDown = () => {
|
||||
isShuttingDown = true
|
||||
@@ -274,8 +326,14 @@ async function start(args: {
|
||||
|
||||
for (let index = 0; index < numCPUs; index++) {
|
||||
const worker = cluster.fork()
|
||||
let workerMode: "server" | "worker" | "shared" = "shared"
|
||||
if (index < serversCount) {
|
||||
workerMode = "server"
|
||||
} else if (index < serversCount + workersCount) {
|
||||
workerMode = "worker"
|
||||
}
|
||||
worker.on("online", () => {
|
||||
worker.send({ index })
|
||||
worker.send({ index, workerMode })
|
||||
})
|
||||
}
|
||||
|
||||
@@ -291,6 +349,10 @@ async function start(args: {
|
||||
process.on("SIGINT", gracefulShutDown)
|
||||
} else {
|
||||
process.on("message", async (msg: any) => {
|
||||
if (msg.workerMode) {
|
||||
process.env.MEDUSA_WORKER_MODE = msg.workerMode
|
||||
}
|
||||
|
||||
if (msg.index > 0) {
|
||||
process.env.PLUGIN_ADMIN_UI_SKIP_CACHE = "true"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user