Search code examples
javaspring-bootcachingredis

Gettig RedisOutOfMemoryException


I am using Reddison to set the TTL for the keys of a HashMap.

This works correctly. But while inserting a large amount of data,like 5 million in 30 min, the data still remains in Redis and does not expire. The Database Memory Usage Percentage reaches 100% after few iterations in AWS ElasticCache

Below is the code which I am using in the sprint boot code implementation and getting and exception

CODE:

RMapCache<String, Object> map = getConnection().getMapCache(mapName);
map.put(key, obj, 15, TimeUnit.MINUTES);

EXCEPTION:

org.redisson.client.RedisOutOfMemoryException: command not allowed when used memory > 'maxmemory'. script: 8002ee0f772fdaac4335bffe445fb53aef8dcd57, on @user_script:1.. channel: [id: 0x17227def, L:/10.224.59.44:59436 - R:10.224.57.111/10.224.57.111:6379] data: CommandData [promise=java.util.concurrent.CompletableFuture@120fc639[Not completed, 1 dependents], command=(EVAL), params=[local insertable = false; local v = redis.call('hget', KEYS[1], ARGV[5]); if v == false then insertable = true; else local t, val = struct.unpack('dLc0', v); local expireDate = 92233720368547758; local expireDateScore = redis.call('zscore', KEYS[2], ARGV[5]); if expireDateScore ~= false then expireDate = tonumber(expireDateScore) end; if t ~= 0 then local expireIdle = redis.call('zscore', KEYS[3], ARGV[5]); if expireIdle ~= false then expireDate = math.min(expireDate, tonumber(expireIdle)) end; end; if expireDate <= tonumber(ARGV[1]) then insertable = true; end; end; if tonumber(ARGV[2]) > 0 then redis.call('zadd', KEYS[2], ARGV[2], ARGV[5]); else redis.call('zrem', KEYS[2], ARGV[5]); end; if tonumber(ARGV[3]) > 0 then redis.call('zadd', KEYS[3], ARGV[3], ARGV[5]); else redis.call('zrem', KEYS[3], ARGV[5]); end; local maxSize = tonumber(redis.call('hget', KEYS[8], 'max-size')); if maxSize ~= nil and maxSize ~= 0 then     local currentTime = tonumber(ARGV[1]);     local lastAccessTimeSe..., 8, cid-phone-qa, redisson__timeout__set:{cid-phone-qa}, redisson__idle__set:{cid-phone-qa}, redisson_map_cache_created:{cid-phone-qa}, redisson_map_cache_updated:{cid-phone-qa}, redisson__map_cache__last_access__set:{cid-phone-qa}, redisson_map_cache_removed:{cid-phone-qa}, {cid-phone-qa}:redisson_options, ...], codec=org.redisson.codec.MarshallingCodec]
    at org.redisson.client.handler.CommandDecoder.decode(CommandDecoder.java:361) ~[redisson-3.18.1.jar!/:3.18.1]
    at org.redisson.client.handler.CommandDecoder.decodeCommand(CommandDecoder.java:205) ~[redisson-3.18.1.jar!/:3.18.1]
    at org.redisson.client.handler.CommandDecoder.decode(CommandDecoder.java:144) ~[redisson-3.18.1.jar!/:3.18.1]
    at org.redisson.client.handler.CommandDecoder.decode(CommandDecoder.java:120) ~[redisson-3.18.1.jar!/:3.18.1]
    at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:501) ~[netty-codec-4.1.52.Final.jar!/:4.1.52.Final]
    at io.netty.handler.codec.ReplayingDecoder.callDecode(ReplayingDecoder.java:366) ~[netty-codec-4.1.52.Final.jar!/:4.1.52.Final]
    at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:276) ~[netty-codec-4.1.52.Final.jar!/:4.1.52.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) ~[netty-transport-4.1.52.Final.jar!/:4.1.52.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) ~[netty-transport-4.1.52.Final.jar!/:4.1.52.Final]
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357) ~[netty-transport-4.1.52.Final.jar!/:4.1.52.Final]
    at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410) ~[netty-transport-4.1.52.Final.jar!/:4.1.52.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) ~[netty-transport-4.1.52.Final.jar!/:4.1.52.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) ~[netty-transport-4.1.52.Final.jar!/:4.1.52.Final]
    at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919) ~[netty-transport-4.1.52.Final.jar!/:4.1.52.Final]
    at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166) ~[netty-transport-4.1.52.Final.jar!/:4.1.52.Final]
    at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:714) ~[netty-transport-4.1.52.Final.jar!/:4.1.52.Final]

Solution

  • This is probably a problem with how Redison is being used.

    You are storing your 5 million pieces of data in a single Hash in Redis and then trying to expire them based on the field. Redis does support field-level expiration in Hashes. It can expire a key, but not elements within a key like fields in a Hash, members of a Set, or items in a List.

    Redison provides this capability somehow. I'm not super familiar with it but I know it involves Lua scripts. My guess is that the Lua scripts aren't getting a chance to run because Redis is receiving data so quickly.

    More importantly, however, I think storing that much data in a single Hash isn't—at least generally speaking—the best solution. That's a lot of data in one place. The key containing that data will exist on a single shard instead of being smeared across the cluster which will affect the horizontal scalability of your solution.

    You would probably be better off storing each value in a String in Redis using a key name in Redis of mapName combined with what you are calling the key. This will use lots of key, which is good, as it allows the data to fan out across the cluster. And, it will allow Redis to handle the expiration instead of Redison.

    Hope this helps!