granian-multiport
granian-multiport runs each Granian worker on a different TCP port. It is
intended for deployments where a reverse proxy such as nginx performs
load-aware dispatch (least_conn) across workers.
Every listening socket is bound once in the parent process and inherited by exactly one worker.
Granian itself is not patched. This package reuses Granian's complete Click
CLI and supplies an external MPServer subclass that binds a distinct listener
for every worker.
Why
Granian uses SO_REUSEPORT to load-balance multiple workers, but Linux distributes connections randomly with SO_REUSEPORT. This means that when distributing 16 connections across 8 workers, there is an approximately 80% chance that one worker will receive 4 or more connections. This can lead to issues with 99%-tile latency and CPU utilization efficiency.
By assigning different ports to each worker process and allowing a reverse proxy such as nginx to implement a better load-balancing algorithm, 99%-tile latency and CPU utilization efficiency can be significantly improved.
In addition, Granian relies on net.ipv4.tcp_migrate_req for graceful restarts when SO_REUSEPORT is enabled. In environments other than Linux, or in environments where this system setting is not configured, connections remaining in the backlog of old workers will be discarded and errors will occur when restarting workers.
This project aims to demonstrate that better load balancing can be achieved through multi-port support.
Install
python -m pip install .
The initial release supports Granian 2.8.x and Python 3.12 or newer on POSIX systems. It intentionally pins the Granian minor series because it uses the private multiprocessing server API.
Run on consecutive ports
The usual Granian --port becomes the first worker port:
granian-multiport \
--interface asgi \
--host 127.0.0.1 \
--port 8000 \
--workers 4 \
myproject.asgi:application
Workers listen on ports 8000, 8001, 8002, and 8003. Every other Granian CLI option remains available.
Run on explicit ports
granian-multiport \
--interface asgi \
--ports 8000,8010,8020,8030 \
myproject.asgi:application
When both --ports and --workers are given, their counts must agree. The
explicit list can also be supplied through GRANIAN_MULTIPORT_PORTS.
Unix domain sockets are intentionally unsupported because the command's purpose
is to expose one TCP endpoint per worker.
nginx
upstream app_workers {
least_conn;
server 127.0.0.1:8000;
server 127.0.0.1:8001;
server 127.0.0.1:8002;
server 127.0.0.1:8003;
keepalive 32;
}
server {
listen 80;
location / {
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_pass http://app_workers;
}
}
With upstream keep-alive enabled, nginx's least_conn choice occurs when it
opens or selects an upstream connection; benchmark with the connection reuse
policy that matches production.
Development
python -m pip install -e '.[test]'
pytest
The integration test starts three workers, requests every port, and verifies that each port consistently reaches a different worker process. ll
Release files for granian-multiport 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| granian_multiport-0.1.1.tar.gz | 9.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| granian_multiport-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 17.2 kB
Release files / granian_multiport-0.1.1.tar.gz
| Download URL | granian_multiport-0.1.1.tar.gz |
|---|---|
| Size | 9.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
fb63a954a1ef6f5ca5f3c0d478d829a31f5db1ba81f38733c52b7e5c12f6d5ec
|
|
BLAKE2b-256 checksum How to use checksums |
91de40a0e59fa41c9e0975dc3ab12e00c8507e253715e5ba348253199426741b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / granian_multiport-0.1.1-py3-none-any.whl
| Download URL | granian_multiport-0.1.1-py3-none-any.whl |
|---|---|
| Size | 7.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e9d4409fa38c8b096bd1ef08565c3d3031f880a65ba58733bc8632c7e8cc331e
|
|
BLAKE2b-256 checksum How to use checksums |
a3f250846a28c7f2c5a3b6939bd6b83073a24a73ff61f7b090c6b74f66808273
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|