Rest API Performance Comparison between Golang and Play
We at Y Media Labs are using the Play framework to build up server components for many of our projects. Play aims to optimize the developer productivity by using convention over configuration. We observe that Golang or Go’s popularity has increased exponentially over past 8 years and continues to grow. In this post, we will compare the performance of REST APIs written in Go (using Go standard packages) and Play framework in same environment and observe the results. Before proceeding to the analysis of their performances, here is a brief introduction about the Go and Play framework.
Golang
Golang is one of the system level language like C. It was developed and open sourced by Google in the year 2009. Go is in such an exciting stage today. It has a very active open source community but is improving and becoming more dynamic all of the time.Current stable version is 1.9. It was created with cloud computing in mind, which will arguably define the future of the tech world. Moreover, it was designed considering the possibilities opened by modern hardware architectures and the needs of our age computing problems. Now a days Golang is mainly used for structured storage and creation of large databases,on-line games, caches,distributed/grid computing,virtual-machines and HTTP server and many other things.
Play
Play is one of popular web framework. Play Framework is written in Scala and have been open sourced. It can be used by other programming languages that are compiled to Bytecode, e.g. Java, which follows the model–view–controller architectural pattern. It is based on a lightweight, stateless, web-friendly architecture. It was developed focusing developer productivity and targeting RESTful Architecture. Applications developed using Play will scale predictably due to a stateless and non-blocking architecture. The ecosystem around Java is huge. There are libraries for everything — most of which can be used in Play easily.
Now when we have basic understanding of these two languages, let’s move forward and setup the things which are required for the comparison.
Benchmark Setup
Database Setup
MySQL Database was used to hold the data for the application. Total of 3 tables namely users, user_session and posts were created. The table users holds user information such as id, username, email, password and created_date. Session information such as id, user_id, session_token and login_time of the user was recorded in user_session table. The posts created by users in session were recorded in posts table with fields: id, user_id, post_description,number_of_likes, number_of_comments and created_date. In both user_session and posts, user_id is the foreign key referring to the primary key id of users table.
Rest API Development
We developed two API’s (POST and GET) in Golang and Play framework which we used to test all the possible scenarios making sure that the business logic remains same.
POST API
Post API is used to create a new post for the user. It just has description which needs to be added to the table called “posts” along with the userId. You may think this as status updates in Facebook which can be millions request per minute. Request body is of Json type which contains the post description and user’s access token.
GET API
Get API is used to list the posts related to the user. UserId of the user who created the posts is sent as query parameter.
Business logic
For POST API
1. Validation of the json request body containing the description and token as the fields.
2. Validation of token field. It should be associated with the userId (In UserSession Table).
3. If everything is fine, add a new entry to the post table with description,userId and created_time.
For GET API
1. Validate the userId sent as query parameter.
2. Get list of posts created by the userId received.
Flow of Control in Play framework
Play is built using Model View Controller (MVC) architectural pattern. The MVC pattern divides the application into 3 layers: a model, a view and a controller. For every rest call, which controller to consult is specified in router file found in conf folder, in the root directory of the application.
The complete play source code can be found here.
Routes
The Play router translates HTTP requests into an action call. The HTTP Requests are considered as events in MVC architecture. Each of these events provide 2 parameters: request path and request HTTP method.
When a HTTP request is made, the Action method mentioned in the request path is called. Here /api/write/createpost and /api/read/getposts are the request path and controllers.UserController.createPostByUser() and controllers.UserController.getPostsByUser() are the action methods respectively to be called in User Controller.
Controllers
Controllers holds the actions and navigation aspects of the application. The controller methods creates model instances and manipulates its state based on business logic.
The userSignUp() serves a POST request. The incoming Json request is first read into a JsonNode from request(). Whereas getPostsByUser() servers a GET request. These methods perform required input validation. Both userSignUp() and getUserPosts() under the model User is called to complete the business logic. Then, an appropriate response based on the success or failure of the method execution is returned.
Model Method
Models represent concepts of business and has central position in Play! Any manipulations of business logic (database queries) is done in the methods written for the same. Play comes with Ebean object relational mapper (orm) to handle RDBMS database queries. Ebean is written in Java to be Session Less, thus making it more simpler to understand and use than JPA (Java Persistence API). Queries can be written using raw sql or Ebean orm syntax.
The methods createPosts() and getUserPosts() make required query calls to database and return the data.
Flow of control in Golang
Rest API development in Golang is not a tedious task. You know what you are doing and you can expect no magic happening.You can control the flow as you want. Golang comes with packages to build up nice apps.
The complete golang source code can be found here.
We used julienschmidt/httprouter package for routing the http request to the handlers(Which handles the request and send back response to http call). To connect to DB we used database/sql package(see db.go file).
Whenever a HTTP request is made, httprouter handles it and routes it to the appropriate handler. In our case, we have two request path namely “/create_post” and “/posts” and respective handlers createPostHandler and getPostsHandler (see main function in main.go).
Validation is one of the important step for any programming model. We used github.com/go-ozzo/ozzo-validation Golang package for data validation. With this package we can validate the struct fields and handle error cases easily.
We handled the same scenarios as explained in Play before.In CreatePost Handler, Initially we validate the Json request body containing all the required fields.Then we validate the user token if it belongs to the user, if not we sent Unauthorized access response.
If everything is fine, then we call createPost method which accepts posts description and userId as parameters. createPost method is responsible for adding a new entry to the “posts” table.
In GetPost Handler, we validate the query parameter “userId” for fetching the posts related to the userId.
We called getPostsListForUser method with userId as parameter. getPostsListForUser returns the list of objects containing the description,created_date as fields.
Scalability Test using Loader.io
Loader.io is a free stress testing service to test Web-Apps/Apis with thousands of concurrent requests. It was developed by SendGrid Labs in 2012. Loader.io allows scalability testing up to 50,000 concurrent connections for free. It is a cloud based no install solution and immediately available for developers to test. It helps keep high quality of code and performance and scalability-minded.
Prerequisites for Stress Test
Requirements to perform stress test are a valid email address and server to test. Create a Loader.io account by choosing an appropriate plan.While creating a HOST for testing, loader.io expects a verification token file response for some get request before validating the test end point. Place the verification token in a file in the root directory of the application such that the file is returned on one of the url calls. Alternatively, the verification token could be returned as string value in a method that can be called at one of the returned urls.
Test Scenarios
The Stress test was performed on APIs written in Golang, Play framework using Ebean orm queries and Play framework using Raw queries. The Stress test was conducted by running the application by connecting to locally installed mySQL database on the same EC2 (t2.medium) instance as well as RDS (m1.medium) instance respectively. There are 3 ways to stimulate clients for the test:
- Clients per Test (Specified number of clients are distributed evenly throughout the test duration)
- Clients per Second (Specified number of clients are generated each second)
- Maintain Client Load (A constant client count will be maintained throughout the test duration)
All the POST and GET APIs were tested by simulating 300, 500, 700 and 1000 clients per second. A 100 posts made by a single user were obtained for each GET request call.
Observation and Result
GET and POST APIs are written in Golang and Play framework respectively. However Play framework allows developer to code with Ebean queries or raw queries, we implemented in both ways. As a result we will be comparing the APIs between Golang, Play with Ebean and Play with raw queries. Database (mysql) can stay in the same instance or it can be hosted on cloud(like RDS), hence we got two more main scenarios.We will first showcase results having DB in the same instance followed by the DB in RDS.
Running Application by connecting to local mysql database
GET APIs
All the GET requests were completed successfully for 300, 500, 700 and 1000 clients per second without any Timeouts and 0% Error Rate. Golang APIs render the response with an average time of 2 ms for all the client load. Whereas Play APIs (Both Ebean and Raw queries) responded with an average time of 3–4 ms.
POST APIs
While performance has been comparable for GET APIs, we see some significant difference for POST APIs with increase in clients per second. The above graph displays the average response time vs increasing client load. Golang responds with an average of 4–5 ms. Whereas, Play Ebean APIs respond with an average ranging from 596–1410 ms and Play Raw APIs with 427–1684 ms.
There was no timeouts/errors for GET APIs for both play and go. However for POST APIs there were timeouts and error cases and it can be seen by the following graphs.
Timeouts for post request varies depending on the client load. At 300 clients per seconds, timeouts were 0 for all three scenarios but for 1000 clients per second, timeouts were Golang — 0, Play Raw Queries — 674 and Play Ebean Queries — 1214.With Increase in load, Play is not able to fulfill the incoming request and hence there is significant increase in the number of timeouts.
Similarly, Error rates at 300 clients per second is 0 for all three APIs but for 1000 clients per second: Golang — 0.6%, Play Raw — 5.3% and Play Ebean — 18%. Error can possibly be internal server error and validation error. Since we pre-processed the input data and data was valid and unique, the error rate is solemnly due to the internal server error which can be caused by any of the following issues:
DB Connection error (whenever no of required db connections > max no of DB connection ).
Memory error (Not enough memory to scale up).
Network Error (Not able to connect to the RDS instance).
Running Application by connecting to RDS instance
GET APIs
The Get APIs when run by connecting to RDS instance for client loads of 300, 500, 700 and 1000 per second, Error rate for all the cases was found to be 0%. There were 0 timeouts for Golang. While, Play Ebean and Play Raw have timeouts of 482 and 211 respectively at the load of 1000 clients per second. The Average response time for Golang has been 9 ms for 1000 clients per second and 4 ms for the rest. Play API with Raw queries responded with an average response time of 400 ms at 1000 clients per second and 8–9 ms for the rest. Play API with Ebean queries responded with an average response time of 383 ms at 1000 clients and 9–42 ms for the rest.
POST APIs
The above graph displays the average response time vs increasing client load for the POST APIs when connected to RDS instance. Golang responds with an average of 11 ms at 300 clients and 311 ms at 1000. Whereas, Play Ebean APIs respond with an average ranging from 26–1230 ms and Play Raw APIs with 20–1272 ms.
No timeouts occurred for Golang irrespective of client load. However, timeouts of 409 and 1003 occurred for Play API with Raw queries at 700 and 1000 clients per second respectively. For Play API with Ebean queries Time outs were 199, 477 and 1059 for 500, 700 and 1000 clients respectively.
Looking at all of the above graphs, You now know that Golang is doing far better job when compared to the play framework in terms of Rest API Performance under stress. However Play Framework comes with most of utilities/functionalities that makes web development easy and fast. Play has most Active community because of JAVA. But Golang has gained pace for last few years and current Golang’s stable version is 1.9
So what we suggest is that if your application needs faster development and doesn’t care about the API performance you can use Play for development. If your application is all about performance and have enough time for development then you can go for Golang. If you have already written your application in some language and you want to increase the performance of the some of the APIs you can always build those services in Golang.
“Go will be the server language of the future.” — Tobias Lütke, Shopify
I would like to thank Harini Jayasimha,who helped me with the Play Application development and contribution to this article.