DEV Community

Cover image for Realtime channels ⚡- RPC, PUB/SUB at scale
Or Weis
Or Weis

Posted on

Realtime channels ⚡- RPC, PUB/SUB at scale

As modern software is exploding (especially in the cloud and at the edge) the need to deliver realtime data, updates, and events at scale across deployed instances is ever increasing.
A challenge we had to solve for our own fullstack authorization as a service solution (authorizon.com)
That's why we've created ⚡ FastAPI-websocket-RPC and 🗞️ FastAPI-websocket-PubSub.

Why Websocket-RPC ?

Often you'd like to trigger events and/or access code on the client side (and not just on the server). Explicitly exposed RPC methods are the easiest way to go about it.
Each side (both client and server) can expose Python methods like this:

class RpcCalculator(RpcMethodsBase):
    async def add(self, a, b):
        return a + b
Enter fullscreen mode Exit fullscreen mode

And the other-side can call them (and wait for a response with a return value) like this:

response = await channel.other.add(a=1,b=2)
print(response.result) # 3
Enter fullscreen mode Exit fullscreen mode

Pub/Sub

Similarly Pub/Sub is the easiest way to have different services share updates.
Once a server is up:

app = FastAPI() 
endpoint = PubSubEndpoint()
endpoint.register_route(app, "/pubsub")
Enter fullscreen mode Exit fullscreen mode

clients can then subscribe

async with PubSubClient(server_uri="ws://localhost/pubsub") as client:
# Callback to be called upon event being published on server
async def on_event(data):
    print("We got an event! with data- ", data)
# Subscribe for the event 
client.subscribe("my_event_topic", on_event)
Enter fullscreen mode Exit fullscreen mode

and publish

async with PubSubClient(server_uri="ws://localhost/pubsub") as client:
    endpoint.publish(["my_event_topic"], data=["my", "data", 1])
Enter fullscreen mode Exit fullscreen mode

With ease 😇

Why Realtime / Websocket?

Polling systems are the worst - constantly creating overhead, and are always delayed and suffering from consistency issues. Websockets are ideal for traversing the cloud and the internet. Thanks to running on top of an initially outgoing HTTP connection, all the common issues one would face with firewalls and routing for most bi-directional channels are inherently solved. When push comes to shove - software should always push rather than pull 😜.

Why on top of FastAPI

With Python, asyncio, Starlette, and Pydantic at its core, FastAPI provides great performance (still not as good as GO, but comparable/superior to Node.js) and enjoys the dev-speed and maintainability of typed Python. Its dependency injection mechanism allows our RPC and PubSub to easily enjoy all the benefits of the underlying HTTP (Such as authorization, cookies, routing) with easy configuration.

Performance

While we haven't created official benchmarks yet, we have successfully run these packages in production with 100s of events per second, per server instance, with no issue. In addition instances can scale horizontally with ease (thanks to the broadcasting mechanism).

Pub/Sub - Why not RabbitMQ, etc. ?

First, RabbitMQ is great - but it's not a cure-all;

FastAPI WS Pub/Sub allows to decouple the backbone Pub/Sub (such as RabbitMQ, Kafka, or Redis) from the API layer - which is more lightweight. Bringing tons of benefits:

  • Allowing to scale-out the lightweight layer without having to scale-out the backbone (which is often much more resource intensive)
  • Being able to work with the native API (e.g. REST, GraphQL) in tandem (being part of the same framework or even process)
  • Choosing whichever backbone Pub/Sub channel you'd like, and being able to easily switch (no lock-in).
  • Code level control - this solution embeds into your Python code, you don't have to look at it as a blackbox (unless you want to).

Bottomline

Realtime data propagation is becoming a common task, and we hope these open-source packages would make it easier for everyone to accomplish.
Take them for a spin and tell us what you think

Top comments (0)