I'm looking at AWS API Gateway websockets support, announced relatively recently -
https://aws.amazon.com/blogs/compute/announcing-websocket-apis-in-amazon-api-gateway/
They have a sample chat server example -
https://github.com/aws-samples/simple-websockets-chat-app/blob/master/sendmessage/app.js
which I have running, very nice.
If you send a message, the sendmessage Lambda broadcasts that message to all connected users via the following -
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: MIT-0
const AWS = require('aws-sdk');
const ddb = new AWS.DynamoDB.DocumentClient({ apiVersion: '2012-08-10' });
const { TABLE_NAME } = process.env;
exports.handler = async (event, context) => {
let connectionData;
try {
connectionData = await ddb.scan({ TableName: TABLE_NAME, ProjectionExpression: 'connectionId' }).promise();
} catch (e) {
return { statusCode: 500, body: e.stack };
}
const apigwManagementApi = new AWS.ApiGatewayManagementApi({
apiVersion: '2018-11-29',
endpoint: event.requestContext.domainName + '/' + event.requestContext.stage
});
const postData = JSON.parse(event.body).data;
const postCalls = connectionData.Items.map(async ({ connectionId }) => {
try {
await apigwManagementApi.postToConnection({ ConnectionId: connectionId, Data: postData }).promise();
} catch (e) {
if (e.statusCode === 410) {
console.log(`Found stale connection, deleting ${connectionId}`);
await ddb.delete({ TableName: TABLE_NAME, Key: { connectionId } }).promise();
} else {
throw e;
}
}
});
try {
await Promise.all(postCalls);
} catch (e) {
return { statusCode: 500, body: e.stack };
}
return { statusCode: 200, body: 'Data sent.' };
};
Now unfortunately (fortunately ??) I have a Python/Erlang background, not Javascript/nodejs. So I can see what some of this is doing, namely iterating over connections in the DynamoDB table and sending the response to each. It also looks as if it's working asynchronously, via use of async and await keywords, which I guess are Promises. But I can't be sure this is acting asynchronously, which is what worries me .. if I have a single Lambda which iterates over a large number of connections and makes synchronous calls, that's not going to work.
So - specifically with respect to this portion of code -
const postCalls = connectionData.Items.map(async ({ connectionId }) => {
try {
await apigwManagementApi.postToConnection({ ConnectionId: connectionId, Data: postData }).promise();
} catch (e) {
if (e.statusCode === 410) {
console.log(`Found stale connection, deleting ${connectionId}`);
await ddb.delete({ TableName: TABLE_NAME, Key: { connectionId } }).promise();
} else {
throw e;
}
}
});
can I be sure that this is posting postData to all connections in a fully asynchronous manner ? Do I need to be worried about a single Lambda potentially pushing messages to thousands of clients ?
It isn't called simple-websockets-chat-app for nothing :)
By reading the code, it does exactly what you are concerned about. Only one lambda instance will fire the message to all connections.
But it's a chat room, is it common to have thousands of users ?
It also looks as if it's working asynchronously, via use of async and await keywords, which I guess are Promises
Yes, it works in an asynchronous way, but the lambda will still execute until all messages have been sent.
About
await apigwManagementApi.postToConnection({ ConnectionId: connectionId, Data: postData }).promise();
It puts very little pressure on the Lambda itself, making just a remote call at a time. And being async it does not wait for the response, but continues to send more and more.
(side note - I would use forEach instead of map in this case)
A solution for a absurdly high traffic chat room would be:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With