logo
Menu

Building Go Applications For AWS Graviton

Learn how to migrate a Go application AWS Graviton EC2 Instances in order to achieve both higher application sustainability and lower costs

Tyler Jones
Amazon Employee
Published Aug 14, 2023
Last Modified Mar 18, 2024
Companies today are making sustainability a key goal for their business in order to improve operational efficiency and drive down cost while also lowering carbon emissions. Achieving these sustainability goals means change across all levels of the business, with application and software development being a key focus. With Go applications, one of the easiest ways to make progress towards a sustainability goal is to adopt AWS Graviton instances. AWS Graviton processors are designed by AWS to deliver the best price performance for your cloud workloads running in Amazon EC2.
In this tutorial, I will walk through the steps to take an existing application running on x86 instances today and migrate to AWS Graviton powered instances in order to achieve a higher level of sustainability for your Go application. This guide includes creating AWS resources that you will be charged for.

What you will learn

  • How to build a Go application for AWS Graviton
  • How to port an existing Go application to AWS Graviton
About
✅ AWS Level200 - Intermediate
⏱ Time to complete30 minutes
💰 Cost to completeFree when using the AWS Free Tier or USD 2.62
🧩 Prerequisites- AWS Account
- Amazon DynamoDB Table
💻 Code SampleCode sample used in tutorial on GitHub
📢 FeedbackAny feedback, issues, or just a 👍 / 👎 ?
⏰ Last Updated2023-07-20

Setup

EC2 Setup

To demonstrate how to move a Go application to AWS Graviton-based Instances, I have built a simple link shortener microservice in Go. I’m not a front end developer, so I will be relying on cURL to interact with my application’s APIs. The application is written with Go 1.20.6 and gin for my HTTP framework. The application generates a unique 10 character string for each URL that it shortens, and stores the original URL and the 10 character string in a Amazon DynamoDB table. The code is not meant to be used in production and is provided as a sample only.
For this demo, launch two EC2 instances running Amazon Linux 2023. The first instance will be of c5.xlarge instance-type, and the second will be of c6g.xlarge instance-type. Once they are running, connect to each instance and install Go.

Code Checkout

To checkout the sample project, go to building-go-applications-for-aws-graviton and clone the repository using command below:
1
git clone https://github.com/build-on-aws/building-go-applications-for-aws-graviton
Check out the code on your x86 instance and your AWS Graviton Instance. You should now have a building-go-applications-for-aws-graviton directory containing all of the appropriate code.

DynamoDB Setup

The link shortener application will leverage DynamoDB as its data store. Our DynamoDB table will run in On-Demand mode. Let's create a table called goUrlShortener with a Partition Key of shortURL for our application to use.
DynamoDB Setup
Notice we have a Partition Key of shortURL (String) and a Capacity mode of On-demand.

Compiling for x86

On your c5.xlarge instance, navigate to the building-go-applications-for-aws-graviton directory and run the following command to build the application:
1
go build -o goLinkShortener
When the build is finished you may not see any output but a binary named goLinkShortener should be in your working directory.

Compiling for AWS Graviton (ARM64)

There are two main ways you can build a binary with Go. The first is by natively compiling the code on the same system architecture you plan to run on in production. This is what we did when compiling for x86. The other option is cross-compiling for a different architecture. This can be useful when experimenting early on in the development process. This post will cover both options, but I recommend building on the same architecture you plan to run on in production whenever possible.

Cross-Compiling for AWS Graviton (ARM64)

On your c5.xlarge instance, navigate to the building-go-applications-for-aws-graviton directory and run the following command to build the application:
1
2
3
4
export GOOS=linux
export GOARCH=arm64

go build -o goLinkShortener_arm64
Just like before when the build is finished you may not see any output, but a binary named goLinkShortener_arm64 should be in your working directory. You can then take and copy this binary to any Linux system running on the arm64 architecture and it will execute. If you try to run it on a Linux system running the x86 architecture it will fail with the following error:
1
bash: ./goLinkShortener_arm64: cannot execute binary file: Exec format error
The Exec format error means you're trying to execute a binary built for a different CPU architecture.

Natively Compiling for AWS Graviton (ARM64)

