Why People Start with Redis and the Challenge It Brings
When developers chase lightning-fast data access, the journey almost always starts in RAM. Redis has become the go-to choice here - thanks to its simplicity: a few core data types and a lightweight command set. But as applications grow beyond basic caching into large-scale, distributed systems, a challenge emerges:
How do you preserve Redis-like speed while also gaining distributed SQL, ACID transactions, and parallel computation at a large scale?
The Redis Design
To understand that challenge, it helps to look at what the Redis website states,
“Redis is a data structure server. At its core, Redis provides a collection of native data types that help you solve a wide variety of problems.”
Redis was designed from day one to be a data structure server rather than a general-purpose cache or distributed compute platform. Redis implements Hash, Set, Sorted Set, List etc. natively in its single-threaded event loop, which makes operations on them atomic, predictable, and blazing fast. So HMSET, SADD, ZADD, LPUSH etc. are not abstractions; they are core commands built into the Redis server. This design is why Redis shines for use cases like caching, session management and leaderboards.
The GridGain Design
GridGain was designed with a different set of primary use cases and hence different architecture. You get the responsiveness that developers love from Redis, but now with the power of distributed SQL, ACID transactions, co-located compute, and parallel data processing across the cluster. In other words, GridGain lets you model Redis-style data structures when you need them - while also giving you the flexibility to run complex analytics and transactional workloads at scale. So even though Redis and GridGain are built for different purposes, “in-memory” and “fast data access” makes the comparison unavoidable.
Mapping Redis to GridGain
Since GridGain version 8 and 9 differ significantly in their architecture and classes, I prefer to show you how to map Redis structure in these 2 versions separately. In this blog, let’s take some examples of how to model Redis data structures in GridGain 8.
Simplest first- String:
SET user:1 "Anuja"
GET user:1
GridGain way in Java:
cache.put("user:1", "Anuja");
cache.get("user:1");
Hash:
HSET user:1 firstName "Anuja"
HSET user:1 lastName “Kumar”
HGETALL user:1
In GridGain, you can achieve this using object mapping as well as SQL.
IgniteCache<Integer, User> cache = ignite.cache("userCache");
User user = new User(1, "Anuja", "Kumar");
cache.put(user.getUserId(), user);
//Get the complete user from cache
User u = cache.get(1);
Note: You might feel userId being used as a key as well as a field in the User POJO as a repetition. It has its own advantages like being able to selectively fetch some fields as shown below. If userId is not in the POJO, you will need to map the cache key(userId) using QueryEntity.
// Get selected field/column instead of the whole object
SqlFieldsQuery sql = new SqlFieldsQuery("SELECT firstName FROM User WHERE userId = ?"
).setArgs(1);
List and Set:
RPUSH queueA "one"
RPUSH queueA "two"
LPOP queueA
GridGain 8 provides IgniteQueue and IgniteSet, implementations of java.util.concurrent.BlockingQueue and java.util.Set interface respectively, which also support all operations from the java.util.Collection interface.
//Create unbounded queue
IgniteQueue<String> queueCache = ignite.queue("queueA", 0, new CollectionConfiguration());
// RPUSH equivalent. It adds to tail
queueCache.put("one");
queueCache.put("two");
//LPOP equivalent. Removes from head and gives the value
String value = queueCache.poll();
Redis Set:
SADD setA “one” “two”
SISMEMBER setA “three”
GridGain modelling of set:
IgniteQueue<String> setCache = ignite.queue("setA", 0, new CollectionConfiguration());
setCache.add(“one”);
setCache.add(“two”);
setCache.contains(“three”); //Returns 1 if exists, else returns 0.
Sorted Set:
ZADD leaderboard 100 "player1"
ZADD leaderboard 200 "player2"
ZRANGE leaderboard 0 -1 WITHSCORES
Similar to the Hashset modelling, we create a POJO with a player name and a score. Since we will be using the ORDER BY clause in the query, it is critical to index the score as well.
IgniteCache<String, LeaderboardEntry> zsetCache = ignite.cache("LeaderboardCache");
zsetCache.put("player1", new LeaderboardEntry(100, "player1"));
zsetCache.put("player2", new LeaderboardEntry(200, "player2"));
// ZRANGE 0 -1 WITHSCORES equivalent. We can use LIMIT clause to get “Top N”
SqlFieldsQuery query = new SqlFieldsQuery("SELECT player, score FROM LeaderboardEntry ORDER BY score DESC");
List<List<?>> results = cache.query(query).getAll();
Redis Streams are essentially an append-only log data structure with commands for producing, consuming, acknowledging, and grouping messages. XADD auto-generates a unique ID for you if you specify the * character as the ID argument; and this is the commonly used way of adding to the stream.
XADD mystream * field value
There can be multiple field-value pairs in each entry of a stream. We can mimic this XADD behaviour in GridGain8, where the key is the stream entry ID (time-sequence) and the value is the message.
IgniteCache<String, String> streamCache = ignite.getOrCreateCache("streamCache");
String id = System.currentTimeMillis() + "-" + UUID.randomUUID();
streamCache.put(id, "value");
We can pass min, max of time or - and + to indicate min and max, in which case we get all the entries of the stream from XRANGE command.
XRANGE mystream - +
We can mimic this using a query. We can even pass the timestamps in the WHERE clause.
SqlFieldsQuery query = new SqlFieldsQuery( "SELECT _key, _val FROM streamCache ORDER BY _key");
If you want the Redis-like behaviour of XREAD to read new events as they arrive, GridGain Continuous Queries can be used. They are stateful: they let you subscribe to changes in a cache, effectively giving you a live stream of updates tied directly to your data.
ContinuousQuery<String, String> query = new ContinuousQuery<>();
query.setLocalListener(events -> {
for (CacheEntryEvent<? extends String, ? extends String> e : events) {
System.out.println("New message: " + e.getKey() + " => " + e.getValue());
}
});
Conclusion
Ultimately, these examples show that anything a developer can do with Redis core data structures, can be achieved with GridGain 8. In the next part of this series, we will see how to model the Redis data structures in GridGain 9.