Performance Comparison on Spring Rest vs GRPC Unary and Stream Processing

Eresh Gorantla
The Startup
Published in
11 min readDec 29, 2020

This story talks about performance of Spring Rest API vs gRPC service implementation. We will use the Apache benchmark to perform a performance test on exhaustive load.

gRPC is an RPC implementation/framework from Google for inter-microservices communication. Google has been using this for more than 15 years (the internal name is Stubby). It is battle tested for more than a decade. Google site shows they have been processing 10 BILLIONS requests / second using gRPC.

We will see why gRPC is faster than REST with a simple. Before that Let see the basic difference between Rest and RPC

GRPC Uses Http 2

GRPC brings in many advantages in using HTTP2 protocol. Please see for the details.

  1. Binary Framing:
  • Transport of message is lighter and safer to decode.
  • It works well with Protocol Buffer.
  • More performant and robust.

2. Header Compression:

  • GRPC uses HPACK for header compression.
  • It reduces overhead and improve performance.

3. Multiplexing:

  • We can send multiple requests and responses in parallel over a TCP connection.
  • It will improve network utilization and reduce latency.
  • One client request, multiple responses.

HTTP/1.1 Vs HTTP/2

There are many websites available to test the performance of HTTP/1.1 over HTTP/2. Please visit this site http://www.http2demo.io/

If you observe HTTP/1.1 took 1.82 seconds to download 200 images where as HTTP/2 took only 0.58 seconds which is almost three times lesser. This is because, it sends multiple requests in parallel over a single TCP connection.

Difference between GRPC and Rest

We could tell that GRPC has many advantages to rest. The only limited support on browser side, GRPC needs gRPC-Web as proxy layer to convert between HTTP/2 to HTTP/1.1.

Where can be gRPC used?

  1. Micro Services
  • Low latency and hight throughput communication.
  • Strong API Contract.

2. Polyglot Environments

  • Code generation is out of the box for many programming languages.

3. Point to Point real time communication

  • With the excellent support for Bi directional streaming support, gRPC suits well for real time data streaming.

4. Low Network or Mobile Networks

  • Light weight message format, the data transfer is faster.

There are four types of service definitions in gRPC

Unary : This is regular blocking service for request and response. Just like our rest stateless calls.

Server Streaming: Server sends multiple messages to the client via single TCP connection. An example could be server pushing updates to clients periodically.

Client Streaming: Client keeps on sending a request to the server by using a single TCP connection. The server might accept all the messages and sends a single response back. An example could be file upload from client side.

Bi-Directional Streaming: Client and Server can keep on sharing messages via single TCP connection. An example could be client and server sharing messages like a chat application.

Performance testing on Rest API vs gRPC Services

The above is the use case we are going to implement. Basically it is generation of userdetails randomly. Just call a method will generate user details. This is same for both gRPC and Rest implementations. The role of enricher / aggregator is, it routes to gRPC implementation or Spring rest based on URI. To perform load test, added the path variable “range” in aggregator service, this is for looping, for each request made to aggregator, it loops from 1 to range values and gives us the response in Json Map.

Let us jump in to implementation

gRPC server

To implement gRPC, first we need to define ProtoBuffer and then we should generate the code base.

The whole application is on gradle build. But this proto buffer code is in maven. Some how I couldn’t generate jar using gradle plugin, I could do it in maven and installed the dependency in my local repo. This is added as dependency to other services.

UserDetails.proto

UserDetailsRequest has firstName, lastName and city. The response has id, numericId, firstName, lastName and city.

Please find source code here.

Do Build by executing
mvn clean package

After this target folder will have jar file created, then deploy this jar to local maven repository.

mvn install:install-file -Dfile=user-details-proto-1.0-SNAPSHOT.jar -DgroupId=com.userdetails.model -DartifactId=user-details-proto -Dversion=1.0-SNAPSHOT -Dpackaging=jar

