Skip to main content

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)

Source distribution for granian-multiport 0.1.1
File Size Uploaded
granian_multiport-0.1.1.tar.gz 9.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for granian-multiport 0.1.1
File Interpreter ABI Platform
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}

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page