Skip to main content

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 whose datacenter_id doesn't match the master's own. bootstrap() cleans the storage file, starts the HTTP server, and kicks off a periodic monitor_nodes loop 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() and heart_beat() log and back off instead of raising.
  • WebsocketServer — Wraps websockets.serve and routes connections to a user-supplied async handler. When WS_CLUSTER is enabled it also bootstraps an IDGeneratorNode, 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ws_scale-0.3.0.tar.gz (12.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ws_scale-0.3.0-py3-none-any.whl (12.5 kB view details)

Uploaded Python 3

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

Hashes for ws_scale-0.3.0.tar.gz
Algorithm Hash digest
SHA256 b4c629410c0b9a957821d9fcfbc75cef78a8c952bb5bc6702a7fe14f2eb80f59
MD5 6ce4d7c4521bbd90e6c3a16bdefce3ca
BLAKE2b-256 0d22ad2e37ff8d13fdb19c98101c9e6edbee37cc05c336a23d09106c70adefc8

See more details on using hashes here.

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

Hashes for ws_scale-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0fd4209e10058f789d69ba14d8d86926fd5a0e3ad7241d6b5087908b66477080
MD5 805e529e1b11f034202993bff10ecbda
BLAKE2b-256 793dead7b7407a729b31994fd5e2ca93232644f0d2a2d9c7e824c0b64f67d68d

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page