The service method has two one as simple request → response way and the other is Bi directional streaming.

gRPC Service implementation

Spring rest implementation

Aggregator Rest API

UserDetailsRestClient

gRPCBlockingClient

gRPCStreamClient

Build and deploy

I have use docker compose file to build these applications as a whole.

To run application

Build all the three applications and run docker compose

gradle clean bootJar
docker-compose up

The console

ereshgorantla@Ereshs-MacBook-Pro spring-rest-vs-grpc-performance % docker-compose up
WARNING: The Docker Engine you're using is running in swarm mode.
Compose does not use swarm mode to deploy services to multiple nodes in a swarm. All containers will be scheduled on the current node.To deploy your application across the swarm, use `docker stack deploy`.Starting spring-rest-vs-grpc-performance_user-grpc-service_1 ... done
Starting spring-rest-vs-grpc-performance_rest-grpc-aggregator_1 ... done
Starting spring-rest-vs-grpc-performance_user-rest-service_1 ... done
Attaching to spring-rest-vs-grpc-performance_user-grpc-service_1, spring-rest-vs-grpc-performance_user-rest-service_1, spring-rest-vs-grpc-performance_rest-grpc-aggregator_1
rest-grpc-aggregator_1 |
rest-grpc-aggregator_1 | . ____ _ __ _ _
rest-grpc-aggregator_1 | /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
rest-grpc-aggregator_1 | ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
rest-grpc-aggregator_1 | \\/ ___)| |_)| | | | | || (_| | ) ) ) )
rest-grpc-aggregator_1 | ' |____| .__|_| |_|_| |_\__, | / / / /
rest-grpc-aggregator_1 | =========|_|==============|___/=/_/_/_/
rest-grpc-aggregator_1 | :: Spring Boot :: (v2.4.1)
rest-grpc-aggregator_1 |
user-grpc-service_1 |
user-grpc-service_1 | . ____ _ __ _ _
user-grpc-service_1 | /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
user-grpc-service_1 | ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
user-grpc-service_1 | \\/ ___)| |_)| | | | | || (_| | ) ) ) )
user-grpc-service_1 | ' |____| .__|_| |_|_| |_\__, | / / / /
user-grpc-service_1 | =========|_|==============|___/=/_/_/_/
user-grpc-service_1 | :: Spring Boot :: (v2.4.1)
user-grpc-service_1 |
user-rest-service_1 |
user-rest-service_1 | . ____ _ __ _ _
user-rest-service_1 | /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
user-rest-service_1 | ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
user-rest-service_1 | \\/ ___)| |_)| | | | | || (_| | ) ) ) )
user-rest-service_1 | ' |____| .__|_| |_|_| |_\__, | / / / /
user-rest-service_1 | =========|_|==============|___/=/_/_/_/
user-rest-service_1 | :: Spring Boot :: (v2.4.1)
user-rest-service_1 |
user-grpc-service_1 | 2020-12-29 06:55:10.560 INFO 7 --- [ main] c.r.g.p.UserGrpcServiceApplication : Starting UserGrpcServiceApplication using Java 11.0.6 on 7cc9ef74b23a with PID 7 (/usr/app/app.jar started by root in /usr/app)
user-grpc-service_1 | 2020-12-29 06:55:10.575 INFO 7 --- [ main] c.r.g.p.UserGrpcServiceApplication : No active profile set, falling back to default profiles: default
rest-grpc-aggregator_1 | 2020-12-29 06:55:10.642 INFO 7 --- [ main] c.r.g.p.RestGrpcAggregatorApplication : Starting RestGrpcAggregatorApplication using Java 11.0.6 on 4e5347aa63dc with PID 7 (/usr/app/app.jar started by root in /usr/app)
rest-grpc-aggregator_1 | 2020-12-29 06:55:10.658 INFO 7 --- [ main] c.r.g.p.RestGrpcAggregatorApplication : No active profile set, falling back to default profiles: default
user-rest-service_1 | 2020-12-29 06:55:10.846 INFO 8 --- [ main] c.r.g.p.UserRestServiceApplication : Starting UserRestServiceApplication using Java 11.0.6 on 4e34622731f2 with PID 8 (/usr/app/app.jar started by root in /usr/app)
user-rest-service_1 | 2020-12-29 06:55:10.871 INFO 8 --- [ main] c.r.g.p.UserRestServiceApplication : No active profile set, falling back to default profiles: default
user-grpc-service_1 | 2020-12-29 06:55:12.118 INFO 7 --- [ main] c.r.g.p.UserGrpcServiceApplication : Started UserGrpcServiceApplication in 2.894 seconds (JVM running for 3.938)
user-grpc-service_1 | 2020-12-29 06:55:12.121 INFO 7 --- [ main] o.l.springboot.grpc.GRpcServerRunner : Starting gRPC Server ...
user-grpc-service_1 | 2020-12-29 06:55:12.202 INFO 7 --- [ main] o.l.springboot.grpc.GRpcServerRunner : 'com.rest.grpc.performance.service.UserDetailsService' service has been registered.
user-grpc-service_1 | 2020-12-29 06:55:12.588 INFO 7 --- [ main] o.l.springboot.grpc.GRpcServerRunner : gRPC Server started, listening on port 9000.
rest-grpc-aggregator_1 | 2020-12-29 06:55:13.004 INFO 7 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
rest-grpc-aggregator_1 | 2020-12-29 06:55:13.020 INFO 7 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
rest-grpc-aggregator_1 | 2020-12-29 06:55:13.020 INFO 7 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.41]
rest-grpc-aggregator_1 | 2020-12-29 06:55:13.138 INFO 7 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
rest-grpc-aggregator_1 | 2020-12-29 06:55:13.138 INFO 7 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 2210 ms
user-rest-service_1 | 2020-12-29 06:55:13.410 INFO 8 --- [ main] o.s.b.web.embedded.netty.NettyWebServer : Netty started on port(s): 9001
user-rest-service_1 | 2020-12-29 06:55:13.455 INFO 8 --- [ main] c.r.g.p.UserRestServiceApplication : Started UserRestServiceApplication in 4.305 seconds (JVM running for 5.275)
rest-grpc-aggregator_1 | 2020-12-29 06:55:14.259 INFO 7 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
rest-grpc-aggregator_1 | 2020-12-29 06:55:14.531 INFO 7 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
rest-grpc-aggregator_1 | 2020-12-29 06:55:14.545 INFO 7 --- [ main] c.r.g.p.RestGrpcAggregatorApplication : Started RestGrpcAggregatorApplication in 5.262 seconds (JVM running for 6.365)

