diff --git a/docs/guides/server-optimization.md b/docs/guides/server-optimization.md index 156f8e774..93a5ef578 100644 --- a/docs/guides/server-optimization.md +++ b/docs/guides/server-optimization.md @@ -32,7 +32,7 @@ This guide will contain a set of recommended optimizations to make your applicat ### K8s limits -**TL;DR**: provide **1000m - 1150m** CPU limit/request +**TL;DR**: provide **1000m - 1150m** CPU limit/request, run Node.js with `--max-old-space-size=(0.75% * memory.limit)` parameter Low CPU limits can significantly slow down your application response time because of CPU throttling. More information why: - https://medium.com/pipedrive-engineering/how-we-choked-our-kubernetes-nodejs-services-932acc8cc2be @@ -42,6 +42,24 @@ Node.js is single-threaded, but can use multiple threads for Garbage Collector o There is a one disadvantage - with this CPU limits, if you maintain a sufficient number of instances with good latency and throughput, you may experience poor CPU utilization. Nevertheless, this represents a trade-off between effective CPU usage and low latency. +With memory limits, to optimize memory optimization and prevent OOM failures, you need to pass [--max-old-space-size](https://nodejs.org/api/cli.html#--max-old-space-sizesize-in-mib) parameter to Node.js options. + +As a start, you can calculate size by the following formula: `0.75 * memory.limit`, or 75% of current container memory limit. Then you can run benchmark and monitor application metrics for a long time, and try to find optimal value. This is recommendation from [Node.js Best Practices](https://github.com/goldbergyoni/nodebestpractices/blob/master/sections/docker/memory-limit.md) guide. + +For setting this parameter you need to run `server.js` using the `node` command with the `--max-old-space-size` parameter. This command is typically located in the `Dockerfile`: + +```Dockerfile title="Dockerfile" +FROM node:20-buster-slim +WORKDIR /app +COPY dist/server /app/ +COPY package.json /app/ +ENV NODE_ENV='production' + +EXPOSE 3000 +// highlight-next-line +CMD [ "node", "--max-old-space-size=350", "/app/server.js" ] +``` + ### Request Limiter **TL;DR**: connect `@tramvai/module-request-limiter` to your application @@ -58,18 +76,25 @@ More information: ### Semi space size -**TL;DR**: set Node.js `--max_semi_space_size` parameter to **64mb** +**TL;DR**: set Node.js `--max-semi-space-size` parameter to **32mb** or **64mb**, run benchmarks, and choose the best balance for CPU usage and memory consumption. + +[--max-semi-space-size documentation](https://nodejs.org/api/cli.html#--max-semi-space-sizesize-in-mib) + +The default value depends on the memory limit. For example, on 64-bit systems +with a memory limit of 512 MiB, the max size of a semi-space defaults to 1 MiB. +On 64-bit systems with a memory limit of 2 GiB, the max size of a semi-space +defaults to 16 MiB. During application performance profiling, you may observe that your code spends a significant amount of time on Garbage Collector (GC) work. By default, GC work too frequently, and we can reduce the number of GC runs by increasing the size of the semi space. This optimization will reduce the CPU workload and make your event loop less busy, resulting in faster response times, especially in the 95th and 99th percentiles. One disadvantage of this optimization is that it will increases the memory usage of your application. **For environments, where memory is limited, for example test deployments, prefer not to use this optimization**. -A good balance between performance and memory usage is achieved with a semi space size of **64mb**. Another possible value for this parameter is **128mb**, but it may not provide a significant improvement in performance and will increase the memory usage of your application. It is recommended to test this parameter in your specific application. +A good balance between performance and memory usage is achieved with a semi space size of **64mb**. It is possible to use event bigger value for this parameter, but it may not provide a significant improvement in performance and will highly increase the memory usage of your application. It is recommended to test this parameter in your specific application. -For setting this parameter you need to run `server.js` using the `node` command with the `--max_semi_space_size` parameter. This command is typically located in the `Dockerfile`: +For setting this parameter you need to run `server.js` using the `node` command with the `--max-semi-space-size` parameter. This command is typically located in the `Dockerfile`: ```Dockerfile title="Dockerfile" -FROM node:18-buster-slim +FROM node:20-buster-slim WORKDIR /app COPY dist/server /app/ COPY package.json /app/ @@ -77,11 +102,12 @@ ENV NODE_ENV='production' EXPOSE 3000 // highlight-next-line -CMD [ "node", "--max_semi_space_size=64", "/app/server.js" ] +CMD [ "node", "--max-semi-space-size=64", "/app/server.js" ] ``` More information: - https://www.alibabacloud.com/blog/better-node-application-performance-through-gc-optimization_595119 +- https://blog.ztec.fr/en/2024/post/node.js-20-upgrade-journey-though-unexpected-heap-issues-with-kubernetes/ - https://github.com/nodejs/node/issues/42511 ### Agent keepAlive diff --git a/packages-versions.json b/packages-versions.json index 186535672..f8dd4bf9f 100644 --- a/packages-versions.json +++ b/packages-versions.json @@ -1,2 +1,2 @@ -{"@tramvai/cli":"5.17.0","@tramvai/swc-integration":"5.17.0","@tinkoff/browser-timings":"0.13.2","@tinkoff/browserslist-config":"0.5.3","@tinkoff/browser-cookies":"5.0.2","@tinkoff/dippy":"0.11.3","@tinkoff/env-validators":"0.4.2","@tinkoff/error-handlers":"0.8.2","@tinkoff/errors":"0.6.2","@tinkoff/eslint-plugin-tramvai":"0.9.2","@tinkoff/hook-runner":"0.7.3","@tramvai/http-client":"0.5.2","@tinkoff/is-modern-lib":"5.0.2","@tramvai/react-lazy-hydration-render":"0.5.2","@tinkoff/logger":"0.10.505","@tinkoff/pack-polyfills":"0.7.3","@tinkoff/measure-express-requests":"5.0.2","@tinkoff/measure-fastify-requests":"0.4.2","@tinkoff/meta-tags-generate":"0.8.3","@tinkoff/metrics-noop":"5.0.2","@tinkoff/minicss-class-generator":"0.5.2","@tinkoff/mocker":"5.0.3","@tinkoff/module-loader-client":"0.7.3","@tinkoff/module-loader-server":"0.8.4","@tinkoff/monkeypatch":"5.0.2","@tinkoff/package-manager-wrapper":"0.4.2","@tinkoff/htmlpagebuilder":"0.8.2","prettier-config-tinkoff":"0.5.2","@tinkoff/pubsub":"0.8.2","@tinkoff/react-hooks":"0.4.2","@tinkoff/router":"0.5.55","@tramvai/safe-strings":"0.8.4","@tinkoff/terminus":"0.4.2","@tinkoff/layout-factory":"0.6.2","@tramvai/tinkoff-request-http-client-adapter":"0.12.55","@tinkoff/url":"0.11.2","@tinkoff/user-agent":"0.7.55","@tinkoff/webpack-dedupe-plugin":"4.0.2","@tramvai/module-autoscroll":"5.17.0","@tramvai/module-cache-warmup":"5.17.0","@tramvai/module-child-app":"5.17.0","@tramvai/module-client-hints":"5.17.0","@tramvai/module-common":"5.17.0","@tramvai/module-cookie":"5.17.0","@tramvai/module-deps-graph":"5.17.0","@tramvai/module-dns-cache":"5.17.0","@tramvai/module-environment":"5.17.0","@tramvai/module-error-interceptor":"5.17.0","@tramvai/module-http-client":"5.17.0","@tramvai/module-http-proxy-agent":"5.17.0","@tramvai/module-log":"5.17.0","@tramvai/module-metrics":"5.17.0","@tramvai/module-micro-sentry":"5.17.0","@tramvai/module-mocker":"5.17.0","@tramvai/module-opentelemetry":"5.17.0","@tramvai/module-page-render-mode":"5.17.0","@tramvai/module-progressive-web-app":"5.17.0","@tramvai/module-react-query":"5.17.0","@tramvai/module-render":"5.17.0","@tramvai/module-request-limiter":"5.17.0","@tramvai/module-router":"5.17.0","@tramvai/module-sentry":"5.17.0","@tramvai/module-seo":"5.17.0","@tramvai/module-server":"5.17.0","@tramvai/tokens-child-app":"5.17.0","@tramvai/tokens-common":"5.17.0","@tramvai/tokens-cookie":"5.17.0","@tramvai/tokens-core":"5.17.0","@tramvai/tokens-core-private":"5.17.0","@tramvai/tokens-http-client":"5.17.0","@tramvai/tokens-metrics":"5.17.0","@tramvai/tokens-react-query":"5.17.0","@tramvai/tokens-render":"5.17.0","@tramvai/tokens-router":"5.17.0","@tramvai/tokens-server":"5.17.0","@tramvai/tokens-server-private":"5.17.0","@tramvai/child-app-core":"5.17.0","@tramvai/core":"5.17.0","@tramvai/experiments":"5.17.0","@tramvai/papi":"5.17.0","@tramvai/pwa-recipes":"5.17.0","@tramvai/react":"5.17.0","@tramvai/react-query":"5.17.0","@tramvai/state":"5.17.0","@tramvai/storybook-addon":"5.17.0","@tramvai/types-actions-state-context":"5.17.0","@tramvai/test-child-app":"5.17.0","@tramvai/test-helpers":"5.17.0","@tramvai/test-integration":"5.17.0","@tramvai/test-integration-jest":"5.17.0","@tramvai/test-jsdom":"5.17.0","@tramvai/test-mocks":"5.17.0","@tramvai/test-pw":"5.17.0","@tramvai/test-puppeteer":"5.17.0","@tramvai/test-react":"5.17.0","@tramvai/test-unit":"5.17.0","@tramvai/test-unit-jest":"5.17.0","@tramvai/build":"6.1.1","@tramvai/tools-check-versions":"0.7.5","@tramvai/create":"5.17.0","@tramvai/tools-generate-schema":"0.4.2","@tramvai/tools-migrate":"0.9.5","@tinkoff-monorepo/depscheck":"3.101.7","@tinkoff-monorepo/fix-ts-references":"3.101.7","@tinkoff-monorepo/pkgs-collector":"3.101.7","@tinkoff-monorepo/pkgs-collector-dir":"3.101.7","@tinkoff-monorepo/pkgs-collector-workspaces":"3.101.7"} +{"@tramvai/cli":"5.18.1","@tramvai/swc-integration":"5.18.1","@tinkoff/browser-timings":"0.13.2","@tinkoff/browserslist-config":"0.5.3","@tinkoff/browser-cookies":"5.0.2","@tinkoff/dippy":"0.11.3","@tinkoff/env-validators":"0.4.2","@tinkoff/error-handlers":"0.8.2","@tinkoff/errors":"0.6.2","@tinkoff/eslint-plugin-tramvai":"0.9.2","@tinkoff/hook-runner":"0.7.3","@tramvai/http-client":"0.5.2","@tinkoff/is-modern-lib":"5.0.2","@tramvai/react-lazy-hydration-render":"0.5.2","@tinkoff/logger":"0.10.505","@tinkoff/pack-polyfills":"0.7.3","@tinkoff/measure-express-requests":"5.0.2","@tinkoff/measure-fastify-requests":"0.4.2","@tinkoff/meta-tags-generate":"0.8.3","@tinkoff/metrics-noop":"5.0.2","@tinkoff/minicss-class-generator":"0.5.2","@tinkoff/mocker":"5.0.3","@tinkoff/module-loader-client":"0.7.3","@tinkoff/module-loader-server":"0.8.4","@tinkoff/monkeypatch":"5.0.2","@tinkoff/package-manager-wrapper":"0.4.2","@tinkoff/htmlpagebuilder":"0.8.2","prettier-config-tinkoff":"0.5.2","@tinkoff/pubsub":"0.8.2","@tinkoff/react-hooks":"0.4.2","@tinkoff/router":"0.5.58","@tramvai/safe-strings":"0.8.4","@tinkoff/terminus":"0.4.2","@tinkoff/layout-factory":"0.6.2","@tramvai/tinkoff-request-http-client-adapter":"0.12.58","@tinkoff/url":"0.11.2","@tinkoff/user-agent":"0.7.58","@tinkoff/webpack-dedupe-plugin":"4.0.2","@tramvai/module-autoscroll":"5.18.1","@tramvai/module-cache-warmup":"5.18.1","@tramvai/module-child-app":"5.18.1","@tramvai/module-client-hints":"5.18.1","@tramvai/module-common":"5.18.1","@tramvai/module-cookie":"5.18.1","@tramvai/module-deps-graph":"5.18.1","@tramvai/module-dns-cache":"5.18.1","@tramvai/module-environment":"5.18.1","@tramvai/module-error-interceptor":"5.18.1","@tramvai/module-http-client":"5.18.1","@tramvai/module-http-proxy-agent":"5.18.1","@tramvai/module-log":"5.18.1","@tramvai/module-metrics":"5.18.1","@tramvai/module-micro-sentry":"5.18.1","@tramvai/module-mocker":"5.18.1","@tramvai/module-opentelemetry":"5.18.1","@tramvai/module-page-render-mode":"5.18.1","@tramvai/module-progressive-web-app":"5.18.1","@tramvai/module-react-query":"5.18.1","@tramvai/module-render":"5.18.1","@tramvai/module-request-limiter":"5.18.1","@tramvai/module-router":"5.18.1","@tramvai/module-sentry":"5.18.1","@tramvai/module-seo":"5.18.1","@tramvai/module-server":"5.18.1","@tramvai/tokens-child-app":"5.18.1","@tramvai/tokens-common":"5.18.1","@tramvai/tokens-cookie":"5.18.1","@tramvai/tokens-core":"5.18.1","@tramvai/tokens-core-private":"5.18.1","@tramvai/tokens-http-client":"5.18.1","@tramvai/tokens-metrics":"5.18.1","@tramvai/tokens-react-query":"5.18.1","@tramvai/tokens-render":"5.18.1","@tramvai/tokens-router":"5.18.1","@tramvai/tokens-server":"5.18.1","@tramvai/tokens-server-private":"5.18.1","@tramvai/child-app-core":"5.18.1","@tramvai/core":"5.18.1","@tramvai/experiments":"5.18.1","@tramvai/papi":"5.18.1","@tramvai/pwa-recipes":"5.18.1","@tramvai/react":"5.18.1","@tramvai/react-query":"5.18.1","@tramvai/state":"5.18.1","@tramvai/storybook-addon":"5.18.1","@tramvai/types-actions-state-context":"5.18.1","@tramvai/test-child-app":"5.18.1","@tramvai/test-helpers":"5.18.1","@tramvai/test-integration":"5.18.1","@tramvai/test-integration-jest":"5.18.1","@tramvai/test-jsdom":"5.18.1","@tramvai/test-mocks":"5.18.1","@tramvai/test-pw":"5.18.1","@tramvai/test-puppeteer":"5.18.1","@tramvai/test-react":"5.18.1","@tramvai/test-unit":"5.18.1","@tramvai/test-unit-jest":"5.18.1","@tramvai/build":"6.1.1","@tramvai/tools-check-versions":"0.7.5","@tramvai/create":"5.18.1","@tramvai/tools-generate-schema":"0.4.2","@tramvai/tools-migrate":"0.9.5","@tinkoff-monorepo/depscheck":"3.101.7","@tinkoff-monorepo/fix-ts-references":"3.101.7","@tinkoff-monorepo/pkgs-collector":"3.101.7","@tinkoff-monorepo/pkgs-collector-dir":"3.101.7","@tinkoff-monorepo/pkgs-collector-workspaces":"3.101.7"} diff --git a/packages/cli/README.md b/packages/cli/README.md index 2654eb99e..af9fd1d6b 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -274,7 +274,7 @@ More details and examples you can find in typescript documentation: You can use `NODE_OPTIONS` env variable, e.g.: ```bash -NODE_OPTIONS="--max_semi_space_size=64" tramvai start-prod {appName} +NODE_OPTIONS="--max-semi-space-size=64" tramvai start-prod {appName} ``` ### How to get CPU profile of @tramvai/cli work? diff --git a/packages/cli/bin/spawn.js b/packages/cli/bin/spawn.js index 3b88a8e7f..db12a5336 100644 --- a/packages/cli/bin/spawn.js +++ b/packages/cli/bin/spawn.js @@ -4,8 +4,8 @@ const args = process.argv.slice(2); const maxMemory = process.env.MAX_USED_MEMORY || '3000'; const maxSemiSpaceSize = process.env.MAX_SEMI_SPACE_SIZE || '128'; const defaultArgs = [ - `--max_old_space_size=${maxMemory}`, - `--max_semi_space_size=${maxSemiSpaceSize}`, + `--max-old-space-size=${maxMemory}`, + `--max-semi-space-size=${maxSemiSpaceSize}`, ]; const paramsIndex = args.findIndex((x) => !x.startsWith('-')); diff --git a/packages/cli/src/utils/clearExecArgv.ts b/packages/cli/src/utils/clearExecArgv.ts index 4498e3a10..6a0ee3bc2 100644 --- a/packages/cli/src/utils/clearExecArgv.ts +++ b/packages/cli/src/utils/clearExecArgv.ts @@ -1,12 +1,12 @@ /** * terser-webpack-plugin use jest-workers with worker threads - https://github.com/webpack-contrib/terser-webpack-plugin/blob/master/src/index.js#L407 * used worker from jest-workers pass all execArgv to NodeJS internal worker https://github.com/facebook/jest/blob/main/packages/jest-worker/src/workers/NodeThreadsWorker.ts#L68 - * max_old_space_size is not supported by NodeJS internal workers, and provide this error: - * Error [ERR_WORKER_INVALID_EXEC_ARGV]: Initiated Worker with invalid execArgv flags: --max_old_space_size=3000 + * max-old-space-size is not supported by NodeJS internal workers, and provide this error: + * Error [ERR_WORKER_INVALID_EXEC_ARGV]: Initiated Worker with invalid execArgv flags: --max-old-space-size=3000 * So, try to remove this flag inside process manually, before worker threads are initialised. * @TODO: Remove after https://github.com/facebook/jest/pull/12097 */ export const clearExecArgv = () => { - const index = process.execArgv.findIndex((a) => a.includes('max_old_space_size')); + const index = process.execArgv.findIndex((a) => a.includes('max-old-space-size')); process.execArgv.splice(index, 1); };