Optimizing your application using Spring Boot 2.x and Amazon ElastiCache for Redis

Standard

Has your project gotten to the point when big data sets and or time consuming calculations begin to affect performance? Struggling to optimize your queries and need to cache some information to avoid continually hitting your database? Then caching could be your solution.

For this article I will demonstrate how to utilize Amazon ElastiCache for Redis to speed up areas of your application. The exmple application we will build uses Spring Boot 2.x and is available on Github.

What is Amazon ElastiCache for Redis?

ElastiCache for Redis is a super fast, in memory, key-value store database. It supports many different kinds of abstract data structures, such as strings, lists, maps, sets, sorted sets, hyperloglogs, bitmaps and spatial indexes. At its highest level, ElastiCache for Redis is an fully managed service for a standard Redis installation and uses all the standard Redis API’s. Which means the app we are building can rely not only on the Amazon ElastiCache flavor of Redis, but any other environment matching the version of your choosing.

Setting up ElastiCache for Redis

Begin by navigating to the ElasticCache dashboard, selecting Redis, and create a new cluster. You will be prompted to define a cache name, description, node type (server size), and number of replicates.

I filled in the name, description, and changed the node type to a smaller instance.

VPC & Security Groups

To be able to access your Redis cluster the instance(s) running our app must have be in the same Virtual Private Network (VPC) and contain the proper security groups. Your EC2 instances must allow the port of your redis cluster (6379) to be able to communicate. By default your Redis cluster is only accessible internally from the VPC selected. This is done purposely, as no internet gateway should be connected as it would defeat the purpose of a high efficiency, in-memory cache that Redis provides. 

Our app will only be able to access Redis once it is deployed to AWS.

If you wish to run the app locally, consider installing Redis using docker. The variables outlined in our application.properties file below can be modified to run locally.

Launching your Redis Cluster

Once your have properly configured your security groups and VPC, click “create”. ElastiCache will now provision and launch you new Redis cluster. When the status turns to available the cluster is ready to handle connections.

We need the primary endpoint for our new spring boot application.

Building our application

For this example application we will be using Spring Boot 2.x with the Spring-Data-Redis and Jedis (client library for redis). I first begin by importing them into my project

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>

These  libraries allow us to setup our caching config in Spring. A important concept to understand is that the Spring Framework provides its own abstraction for transparently adding caching. You do not only have to use Redis, the abstraction provides a list of providers; CouchbaseEhCache 2.xHazelcast, etc. As you will see adding caching to a service method is as simple as providing the appropriate annotation.

Now that we have included the required libraries in our pom file, Spring will try to autoconfigure a RedisCacheManager. I personally do not like magic behind the scenes so we are going to setup and configure our own annotation based RedisCacheManager. 

A RedisCacheManager is how we configure, and build a cacheManger for Spring to use. Notice that I have defined the redisHostName, redisPort, and redisPrefix for the Jedis (client library for redis) to use to connect to our cluster.

@Configuration
@EnableCaching
public class RedisConfig {

@Value("${redis.hostname}")
private String redisHostName;

@Value("${redis.port}")
private int redisPort;

@Value("${redis.prefix}")
private String redisPrefix;

@Bean
JedisConnectionFactory jedisConnectionFactory() {
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(redisHostName, redisPort);
return new JedisConnectionFactory(redisStandaloneConfiguration);
}

@Bean(value = "redisTemplate")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
return redisTemplate;
}

@Primary
@Bean(name = "cacheManager") // Default cache manager is infinite
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
return RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(RedisCacheConfiguration.defaultCacheConfig().prefixKeysWith(redisPrefix)).build();
}

@Bean(name = "cacheManager1Hour")
public CacheManager cacheManager1Hour(RedisConnectionFactory redisConnectionFactory) {
Duration expiration = Duration.ofHours(1);
return RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(RedisCacheConfiguration.defaultCacheConfig().prefixKeysWith(redisPrefix).entryTtl(expiration)).build();
}
}

I have defined 2 cache managers. One that is infinite (default), and once that will cause all keys to expire in 1 hour called “cacheManager1Hour” .

The cluster information is passed in from the applications.properties:

redis.hostname=URL_TO_ELASTIC_CACHE_REDIS_CLUSTER
redis.port=6379
redis.prefix=testing

Implementing a simple service

Now that our Redis cluster is configured in Spring, annotation based caching is enabled. Let’s assume you have a long running task that takes 2 seconds to do its work. By annotation the service with @Cacheable the result of the method call will be cached. I have given this cachable a value of “getLongRunningTaskResult” which will be used in its compound key, a key (by default is generated for you), and a cacheManager (“cacheManager1Hour” configured above).

