Search Snippets

Updates from the Bonsai Elasticsearch team, from One More Cloud: the first and best cloud search platform, since 2009.

Share on facebook
Share on twitter
Share on linkedin
Share on pinterest

Comparison of Elasticsearch Ruby Gems

If you’ve ever wanted to supercharge your app’s search capabilities, there’s no better tool to use than Elasticsearch. Integrating Elasticsearch into a Ruby on Rails app is a fairly straightforward process once you’ve selected a framework.

The framework you choose will act as an interface between your app and Elasticsearch. This choice should not be made lightly; while all frameworks can generally perform the same tasks, they have dramatically different approaches for doing so.

In this post, we’re going to explore the differences between the three most popular Elasticsearch frameworks for Rails apps: SearchkickElasticsearch Rails, and Chewy.

What’s the Difference?

Under the hood, all three of these gems are leveraging the Elasticsearch Ruby gem. Elasticsearch Rails adds some Rails-specific niceties like ActiveRecord integration and convenience methods like search and index to various models. The Searchkick and Chewy gems do more or less the same, albeit in slightly different ways.

So what are the key differences to developers?

Data Modeling

There are a number of ways to organize your data in a cluster. The Index-per-Application paradigm is where all of your application’s data is stored in a single index. Queries then use fields to filter and cache results as needed. Chewy uses this paradigm.

Alternatively, the Index-per-Model paradigm has data logically separated by model into its own Elasticsearch index. Elasticsearch Rails and Searchkick use this paradigm.

So which approach is “best?” Well, suppose you have a Rails application and want to store data about users, posts and comments. This table compares how the each paradigm handles the data separation:

Paradigm Index-Per-Application Index-Per-Model
Gem Chewy Elasticsearch Rails, Searchkick
Models User, Post, and Comment User, Post, and Comment
Elasticsearch Indices 1 3
Mapping One mapping for all models Each model has its own mapping
Settings Master settings for all models Each model can have its own settings
Queries (default) Performed against all documents. Searching only documents of a specific type possible with filters. Performed against only documents associated with its mapped model. Searching against all documents can be done by explicitly naming which indices to search.
Pros Simplifies the indexed models by having the configurations mainly in one file separate from the models Logic and settings can be tuned on a per-index basis. No cross-contamination of relevancy scoring caused by other indices
Cons A single mapping can have field name collisions and lead to sparse documents, which has performance implications. Relevancy scores for a type could be influenced by data in a different type Can be complicated to manage when to perform a search against one index or several
Good Fit For… Applications where Elasticsearch needs to be able to search from many models at once. Applications where related objects need to be denormalized (updates to one object affect one or more related objects). Examples: Media catalog with different types of media (TV, movies, music, books); Blog with tags, where changes to a tag should be reflected on all entries with the tag. Applications where Elasticsearch really only needs to cover one model, or where search is logically separated. Applications where Elasticsearch is used to query time-series data (logs, metrics, etc). Examples: Online store with many products; A restaurant recommendation app, where searching for users is a different interface from searching for nearby restaurants.

Coding

Let’s explore the code of the previously mentioned gem paradigms with their User, Post, and Comment models. I will omit creating routes, views, and getting data into Elasticsearch.

An example setup for Elasticsearch Rails

Model-observing code:

require 'elasticsearch/model'

class User < ApplicationRecord
  include Elasticsearch::Model
  include Elasticsearch::Model::Callbacks
  settings index: { number_of_shards: 1 }
end

class Post < ApplicationRecord
  include Elasticsearch::Model
  include Elasticsearch::Model::Callbacks
  settings index: { number_of_shards: 1 }
end

class Comment < ApplicationRecord
  include Elasticsearch::Model
  include Elasticsearch::Model::Callbacks
  settings index: { number_of_shards: 1 }
end

Search Controller contains:

class SearchController< ApplicationController
  def search_all
  	# This will search all models that have `include Elasticsearch::Model`
  	@results = Elasticsearch::Model.search(params[:q]).records
  end

  def search_users
  	@users = User.search(params[:q]).records
  end

  def search_posts
  	@posts = Post.search(params[:q]).records
  end

   def search_comments
  	@comments = Comment.search(params[:q]).records
  end
end

An example setup for Searchkick

Model-observing code:

class User < ApplicationRecord
  searchkick
end

class Post < ApplicationRecord
  searchkick
end

class Comment < ApplicationRecord
  searchkick
end

Search Controller contains:

class SearchController< ApplicationController
  def search_all
	# This will search all models that include `searchkick`
  	@results = Searchkick.search(params[:q]), models: [User, Post, Comment]
  end

  def search_users
  	@users = User.search(params[:q])
  end

  def search_posts
  	@posts = Post.search(params[:q])
  end

   def search_comments
  	@comments = Comment.search(params[:q])
  end
end

An example setup for Chewy

Model-observing code:

class User < ApplicationRecord
   update_index('users') { self }
end

class Post < ApplicationRecord
   update_index('users') { users }
end

class Comment < ApplicationRecord
   update_index('users') { users }
end

Index definition created in app/chewy/users_index.rb:

class UsersIndex < Chewy::Index
   index_scope User.active.includes(:post, :comment)
   field :first_name, :last_name
   field :post do
      field :title
      field :description
   field :comment do
      field :description
end

Users Controller contains:

def search
  @results = UsersIndex.query(query_string: { fields: [:first_name, :last_name, ...], query:params[:q], default_operator: 'and' })
end

Features Comparison

The table below shows the different features offered by each gem out of the box. This is by no means an exhaustive list. Note that some things are possible with extra work; these are left blank because the gem doesn’t provide a direct method for doing the job.

Features Elasticsearch Rails Searchkick Chewy
Reindex without downtime
Stemming
Special characters like ñ
Catch Misspellings
Personalize results for each user
Autocomplete
“Did you mean” suggestions
ActiveRecord support
Mongoid support
Supports ES 7.x
Supports ES 6.x
Supports ES 5.x
Supports ES 2.x
Supports ES 1.x
Provides Rake tasks
Licensing Apache 2 License; Starting in 2021, Server Side Public License (SSPL) MIT License MIT License

Which one should you choose?

Searchkick is easy to set up and ideal if you don’t need fine grained control over how your app searches. It has many out-of-the-box features to get searching quickly. Stemming, spell checking, personalized search results, autocomplete, and “Did you mean” suggestions are some of the features you can expect. True, you can build these yourself with Elasticsearch-Rails and Chewy, but it will take some extra work (and time to fix bugs!).

Chewy logically separates its index from model code, resulting in leaner models. Chewy is built for some niche use cases, so it’s not ideal for everyone.

If you are already familiar with the inner workings of Elasticsearch and want the most hands on approach to managing search features (like stemming, spell checking, reindexing, autocomplete, and more, then the Elasticsearch Rails gem may be for you. It allows you to manage the details of how your search works, and implement your own opinions on the process.

What are your experiences with these gems? How did they compare with different app use cases? Share your thoughts on Twitter: @bonsaisearch.

Find out how we can help you.

Schedule a free consultation to see how we can create a customized plan to meet your search needs.