Let us use Apache benchmark to do load test.

My System Configuration

For Spring Rest end point

I observed that CPU utilization went till 58% for 800 requests with concurrency of 100 for spring rest. Total Time- ~63 seconds , Number of requests/sec — ~13 requests.

ereshgorantla@Ereshs-MacBook-Pro ~ % ab -n 800 -c 100 -s 500 http://localhost:8080/api/rest/unary/user/1000
This is ApacheBench, Version 2.3 <$Revision: 1879490 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking localhost (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Completed 600 requests
Completed 700 requests
Completed 800 requests
Finished 800 requests
Server Software:
Server Hostname: localhost
Server Port: 8080
Document Path: /api/rest/unary/user/1000
Document Length: 176001 bytes
Concurrency Level: 100
Time taken for tests: 63.380 seconds
Complete requests: 800
Failed requests: 0
Total transferred: 140884800 bytes
HTML transferred: 140800800 bytes
Requests per second: 12.62 [#/sec] (mean)
Time per request: 7922.475 [ms] (mean)
Time per request: 79.225 [ms] (mean, across all concurrent requests)
Transfer rate: 2170.77 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 1 1.9 0 15
Processing: 200 7820 1522.5 7379 11374
Waiting: 193 7815 1520.7 7374 11365
Total: 200 7821 1523.2 7380 11378
Percentage of the requests served within a certain time (ms)
50% 7380
66% 8342
75% 8941
80% 9405
90% 10205
95% 10953
98% 11007
99% 11356
100% 11378 (longest request)

For gRPC Unary API

I observed that CPU utilization went till 35% for 800 requests with concurrency of 100 for spring rest. Total Time- ~28 seconds , Number of requests/sec — ~28 requests.

ereshgorantla@Ereshs-MacBook-Pro ~ % ab -n 800 -c 100 -s 500 http://localhost:8080/api/grpc/unary/user/1000
This is ApacheBench, Version 2.3 <$Revision: 1879490 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking localhost (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Completed 600 requests
Completed 700 requests
Completed 800 requests
Finished 800 requests
Server Software:
Server Hostname: localhost
Server Port: 8080
Document Path: /api/grpc/unary/user/1000
Document Length: 176001 bytes
Concurrency Level: 100
Time taken for tests: 27.700 seconds
Complete requests: 800
Failed requests: 0
Total transferred: 140884800 bytes
HTML transferred: 140800800 bytes
Requests per second: 28.88 [#/sec] (mean)
Time per request: 3462.465 [ms] (mean)
Time per request: 34.625 [ms] (mean, across all concurrent requests)
Transfer rate: 4966.94 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.7 0 3
Processing: 249 3424 258.1 3496 3722
Waiting: 244 3422 258.2 3494 3719
Total: 249 3425 257.9 3496 3722
Percentage of the requests served within a certain time (ms)
50% 3496
66% 3592
75% 3615
80% 3631
90% 3664
95% 3683
98% 3700
99% 3713
100% 3722 (longest request)

For gRPC Bi Directional Stream

I observed that CPU utilization went till 15% for 800 requests with concurrency of 100 for spring rest. Total Time- ~11 seconds , Number of requests/sec — ~72 requests.

ereshgorantla@Ereshs-MacBook-Pro ~ % ab -n 800 -c 100 -s 500 http://localhost:8080/api/grpc/stream/user/1000
This is ApacheBench, Version 2.3 <$Revision: 1879490 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking localhost (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Completed 600 requests
Completed 700 requests
Completed 800 requests
Finished 800 requests
Server Software:
Server Hostname: localhost
Server Port: 8080
Document Path: /api/grpc/stream/user/1000
Document Length: 176001 bytes
Concurrency Level: 100
Time taken for tests: 11.061 seconds
Complete requests: 800
Failed requests: 0
Total transferred: 140884800 bytes
HTML transferred: 140800800 bytes
Requests per second: 72.32 [#/sec] (mean)
Time per request: 1382.678 [ms] (mean)
Time per request: 13.827 [ms] (mean, across all concurrent requests)
Transfer rate: 12438.07 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 1 0.8 0 6
Processing: 66 1370 204.8 1350 1890
Waiting: 62 1366 203.3 1346 1888
Total: 66 1371 205.2 1350 1891
WARNING: The median and mean for the initial connection time are not within a normal deviation
These results are probably not that reliable.
Percentage of the requests served within a certain time (ms)
50% 1350
66% 1364
75% 1387
80% 1402
90% 1843
95% 1873
98% 1878
99% 1880
100% 1891 (longest request)

By the numbers it is evident that gRPC blocking/unary out performs Spring rest considerably good in terms of cpu utilization and throughput wise. The gRPC Bi-directional stream improved the performance a lot in terms of memory and throughput which is terrific.

Comparison Rest vs gRPC unary vs gRPC Bi-directional

Please find the complete source code here

--

--

Eresh Gorantla
The Startup

Experience in Open source stack, microservices, event-driven, analytics. Loves Cricket, cooking, movies and travelling.