Asynchronous Client Interaction in AWS Serverless: Polling, WebSocket, SSE or AppSync?
The event-driven paradigm is usually adopted with serverless architectures, dispatching asynchronous events to trigger wanted effects.
This behaviour comes natively with most AWS services:
- if someone uploads a picture on your S3 bucket, you can make a Lambda listen and react to this event
- if a Lambda adds a new object to your DynamoDB table, you can react in the same way
And when developing your micro-services, we find it best to connect them with the same pattern:
- when a synchronous Lambda (for example inviteUser) dispatches an event through EventBridge triggering another Lambda (such as sendEmail) (Side note: why EventBridge is a key component in serverless architectures is well explained in this article from Ben Ellerby)
After treating events asynchronously, you will want to update the end-users by pushing data from the back-end to the front-end.
Let’s review our options
This problematic is not new, classic architectures have well-known options: (Long) Polling, WebSocket or Server-Sent Events (SSE). On top of those, AWS offers an higher level abstraction: AWS AppSync.
Let’s deep dive and assess each of them in a serverless context:
Your front-end has the responsibility of regularly asking your back-end if there is any fresh data. Hence the front will make the same call every few seconds/minutes. Sometimes one of those calls will have a fresh data to handle.
Long polling is different in the fact that the request is kept open by the server as long as possible until it eventually returns a fresh data or reaches a timeout.
- HTTP: robust and easy to handle.
- With Polling, you usually add a delay between requests to not make it too intensive, this naturally delays your data arrival (not real-time).
- Implementation with Lambdas: each Polling request hits a custom Lambda, and keeps its connection alive for the allocated amount of time before sending a potential empty response.
- Cost with Lambdas: too costly because pinging “useless” HTTP routes and Lambdas as most of the polling requests won’t see a change in data. This con is a deal-breaker.
Your front-end opens a long-lasting, bi-directional communication with your back-end through a WebSocket protocol. Thus, the back can push a message as soon as necessary.
- Implementation with Lambdas: WebSocket APIs is an official solution from AWS API Gateway. It handles the connections for you, and only pings your Lambdas on messages.
- Cost with Lambdas: cheap, using AWS API Gateway pricing source we calculated that a small app with around 25,000 messages per day, triggering 25,000 Lambdas behind, would cost below $1 per month.
- WebSocket is a different protocol for delivering data, you need to deal with two different paradigms in your app, HTTP and WS.
- WebSocket is not automatically multiplexed (compared to HTTP/2). Implementing multiplexing both on the server and the client is a bit complicated.
Your front-end opens a long-lasting, uni-directional communication from your back-end through the HTTP protocol. Here as well, the back-end can push a message as soon as necessary.
- Simple implementation and data efficient with HTTP.
- It is automatically multiplexed over HTTP/2 out of the box.
- Implementation with Lambdas: requires to keep a live connection between the client and your Lambdas in the back-end. Lambdas are not meant to be kept alive for a whole client session. Going around this limit is a hassle.
- Cost with Lambdas: too costly because of the necessity to keep live Lambdas up and running. This con is a deal-breaker.
AppSync is a fully managed GraphQL API layer. It handles the parsing and resolution of requests connecting them to different data sources such as Lambdas, DynamoDB or HTTP APIs.
It, out of the box, includes real-time GraphQL subscriptions. To which you can connect with your favourite GraphQL front-end framework such as Apollo or Relay.
- AppSync is serverless by design.
- Easy to use, providing GraphQL schema and resolver templates is enough to generate an available GraphQL endpoint which supports GraphQL subscription.
- Implementation with Lambdas: out of the box.
- Cost with Lambdas: can be cheaper than WebSocket API as AppSync cost per million connection minutes is $0.08, whereas it’s $0.29 for WebSocket. However the price per million messages is higher ($1.14 for WS against $2 for AppSync).
- GraphQL only.
- Vendor (AWS) lock-in.
- There is no proper way to implement custom authorizers (only accepting API KEY, AWS IAM, OpenID Connect and AWS Cognito).
- Hard to pair with an Event-Driven micro-services approach and often results in a monolith or distributed monolith structure.
What’s the best choice then?
We recommend going for WebSocket, because:
- It’s the only flexible and standard solution officially supported by AWS.
- It has the smaller cost impact as the connection is kept alive by API Gateway and Lambdas are only triggered on useful events.
- You’ll find lots of online content and libraries helping you implement WebSocket in a serverless context.
How to set it up?
Pre-requesite: In the context of the Serverless Framework on AWS in TypeScript.
Development time: This solution takes less than half a day to set-up, test and deploy (not including the front-end).
- Start and keep track of the live WebSocket connection
- An event, like a SaaS hook, triggers a DB update
- A DynamoDB event then triggering a Lambda to notify the front-end of the updated data
Configure connect and disconnect Lambdas to keep track of the active connections. When you declare the
websocket event type for the first time, Serverless Framework will create a new WebSocket API Gateway.
$disconnect are official WebSocket route events.
Then add a Lambda responsible to push WebSocket messages to the front-ends.
And that’s it! With a few lines of code your serverless back-end is ready to handle live WebSocket connections.
That’s the solution we now use by default on our serverless projects in my company (Theodo), until a better one arises!
Beyond that, we are closely looking out for SSE news in serverless. Because outside of serverless, we believe it solves the problem in a nicer way than WebSocket. Some amazing tools exist that we would love to be able to use (for instance Mercure: https://github.com/dunglas/mercure).
I do hope this article answered some of your questions. If you have any question or feedback, feel free to drop a comment, we’d love to hear from you!
- Alternative comparison from Theodo: https://blog.theodo.com/2019/07/build-a-real-time-serverless-web-application-with-aws/
- Comparision between Polling, Websocket and SSE: https://codeburst.io/polling-vs-sse-vs-websocket-how-to-choose-the-right-one-1859e4e13bd9
- Another comparison: https://blog.stanko.io/do-you-really-need-websockets-343aed40aa9b
- SSE instead of Websocket: www.smashingmagazine.com/2018/02/sse-websockets-data-flow-http2/
- AWS article pushing toward WebSocket: https://aws.amazon.com/blogs/compute/from-poll-to-push-transform-apis-using-amazon-api-gateway-rest-apis-and-websockets/
- WebSocket 101: https://lucumr.pocoo.org/2012/9/24/websockets-101/
- WebSocket API Gateway article from AWS: https://aws.amazon.com/blogs/compute/announcing-websocket-apis-in-amazon-api-gateway/
- Websocket API Gateway example repository: https://github.com/aws-samples/simple-websockets-chat-app
- Websocket in Serverless Framework documentation: https://serverless.com/framework/docs/providers/aws/events/websocket/
- Websocket article from Serverless Framework: https://serverless.com/blog/api-gateway-websockets-example/
- Multiplexing with Websocket: https://www.rabbitmq.com/blog/2012/02/23/how-to-compose-apps-using-websockets/
- Websocket API Gateway pricing: https://aws.amazon.com/api-gateway/pricing/#WebSocket_APIs