In my recent project, I had a situation when I need to store data under two different keys. I didn’t want to duplicate content because of difficulties with changes synchronization. I found that it’s possible to do this with LUA script, but not without any cost.
Background
I use Spring Data with Redis support so what can be done is:
RedisTemplate<String, T> redisTemplate;
HashOperations<String, String, T> hashOps = redisTemplate.opsForHash();
hashOps.put(getKey(), key2, value);
hashOps.get(getKey(), key2);
but when you have situation like compositeKey -> id and id -> serialized data you have to make 2 calls to get data.
Solution
With support of LUA scripting it can be done with one call, and another but without sending partial data on network.
local x = redis.call('hget', KEYS[1], KEYS[2]); if not x then return nil; else return redis.call('hget', KEYS[1], x); end;
this query will work fine if used in redis-cli, but we will get into troubles if we want to use it with mix of hashOps from first code excerpt and execute method, because of Spring object serialization. So my solution was to write all code as LUA scripts, then all keys will be regular strings.
DefaultRedisScript ADD_ENTRY_REDIS_SCRIPT = new DefaultRedisScript("redis.call('hset', KEYS[1], KEYS[2], ARGV[1]);");
DefaultRedisScript GET_ENTRY_REDIS_SCRIPT
= new DefaultRedisScript("local x = redis.call('hget', KEYS[1], KEYS[2]); if not x then return nil; else return redis.call('hget', KEYS[1], x); end;");
redisTemplate.execute(ADD_ENTRY_REDIS_SCRIPT, new JdkSerializationRedisSerializer(), new JdkSerializationRedisSerializer(),
Lists.newArrayList(key1, compositeKey), /* String */ key2);
redisTemplate.execute(ADD_ENTRY_REDIS_SCRIPT, new JdkSerializationRedisSerializer(), new JdkSerializationRedisSerializer(),
Lists.newArrayList(key1, key2), /* List<SomeDTO>(...) */ value);
(List< SomeDTO >) redisTemplate.execute(GET_ENTRY_REDIS_SCRIPT, new JdkSerializationRedisSerializer(),
new JdkSerializationRedisSerializer(), Lists.newArrayList(key1, key2));
For deserialization was also required: GET_ENTRY_REDIS_SCRIPT.setResultType(byte[].class);
before using it, and as DefaultRedisScript should be singleton I’ve placed it in constructor.