Introduction

Redis is an advanced key-value store. It is similar to memcached except the dataset is not volatile. Like memcached, Redis can store string values, but it can also store lists, sets, and ordered sets. All these data types can be manipulated with atomic operations that push, pop, add and remove elements, perform server side union, intersection, difference between sets, and more. Redis also supports different kinds of sorting.

This project aims to provide an object-mapping layer on top of Redis to ease common activities such as:

  • Marshalling from Redis strings to Groovy/Java types and back again

  • Creating and caching indices for querying

  • Working with transactions

  • Validating domain instances backed by the Redis datastore

Compatibility

This implementation tries to be as compatible as possible with GORM for Hibernate. In general you can refer to the GORM documentation and the "Domain Classes" section of the reference guide (see the left nav) for usage information.

The following key features are supported by GORM for Redis:

  • Simple persistence methods

  • Dynamic finders

  • Criteria queries

  • Named queries

  • Inheritance

However, some features are not supported:

  • HQL queries

  • Dirty checking methods

  • Composite primary keys

  • Many-to-many associations (these can be modelled with a mapping class)

  • Any direct interaction with the Hibernate API

  • Sorting on strings

  • Custom Hibernate user types

There may be other limitations not mentioned here so in general it shouldn’t be expected that an application based on GORM for Hibernate will "just work" without some tweaking involved. Having said that, the large majority of common GORM functionality is supported.

Getting Started

To get started with GORM for Redis you need to add the Redis GORM plugin as a dependency in build.gradle:

compile "org.grails.plugins:redis-gorm:VERSION"

With that done you need to set up a running Redis server. GORM for Redis requires Redis 2.0.0 or above which you can download at on the Redis download page. Once downloaded extract the archive and run the following commands from the redis-2.0.0 directory:

make
./redis-server

With the above commands executed in a terminal window you should see output like the following appear:

<<66243>> 03 Sep 17:35:12 * Server started, Redis version 2.0.0
<<66243>> 03 Sep 17:35:12 * DB loaded from disk: 0 seconds
<<66243>> 03 Sep 17:35:12 * The server is now ready to accept connections on port 6379

As you can see the server is running on port 6379, but don’t worry the Redis plugin for Grails will automatically configure itself to look for Redis on that port by default.

If you want to configure how Grails connects to redis then you can do so using the following settings in grails-app/conf/application.yml:

grails:
    redis-gorm:
        host: "myserver"
        port: 6380
        password: "secret"
        pooled: true
        resources: 15
        timeout: 5000

Using GORM for Redis Standalone

If you plan to use Redis as your primary datastore then you need to remove the Hibernate plugin definition from build.gradle.

compile "org.grails.plugins:hibernate"

With this done all domain classes in grails-app/domain will be persisted via Redis and not Hibernate. You can create a domain class by running the regular create-domain-class command:

grails create-domain-class Person

The Person domain class will automatically be a persistent entity that can be stored in Redis.

Combining Redis and Hibernate

If you have both the Hibernate and Redis plugins installed then by default all classes in the grails-app/domain directory will be persisted by Hibernate and not Redis. If you want to persist a particular domain class with Redis then you must use the mapWith property in the domain class:

static mapWith = "redis"

Object Mapping

Object mapping works largely as described in the documentation on GORM. In general you can continue to model your associations using typical GORM notation such as hasMany, belongsTo and so on.

The one notable exception is that the mapping block works differently to GORM for Hibernate. Most of the mapping configuration options available to GORM for Hibernate are specific to SQL databases and hence don’t make sense to Redis.

Here is an example of a domain class that can be persisted to Redis:

class Person {

    String firstName
    String lastName

    static constraints = {
        firstName blank:false
        lastName blank:false
    }

    static mapping = {
        lastName index:true
    }
}

Note that one key difference with GORM for Redis is that you must specify the properties you want to index before you can execute any query against the property. In the example above the lastName property can be queried with a dynamic finder such as:

Person.findByLastName("Simpson")

However, the firstName property cannot be queried and an error will be thrown if you attempt the equivalent dynamic finder for the firstName property unless you specify index:true for that property too.

In other words, unlike SQL where every single property can be queried with Redis you must specify which properties can be queried up front.

Querying

As mentioned in the previous section you must specify the properties that can be queried with index:true in your mapping block. Once a property can be queried upon then there are several ways to do it including:

These queries operate in largely the same way as they do with GORM for Hibernate. Note, however, that each criterion used in a query results in querying another index and although GORM for Redis caches query results you should be careful not to use too many criteria as it will impact query performance.

A good way to debug your query performance is to go to your Redis distribution and type the following in a command or terminal window:

$ ./redis-cli
$ MONITOR

The MONITOR command will allow you to monitor the commands that the currently running Redis server is receiving and responding to. Great for debugging!

Query Limitations

There are some limitations with queries in Redis that you should be aware of, after all Redis is not a relational database. Below are some of the known limitations:

  • Sorting can only be done on dates and numbers and not String values / complex types

  • Querying associations via nested criteria is not currently supported

Redis Specific Extensions

As well as all the regular capabilities offered by the GORM API there are a couple of additional GORM methods that take advantage of some key Redis features. The first one is the random method which will return a random instance of the domain class:

def randomPerson = Person.random()

Implementing a random function in a SQL database is typically quite hard to do in a performant way whilst something like Redis makes it easy.

There is also a pop method that will return and remove a random domain instance in one step:

def randomPerson = Person.pop()

Reference

Domain Classes

expire

Purpose

Expires a domain instance using the given time to live (TTL) in seconds

Examples

Instance method:

def person = Person.get(1)
person.expire(60) // expire after 60 seconds

Static method:

Person.expire(1, 60) // expire Person:1 after 60 seconds
Description

Redis supports expiring of individual entries after a timeout (time to live) period. The expire method allows you to expire an object persisted to Redis.

pop

Purpose

Returns and deletes random domain instance.

Examples
def randomPerson = Person.pop()
Description

Similar to the random method but deletes the domain instance at the same time.

random

Purpose

Returns a random domain instance.

Examples
def randomPerson = Person.random()
Description

One of the major benifits of Redis is its great support for set operations including returning random set elements. This method takes advantage of that capability by returning a random instance of the corresponding domain class.