@Cacheable(value = "getLongRunningTaskResult", key="{#seconds}", cacheManager = "cacheManager1Hour")
public Optional<TaskDTO> getLongRunningTaskResult(long seconds) {
try {
long randomMultiplier = new Random().nextLong();
long calculatedResult = randomMultiplier * seconds;
TaskDTO taskDTO = new TaskDTO();
taskDTO.setCalculatedResult(calculatedResult);
Thread.sleep(2000); // 2 Second Delay to Simulate Workload
return Optional.of(taskDTO);
} catch (InterruptedException e) {
return Optional.of(null);
}
}

Note: It is important that the resulting object of the method is serializable otherwise the cacheManager will not be able to cache the result.

Testing for performance improvements

To easy test the API, I have included swagger-ui which make it simple for developers to interact with the api we have built. I have also created a few simple endpoints to create, and flush the cache.

@ApiOperation(
value = "Perform the long running task")
@RequestMapping(value = "/{seconds}", method = RequestMethod.GET)
public ResponseEntity<TaskDTO> longRunningTask(@PathVariable long seconds) {
Optional<TaskDTO> user = taskService.getLongRunningTaskResult(seconds);
return ResponseUtil.wrapOrNotFound(user);
}

@ApiOperation(
value = "Resets the cache for a key")
@RequestMapping(value = "/reset/{seconds}", method = RequestMethod.DELETE)
public ResponseEntity<?> reset(@PathVariable long seconds) {
taskService.resetLongRunningTaskResult(seconds);
return new ResponseEntity<>(HttpStatus.ACCEPTED);
}

Once you deploy your app to EC2 navigate to URL path: /swagger-ui.html

From this GUI you can easily test your API performance improvements. Calling the GET endpoint for the first time should take roughly 2 seconds to return the new calculated result. Calling it subsequently will return an almost instant response as the long running task is now cached in Redis.

Final thoughts

Today’s applications demand a responsive user experience, design your queries and calculations to be as performant as possible, but every once in awhile when you can sacrifice real time data, just cache it.

Full project code is available at: https://github.com/sixthpoint/spring-boot-elasticache-redis-tutorial

Quickstart AWS SQS + Spring Boot Processing FIFO Queues

Standard

AWS SQS (Simple Queue Service) can provide developers with flexibility, and scalability when building microservice application(s). In this quickstart tutorial it demonstrates how to configure a FIFO queue for a fictional online marketplace.

The code for this application is available on Github.

What is a FIFO queue?

A FIFO (First in first out) queue is used when the order of items or events is critical, or where duplicates items in the queue are not permitted. For example;

  • Prevent a user from buying an item that isn’t available on a marketplace.

Configuring AWS

This tutorial assumes you have an AWS account already created. Begin by creating a SQS queue call PendingOrders.fifo in a region of your choice with content-based deduplication enabled. Each queue has its own unique URL.

The format of this URL is https://sqs.<region>.amazonaws.com/<account_id>/<queue_name>. Take note of this URL, as it will need it to run the application (Set this in a SIXTHPOINT_SQSURL environment variable). You can see the URL for your SQS in the details screen of SQS in AWS console or by using the aws cli command aws sqs list-queues. Please see Figure 1 for reference.

Figure 1: PendingOrders.fifo created with Content-Based Deduplication enabled

Content-Based Deduplication:

FIFO queues do not allow for duplicate messages. If you retry to send an identical message within the 5 minute deduplication interval, the message will be tossed out. This assures that your messages will be processes exactly once. So by enabling this feature AWS SQS uses a SHA-256 hash to generate the deduplication ID using the body of the message; Not the attributes of the message.

This is useful for our simple example because our message payload will contain only a itemCount which is the total number of items they are buying, and a requestTime which is the epoch time the order was received.

The Application

The sample application is composed of a single OrderProcessingService which is stateful. This service handles submitting the order to the queue, as well as listening for orders via the @SqsListener annotation.

Creating an order

To create an order a PendingOrder class is written to a single json string. The default client for AmazonSQSClientBuilder is then used to send a message to the queue.

public void createOrder(int itemCount) {
try {
PendingOrder pendingOrder = new PendingOrder();
pendingOrder.setItemCount(itemCount);
pendingOrder.setRequestTime(LocalDateTime.now().toEpochSecond(ZoneOffset.UTC));
String pendingOrderJson = configProperties.getObjectMapper().writeValueAsString(pendingOrder);

final AmazonSQS sqs = AmazonSQSClientBuilder.defaultClient();
sqs.sendMessage(new SendMessageRequest(configProperties.getSqsURL(), pendingOrderJson).withMessageGroupId("sixthpointGroupId"));

} catch (final AmazonClientException | JsonProcessingException ase) {
log.error("Error Message: " + ase.getMessage());
}
}

The PendingOrder contains an itemCount which is the total number of items they are trying to purchase, and a requestTime. Since we are using content-based deduplication this means that no more than 1 request can be done per second with the same itemCount.

