From 9f49bd6c78526f2a11d0450dfca50cc51e12a102 Mon Sep 17 00:00:00 2001 From: HARSH THAKARE <151563948+harsh21234i@users.noreply.github.com> Date: Sun, 11 Oct 2026 01:41:55 +0530 Subject: [PATCH 01/12] Cap automatically selected backend workers --- reflex/utils/processes.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/reflex/utils/processes.py b/reflex/utils/processes.py index 934bc37b774..2e5d449c582 100644 --- a/reflex/utils/processes.py +++ b/reflex/utils/processes.py @@ -29,6 +29,8 @@ logger = logging.getLogger(__name__) +MAX_BACKEND_WORKERS = 8 + def kill(pid: int): """Kill a process. @@ -61,7 +63,7 @@ def get_num_workers() -> int: except RedisError as re: logger.error(f"Unable to connect to Redis: {re}") raise SystemExit(1) from None - return (os.cpu_count() or 1) * 2 + 1 + return min((os.cpu_count() or 1) * 2 + 1, MAX_BACKEND_WORKERS) def _can_bind_at_port( From 936b9bee798d8d11312a4c2731d6bae903ebff4e Mon Sep 17 00:00:00 2001 From: HARSH THAKARE <151563948+harsh21234i@users.noreply.github.com> Date: Sun, 11 Oct 2026 01:42:10 +0530 Subject: [PATCH 02/12] Test capped backend worker defaults --- tests/units/utils/test_processes.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/units/utils/test_processes.py b/tests/units/utils/test_processes.py index e16e321051b..b4917b258bd 100644 --- a/tests/units/utils/test_processes.py +++ b/tests/units/utils/test_processes.py @@ -29,6 +29,27 @@ ) +@pytest.mark.parametrize( + ("cpu_count", "expected"), + [(None, 3), (0, 3), (1, 3), (2, 5), (3, 7), (4, 8), (64, 8)], +) +def test_get_num_workers_caps_automatic_worker_count(cpu_count, expected): + """Cap automatic worker counts while preserving the CPU-based default below the cap.""" + redis_client = mock.Mock() + with ( + mock.patch.object( + processes, "get_config", return_value=mock.Mock(transport="websocket") + ), + mock.patch.object( + processes.prerequisites, "get_redis_sync", return_value=redis_client + ), + mock.patch.object(processes.os, "cpu_count", return_value=cpu_count), + ): + assert processes.get_num_workers() == expected + + redis_client.ping.assert_called_once_with() + + def test_is_process_on_port_free_port(): """Test is_process_on_port returns False when port is free.""" # Find a free port From 7cd8da0e47938c9a6194cc52f927796be69a68cc Mon Sep 17 00:00:00 2001 From: HARSH THAKARE <151563948+harsh21234i@users.noreply.github.com> Date: Sun, 11 Oct 2026 01:42:47 +0530 Subject: [PATCH 03/12] Document capped backend worker default --- docs/hosting/self-hosting.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/hosting/self-hosting.md b/docs/hosting/self-hosting.md index cd32acdada1..d5afa592dd4 100644 --- a/docs/hosting/self-hosting.md +++ b/docs/hosting/self-hosting.md @@ -44,8 +44,8 @@ Production mode compiles the app, builds an optimized static frontend, and serves it together with the backend (event websocket, `/ping`, `/_upload`) from a single server on port `3000`. Pass `--frontend-port` or `--backend-port` to listen on a different port. When redis is configured, -the server runs `2 * cpu_count + 1` worker processes; set `GRANIAN_WORKERS` -to override this. +the server runs up to 8 worker processes by default (`2 * cpu_count + 1`, capped at 8). +Set `GRANIAN_WORKERS` to override the default, including to use more workers. The frontend and backend can also run as separate processes, for example to serve the frontend from a CDN and scale the backend independently. The From cdb236a87b2641f986050994f4a1c6700c61ee4c Mon Sep 17 00:00:00 2001 From: HARSH THAKARE <151563948+harsh21234i@users.noreply.github.com> Date: Sun, 11 Oct 2026 01:42:51 +0530 Subject: [PATCH 04/12] Document capped backend worker default --- docker-example/production/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-example/production/README.md b/docker-example/production/README.md index 8515678e66a..2825882fffd 100644 --- a/docker-example/production/README.md +++ b/docker-example/production/README.md @@ -31,8 +31,8 @@ To listen on a different port, pass `--build-arg PORT=10000`. docker run -p 8080:8080 reflex-production ``` -By default the backend runs `2 * cpu_count + 1` workers. Set `GRANIAN_WORKERS` -to a smaller number in memory constrained environments: +By default the backend runs up to 8 workers (`2 * cpu_count + 1`, capped at 8). +Set `GRANIAN_WORKERS` to override the default, including to use more workers: ```console docker run -e GRANIAN_WORKERS=2 -p 8080:8080 reflex-production From a642e10cb7c02e1dfcca26f5c0a5cb65d5f0d9ae Mon Sep 17 00:00:00 2001 From: HARSH THAKARE <151563948+harsh21234i@users.noreply.github.com> Date: Sun, 11 Oct 2026 01:43:15 +0530 Subject: [PATCH 05/12] Document capped backend worker default --- docker-example/production-compose/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docker-example/production-compose/README.md b/docker-example/production-compose/README.md index bcb88be4789..c2215fc2cc5 100644 --- a/docker-example/production-compose/README.md +++ b/docker-example/production-compose/README.md @@ -68,9 +68,9 @@ Add `psycopg[binary]` to `requirements.txt` so the backend can connect to postgres; the binary wheel bundles libpq, so no extra system packages are needed in the image. -With redis available, the backend runs `2 * cpu_count + 1` worker processes. -Set `GRANIAN_WORKERS` in the `app` environment to cap this on memory -constrained hosts. +With redis available, the backend runs up to 8 workers by default (`2 * cpu_count + 1`, +capped at 8). Set `GRANIAN_WORKERS` in the `app` environment to override the +default, including to use more workers. Postgres uses its own named docker volume for data persistence. From 31504eecaefed2ca6ed701e13c11ad61276ef32a Mon Sep 17 00:00:00 2001 From: HARSH THAKARE <151563948+harsh21234i@users.noreply.github.com> Date: Sun, 11 Oct 2026 01:43:20 +0530 Subject: [PATCH 06/12] Document capped backend worker default --- docker-example/app-platform-backend/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docker-example/app-platform-backend/README.md b/docker-example/app-platform-backend/README.md index 75365b8a2dd..3f3911e5713 100644 --- a/docker-example/app-platform-backend/README.md +++ b/docker-example/app-platform-backend/README.md @@ -38,8 +38,9 @@ The backend is built by the `Dockerfile` in this directory. When deploying the backend, be sure to set REFLEX_REDIS_URL=redis://internal-redis-hostname to connect to the redis service. -With redis available, each replica runs `2 * cpu_count + 1` worker processes. -Set `GRANIAN_WORKERS` to cap this on small instance sizes. +With redis available, each replica runs up to 8 workers by default +(`2 * cpu_count + 1`, capped at 8). Set `GRANIAN_WORKERS` to override the +default, including to use more workers. If the app uses postgres, add `psycopg[binary]` to `requirements.txt`; the binary wheel bundles libpq, so no extra system packages are needed in the image. From 2dc66593fb067b14371594825a6bd458586d7559 Mon Sep 17 00:00:00 2001 From: HARSH THAKARE <151563948+harsh21234i@users.noreply.github.com> Date: Sun, 11 Oct 2026 01:43:23 +0530 Subject: [PATCH 07/12] Add release note for worker cap --- news/+cap-default-backend-workers.bugfix.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 news/+cap-default-backend-workers.bugfix.md diff --git a/news/+cap-default-backend-workers.bugfix.md b/news/+cap-default-backend-workers.bugfix.md new file mode 100644 index 00000000000..89874b66ed1 --- /dev/null +++ b/news/+cap-default-backend-workers.bugfix.md @@ -0,0 +1 @@ +Cap the automatically selected number of Redis-backed backend workers at eight. Set `GRANIAN_WORKERS` to override the default. \ No newline at end of file From 432d868bf394b27060ceeb10cd701d69ad58ceeb Mon Sep 17 00:00:00 2001 From: HARSH THAKARE <151563948+harsh21234i@users.noreply.github.com> Date: Sun, 11 Oct 2026 02:13:13 +0530 Subject: [PATCH 08/12] Clarify Granian worker override scope --- docs/hosting/self-hosting.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/hosting/self-hosting.md b/docs/hosting/self-hosting.md index d5afa592dd4..43f5dd38c5f 100644 --- a/docs/hosting/self-hosting.md +++ b/docs/hosting/self-hosting.md @@ -45,7 +45,7 @@ serves it together with the backend (event websocket, `/ping`, `/_upload`) from a single server on port `3000`. Pass `--frontend-port` or `--backend-port` to listen on a different port. When redis is configured, the server runs up to 8 worker processes by default (`2 * cpu_count + 1`, capped at 8). -Set `GRANIAN_WORKERS` to override the default, including to use more workers. +When using Granian, set `GRANIAN_WORKERS` to override the default, including to use more workers. The frontend and backend can also run as separate processes, for example to serve the frontend from a CDN and scale the backend independently. The From affeee7dd72bc6f8c691ab353cc7198bb50ccdd8 Mon Sep 17 00:00:00 2001 From: HARSH THAKARE <151563948+harsh21234i@users.noreply.github.com> Date: Sun, 11 Oct 2026 02:13:20 +0530 Subject: [PATCH 09/12] Clarify Granian worker override scope --- docker-example/production/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-example/production/README.md b/docker-example/production/README.md index 2825882fffd..91e3ea04443 100644 --- a/docker-example/production/README.md +++ b/docker-example/production/README.md @@ -32,7 +32,7 @@ docker run -p 8080:8080 reflex-production ``` By default the backend runs up to 8 workers (`2 * cpu_count + 1`, capped at 8). -Set `GRANIAN_WORKERS` to override the default, including to use more workers: +When using Granian, set `GRANIAN_WORKERS` to override the default, including to use more workers: ```console docker run -e GRANIAN_WORKERS=2 -p 8080:8080 reflex-production From 923c0c83a7f4708168357740d6a4607a0f568056 Mon Sep 17 00:00:00 2001 From: HARSH THAKARE <151563948+harsh21234i@users.noreply.github.com> Date: Sun, 11 Oct 2026 02:13:36 +0530 Subject: [PATCH 10/12] Clarify Granian worker override scope --- docker-example/production-compose/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-example/production-compose/README.md b/docker-example/production-compose/README.md index c2215fc2cc5..60159775696 100644 --- a/docker-example/production-compose/README.md +++ b/docker-example/production-compose/README.md @@ -69,8 +69,8 @@ postgres; the binary wheel bundles libpq, so no extra system packages are needed in the image. With redis available, the backend runs up to 8 workers by default (`2 * cpu_count + 1`, -capped at 8). Set `GRANIAN_WORKERS` in the `app` environment to override the -default, including to use more workers. +capped at 8). When using Granian, set `GRANIAN_WORKERS` in the `app` environment +to override the default, including to use more workers. Postgres uses its own named docker volume for data persistence. From adf3a98dce7a91c91f0331e944a20d9ae304b385 Mon Sep 17 00:00:00 2001 From: HARSH THAKARE <151563948+harsh21234i@users.noreply.github.com> Date: Sun, 11 Oct 2026 02:13:42 +0530 Subject: [PATCH 11/12] Clarify Granian worker override scope --- docker-example/app-platform-backend/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-example/app-platform-backend/README.md b/docker-example/app-platform-backend/README.md index 3f3911e5713..c693719da81 100644 --- a/docker-example/app-platform-backend/README.md +++ b/docker-example/app-platform-backend/README.md @@ -39,8 +39,8 @@ backend, be sure to set REFLEX_REDIS_URL=redis://internal-redis-hostname to conn the redis service. With redis available, each replica runs up to 8 workers by default -(`2 * cpu_count + 1`, capped at 8). Set `GRANIAN_WORKERS` to override the -default, including to use more workers. +(`2 * cpu_count + 1`, capped at 8). When using Granian, set `GRANIAN_WORKERS` +to override the default, including to use more workers. If the app uses postgres, add `psycopg[binary]` to `requirements.txt`; the binary wheel bundles libpq, so no extra system packages are needed in the image. From 993cd1ce31428ab20f4099910f5cea451c50b519 Mon Sep 17 00:00:00 2001 From: HARSH THAKARE <151563948+harsh21234i@users.noreply.github.com> Date: Sun, 11 Oct 2026 02:22:13 +0530 Subject: [PATCH 12/12] Update docs/hosting/self-hosting.md Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> --- docs/hosting/self-hosting.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/hosting/self-hosting.md b/docs/hosting/self-hosting.md index 43f5dd38c5f..8ef5b76e006 100644 --- a/docs/hosting/self-hosting.md +++ b/docs/hosting/self-hosting.md @@ -44,7 +44,7 @@ Production mode compiles the app, builds an optimized static frontend, and serves it together with the backend (event websocket, `/ping`, `/_upload`) from a single server on port `3000`. Pass `--frontend-port` or `--backend-port` to listen on a different port. When redis is configured, -the server runs up to 8 worker processes by default (`2 * cpu_count + 1`, capped at 8). +the server runs up to 8 backend workers by default (`2 * cpu_count + 1`, capped at 8). When using Granian, set `GRANIAN_WORKERS` to override the default, including to use more workers. The frontend and backend can also run as separate processes, for example to