On your c6g.xlarge instance, navigate to the building-go-applications-for-aws-graviton directory and run the following command to build the application:
1
go build -o goLinkShortener
You now have a working binary named goLinkShortener built natively on the arm64 architecture. Lets move on to the next step and test our application to verify its working as expected.

Start the Application

On both your c5.xlarge and c6g.xlarge instances run the following commands to start the application.
1
2
3
#Set GIN_MODE=release to minimize the amount of debug logging that would otherwise occur
export GIN_MODE=release
./goLinkShortener

Testing the Application

To test the application we will use cURL to make a few example requests and verify our application is working properly. All of the commands below can be run against both the EC2 instances.

Shortening a URL

The following command will shorten a URL. My c5.xlarge instance has an IP address of 10.3.71.184, so I’m using that in my command. Make sure to replace the IP address with the address of your EC2 Instance:
1
curl -X POST http://10.3.71.184:8080/shortenURL -H 'Content-Type: application/json' -d '{"OriginalURL":"https://aws.amazon.com/ec2/graviton/"}'
You should get output that looks like the following:
1
2
3
4
{
"shortURL": "7fcLy5Cqwd",
"originalURL": "https://aws.amazon.com/ec2/graviton/"
}
The 7fcLy5Cqwd is our application’s identifier for our URL. In a finished application this shortURL value would be used to redirect the user from the shortenedURL to their original URL. For the purposes of this demo, the JSON response is good enough.

Retrieving Full URL

In order to retrieve the original URL, we will need to make another request to the application and pass this value in to the getFullURL API, as shown in the following command.
Replace the shortened URL identifier with the identifier you got from the previous command, and make sure your IP address is correct.
1
curl -X GET http://10.3.69.250:8080/getFullURL/7fcLy5Cqwd
You should get output that looks like the following:
1
"https://aws.amazon.com/ec2/graviton/

Load Testing Results

Performance testing is key when comparing multiple instance types. In order to compare a c6g.xlarge and a c5.xlarge instance we will be performing a load test to verify that the application built for Graviton is working as expected. We discuss various load testing methodologies in the Graviton Technical Guide on GitHub and recommend using a framework like wrk2. wrk2 is a version of wrk that is modified to produce a constant throughput load and report accurate latency details across various percentiles. I decided to go ahead and use wrk2 to test the shortenURL function of our application and compare the total requests per second served as well as the latency at each percentile during our load test. I've kept the load tests simple in this guide to illustrate that testing is important.
Every time you make a software or hardware change you should re-evaluate your existing configuration and assumptions to ensure you are getting the full benefit of your new configuration. While full performance testing and optimization is outside the scope of this blog we have a comprehensive performance runbook and Go specific page in our Graviton Technical Guide on GitHub and your AWS team is always ready to help with any questions you may have.

Load test setup

Because our shortenURL function uses the POST method and requires some data, we need a Lua config file to pass to wrk2. My post.lua file has the following content:
1
2
3
wrk.method = "POST"
wrk.headers["content-type"] = "application/json"
wrk.body = '{"OriginalURL":"https://aws.amazon.com/ec2/graviton/"}'

Running the load test

Load tests are great for verifying your application performs correctly under load. For our example application lets pretend that the application should serve 99.9% of requests in under 70ms. I will then run a load test against each instance to see how many requests per second each instance can handle before this latency threshold is breached. I'll be running my load tests from a c5.18xlarge instance in the same availability zone as our test instances. I'm using a c5.18xlarge instance because I know it will be able to load test our instances thoroughly because it is a much larger instance size than any of example instances.
To start with I ran a 30 minutes load test against each instance using the following commands. While running each command, make sure your IP address is correct. The -c parameter controls how many connections are made during the load test. The -d test specifies how long the test should run. The -L parameter enables reporting of latency statistics across various percentiles. The -R parameter specifies how many requests per second the load test should run with. The -s parameter allows us to specify our Lua script we defined above.
1
2
3
4
5
# AWS C5 Instance
./wrk -c60 -t30 -d 30m -L -R 600 -s ./post.lua http://10.3.69.250:8080/shortenURL

#
AWS Graviton2 Instance
./wrk -c60 -t30 -d 30m -L -R 600 -s ./post.lua http://10.3.71.184:8080/shortenURL

