Ruby vs Elixir vs Go: A concurrency comparision

It’s has been 8 years since I have started working in Ruby on Rails. Over a period of time, Ruby made so many performance improvements which helped us to build applications which performs well under decent load. Though current ruby version performs better compare to previous versions developers aren’t happy with the performance when they are building highly concurrent web applications. I have gone through the internet and many developers posted many use cases in which Ruby didn’t yield to them. But still, I haven’t convinced with the opinions that they have provided. So I have decided to a make a smaller experiment with Ruby to know more about its performance side.

As we all know, for comparison you need more than one language. So, I choose Golang and Elixir along with Ruby. When I search about building highly concurrent applications most of the results would involve either one of these languages and moreover I like these two languages. Golang for its simplicity and Elixir for its Ruby like features. So, without being late let’s start the experiment.

Use case: Fetching a document from AWS Elastic search

Hardware: MacBook Pro, 2.7 GHz Intel Core i5, 16 GB 1867 MHz DDR3

For this experiment I have used Sinatra in Ruby with Puma as server, standard net/http library in Golang and Trot in Elixir.

Here are the code snippets in different languages.

Ruby:

# Version: 2.4.1p111
require 'sinatra'
require 'elasticsearch'
require 'elasticsearch/transport'
get '/' do
client = Elasticsearch::Client.new log: true, url: "<AWS URL>"
  response = client.search index: 'staging_shambas', 
body: { query: { match: { id: "100"}}}
response.to_json
end

Golang:

# Version: 1.10
package main
import (
"context"
"gopkg.in/olivere/elastic.v5"
"log"
"net/http"
"encoding/json"
)
func handler(w http.ResponseWriter, r *http.Request) {
ctx := context.Background()
client, err := elastic.NewClient(elastic.SetSniff(false),
elastic.SetURL("<AWS URL>"))
 if err != nil {
// Handle error
panic(err)
}
 searchResult, err := client.Get().
Index("staging_shambas"). // search in index "twitter"
Type("shamba").
Id("100").
Do(ctx)

if err != nil {
panic(err)
}
  w.Header().Set("Content-Type", "application/json")
  json.NewEncoder(w).Encode(searchResult)
}
func main() {
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe(":8080", nil))
}

Elixir:

# Version: 1.5.2
defmodule EsSearch.Router do
use Trot.Router
import Tirexs.HTTP
  Trot.Router.get "/index" do
conn = put_resp_content_type(conn, "application/json")
{:ok, 200,
records} = Tirexs.HTTP.get("/staging_shambas/shamba/100")
send_resp(conn, 200, records |> Poison.encode!([]))
end
import_routes Trot.NotFound
end

To do performance testing I have used a tool called siege which is similar to AB (Apache bench). Below is the response graph for concurrent requests which varies range from 8 to 1023.

If you notice the graph, response time has been increased to ~11sec in Ruby where as in Elixir and Golang it is under ~3 sec. Clearly, as per this graph, Golang and Elixir have surpassed the Ruby under heavy load.

Though Ruby has stood in last position I love Ruby because its language semantics similar to human languages and awesome community support.

I hope this would help you to understand that how languages would behave under load.

If you like it leave your feedback in the comment section and share this post.