7
votes

I have written a node.js lambda function that triggers based on a dynamodb stream when new records are inserted into a particular table.

The function receives only new events, filters for inserted records, and then for each record, uses a couple of fields to retrieve data from other tables. Using this combined data a message is composed and sent via SNS to specific target ARN.

The function performs correctly. All the relevant data is retrieved, and a push notification is sent out.

However, for some reason the function appears to be called several times for the same stream, and processes the newly inserted records several times. The result is the target device receiving the same push notification several times.

Should I be placing the callback in a different place, or am I not calling on the context correctly?

This is the function:

'use strict';

var AWS = require("aws-sdk");
var dynamodb = new AWS.DynamoDB();
var sns = new AWS.SNS();

console.log('Loading function');

exports.handler = (event, context, callback) => {
  console.log('Received event:', JSON.stringify(event, null, 2));

  event.Records.forEach((record) => {
    console.log(record.eventID);
    console.log(record.eventName);
    console.log('DynamoDB Record: %j', record.dynamodb);

    if (record.eventName == 'INSERT') {
      var matchId = record.dynamodb.NewImage.eventId.S;
      var match_params = {
        Key: {
          "eventId": {
            S: matchId
          }
        },
        TableName: "xxxxxxxxxxx-mobilehub-xxxxxxx-Event"
      };

      //retrieve the match information from Event table
      dynamodb.getItem(match_params, function(err, data) {
        var match_description = "";
        if (err) {
          console.log(err, err.stack);
          context.fail('No match event record found in Event table');
        } else {
          match_description = data.Item.description.S;

          var uId = record.dynamodb.NewImage.participantUserId.S; //participantUserId 
          var user_params = {
            Key: {
              "userId": {
                S: uId
              }
            },
            TableName: "xxxxxxxxxxx-mobilehub-xxxxxxxxx-User"
          };

          //retrieve the user record from User table
          dynamodb.getItem(user_params, function(err, data) {
            if (err) {
              console.log(err, err.stack); // an error occurred  
              context.fail('Error occurred. See log.');
            } else {
              console.log(data); // successful response
              if (data.length === 0) {
                console.log("No User Record Found.");
                context.fail('No user found for participantUserId.');

              } else {

                var deviceARN = data.Item.device_arn.S;
                if (deviceARN <= 1) {
                  console.log("User has not registered their device for push notifications.");
                  context.fail('User has not registered for notifications');
                } else {

                  var json_message = JSON.stringify({
                    APNS_SANDBOX: JSON.stringify({
                      aps: {
                        alert: "You are playing in an upcoming match " + match_description,
                        badge: 1,
                        sound: 'default'
                      }
                    })
                  });

                  var snsparams = {
                    Message: json_message,
                    MessageStructure: 'json',
                    TargetArn: deviceARN
                  };

                  sns.publish(snsparams, function(err, data) {
                    if (err) {
                      console.log(err); // an error occurred
                      context.fail('SNS send failed. See log.');
                    } else {
                      console.log(data); // successful response
                      context.success('Push notification sent to user.');
                    }
                  });
                }
              }
            }
          });
        }
      });
    }
  });
  callback(null, `Successfully processed ${event.Records.length} records.`);
};
2
What version of node are you using? You seem to be mixing old style with new style callbacks. The current version of nodejs on Lambda doesn't have context.fail methods any more, and the version of node that did didn't have the callback argument.DF_
@Deif the latest version of node supported on Lambda is 4.3.2 and it still has both context.fail and context.success.idbehold
@idbehold Oh right, yeah, they're there for backwards compatibility. Still weird seeing both used in the same function though.DF_
@Deif Node.js is version 4.3. The context calls were my attempt to resolve the issue. .. This is just a thought, but if every request back to DynamoDB is asynchronous, and I am running a set of requests in a loop if there is more than one in a stream, then would I have to call the callback function when all the requests are compete?nightbird
Yes I would assume that the callback is invoked before the dynamo items are retrieved but it doesn't explain why the function is called again, unless your external program is immediately checking and sending new requests (in which case, the Lambda container will still be processing the previous request).DF_

2 Answers

0
votes

In my case, I added the same event source multiple times.

Quote from the conversation with an AWS support engineer:

Using my internal tools, I noticed that the Lambda function xxxxxx has the event source: arn:aws:events:my_region:my_acct_id:rule/my_event_target configured twice as push event source. This means that this might be the cause why you are seeing two invokes at every minute. Would you please confirm on your side if this event is configured twice for the $LATEST version of your lambda and also confirm if it's intended?

I hope this could save someelse :)

0
votes

In your lambda page at the bottom, try tweaking "Concurrency" Unreserved account concurrency to 1 and "Asynchronous invocation" Retry attempts to 0 . As a test try these and observe the behaviour. Might help.