0

I wrote next implementation to read messages from AWS Kinesis and deployed it on AWS ECS:

Application.java

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import com.amazonaws.event.ProgressListener;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;

@SpringBootApplication
public class Application {

  public static void main(final String[] args) {
    SpringApplication.run(Application.class, args);
  }

  @Bean
  public ObjectMapper objectMapper() {
    ObjectMapper objectMapper = new ObjectMapper();
    SimpleModule simpleModule = new SimpleModule()
        .addAbstractTypeMapping(ProgressListener.class, ProgressListener.NoOpProgressListener.class);
    objectMapper.registerModule(simpleModule);
    objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    return objectMapper;
  }
}

ConsumerConfiguration.java

import java.nio.charset.StandardCharsets;
import java.util.function.Consumer;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.amazonaws.services.kinesis.model.PutRecordsRequest;
import com.amazonaws.services.kinesis.model.PutRecordsRequestEntry;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import lombok.RequiredArgsConstructor;
import reactor.util.Logger;
import reactor.util.Loggers;

@Configuration
@RequiredArgsConstructor
public class ConsumerConfiguration {
  private static final Logger LOG = Loggers.getLogger(ConsumerConfiguration.class);

  private final ObjectMapper objectMapper;

  private void processRecords(byte[] bytes) {

    String jsonRecords = new String(bytes, StandardCharsets.UTF_8);
    LOG.info("New Records consumed...");

    try {
      PutRecordsRequest putRecordsRequest = objectMapper.readValue(jsonRecords, PutRecordsRequest.class);
      LOG.info("Records size = {}", putRecordsRequest.getRecords().size());
      putRecordsRequest.getRecords().stream()
          .map(PutRecordsRequestEntry::getData)
          .map(data -> new String(data.array(), StandardCharsets.UTF_8))
          .forEach(data -> LOG.info("Record data length received {}", data.length()));
    } catch (JsonProcessingException e) {
      LOG.error("Error while reading records from jsonRecords string.", e);
    }
  }

  @Bean
  public Consumer<byte[]> myConsumer() {
    return this::processRecords;
  }

}

application.yml

management:
  metrics:
    export:
      cloudwatch:
        namespace: kinesis-reader-app-java
        batchSize: 20
spring:
  application:
    name: kinesis-reader-app
  cloud:
    stream:
      bindings:
        myConsumer-in-0:
          group: kinesis-reader-app-group
          consumer:
            concurrency: 5
            headerMode: none
            use-native-decoding: true
          destination: my-kinesis-stream
      kinesis:
        bindings:
          my-kinesis-stream:
            consumer:
              checkpoint-mode: periodic
              checkpoint-interval: 3000
              idle-between-polls: ${KINESIS_CONSUMER_IDLE_BETWEEN_POLLS:1000}
              consumer-backoff: ${KINESIS_CONSUMER_BACKOFF:1000}
              records-limit: ${KINESIS_CONSUMER_RECORDS_LIMIT:5000}
              shard-iterator-type: TRIM_HORIZON
              worker-id: kinesis-reader-worker-id
        binder:
          checkpoint:
            table: kinesis-reader-stream-metadata
            read-capacity: 50
          locks:
            table: kinesis-reader-lock-registry
            lease-duration: 30
            refresh-period: 3000
            read-capacity: 50
          kpl-kcl-enabled: true
          auto-create-stream: false
          auto-add-shards: false


cloud:
  aws:
    region:
      static: eu-west-1
      auto: false
    stack:
      auto: false

My typescript AWS CDK stack to deploy on AWS:

kinesis-reader-infrastructure-stack.ts

import {RemovalPolicy, Stack, StackProps} from 'aws-cdk-lib';
import {Construct} from 'constructs';
import * as cdk from 'aws-cdk-lib';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as logs from 'aws-cdk-lib/aws-logs';
import * as ecsPatterns from 'aws-cdk-lib/aws-ecs-patterns';
import * as ecr from 'aws-cdk-lib/aws-ecr';


// Create and prepare the infrastructure needed for KinesisReader application to work
// Like the Kinesis stream that will receive data from the application and the ECS cluster
export class KinesisReaderInfrastructureStack extends Stack {