Processing the queue

Using the @SqsListener annotation the application checks the PendingOrders.fifo periodically to process pending items. An item is read in and mapped using the object mapper. The availableItems is either decremented by the supplied pendingOrder count or an error logged for no more items remaining.

private int availableItems = 5;

@SqsListener("PendingOrders.fifo")
public void process(String json) throws IOException {
    PendingOrder pendingOrder = configProperties.getObjectMapper().readValue(json, PendingOrder.class);
    if(availableItems > 0 && availableItems >= pendingOrder.getItemCount())
    {
        availableItems = availableItems - pendingOrder.getItemCount();
        log.info("Items purchased, now have {} items remaining", availableItems);
    } else {
         log.error("No more items are available");
    }
}

Wrap up

This article quickly shows how to setup and configure your first AWS SQS FIFO queue using Spring Boot.

Full code with configurable environment variables can be found on
Github.

Using Docker + AWS to build, deploy and scale your application

Standard

I recently worked to develop a software platform that relied on Spring Boot and Docker to prop up an API. Being the only developer on the project, I needed to find a way to quickly and efficiently deploy new releases. However, I found many solutions overwhelming to set up.

That was until I discovered AWS has tools that allow any developer to quickly build and deploy their application.

In this 30 minute tutorial, you will discover how to utilize the following technologies:

Once finished, you will have a Docker application running that automatically builds your software on commit, and deploys it to the Elastic beanstalk sitting behind a load balancer for scalability. This continuous integration pipeline will allow you to worry less about your deployments and get back to focusing on feature development within your application.

Here is the order in which to configure services:

  1. Git repository initialization using CodeCommit
  2. CodeBuild Setup
  3. EBS Configuration
  4. CodePipeline Configuration
Background knowledge

I am using Docker for this tutorial application. However AWS supports a wide range of configurable environments in the Elastic beanstalk; .NET, Java, NodeJS, PHP, Python, Ruby. Docker was chosen for this tutorial so that the reader can focus more on the build process and less on the project setup. With that being said, I will not be diving deeply into Docker. If you wish to learn more about Docker, start by reading the introduction on the Docker website.

The Application

The example Spring Boot source code that will be used can be found at: https://github.com/sixthpoint/Docker-AWS-CodePipeline

The application is a Spring Boot project configured to run on port 5000 and has a REST controller with a single endpoint.

The API REST controller is very basic. It maps /api/ path to a method which returns a list of strings in JSON format. This is the endpoint we will use to verify our application has successfully built and deployed on the AWS EBS.

ApiController.java
@RestController
@RequestMapping( value = "/api" )
public class ApiController {

    @RequestMapping( value = "/", method = RequestMethod.GET )
    public List<String> index() {
        List<String> s = new ArrayList<>();
        s.add("Docker + AWS CodePipline Tutorial");
        s.add("Learn more at: https://github.com/sixthpoint/Docker-AWS-CodePipeline");
        return s;
    }
}

The application creates am example-1.0.0-SNAPSHOT.jar file when built using Maven. This file is important for us to reference in our Dockerfile.

Maven build:
mvn clean install

Would produce target/example-1.0.0-SNAPSHOT.jar. The Dockerfile below uses a flavor of Alpine Linux to add, expose and run the Spring Boot application.

Dockerfile
FROM openjdk:8-jdk-alpine
VOLUME /tmp
ADD target/example-1.0.0-SNAPSHOT.jar app.jar
EXPOSE 5000
ENV JAVA_OPTS=""
ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar" ]
1. Git repository initialization using CodeCommit

First things first, we need a git repository to build our code from. AWS CodeCommit is cheap, reliable, and secure. It uses S3 which is a scalable storage solution subject to S3 storage pricing.

Begin by logging into your AWS console and creating a repository in CodeCommit. For the purpose of this tutorial, I have called the repository name the same name as the Spring Boot application. Once created, you will be presented with the standard HTTPS and SSH URLs of the repository.

The above example has generated the following repository location, notice if I try to do a clone from the repository access is denied.

git clone https://git-codecommit.us-east-1.amazonaws.com/v1/repos/DockerCodePipleline

The above example has generated the following repository location; notice if I try to do a clone from the repository, access is denied.

1A. CONFIGURING IDENTITY AND ACCESS MANAGEMENT (IAM)

IAM or Identity and Access Management enables you to securely control access to AWS services and resources. To authorize a user to access our private git repository, navigate to the IAM services page. Begin by adding a user. I have named the user the same name as the project and git repository. Choose programmatic access which will allow for policies to be added.

In order to allow this new user to fully administer our new git repository, attach the AWSCodeCommitFullAccess policy. Once added, click through to finish creating your user.

