Skip to content

Commit 5d15ee5

Browse files
Cap the default backend worker count (#7542)
* Cap automatically selected backend workers * Test capped backend worker defaults * Document capped backend worker default * Document capped backend worker default * Document capped backend worker default * Document capped backend worker default * Add release note for worker cap * Clarify Granian worker override scope * Clarify Granian worker override scope * Clarify Granian worker override scope * Clarify Granian worker override scope * Update docs/hosting/self-hosting.md Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> --------- Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
1 parent 3634eed commit 5d15ee5

7 files changed

Lines changed: 35 additions & 10 deletions

File tree

‎docker-example/app-platform-backend/README.md‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,9 @@ The backend is built by the `Dockerfile` in this directory. When deploying the
3838
backend, be sure to set REFLEX_REDIS_URL=redis://internal-redis-hostname to connect to
3939
the redis service.
4040

41-
With redis available, each replica runs `2 * cpu_count + 1` worker processes.
42-
Set `GRANIAN_WORKERS` to cap this on small instance sizes.
41+
With redis available, each replica runs up to 8 workers by default
42+
(`2 * cpu_count + 1`, capped at 8). When using Granian, set `GRANIAN_WORKERS`
43+
to override the default, including to use more workers.
4344

4445
If the app uses postgres, add `psycopg[binary]` to `requirements.txt`; the
4546
binary wheel bundles libpq, so no extra system packages are needed in the image.

‎docker-example/production-compose/README.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,9 @@ Add `psycopg[binary]` to `requirements.txt` so the backend can connect to
6868
postgres; the binary wheel bundles libpq, so no extra system packages are
6969
needed in the image.
7070

71-
With redis available, the backend runs `2 * cpu_count + 1` worker processes.
72-
Set `GRANIAN_WORKERS` in the `app` environment to cap this on memory
73-
constrained hosts.
71+
With redis available, the backend runs up to 8 workers by default (`2 * cpu_count + 1`,
72+
capped at 8). When using Granian, set `GRANIAN_WORKERS` in the `app` environment
73+
to override the default, including to use more workers.
7474

7575
Postgres uses its own named docker volume for data persistence.
7676

‎docker-example/production/README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ To listen on a different port, pass `--build-arg PORT=10000`.
3131
docker run -p 8080:8080 reflex-production
3232
```
3333

34-
By default the backend runs `2 * cpu_count + 1` workers. Set `GRANIAN_WORKERS`
35-
to a smaller number in memory constrained environments:
34+
By default the backend runs up to 8 workers (`2 * cpu_count + 1`, capped at 8).
35+
When using Granian, set `GRANIAN_WORKERS` to override the default, including to use more workers:
3636

3737
```console
3838
docker run -e GRANIAN_WORKERS=2 -p 8080:8080 reflex-production

‎docs/hosting/self-hosting.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ Production mode compiles the app, builds an optimized static frontend, and
4444
serves it together with the backend (event websocket, `/ping`, `/_upload`)
4545
from a single server on port `3000`. Pass `--frontend-port` or
4646
`--backend-port` to listen on a different port. When redis is configured,
47-
the server runs `2 * cpu_count + 1` worker processes; set `GRANIAN_WORKERS`
48-
to override this.
47+
the server runs up to 8 backend workers by default (`2 * cpu_count + 1`, capped at 8).
48+
When using Granian, set `GRANIAN_WORKERS` to override the default, including to use more workers.
4949

5050
The frontend and backend can also run as separate processes, for example to
5151
serve the frontend from a CDN and scale the backend independently. The
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Cap the automatically selected number of Redis-backed backend workers at eight. Set `GRANIAN_WORKERS` to override the default.

‎reflex/utils/processes.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929

3030
logger = logging.getLogger(__name__)
3131

32+
MAX_BACKEND_WORKERS = 8
33+
3234

3335
def kill(pid: int):
3436
"""Kill a process.
@@ -61,7 +63,7 @@ def get_num_workers() -> int:
6163
except RedisError as re:
6264
logger.error(f"Unable to connect to Redis: {re}")
6365
raise SystemExit(1) from None
64-
return (os.cpu_count() or 1) * 2 + 1
66+
return min((os.cpu_count() or 1) * 2 + 1, MAX_BACKEND_WORKERS)
6567

6668

6769
def _can_bind_at_port(

‎tests/units/utils/test_processes.py‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,27 @@
2929
)
3030

3131

32+
@pytest.mark.parametrize(
33+
("cpu_count", "expected"),
34+
[(None, 3), (0, 3), (1, 3), (2, 5), (3, 7), (4, 8), (64, 8)],
35+
)
36+
def test_get_num_workers_caps_automatic_worker_count(cpu_count, expected):
37+
"""Cap automatic worker counts while preserving the CPU-based default below the cap."""
38+
redis_client = mock.Mock()
39+
with (
40+
mock.patch.object(
41+
processes, "get_config", return_value=mock.Mock(transport="websocket")
42+
),
43+
mock.patch.object(
44+
processes.prerequisites, "get_redis_sync", return_value=redis_client
45+
),
46+
mock.patch.object(processes.os, "cpu_count", return_value=cpu_count),
47+
):
48+
assert processes.get_num_workers() == expected
49+
50+
redis_client.ping.assert_called_once_with()
51+
52+
3253
def test_is_process_on_port_free_port():
3354
"""Test is_process_on_port returns False when port is free."""
3455
# Find a free port

0 commit comments

Comments
 (0)