    constructor(scope: Construct, id: string, props?: StackProps) {
        super(scope, id, props);

        // The code that defines your stack goes here

        // Create repository and docker image in it to contain the java app that will produce load to kinesis
        const imageRepository = ecr.Repository.fromRepositoryName(this, 'KinesisReaderRepository', 'sandbox/kinesis-reader-app');
        const appDockerImage = ecs.ContainerImage.fromEcrRepository(imageRepository);

        // Create DynamoDb
        new dynamodb.Table(this, 'kinesis-delivery-stream-checkpoint', {
            partitionKey: {
                name: "id",
                type: dynamodb.AttributeType.STRING
            },
            billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
            removalPolicy: RemovalPolicy.DESTROY
        });

        new dynamodb.Table(this, 'kinesis-delivery-stream-locks', {
            partitionKey: {
                name: "id",
                type: dynamodb.AttributeType.STRING
            },
            billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
            removalPolicy: RemovalPolicy.DESTROY,
        });

        // Lookup existing VPC
        const vpc = ec2.Vpc.fromLookup(this, 'KinesisReaderVpc', {
            // specify a 'vpcName' or 'tags'.
            vpcId: 'vpc-xxxxxxxxxxxxxxxxx',
            isDefault: false
        });

        // Create ECS Cluster
        const cluster = new ecs.Cluster(this, 'KinesisReaderCluster', {
            clusterName: 'kinesis-reader-cluster',
            vpc: vpc
        });

        // Permissions added to ECS role
        const ecsPermissions = new iam.PolicyStatement({
            actions: [
                'kinesis:Get*',
                'kinesis:ListShards',
                'kinesis:ListStreams',
                'kinesis:DescribeStreamSummary',
                'dynamodb:Scan',
                'dynamodb:GetItem',
                'dynamodb:PutItem',
                'dynamodb:UpdateItem',
                'dynamodb:CreateTable',
                'dynamodb:DescribeTable',
                'cloudwatch:PutMetricData'
            ],
            resources: ['*'],
        });

        // Create the ECS Role and attach policy to the role
        const ecsTaskRole = new iam.Role(this, 'KinesisReaderTaskRole', {
            roleName: 'kinesis-reader-task-role',
            assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
        });
        ecsTaskRole.addToPolicy(ecsPermissions);

        // Create Cloudwatch log group
        const logGroup = new logs.LogGroup(this, 'KinesisReaderLogGroup', {
            logGroupName: 'kinesis-reader-log-group',
            removalPolicy: cdk.RemovalPolicy.DESTROY,
            retention: logs.RetentionDays.ONE_DAY,
        });

        const fargateKinesisReaderService = new ecsPatterns.ApplicationLoadBalancedFargateService(this, 'FargateKinesisReaderService', {
            serviceName: 'kinesis-reader-service',
            cluster: cluster,
            cpu: 2048,
            desiredCount: 30,
            memoryLimitMiB: 4096,
            healthCheckGracePeriod: cdk.Duration.minutes(4),
            assignPublicIp: true,
            publicLoadBalancer: true,

            taskImageOptions: {
                image: appDockerImage,
                taskRole: ecsTaskRole,
                containerPort: 1080,
                environment: {
                    KINESIS_CONSUMER_IDLE_BETWEEN_POLLS: '2000',
                    KINESIS_CONSUMER_BACKOFF: '1000',
                    KINESIS_CONSUMER_RECORDS_LIMIT: '10000'
                },
                logDriver: ecs.LogDrivers.awsLogs({
                    streamPrefix: 'reader',
                    mode: ecs.AwsLogDriverMode.NON_BLOCKING,
                    logGroup: logGroup,
                }),
            },
        });

        const healthCheck = {
            path: "/actuator/health",
            interval: cdk.Duration.seconds(45),
            timeout: cdk.Duration.seconds(44)
        };

        fargateKinesisReaderService.targetGroup.configureHealthCheck(healthCheck);

    }
}

But after deploying, instances goes quickly shutdown and a new instances created due to heap memory exception, even when i tried to increase CPU and Memory, here is the exception i got and as you see i read a different sizes of payload:

