In our scalable AWS serverless application (DynamoDB, Lambda, API Gateway WebSockets) we want to broadcast messages from a Lambda to very many connected browser sessions simultaneously.
We have many topics, and to limit the load on the system we want to limit each browser session to subscribe to max. two topics.
The AWS example app linked in this official tutorial does this by looping through the connectionIds and doing an await postToConnection for each of them, which doesn't scale well as each postToConnection takes 40 ms (average):
const AWS = require('aws-sdk');
const ddb = new AWS.DynamoDB.DocumentClient();
exports.handler = async function (event, context) {
let connections;
try {
connections = await ddb.scan({ TableName: process.env.table }).promise();
} catch (err) {
return {
statusCode: 500,
};
}
const callbackAPI = new AWS.ApiGatewayManagementApi({
apiVersion: '2018-11-29',
endpoint:
event.requestContext.domainName + '/' + event.requestContext.stage,
});
const message = JSON.parse(event.body).message;
const sendMessages = connections.Items.map(async ({ connectionId }) => {
if (connectionId !== event.requestContext.connectionId) {
try {
await callbackAPI
.postToConnection({ ConnectionId: connectionId, Data: message })
.promise();
} catch (e) {
console.log(e);
}
}
});
try {
await Promise.all(sendMessages);
} catch (e) {
console.log(e);
return {
statusCode: 500,
};
}
return { statusCode: 200 };
};
How can we make this broadcast scalable?
Related
I am trying to implement fleet provisioning in AWS Lambda function. As a starting point, I have this code:
'use strict';
var AWS = require('aws-sdk');
var iot = new AWS.Iot({
endpoint: 'apiendpoint',
accessKeyId: "AAAABBBBBCCCCDDDDD",
secretAccessKey: "AAAAABBBBCCCDD/1234122311222",
region: 'ap-south-1'
});
exports.handler = async (event, context) => {
var params = {
setAsActive: true
};
return {
statusCode: 200,
body:JSON.stringify(await createCertAndKey(params))
}
}
const createCertAndKey = async (params) => {
return new Promise((resolve, reject) => {
iot.createKeysAndCertificate(params, function(err, data){
if(err){
console.log(err);
reject(err)
}
else{
console.log("success?");
resolve(data)
}
})
})
}
I get a ResourceNotFound exception for calling createKeysAndCertificate. I also tried calling other functions of iot, but it gives the same exception.
What am I doing wrong here?
endpoint passed when intialzing an object should be generic AWS service in format https://{service}.{region}.amazonaws.com. we don't need to pass it, AWS will assume based on region and object we are initializing.
var iot = new AWS.Iot({
endpoint: 'iot.ap-south-1.amazonaws.com',
accessKeyId: "AAAABBBBBCCCCDDDDD",
secretAccessKey: "AAAAABBBBCCCDD/1234122311222",
region: 'ap-south-1'
});
[EDIT]
I am new to AWS and I've been trying to make a real-time chat application. I saw this code, so I might as well try this. I tried to follow everything but it seems there is a problem.
[END]
I got an error with this code stating, Cannot read "domainName" of undefined which got me confused. Is this because of my API Gateway? I already connected this to another route named "Message" and I already have the integration request of my connect and disconnect.
Below is my code:
const AWS = require('aws-sdk');
const db = new AWS.DynamoDB.DocumentClient({region: 'us-east-1'});
require('./index.js');
let send = undefined;
function init(event) {
console.log(event);
const apigwManagementApi = new AWS.ApiGatewayManagementApi({
apiVersion: '2018-11-29',
endpoint: event.requestContext.domainName + '/' + event.requestContext.stage
});
send = async (connectionId, data) => {
await apigwManagementApi.postToConnection({
ConnectionId: connectionId,
Data: `Echo: ${data}` }).promise();
}}
exports.handler = (event, context, callback) => {
init(event);
let message = JSON.parse(event.body).message;
getConnections().then((data) => {
console.log(data.Items);
callback(null, {
statusCode: 200,
});
data.Items.forEach(function(connection) {
console.log("Connection " +connection.connection_id);
send(connection.connection_id, message);
});
});
return {};
};
function getConnections(){
const params = {
TableName: 'GroupChat',
}
return db.scan(params).promise();
}
In some of my routes the lambda function times out after 6 seconds when it is using an existing mongodb connection.
I am sure the function itself is not the issue as I have added lots of logs and it takes 0.1 seconds to finish but the lambda does not return anything for 6+ seconds. In most of the tutorials online the db connection is in the same folder as the lambda function but I have moved it out to its own package as I have 5+ endpoints.
This is the most basic endpoint that times out every now and then
module.exports.getAll = async (event, context, callback) => {
context.callbackWaitsForEmptyEventLoop = false;
try {
await databaseModels.db();
const x = await databaseModels.items.model.find({});
callback(null, {
statusCode: 200,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(x)
})
} catch (error) {
callback(error, {
statusCode: 500,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(error)
})
}
};
This is my connect to database function which I call at the start of every lambda function
const url = ``
let isConnected;
module.exports = connectToDatabase = () => {
if (isConnected) {
console.log('=> using existing database connection');
return Promise.resolve();
}
console.log('=> using new database connection');
return mongoose.connect(url).then(db => {
isConnected = db.connections[0].readyState;
});
};
In one of the turorials I found the author opened a connection for each request and closed it when the computation was done. Is this a good practice?
I have used a very simple code slightly modified from AWS provided example:
exports.handler = async (event) => {
// Load the AWS SDK for Node.js
var AWS = require('aws-sdk');
// Set region
AWS.config.update({region: 'ap-southeast-2'});
// Create publish parameters
var params = {
Message: 'This is a sample message',
Subject: 'Test SNS From Lambda',
TopicArn: 'arn:aws:sns:ap-southeast-2:577913011449:TestTopic'
};
// Create promise and SNS service object
var publishTextPromise = new AWS.SNS().publish(params).promise();
let response = {
statusCode: 200,
body: JSON.stringify('Hello from Lambda!'),
};
// Handle promise's fulfilled/rejected states
publishTextPromise.then(
function(data) {
console.log("Message ${params.Message} send sent to the topic ${params.TopicArn}");
console.log("MessageID is " + data.MessageId);
response.result = 'Success';
}).catch(
function(err) {
console.error(err, err.stack);
response.result = 'Error';
});
return response;
};
And I am getting a timeout error when testing this service. 3 seconds is the limit.
Since this is a very simply process, I suppose it shouldn't take more than 3 seconds to execute.
I have checked my IAM setting and grant my profile (admin profile) to have full access to SNS service. But the error still persists. I am wondering what is being wrong here and how should I fix this?
I am not sure why you're getting the timeout, but your code isn't supposed to work the way you might expect.
See that you're returning your response outside your .then() code, meaning that your code is going to return before your .then() code is even run (Promises are asynchronous).
Since you're already using Node 8, you're better off using async/await instead of using the old .then().catch() approach.
I have refactored your code a little bit and it works just fine. I have kept the original parameters for your convenience. See how the code is much easier to read and debug.
'use strict';
// Load the AWS SDK for Node.js
const AWS = require('aws-sdk');
// Set region
AWS.config.update({region: 'ap-southeast-2'});
const sns = new AWS.SNS()
module.exports.handler = async (event) => {
const params = {
Message: 'This is a sample message',
Subject: 'Test SNS From Lambda',
TopicArn: 'arn:aws:sns:ap-southeast-2:577913011449:TestTopic'
};
let response = {
statusCode: 200,
body: JSON.stringify('Hello from Lambda!'),
};
try {
const data = await sns.publish(params).promise();
response.messageId = data.MessageId,
response.result = 'Success'
} catch (e) {
console.log(e.stack)
response.result = 'Error'
}
return response
};
If for whatever reason you don't want to use async/await, you then need to move the return of your function inside your .then() code, as well as return as soon as the promise is invoked, like this:
'use strict';
// Load the AWS SDK for Node.js
var AWS = require('aws-sdk');
// Set region
AWS.config.update({region: 'ap-southeast-2''});
// Create publish parameters
var params = {
Message: 'This is a sample message',
Subject: 'Test SNS From Lambda',
TopicArn: 'arn:aws:sns:ap-southeast-2:577913011449:TestTopic'
};
module.exports.handler = async (event) => {
// Create promise and SNS service object
var publishTextPromise = new AWS.SNS().publish(params).promise();
let response = {
statusCode: 200,
body: JSON.stringify('Hello from Lambda!'),
};
// Handle promise's fulfilled/rejected states
return publishTextPromise.then(
function(data) {
console.log("Message ${params.Message} send sent to the topic ${params.TopicArn}");
console.log("MessageID is " + data.MessageId);
response.result = 'Success';
return response;
}).catch(
function(err) {
console.error(err, err.stack);
response.result = 'Error';
return response
});
};
I highly recommend you go with approach #1 though.
I am trying to do a pretty simple "hello world" in AWS Lambda. I tried a few services that only call the AWS SDK and just try to read. My callback never gets called. I have to be missing something. Any help appreciated!
var AWS = require("aws-sdk");
exports.handler = async (event) => {
// TODO implement
const response = {
statusCode: 200,
body: JSON.stringify('Hello from Lambda!'),
};
var s3 = new AWS.S3(); // confirmed this is not null
s3.listBuckets({}, function(err, data) {
// never reaches here!
if (err) console.log(err, err.stack); // an error occurred
else console.log(data); // successful response
});
return response;
};
I did create a role this lambda is using that has S3 access. :-)
It seems that because I chose the Node 8.x runtime, I needed to use one of those async constructs. This worked...
let AWS = require('aws-sdk');
let s3 = new AWS.S3();
exports.handler = async (event) => {
return await s3.listBuckets().promise() ;
};
This is a synchronization problem.
Your return response code is executed before your callback is invoked.
you'll have to put your return statement inside your callback or use async/await
Returning inside your callback:
var AWS = require("aws-sdk");
exports.handler = async (event) => {
const response = {
statusCode: 200,
body: JSON.stringify('Hello from Lambda!'),
};
var s3 = new AWS.S3();
s3.listBuckets({}, function (err, data) {
if (err) {
console.log(err, err.stack);
return {
statusCode: 500,
message: 'some error'
}
}
return response
});
}
Using async/await:
var AWS = require("aws-sdk");
exports.handler = async (event) => {
const response = {
statusCode: 200
};
var s3 = new AWS.S3();
await s3.listBuckets().promise();
return response;
}
I'd go with the async/await approach as it's much cleaner and more readable. It's also easier to work with promises than with callbacks.
EDIT: The OP claimed it didn't work. So I have decided to test it on my own. The above code works, with a very small change just to add the listed buckets to the response. Here's the final code:
var AWS = require("aws-sdk");
exports.handler = async (event) => {
const response = {
statusCode: 200
};
var s3 = new AWS.S3();
const buckets = await s3.listBuckets().promise();
response.body = JSON.stringify(buckets);
return response;
}
And here's the output in CloudWatch Logs: