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}`,
|
: `Set port. Defaults to ${defaultPort}`,
|
||||||
})
|
})
|
||||||
.option(`cluster`, {
|
.option(`cluster`, {
|
||||||
type: `number`,
|
type: `string`,
|
||||||
describe:
|
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(
|
handler: handlerP(
|
||||||
getCommandHandler(`start`, (args, cmd) => {
|
getCommandHandler(`start`, (args, cmd) => {
|
||||||
|
|||||||
@@ -26,6 +26,32 @@ const EVERY_SIXTH_HOUR = "0 */6 * * *"
|
|||||||
const CRON_SCHEDULE = EVERY_SIXTH_HOUR
|
const CRON_SCHEDULE = EVERY_SIXTH_HOUR
|
||||||
const INSTRUMENTATION_FILE = "instrumentation"
|
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
|
* Imports the "instrumentation.js" file from the root of the
|
||||||
* directory and invokes the register function. The existence
|
* directory and invokes the register function. The existence
|
||||||
@@ -142,9 +168,30 @@ async function start(args: {
|
|||||||
host?: string
|
host?: string
|
||||||
port?: number
|
port?: number
|
||||||
types?: boolean
|
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) {
|
async function internalStart(generateTypes: boolean) {
|
||||||
track("CLI_START")
|
track("CLI_START")
|
||||||
@@ -261,12 +308,17 @@ async function start(args: {
|
|||||||
* cluster mode
|
* cluster mode
|
||||||
*/
|
*/
|
||||||
if ("cluster" in args) {
|
if ("cluster" in args) {
|
||||||
const maxCpus = os.cpus().length
|
const cpus = clusterSizeNum
|
||||||
const cpus = args.cluster ?? maxCpus
|
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) {
|
if (cluster.isPrimary) {
|
||||||
let isShuttingDown = false
|
let isShuttingDown = false
|
||||||
const numCPUs = Math.min(maxCpus, cpus)
|
|
||||||
const killMainProccess = () => process.exit(0)
|
const killMainProccess = () => process.exit(0)
|
||||||
const gracefulShutDown = () => {
|
const gracefulShutDown = () => {
|
||||||
isShuttingDown = true
|
isShuttingDown = true
|
||||||
@@ -274,8 +326,14 @@ async function start(args: {
|
|||||||
|
|
||||||
for (let index = 0; index < numCPUs; index++) {
|
for (let index = 0; index < numCPUs; index++) {
|
||||||
const worker = cluster.fork()
|
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.on("online", () => {
|
||||||
worker.send({ index })
|
worker.send({ index, workerMode })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,6 +349,10 @@ async function start(args: {
|
|||||||
process.on("SIGINT", gracefulShutDown)
|
process.on("SIGINT", gracefulShutDown)
|
||||||
} else {
|
} else {
|
||||||
process.on("message", async (msg: any) => {
|
process.on("message", async (msg: any) => {
|
||||||
|
if (msg.workerMode) {
|
||||||
|
process.env.MEDUSA_WORKER_MODE = msg.workerMode
|
||||||
|
}
|
||||||
|
|
||||||
if (msg.index > 0) {
|
if (msg.index > 0) {
|
||||||
process.env.PLUGIN_ADMIN_UI_SKIP_CACHE = "true"
|
process.env.PLUGIN_ADMIN_UI_SKIP_CACHE = "true"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user