INFO 1 --- [askExecutor-116] c.v.kinesis.ConsumerConfiguration   : Record data length received 482
INFO 1 --- [askExecutor-116] c.v.kinesis.ConsumerConfiguration   : Record data length received 482
INFO 1 --- [askExecutor-116] c.v.kinesis.ConsumerConfiguration   : Record data length received 482
INFO 1 --- [askExecutor-137] c.v.kinesis.ConsumerConfiguration   : New Records consumed...
INFO 1 --- [askExecutor-137] c.v.kinesis.ConsumerConfiguration   : Records size = 6
INFO 1 --- [askExecutor-137] c.v.kinesis.ConsumerConfiguration   : Record data length received 121128
INFO 1 --- [askExecutor-137] c.v.kinesis.ConsumerConfiguration   : Record data length received 121128
INFO 1 --- [askExecutor-137] c.v.kinesis.ConsumerConfiguration   : Record data length received 121128
INFO 1 --- [askExecutor-137] c.v.kinesis.ConsumerConfiguration   : Record data length received 121128
INFO 1 --- [askExecutor-137] c.v.kinesis.ConsumerConfiguration   : Record data length received 121128
Native Memory Tracking:
Total: reserved=5102531491, committed=1291543459
-                 Java Heap (reserved=4294967296, committed=1073741824)
                            (mmap: reserved=4294967296, committed=1073741824) 
 
-                     Class (reserved=268005852, committed=76214748)
                            (classes #13566)
                            (  instance classes #12782, array classes #784)
                            (malloc=2097628 #34162) 
                            (mmap: reserved=265908224, committed=74117120) 
                            (  Metadata:   )
                            (    reserved=65011712, committed=64393216)
                            (    used=63054472)
                            (    free=1338744)
                            (    waste=0 =0.00%)
                            (  Class space:)
                            (    reserved=200896512, committed=9723904)
                            (    used=8903056)
                            (    free=820848)
                            (    waste=0 =0.00%)
 
-                    Thread (reserved=57068704, committed=5647520)
                            (thread #54)
                            (stack: reserved=56807424, committed=5386240)
                            (malloc=198448 #326) 
                            (arena=62832 #106)
 
-                      Code (reserved=255618480, committed=28605872)
                            (malloc=1985968 #9551) 
                            (mmap: reserved=253632512, committed=26619904) 
 
-                        GC (reserved=202784183, committed=83246519)
                            (malloc=9285047 #18310) 
                            (mmap: reserved=193499136, committed=73961472) 
 
-                  Compiler (reserved=1365978, committed=1365978)
                            (malloc=471498 #941) 
                            (arena=894480 #11)
 
-                  Internal (reserved=824358, committed=824358)
                            (malloc=791590 #2936) 
                            (mmap: reserved=32768, committed=32768) 
 
-                     Other (reserved=8, committed=8)
                            (malloc=8 #1) 
 
-                    Symbol (reserved=17039968, committed=17039968)
                            (malloc=14609464 #168653) 
                            (arena=2430504 #1)
 
-    Native Memory Tracking (reserved=3852024, committed=3852024)
                            (malloc=15096 #188) 
                            (tracking overhead=3836928)
 
-               Arena Chunk (reserved=510560, committed=510560)
                            (malloc=510560) 
 
-                   Tracing (reserved=97, committed=97)
                            (malloc=97 #5) 
 
-                   Logging (reserved=4572, committed=4572)
                            (malloc=4572 #192) 
 
-                 Arguments (reserved=19099, committed=19099)
                            (malloc=19099 #495) 
 
-                    Module (reserved=251304, committed=251304)
                            (malloc=251304 #2088) 
 
-              Synchronizer (reserved=210816, committed=210816)
                            (malloc=210816 #1724) 
 
-                 Safepoint (reserved=8192, committed=8192)
                            (mmap: reserved=8192, committed=8192) 

I don't know why i got heap memory exception which leads to shutdown the instances, and there is no much details in the exception logged, i even tried this with small, medium and large (CPUs and Memory) instances, but same behaviour happen on all type of AWS instances. So what the problem is??

I am using spring-cloud-stream-binder version 2.1.0

mibrahim.iti
  • 1,928
  • 5
  • 22
  • 50

0 Answers0