How to Migrate a Spring App to the Cloud: Part II

Picking a Cloud Infrastructure — EC2 & Lambda solutions

Melina Schweizer
My Local Farmer Engineering
8 min readOct 5, 2021

--

TL;DR; These are the considerations we went through while analyzing several ways of migrating our Java Spring application.
Spoiler alert: we chose AWS ECS w/ FARGATE

So, we have a Java Spring application that needs to be migrated. As most migrations onto the cloud, there’s a bunch of ways of doing this, so how do we pick? In this post, we will cover the options we researched, mention highlights and talk about the elephant in the room… the running costs of each.
We’re going to use a medium-sized app with an RDS Database as the base for the cost analysis, with the on-prem infrastructure detailed on this post.

Disclaimer
I Love My Local Farmer is a fictional company inspired by customer interactions with AWS Solutions Architects. Any stories told in this blog are not related to a specific customer. Similarities with any real companies, people, or situations are purely coincidental. Stories in this blog represent the views of the authors and are not endorsed by AWS.

For the cost comparison, we’re going to catalogue the solutions we’re presenting into 2 groups: those that need EC2 as the underlying infrastructure, and those that don’t (i.e. serverless). For the EC2 group, I will assume using 4 m5.2xlarge instances, which are comparable to what we have on-prem. These will give us plenty of high availability. For the serverless solutions based on number of requests, we will use 1M daily requests at about 10TB, which corresponds to what we’ve seen in our on-prem environment. Low and behold though, this is not an exact science!

I will assume medium traffic patterns that are not terribly spiky, for simplicity. I will also assume On-Demand pricing, though we are aware that subscribing to Savings Plans or using Spot instances could help us save quite a bit (we’ll look into that once the migration is settled). The price for the extra services (e.g. CloudWatch, X-Ray, EBS, ECR, S3, etc) will not be included, we won’t include Data Transfer costs since those will stay the same across all the options available (watch out, those can be considerable!), nor will we factor in any free-tiers.

Elastic Beanstalk

During our trial run we found this service to be very easy to set up. We literally chose the type of environment we wanted (e.g. Tomcat & Apache in our case), uploaded the WAR file, and edited our RDS database’s Security Group to allow the inbound connection from the new environment. That’s it!

The pre-configured environment is already integrated with monitoring (CloudWatch for logging & X-Ray for tracing), and has load balancing and scaling set up which you can customize. To do High Availability you should have at least 2 or preferably 3 EC2 instances up for handling requests. With this service you can even specify how your deployments will be done (e.g. all-at-once? rolling? blue/green?).

So what are the drawbacks? This is a managed service which offers a lot of automation, so you’re limited to the environments that are available, and when things go left it might require a bit of detective work, e.g. for a failed or too slow deployment, and further customizations might not be that easy to do. Cost-wise, Beanstalk itself is free but you’ll pay for all the underlying infrastructure it sets up.

Given its pros and cons, this seems to be a good solution for applications which need to be migrated quickly and painlessly, or perhaps an intranet app, and it might be a great way to start off in the cloud. On the other hand, it might be costlier than other alternatives since you’re paying for EC2 instances, but there’s also the option to bring down the monthly bill down considerably if you’re able to leverage Savings Plans or Spot instances.

With the assumptions we talked about, our EC2 m5.2xlarge instances in the Paris region will cost us somewhere between:

  • if using a min. of 2 EC2s: $0.448 * 2 instances * 24 hr * 30 day = $645/month , and
  • if using a min. of 4 EC2s: $0.448 * 4 instances * 24 hr * 30 day = $1290/month

The true cost will depend on the shape of the traffic and how the scaling responds.

“Lift & Shift”, i.e. copy onto EC2

This is a bare-bones solution, where basically you set up your entire infrastructure (using EC2 instances, the cloud version of a server) similar to how your on-prem servers would be set up, and place the app on it. It’s basically what Elastic Beanstalk would do for you, but you’ll have to do scaling, networking, monitoring, handle the deployment, and everything else on your own.

Advantages? You’re doing everything manually, so you can set up whatever you want.
Cons? You’re doing everything manually, so you’ll HAVE to set up whatever you want 😅

The infrastructure cost will be similar to that of Elastic Beanstalk, but there will be higher operational costs due to the time it takes to patching, upgrading the base AMI, etc. Also, novice cloud teams will probably have a bigger learning curve ahead of them since they’ll need to figure out on their own how to set up the environment efficiently and securely.

  • Cost: similar to Elastic Beanstalk: $645 — $1290/month

Lambda — JVM