Now that a user has been created with the correct policies, GIT credentials are needed to work with the new CodeCommit repository. Navigate to the new user and look for the “HTTPS Git credentials for AWS CodeCommit” shown below. Generate a new username and password and download the .gitCrendientialsfile once prompted. Inside that file is the information needed to access your repository.

Note: Only two keys are allowed per user at this time. If you lose your key, a new one will need to be generated to access the repository. For more in-depth information on setting up git credentials in AWS, check out the guide for setting up HTTPS users using Git credentials.

1B. MOVING THE CODE TO THE NEW CODECOMMIT REPOSITORY

With the new repository created, clone the Github repository holding our sample Spring Boot application. Change the remote to your new CodeCommit repository location, then finally push the master branch to master.

git clone https://github.com/sixthpoint/Docker-AWS-CodePipeline.git
git remote set-url origin git://https://git-codecommit.us-east-1.amazonaws.com/v1/repos/DockerCodePipleline
git push master master
2. CodeBuild Setup

Now that the CodeCommit repository holds our sample Spring boot application, the code needs to be built for deployment. Navigate to CodeBuild. CodeBuild is a source code compiler which is pay on demand.

Start by creating a new build project and point the source to the AWS CodeCommit repository that was created in Step 1. You can see I have pointed this new build project to the AWS CodeCommit source provider, and specified the DockerCodePipeline repository.

Next it asks for environmental information. The default system image is fine for this build process. The most important part is to tell CodeBuild to use the buildspec.yml. The buildspec contains the necessary commands to generate the artifacts needed to deploy to the EBS.

Included in the sample Spring Boot application is a buildspec.yml. This file is used to tell CodeBuild what commands to run in each phase, and what files to bundle up and save in the artifacts.

Additional configuration options can be found at: http://docs.aws.amazon.com/codebuild/latest/userguide/build-spec-ref.html.

Buildspec.yml
version: 0.2

phases:
  build:
    commands:
      - mvn clean
      - mvn install
artifacts:
  files:
    - 'Dockerfile'
    - 'target/example-1.0.0-SNAPSHOT.jar'

Final setup for the build process is to specify the location where the artifact made from the buildspec.ymlwill be stored. In the example below, I put all artifacts in the Amazon S3 under the name dockerAWSCodePipeline, and in a bucket named irdb-builds. The bucket can be in bucket of your choice. You must go into S3 and create this bucket prior to creating the build project.



The build project is now configured and ready to use. Builds can manually be run from the console creating artifacts stored in S3 as defined above.

4. EBS Setup

Now that the code is in CodeCommit, and the artifacts are built using CodeBuild, the final resource needed is a server to deploy the code. That is where the Elastic beanstalk comes in useful. The EBS is a service that automatically handles provisioning, load balancing, auto-scaling, etc. It is a very powerful tool to help your manage and monitor your applications servers.

Let’s assume, for example, my API needs to have four servers due to the amount of requests I am receiving. The EBS makes the scaling of those servers simple with configuration options.

Begin by creating a new webserver environment and give it a name and domain name. This domain name is your AWS domain name; if you have a personal domain name you can point it to this load balancer being created using Route53.

The last step of creating your webserver worker environment is to tell EBS that we want to run Docker and to use the example application code. Later our code from CodeBuild will replace the AWS sample application.

The server and environment will take several minutes to start. Once complete, navigate to the configuration page of your new EBS environment.

By default the environment has a load balancer installed and auto scales. A scaling trigger can be set to adjust the number of instances to run given certain requirements. For example: I could set my minimum instances to 1 and maximum to 4 and tell the trigger to start a new instance each time the CPUUtilization exceeds 75%. The load balancer would then scale requests across the number of instances currently running.

5. CodePipeline Configuration

This is the final piece of the puzzle which brings all steps 1-4 above together. You will notice that up until now we have had to manually tell CodeBuild to run, then would have to go to the EBS and manually specify the artifact for deployment. Wouldn’t it be great if all this could be done for us?

That is exactly what Codepipeline does. It fully automates the building and provisioning of the project. Once new code is checked in, the system magically takes care of the rest. Here is how to set it up.

Begin by creating a new CodePipeline. In each step. select the repository, build project, and EBS environment created in step 1-4 above.

 

Once complete the CodePipeline will begin monitoring changes to your repository. When a change is detected, it will build the project, and deploy it to the available servers in your EBS application. You can monitor the CodePipeline in real time from the pipelines detail page.

A Final Word

When configured properly, the CodePipeline is a handy tool for the developer who wants to code more and spend less time on DevOps.

This pipeline gives a developer easy access to manage a application big or small. It doesn’t take a lot of time or money to set yourself up with a scalable application that utilizes a quick and efficient build and deployment process.

If you are in need of a solution to build, test, deploy, and scale your application, consider AWS CodePipeline as a great solution to get your project up and running quickly.