Initial Results

Latency PercentilesC5.xlargeC6g.xlarge
5031.02ms15.02ms
7542.88ms22.82ms
9049.73ms26.64ms
9957.22ms35.42ms
99.969.31ms49.38ms
99.9993.38ms87.87ms
99.999180.48ms172.80ms
100230.65ms240.64ms

Driving More Load

So far our initial results look great. Our C5 instance is staying just under our threshold and our Graviton instances look like there is enough overhead to take additional traffic without breaching our latency target. Lets push each instance a bit harder and see what happens to our latency target under more load. Lets bump it up to 700 requests per second by adjusting the -R parameter to 700.
1
2
3
4
5
# AWS C5 Instance
./wrk -c60 -t30 -d 30m -L -R 700 -s ./post.lua http://10.3.69.250:8080/shortenURL

#
AWS Graviton2 Instance
./wrk -c60 -t30 -d 30m -L -R 700 -s ./post.lua http://10.3.71.184:8080/shortenURL
Latency PercentilesC5.xlargeC6g.xlarge
5029.23ms17.15ms
7543.07ms24.43ms
9050.37ms28.05ms
9957.57ms36.70ms
99.972.06ms58.56ms
99.99312.58ms132.74ms
99.999427.26ms327.17ms
100571.39ms728.58ms

Pushing Graviton To The Limit

Our C5 instances are now breaching our latency target, but Graviton instances still are under threshold. How much more can we push it until they tip over? Lets find out. Increase the load test to 800 requests per second by adjusting the -R parameter to 800.
1
2
# AWS Graviton2 Instance
./wrk -c60 -t30 -d 30m -L -R 800 -s ./post.lua http://10.3.71.184:8080/shortenURL
Latency PercentilesC6g.xlarge
5017.10ms
7524.43ms
9028.03ms
9937.12ms
99.958.49ms
99.9992.10ms
99.999217.85ms
100480.00ms
Lets see what 900 requests per second looks like by adjusting the -R parameter to 900.
1
2
# AWS Graviton2 Instance
./wrk -c60 -t30 -d 30m -L -R 900 -s ./post.lua http://10.3.71.184:8080/shortenURL
Latency PercentilesC6g.xlarge
5013.42ms
7522.45ms
9026.38ms
9934.72ms
99.956.83ms
99.99105.60ms
99.999184.83ms
100462.59ms
We're getting pretty close to the breaking point here. This next test is likely to breach all of our latency thresholds, but lets give it a shot to be sure. Lets bump it up to 1000 requests per second by adjusting the -R parameter to 1000.
1
2
# AWS Graviton2 Instance
./wrk -c60 -t30 -d 30m -L -R 1000 -s ./post.lua http://10.3.71.184:8080/shortenURL
Latency PercentilesC6g.xlarge
5012.28ms
7520.78ms
9025.28ms
9957.82ms
99.91.18s
99.992.19s
99.9992.4s
1002.49s
1,000 requests per second is definitely too many requests to handle while maintaining acceptable latency thresholds. Our Graviton instance is capable of powering 900 requests per second before breaching latency thresholds. Our x86-based C5 instance can only handle approximately 700 before breaching our latench thresholds. This means our application can serve approximately 28% more requests per instance on a C6g instance than on a C5 instance. With further testing in a production scenario we can capitalize on this by running fewer instances to serve the same amount of traffic leading to even greater cost and sustainability gains. For Go applications Graviton is proving to be the most cost effective and sustainable instance selection AWS offers today.

Cleanup

Now that we are done testing, it is time to clean up all the resources we created in this tutorial. Make sure to terminate any EC2 Instances you launched and delete your DynamoDB table so you won't incur any additional costs.

Conclusion

Migrating your Go applications from x86 EC2 Instances to AWS Graviton powered instances is simple and easy, as shown in this tutorial. AWS Graviton powered instances performed better in our load test across all latency percentiles. The ability to serve more load per instance unlocks even more cost and efficiency savings with AWS Graviton when compared to other x86 instances.
For common performance considerations and other information, visit our Graviton Technical Guide repository on Github and start migrating your application today.
 

Any opinions in this post are those of the individual author and may not reflect the opinions of AWS.

Comments