A websocket server application
Project description
ws-scale
A Python library for building scalable WebSocket servers with distributed Snowflake-style ID generation. Each WebSocket node registers with a central master, receives a unique node ID, and generates collision-free 64-bit IDs across the cluster. Node state is persisted to a local JSON file on the master — no external datastore required.
Architecture
┌─────────────────────────┐
│ IDGeneratorMaster │ HTTP :9000
│ (aiohttp, one per DC) │◄──── register / heartbeat
└────────────┬────────────┘
│ storage.json
┌────────────▼────────────┐ ┌──────────────────────────┐
│ WebsocketServer #1 │ │ WebsocketServer #2 │
│ + IDGeneratorNode │ │ + IDGeneratorNode │
│ (websockets, :8080) │ │ (websockets, :8081) │
└─────────────────────────┘ └──────────────────────────┘
- IDGeneratorMaster — HTTP server that allocates node IDs and evicts nodes that have missed a heartbeat for
MONITOR_NODES_INTERVAL_SECONDS(120s by default). Supports up to 32 nodes per datacenter. Rejects registrations and heartbeats with an invalid body or whosedatacenter_iddoesn't match the master's own.bootstrap()cleans the storage file, starts the HTTP server, and kicks off a periodicmonitor_nodesloop in one call. - IDGeneratorNode — Registers with the master on startup, sends heartbeats every 60 s via
PUT /heartbeat, and generates Snowflake IDs (41-bit timestamp | 5-bit datacenter | 5-bit node | 12-bit sequence) using an in-process, lock-guarded sequence counter. Timestamps are relative to a fixed custom epoch (2025-01-01 UTC). Tolerates a temporarily unreachable master —register()andheart_beat()log and back off instead of raising. - WebsocketServer — Wraps
websockets.serveand routes connections to a user-supplied async handler. WhenWS_CLUSTERis enabled it also bootstraps anIDGeneratorNode, registering it with the master and starting its heartbeat loop; otherwise it just serves WebSocket connections standalone.
Requirements
- Python 3.13+
Installation
pip install ws-scale
Or with uv:
uv add ws-scale
Configuration
Settings can be provided via a Python module or environment variables.
| Setting | Default | Description |
|---|---|---|
WS_MASTER_HOST |
http://0.0.0.0 |
ID master hostname, used by WebsocketServer to reach the master |
WS_MASTER_PORT |
9000 |
ID master port |
WS_DATACENTER_ID |
— | 5-bit datacenter identifier for this node (0–31) |
WS_CLUSTER |
— | "true"/"True" or "false"/"False" (also accepts a bool or 0/1 when set programmatically). When true, the node registers with the master and sends heartbeats; when false, WebsocketServer runs standalone without contacting any master. Any other value raises ValueError |
These settings only configure WebsocketServer. IDGeneratorMaster takes datacenter_id and port directly as constructor arguments (see API Reference).
Via settings module (settings.py):
WS_MASTER_HOST = "http://0.0.0.0"
WS_MASTER_PORT = 9000
WS_DATACENTER_ID = 0
WS_CLUSTER = True
Via environment variables (pass settings_path=None):
export WS_MASTER_HOST=http://0.0.0.0
export WS_MASTER_PORT=9000
export WS_DATACENTER_ID=0
export WS_CLUSTER=true
Quick Start
import asyncio
from wsscale import IDGeneratorMaster, WebsocketServer
async def handle(conn):
async for message in conn:
await conn.send(f"echo: {message}")
async def main():
master = IDGeneratorMaster(datacenter_id=0, port=9000)
server = WebsocketServer(port=8080, settings_path="settings")
await master.bootstrap(monitor_interval_seconds=60) # reset storage, start HTTP master, start node monitor
await server.bootstrap(handler=handle) # (if WS_CLUSTER) register node, start heartbeat, serve WS
asyncio.run(main())
Run the bundled example from the command line:
python src/example.py --port 8080 --datacenter 0 --generator True
API Reference
WebsocketServer(port, settings_path=None)
| Parameter | Type | Description |
|---|---|---|
port |
int |
TCP port for the WebSocket server |
settings_path |
str | None |
Dotted module path to a settings file, or None to use env vars |
Reads WS_MASTER_HOST, WS_MASTER_PORT, WS_DATACENTER_ID, and WS_CLUSTER from the settings module or environment.
await bootstrap(handler) — If WS_CLUSTER is true, registers the ID node and starts its heartbeat loop; then runs the WebSocket server, dispatching connections to handler.
shutdown() — Closes the underlying WebSocket server (sync call).
IDGeneratorMaster(datacenter_id, port)
| Parameter | Type | Description |
|---|---|---|
datacenter_id |
str |
Logical datacenter identifier |
port |
int | str |
TCP port for the HTTP master |
await bootstrap(monitor_interval_seconds=60) — Calls cleanup(), then serve(), then schedules monitor_nodes to run every monitor_interval_seconds in the background. The recommended way to start a master.
await serve() — Starts the aiohttp HTTP server exposing POST /register and PUT /heartbeat.
await cleanup() — Resets the node storage file (call before serve() on a fresh start).
await monitor_nodes() — Evicts nodes that have missed heartbeats for more than MONITOR_NODES_INTERVAL_SECONDS (class attribute, default 120s) — distinct from monitor_interval_seconds, which is how often this check runs.
await shutdown() — Stops the HTTP server and cancels the monitor loop started by bootstrap().
get_nodes_data() — Returns the raw contents of the node storage file (sync call, mainly useful for debugging/tests).
IDGeneratorNode(server_host, server_port, data_center_id)
Typically instantiated automatically by WebsocketServer (only used when WS_CLUSTER is enabled), but can be used standalone.
await generate_id(datacenter_id, node_id) → int — Returns a 64-bit Snowflake-style unique ID.
await register() — Contacts the master with this node's datacenter_id and obtains a node_id, setting registered = True on success. Leaves registered = False (without raising) if the datacenter doesn't match or the master is unreachable.
await heart_beat() — Sends a single heartbeat, setting synced to reflect whether it succeeded.
await heart_beat_loop(interval_seconds) — Calls heart_beat() on a fixed interval forever.
Development
Install dev dependencies and run the test suite with uv:
uv sync --group dev
uv run pytest
License
MIT — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ws_scale-0.3.0.tar.gz.
File metadata
- Download URL: ws_scale-0.3.0.tar.gz
- Upload date:
- Size: 12.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","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}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b4c629410c0b9a957821d9fcfbc75cef78a8c952bb5bc6702a7fe14f2eb80f59
|
|
| MD5 |
6ce4d7c4521bbd90e6c3a16bdefce3ca
|
|
| BLAKE2b-256 |
0d22ad2e37ff8d13fdb19c98101c9e6edbee37cc05c336a23d09106c70adefc8
|
File details
Details for the file ws_scale-0.3.0-py3-none-any.whl.
File metadata
- Download URL: ws_scale-0.3.0-py3-none-any.whl
- Upload date:
- Size: 12.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","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}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0fd4209e10058f789d69ba14d8d86926fd5a0e3ad7241d6b5087908b66477080
|
|
| MD5 |
805e529e1b11f034202993bff10ecbda
|
|
| BLAKE2b-256 |
793dead7b7407a729b31994fd5e2ca93232644f0d2a2d9c7e824c0b64f67d68d
|