AWS Lambda is a serverless solution, which was born to handle stateless, microservice workloads . We have used this service in a past project and were very pleased with the results. The back-end APIs for that project only suffer a 2–3s cold start when the Lambda container needs to be initialized… but our full-stack Spring app is the opposite of a stateless microservice app. Our tests running our Spring app under a regular JVM Lambda results in a 9s cold start, which we deemed unacceptable for our requirements.

Even so, if we were to go down this path, using the AWS Calculator and assuming 1M requests/day with an avg of 200ms for each request in the Paris region we have:

  • when running a 2GB Lambda: $206/month

It’s a very cost-effective alternative, as long as you can ignore the cold starts (maybe for our next intranet app!) or can fine-tune for performance.

However, our research has uncovered other alternatives using Lambda more efficiently for these kinds of apps and so we did a bit of research on these options as well. We’ll cover a couple, but note that there’s been a lot of movement very recently on the Java Lambda front for improving performance (e.g. Tiered Compilation , Java Lambda & Quarkus, Spring Native (Beta)), so make sure to look at the latest updates when deciding which serverless solution to pursue.

Lambda — GraalVM

GraalVM is an alternative to using a regular JVM, where it compiles classes ahead of time (in the build step) and produces a native executable (i.e. no JVM needed to run it anymore). This allows for a significant cut in start-up time, which in turns makes it a more viable options for using our Spring app as a serverless workload. Some key distinctions for the execution are:

  • all classes are already loaded, linked and sometimes even initialized partly
  • runs with less memory and starts a lot faster than a regular JVM Lambda (one of our Lambdas had a cold start of 1.7s on a 2GB Java 11 Lambda, whereas the GraalVM Lambda was ~300ms with only 256MB)
  • heavy use of reflection done for Dependency Injection in some Frameworks (i.e. Spring) means that the classes cannot be compiled/loaded ahead of time, which compromises the move to GraalVM. To circumvent this issue, we must use readily-available config files for wiring the classes, or generate our own thru the GraalVm tracing agent

This looks like a promising alternative but requires quite a bit of setting up and a large learning curve, even for cloud-savvy folks. To get this working, we needed to set up a Lambda Custom Runtime, Docker, and change our pom.xml to handle the building of the native executable. More importantly, you need to be able to run the GraalVM tracing agent on all the execution paths within your app, so that they can be inspected and a config file generated. This requires building unit tests to cover all of these, or a main() function. In our experimentation, we focused on a utility class which connects to a database via user/password and another which connects via RDS IAM authentication.

After two weeks of researching and trying out things, we were able to execute the former successfully (super fast!), but gave up on the latter. Full disclaimer though, we’re not sure if the issue lies on the GraalVM setup or the IAM authentication, since this type of DB-authentication can be very difficult to debug. Also to take into account, once we were past the learning curve things became a lot easier. But by then we had run out of time to continue down this path. In any case, if you pursue this route, make sure you have sufficient time for trial & error.

In the case of getting this to work, the benefits are huge, for both cost & start-up times. We used the AWS Calculator for pricing, and assuming our 1M requests/day with an avg of 200ms for each request, for the Paris region we have:are:

  • when running a 256MB Lambda: $31/month
  • when running a 512MB Lambda: $56/month

Granted, this may or may not be a fair comparison against our other options, since one handles # requests & duration vs instance sizes capable of handling them. It should however give you a rough idea of where things stand.

Lambda — Micronaut

Micronaut is a JVM framework known for fast start-up times and small memory footprint, which does Dependency Injection at compile time. We’ve read a few articles on Spring (SpringBoot, to be exact) vs Micronaut, and the migration seems to require some code changes, which we wanted to avoid for now. Among other things, it requires redefining annotated beans into corresponding Micronaut types, watching out for different default bean scopes, tweaking a few configuration files, and obviously, a lot of testing.

The example articles (Article 1, Article 2) we saw spoke of a cold start for their migrated Micronaut apps between 2–4s, which is pretty good for an application of this type. To put it in perspective, that’s the same cold start we have with one of our bare-bones microservice JVM Lambda with database connectivity, with no Hibernate nor any other frills. That’s pretty amazing!

Because of lack of time we didn’t get a chance to try out this route, but it is definitely in our radar for a 2nd iteration, once we have gained a more solid footing on the cloud and are not under a time constraint. Pricing-wise, it will probably be similar as the GraalVM Option.

In this post, we’ve covered some EC2 options and tackled Serverless using different options for Lambda as well. To conclude our research, the next post will cover containers using EC2-backed ECS as well as its serverless counterpart, ECS w/Fargate.

Stay tuned!

--

--

Melina Schweizer
My Local Farmer Engineering

Melina Schweizer is a Sr. Partner Solutions Architect at AWS and an avid do-it-yourselfer.