Skip to content

Commit

Permalink
Merge pull request #11207 from AndriiLandiak/feature/redis-disable-ca…
Browse files Browse the repository at this point in the history
…ching

Redis - disable caching in case maxSize is 0
  • Loading branch information
ViacheslavKlimov authored Jul 17, 2024
2 parents 097429b + fca1eef commit 90d945c
Show file tree
Hide file tree
Showing 6 changed files with 172 additions and 75 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@
*/
package org.thingsboard.server.cache;

import jakarta.annotation.PostConstruct;
import lombok.Data;
import lombok.Getter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.thingsboard.server.common.data.CacheConstants;

import jakarta.annotation.PostConstruct;
import java.util.Map;

@Configuration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.thingsboard.server.common.data.FstStatsService;
import redis.clients.jedis.Connection;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.util.JedisClusterCRC16;
Expand All @@ -40,6 +39,8 @@
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Supplier;

@Slf4j
public abstract class RedisTbTransactionalCache<K extends Serializable, V extends Serializable> implements TbTransactionalCache<K, V> {
Expand All @@ -57,6 +58,7 @@ public abstract class RedisTbTransactionalCache<K extends Serializable, V extend
private final TbRedisSerializer<K, V> valueSerializer;
private final Expiration evictExpiration;
private final Expiration cacheTtl;
private final boolean cacheEnabled;

public RedisTbTransactionalCache(String cacheName,
CacheSpecsMap cacheSpecsMap,
Expand All @@ -73,10 +75,19 @@ public RedisTbTransactionalCache(String cacheName,
.map(CacheSpecs::getTimeToLiveInMinutes)
.map(t -> Expiration.from(t, TimeUnit.MINUTES))
.orElseGet(Expiration::persistent);
this.cacheEnabled = Optional.ofNullable(cacheSpecsMap)
.map(CacheSpecsMap::getSpecs)
.map(x -> x.get(cacheName))
.map(CacheSpecs::getMaxSize)
.map(size -> size > 0)
.orElse(false);
}

@Override
public TbCacheValueWrapper<V> get(K key) {
if (!cacheEnabled) {
return null;
}
try (var connection = connectionFactory.getConnection()) {
byte[] rawKey = getRawKey(key);
byte[] rawValue = connection.get(rawKey);
Expand All @@ -98,27 +109,39 @@ public TbCacheValueWrapper<V> get(K key) {

@Override
public void put(K key, V value) {
if (!cacheEnabled) {
return;
}
try (var connection = connectionFactory.getConnection()) {
put(connection, key, value, RedisStringCommands.SetOption.UPSERT);
}
}

@Override
public void putIfAbsent(K key, V value) {
if (!cacheEnabled) {
return;
}
try (var connection = connectionFactory.getConnection()) {
put(connection, key, value, RedisStringCommands.SetOption.SET_IF_ABSENT);
}
}

@Override
public void evict(K key) {
if (!cacheEnabled) {
return;
}
try (var connection = connectionFactory.getConnection()) {
connection.del(getRawKey(key));
}
}

@Override
public void evict(Collection<K> keys) {
if (!cacheEnabled) {
return;
}
//Redis expects at least 1 key to delete. Otherwise - ERR wrong number of arguments for 'del' command
if (keys.isEmpty()) {
return;
Expand All @@ -130,6 +153,9 @@ public void evict(Collection<K> keys) {

@Override
public void evictOrPut(K key, V value) {
if (!cacheEnabled) {
return;
}
try (var connection = connectionFactory.getConnection()) {
var rawKey = getRawKey(key);
var records = connection.del(rawKey);
Expand All @@ -153,6 +179,14 @@ public TbCacheTransaction<K, V> newTransactionForKeys(List<K> keys) {
return new RedisTbCacheTransaction<>(this, connection);
}

@Override
public <R> R getAndPutInTransaction(K key, Supplier<R> dbCall, Function<V, R> cacheValueToResult, Function<R, V> dbValueToCacheValue, boolean cacheNullValue) {
if (!cacheEnabled) {
return dbCall.get();
}
return TbTransactionalCache.super.getAndPutInTransaction(key, dbCall, cacheValueToResult, dbValueToCacheValue, cacheNullValue);
}

private RedisConnection getConnection(byte[] rawKey) {
if (!connectionFactory.isRedisClusterAware()) {
return connectionFactory.getConnection();
Expand Down Expand Up @@ -214,6 +248,9 @@ private byte[] getRawValue(V value) {
}

public void put(RedisConnection connection, K key, V value, RedisStringCommands.SetOption setOption) {
if (!cacheEnabled) {
return;
}
byte[] rawKey = getRawKey(key);
byte[] rawValue = getRawValue(value);
connection.set(rawKey, rawValue, cacheTtl, setOption);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,20 @@ default V getOrFetchFromDB(K key, Supplier<V> dbCall, boolean cacheNullValue, bo
}

default V getAndPutInTransaction(K key, Supplier<V> dbCall, boolean cacheNullValue) {
return getAndPutInTransaction(key, dbCall, Function.identity(), Function.identity(), cacheNullValue);
}

default <R> R getAndPutInTransaction(K key, Supplier<R> dbCall, Function<V, R> cacheValueToResult, Function<R, V> dbValueToCacheValue, boolean cacheNullValue) {
TbCacheValueWrapper<V> cacheValueWrapper = get(key);
if (cacheValueWrapper != null) {
return cacheValueWrapper.get();
V cacheValue = cacheValueWrapper.get();
return cacheValue != null ? cacheValueToResult.apply(cacheValue) : null;
}
var cacheTransaction = newTransactionForKey(key);
try {
V dbValue = dbCall.get();
R dbValue = dbCall.get();
if (dbValue != null || cacheNullValue) {
cacheTransaction.putIfAbsent(key, dbValue);
cacheTransaction.putIfAbsent(key, dbValueToCacheValue.apply(dbValue));
cacheTransaction.commit();
return dbValue;
} else {
Expand All @@ -94,27 +99,4 @@ default <R> R getOrFetchFromDB(K key, Supplier<R> dbCall, Function<V, R> cacheVa
}
}

default <R> R getAndPutInTransaction(K key, Supplier<R> dbCall, Function<V, R> cacheValueToResult, Function<R, V> dbValueToCacheValue, boolean cacheNullValue) {
TbCacheValueWrapper<V> cacheValueWrapper = get(key);
if (cacheValueWrapper != null) {
var cacheValue = cacheValueWrapper.get();
return cacheValue == null ? null : cacheValueToResult.apply(cacheValue);
}
var cacheTransaction = newTransactionForKey(key);
try {
R dbValue = dbCall.get();
if (dbValue != null || cacheNullValue) {
cacheTransaction.putIfAbsent(key, dbValueToCacheValue.apply(dbValue));
cacheTransaction.commit();
return dbValue;
} else {
cacheTransaction.rollback();
return null;
}
} catch (Throwable e) {
cacheTransaction.rollback();
throw e;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,5 @@ public void givenCacheConfig_whenCacheManagerReady_thenVerifyExistedCachesWithNo
public void givenCacheConfig_whenCacheManagerReady_thenVerifyNonExistedCaches() {
assertThat(cacheManager.getCache("rainbows_and_unicorns")).isNull();
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.cache;

import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.thingsboard.server.cache.CacheSpecsMap;
import org.thingsboard.server.cache.RedisSslCredentials;
import org.thingsboard.server.cache.TBRedisCacheConfiguration;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.dao.relation.RelationCacheKey;
import org.thingsboard.server.dao.relation.RelationRedisCache;

import java.util.List;
import java.util.UUID;

import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;

@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {RelationRedisCache.class, CacheSpecsMap.class, TBRedisCacheConfiguration.class})
@TestPropertySource(properties = {
"cache.type=redis",
"cache.specs.relations.timeToLiveInMinutes=1440",
"cache.specs.relations.maxSize=0",
})
@Slf4j
public class RedisTbTransactionalCacheTest {

@MockBean
private RelationRedisCache relationRedisCache;
@MockBean
private RedisConnectionFactory connectionFactory;
@MockBean
private RedisConnection redisConnection;
@MockBean
private RedisSslCredentials redisSslCredentials;

@Test
public void testNoOpWhenCacheDisabled() {
when(connectionFactory.getConnection()).thenReturn(redisConnection);

relationRedisCache.put(createRelationCacheKey(), null);
relationRedisCache.putIfAbsent(createRelationCacheKey(), null);
relationRedisCache.evict(createRelationCacheKey());
relationRedisCache.evict(List.of(createRelationCacheKey()));
relationRedisCache.getAndPutInTransaction(createRelationCacheKey(), null, false);
relationRedisCache.getAndPutInTransaction(createRelationCacheKey(), null, null, null, false);
relationRedisCache.getOrFetchFromDB(createRelationCacheKey(), null, false, false);

verify(connectionFactory, never()).getConnection();
verifyNoInteractions(redisConnection);
}

private RelationCacheKey createRelationCacheKey() {
return new RelationCacheKey(new DeviceId(UUID.randomUUID()), new DeviceId(UUID.randomUUID()), null, RelationTypeGroup.COMMON);
}

}
Loading

0 comments on commit 90d945c

Please sign in to comment.