If you are using Rails, chances are you are using Redis for something whether it is as a cache, ActionCable, or ActiveJob. So why not use it for one more thing?
Starting in v4 of Redis, Redis Modules were introduced which are add ons built to extend Redis' functionality. One of the first modules was RediSearch, a text search engine built on top of Redis. According to RediSearch.io:
Unlike other Redis search libraries, it does not use the internal data
structures of Redis like sorted sets. Using its own highly optimized data
structures and algorithms, it allows for advanced search features, high
performance, and low memory footprint. It can perform simple text searches, as
well as complex structured queries, filtering by numeric properties and
geographical distances. RediSearch supports continuous indexing with no
performance degradation, maintaining concurrent loads of querying and
indexing. This makes it ideal for searching frequently updated databases,
without the need for batch indexing and service interrupts.
Some of the headline features of RediSearch include:
Full-Text indexing of multiple fields in a document, including:
Exact phrase matching.
Stemming in many languages.
Prefix queries.
Optional, negative and union queries.
Distributed search on billions of documents.
Numeric property indexing.
Geographical indexing and radius filters.
Incremental indexing without performance loss.
A powerful auto-complete engine with fuzzy matching.
Concurrent low-latency insertion and updates of documents.
Let's walk through how to get RediSearch integrated into your Rails app!
First, start out by installing Redis and RediSearch. Check out Redis.io for full installation instructions for Redis. If Homebrew is available you can brew install redis. As of v1.6, to build RediSearch do the following:
Once RediSearch is built you will need to tell Redis to load the module. The best way is to add loadmodule /path/to/redisearch.so to your redis.conf file to always load the module. (On macOS the redis.conf file can be found at /usr/local/etc/redis.conf). Once the conf file has been updated restart Redis. For full instructions on installing RediSearch visit RediSearch.io.
Alternatively, you can run Redis and RediSearch with Docker using docker run -p 6379:6379 redislabs/redisearch:latest.
Once Redis and RediSearch are installed add the redi_search gem to your Gemfile:
Calling the redi_search class method inside a model accepts one required named parameter called schema. This defines the fields inside an index and the attributes for those fields. RediSearch has text, numeric, geo, and tag fields. The phonetic option was passed above because we are indexing names and it makes it easier to search for names with similar sounds but spelled differently. The full list of available options for the different field types can be found here.
User.reindex
Calling the redi_search class method inside a model adds a couple of useful methods, including reindex which does a couple of things:
Creates the index if it doesn't exist
Calls the search_import scope to fetch all the records from the database
Converts those records to RediSearch Documents
Indexes all those documents into Redis
User.search("jak")
Now that we have all of our users indexed we can start searching for them. Querying is similar to the ActiveRecord interface where clauses and conditions can be chained together and the search is executed lazily. Some simple queries are:
# simple phrase query - jak AND daxterUser.search("jak").and("daxter")# exact phrase query - jak FOLLOWED BY daxterUser.search("jak daxter")# union query - jak OR daxterUser.search("jak").or("daxter")# negation query - jak AND NOT daxterUser.search("jak").and.not("daxter")
Some more complex queries are:
# intersection of unions - (hello OR halo) AND (world OR werld)User.search(User.search("hello").or("halo")).and(User.search("world").or("werld"))# negation of union - hello AND NOT (world or werld)User.search("hello").and.not(User.search("world").or("werld"))# union inside phrase - hello AND (world OR werld)User.search("hello").and(User.search("world").or("werld"))
All terms support a few options that can be applied.
Prefix terms: match all terms starting with a prefix. (Akin to like term% in SQL)
Fuzzy terms: matches are performed based on Levenshtein distance. The maximum Levenshtein distance supported is 3.
User.search("zuchini",fuzziness: 1)
Search terms can also be scoped to specific fields using a where clause:
# Simple field specific queryUser.search.where(name: "john")# Using where with optionsUser.search.where(first: "jon",fuzziness: 1)# Using where with more complex queryUser.search.where(first: User.search("bill").or("bob"))
Searching for numeric fields accepts a range:
User.search.where(number: 0..100)# Searching to infinityUser.search.where(number: 0..Float::INFINITY)User.search.where(number: -Float::INFINITY..0)
When searching, by default a collection of Documents is returned. Calling #results on the search query will execute the search, and then look up all the found records in the database and return an ActiveRecord relation.
Another useful method redi_search adds is spellcheck and responds with suggestions for misspelled search terms.
Ruby wrapper around RediSearch that can integrate with Rails
RediSearch
A simple, but powerful, Ruby wrapper around RediSearch, a search engine on top of
Redis.
Installation
Firstly, Redis and RediSearch need to be installed.
You can download Redis from https://redis.io/download, and check out
installation instructions
here. Alternatively, on
macOS or Linux you can install via Homebrew.
To install RediSearch check out
https://oss.redislabs.com/redisearch/Quick_Start.html.
Once you have RediSearch built, if you are not using Docker, you can update your
redis.conf file to always load the RediSearch module with
loadmodule /path/to/redisearch.so. (On macOS the redis.conf file can be found
at /usr/local/etc/redis.conf)
After Redis and RediSearch are up and running, add the following line to your
Gemfile:
gem'redi_search'
And then:
❯ bundle
Or install it yourself:
❯ gem install redi_search
and require it:
require'redi_search'
Once the gem is installed and required you'll need to configure it with your
Redis configuration. If you're on Rails, this should…