I recently implemented a GraphQL + GRPC server, with a bridge between the two. This way, clients can connect via GRPC or via GraphQL but have complete feature parity. In this post, I’ll analyze the performance (speed) of each.
The Setup
For this test, I created a complicated GRPC function that needed to load an item from the database, update it, and then return a huge amount of nested objects with lots of potential for N+1s or other inefficient queries. Expressed in terms of GraphQL, the request looked like this:
mutation { Resume(token:$token){ ID token account { ID players { ID type parentID items { ID } } } } }
It’s a basic “resume” request for an OAuth2 access token. In the response, the account includes several players, and the players each have many items. I then re-implemented the same method in pure Ruby/GraphQL (for comparison). Therefore, I had three ways of making the request:
- Pure Ruby/GraphQL
- Ruby -> GRPC GraphQL gateway
- Pure GRPC
In pure GRPC, the same request looks like this:
ResumeReq req = new ResumeReq() { Token = token, Selections = { new GraphSelection() { Name = "ID" }, new GraphSelection() { Name = "token" }, new GraphSelection() { Name = "user", Selections = { new GraphSelection() { Name = "account", Selections = { new GraphSelection() { Name = "ID" }, new GraphSelection() { Name = "players", Selections = { new GraphSelection() { Name = "ID" }, new GraphSelection() { Name = "type" }, new GraphSelection() { Name = "parentID" }, new GraphSelection() { Name = "items", Selections = { new GraphSelection() { Name = "ID" } }} }} }} }}, } }; client.Resume(req);
All requests were made to my servers running on Amazon Elastic Beanstalk, with thousands of trials for testing. It’s worth noting that the GRPC server is built in C# with .NET 4.6.
The Results
- Pure GraphQL/Ruby: 166msaverage.
- Ruby->GRPC Gateway: 128msaverage (23% reduction).
- Pure GRPC: 63msaverage (62% reduction).
Analysis
There are a lot of variables at play here. Much of the benefits seen in the pure GRPC implementation are certainly due to HTTP/2 vs. HTTP/1.1. With much lower overhead per request, it’s no surprise that requests take 1/3 the time.
Still, there was a meaningful reduction with the Ruby->GRPC gateway. Since the client still connects with HTTP/1.1 in this case, it cannot be explained by protocol. Furthermore, the server has to go through the additional hop of translating the JSON into a GRPC call and forwarding it to the GRPC service behind the Ruby server. One would expect that this would increase overhead, not decrease it (as compared to pure GraphQL/Ruby).
One explanation would be differences in implementation efficiency between the pure GraphQL and pure GRPC servers. However, this doesn’t hold much water: the database queries are nearly identical in both cases. The rest of the logic is rather straightforward serialization and deserialization. If anything, the overhead of going through a JSON<->GRPC conversion should increase the time for the gateway.
I cannot conclude that Ruby is necessarily a “slow language”, as many have done. Still, whatever the reason, C# serving GRPC calls through a gateway was still faster than the Ruby gateway itself doing the work.