Browse Source

AI rule node: add cache to AI settings

pull/13371/head
Dmytro Skarzhynets 1 year ago
parent
commit
a4db17c859
No known key found for this signature in database GPG Key ID: 2B51652F224037DF
  1. 3
      application/src/main/resources/thingsboard.yml
  2. 5
      common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java
  3. 29
      dao/src/main/java/org/thingsboard/server/dao/ai/AiSettingsCacheEvictEvent.java
  4. 47
      dao/src/main/java/org/thingsboard/server/dao/ai/AiSettingsCacheKey.java
  5. 33
      dao/src/main/java/org/thingsboard/server/dao/ai/AiSettingsCaffeineCache.java
  6. 36
      dao/src/main/java/org/thingsboard/server/dao/ai/AiSettingsRedisCache.java
  7. 68
      dao/src/main/java/org/thingsboard/server/dao/ai/AiSettingsServiceImpl.java

3
application/src/main/resources/thingsboard.yml

@ -647,6 +647,9 @@ cache:
trendzSettings:
timeToLiveInMinutes: "${CACHE_SPECS_TRENDZ_SETTINGS_TTL:1440}" # Trendz settings cache TTL
maxSize: "${CACHE_SPECS_TRENDZ_SETTINGS_MAX_SIZE:10000}" # 0 means the cache is disabled
aiSettings:
timeToLiveInMinutes: "${CACHE_SPECS_AI_SETTINGS_TTL:1440}" # AI settings cache TTL
maxSize: "${CACHE_SPECS_AI_SETTINGS_MAX_SIZE:10000}" # 0 means the cache is disabled
# Deliberately placed outside the 'specs' group above
notificationRules:

5
common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java

@ -15,7 +15,8 @@
*/
package org.thingsboard.server.common.data;
public class CacheConstants {
public final class CacheConstants {
public static final String DEVICE_CREDENTIALS_CACHE = "deviceCredentials";
public static final String RELATIONS_CACHE = "relations";
public static final String DEVICE_CACHE = "devices";
@ -36,6 +37,7 @@ public class CacheConstants {
public static final String NOTIFICATION_SETTINGS_CACHE = "notificationSettings";
public static final String SENT_NOTIFICATIONS_CACHE = "sentNotifications";
public static final String TRENDZ_SETTINGS_CACHE = "trendzSettings";
public static final String AI_SETTINGS_CACHE = "aiSettings";
public static final String ASSET_PROFILE_CACHE = "assetProfiles";
public static final String ATTRIBUTES_CACHE = "attributes";
@ -54,4 +56,5 @@ public class CacheConstants {
public static final String ALARM_TYPES_CACHE = "alarmTypes";
public static final String QR_CODE_SETTINGS_CACHE = "qrCodeSettings";
public static final String MOBILE_SECRET_KEY_CACHE = "mobileSecretKey";
}

29
dao/src/main/java/org/thingsboard/server/dao/ai/AiSettingsCacheEvictEvent.java

@ -0,0 +1,29 @@
/**
* Copyright © 2016-2025 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.ai;
import org.thingsboard.server.common.data.id.AiSettingsId;
import org.thingsboard.server.common.data.id.TenantId;
import java.util.Set;
record AiSettingsCacheEvictEvent(Set<AiSettingsCacheKey> keys) {
static AiSettingsCacheEvictEvent of(TenantId tenantId, AiSettingsId aiSettingsId) {
return new AiSettingsCacheEvictEvent(Set.of(AiSettingsCacheKey.of(tenantId, aiSettingsId)));
}
}

47
dao/src/main/java/org/thingsboard/server/dao/ai/AiSettingsCacheKey.java

@ -0,0 +1,47 @@
/**
* Copyright © 2016-2025 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.ai;
import org.thingsboard.server.cache.VersionedCacheKey;
import org.thingsboard.server.common.data.id.AiSettingsId;
import org.thingsboard.server.common.data.id.TenantId;
import java.util.UUID;
import static java.util.Objects.requireNonNull;
record AiSettingsCacheKey(UUID tenantId, UUID aiSettingsId) implements VersionedCacheKey {
AiSettingsCacheKey {
requireNonNull(tenantId);
requireNonNull(aiSettingsId);
}
static AiSettingsCacheKey of(TenantId tenantId, AiSettingsId aiSettingsId) {
return new AiSettingsCacheKey(tenantId.getId(), aiSettingsId.getId());
}
@Override
public boolean isVersioned() {
return true;
}
@Override
public String toString() {
return tenantId + "_" + aiSettingsId;
}
}

33
dao/src/main/java/org/thingsboard/server/dao/ai/AiSettingsCaffeineCache.java

@ -0,0 +1,33 @@
/**
* Copyright © 2016-2025 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.ai;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Component;
import org.thingsboard.server.cache.VersionedCaffeineTbCache;
import org.thingsboard.server.common.data.CacheConstants;
import org.thingsboard.server.common.data.ai.AiSettings;
@Component("AiSettingsCache")
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true)
class AiSettingsCaffeineCache extends VersionedCaffeineTbCache<AiSettingsCacheKey, AiSettings> {
AiSettingsCaffeineCache(CacheManager cacheManager) {
super(cacheManager, CacheConstants.AI_SETTINGS_CACHE);
}
}

36
dao/src/main/java/org/thingsboard/server/dao/ai/AiSettingsRedisCache.java

@ -0,0 +1,36 @@
/**
* Copyright © 2016-2025 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.ai;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.stereotype.Component;
import org.thingsboard.server.cache.CacheSpecsMap;
import org.thingsboard.server.cache.TBRedisCacheConfiguration;
import org.thingsboard.server.cache.TbJsonRedisSerializer;
import org.thingsboard.server.cache.VersionedRedisTbCache;
import org.thingsboard.server.common.data.CacheConstants;
import org.thingsboard.server.common.data.ai.AiSettings;
@Component("AiSettingsCache")
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis")
class AiSettingsRedisCache extends VersionedRedisTbCache<AiSettingsCacheKey, AiSettings> {
AiSettingsRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) {
super(CacheConstants.AI_SETTINGS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJsonRedisSerializer<>(AiSettings.class));
}
}

68
dao/src/main/java/org/thingsboard/server/dao/ai/AiSettingsServiceImpl.java

@ -15,10 +15,11 @@
*/
package org.thingsboard.server.dao.ai;
import com.google.common.collect.Sets;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.event.TransactionalEventListener;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ai.AiSettings;
import org.thingsboard.server.common.data.id.AiSettingsId;
@ -27,26 +28,33 @@ import org.thingsboard.server.common.data.id.HasId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.entity.CachedVersionedEntityService;
import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent;
import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent;
import org.thingsboard.server.dao.service.DataValidator;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import static org.thingsboard.server.dao.entity.AbstractEntityService.checkConstraintViolation;
import static org.thingsboard.server.dao.service.Validator.validatePageLink;
@Service
@RequiredArgsConstructor
class AiSettingsServiceImpl implements AiSettingsService {
class AiSettingsServiceImpl extends CachedVersionedEntityService<AiSettingsCacheKey, AiSettings, AiSettingsCacheEvictEvent> implements AiSettingsService {
private final ApplicationEventPublisher eventPublisher;
private final DataValidator<AiSettings> aiSettingsValidator;
private final AiSettingsDao aiSettingsDao;
@Override
@TransactionalEventListener
public void handleEvictEvent(AiSettingsCacheEvictEvent event) {
cache.evict(event.keys());
}
@Override
@Transactional
public AiSettings save(AiSettings aiSettings) {
AiSettings oldSettings = aiSettingsValidator.validate(aiSettings, AiSettings::getTenantId);
@ -58,15 +66,22 @@ class AiSettingsServiceImpl implements AiSettingsService {
throw e;
}
boolean created = oldSettings == null;
boolean updated = oldSettings != null;
eventPublisher.publishEvent(SaveEntityEvent.builder()
.tenantId(savedSettings.getTenantId())
.entity(savedSettings)
.oldEntity(oldSettings)
.entityId(savedSettings.getId())
.created(oldSettings == null)
.created(created)
.broadcastEvent(true)
.build());
if (updated) {
publishEvictEvent(AiSettingsCacheEvictEvent.of(savedSettings.getTenantId(), savedSettings.getId()));
}
return savedSettings;
}
@ -83,25 +98,20 @@ class AiSettingsServiceImpl implements AiSettingsService {
@Override
public Optional<AiSettings> findAiSettingsByTenantIdAndId(TenantId tenantId, AiSettingsId aiSettingsId) {
return aiSettingsDao.findByTenantIdAndId(tenantId, aiSettingsId);
var cacheKey = AiSettingsCacheKey.of(tenantId, aiSettingsId);
return Optional.ofNullable(cache.get(cacheKey, () -> aiSettingsDao.findByTenantIdAndId(tenantId, aiSettingsId).orElse(null)));
}
@Override
@Transactional
public boolean deleteByTenantIdAndId(TenantId tenantId, AiSettingsId aiSettingsId) {
Optional<AiSettings> aiSettingsOpt = aiSettingsDao.findByTenantIdAndId(tenantId, aiSettingsId);
if (aiSettingsOpt.isEmpty()) {
return false;
}
boolean deleted = aiSettingsDao.deleteByTenantIdAndId(tenantId, aiSettingsId);
if (deleted) {
publishDeleteEvent(aiSettingsOpt.get());
}
return deleted;
return deleteByTenantIdAndIdInternal(tenantId, aiSettingsId);
}
@Override
public Optional<HasId<?>> findEntity(TenantId tenantId, EntityId entityId) {
return Optional.ofNullable(aiSettingsDao.findById(tenantId, entityId.getId()));
return findAiSettingsByTenantIdAndId(tenantId, (AiSettingsId) entityId)
.map(aiSettings -> aiSettings); // necessary to cast to HasId<?>
}
@Override
@ -110,8 +120,22 @@ class AiSettingsServiceImpl implements AiSettingsService {
}
@Override
@Transactional
public void deleteEntity(TenantId tenantId, EntityId id, boolean force) {
deleteByTenantIdAndId(tenantId, new AiSettingsId(id.getId()));
deleteByTenantIdAndIdInternal(tenantId, new AiSettingsId(id.getId()));
}
private boolean deleteByTenantIdAndIdInternal(TenantId tenantId, AiSettingsId aiSettingsId) {
Optional<AiSettings> aiSettingsOpt = aiSettingsDao.findByTenantIdAndId(tenantId, aiSettingsId);
if (aiSettingsOpt.isEmpty()) {
return false;
}
boolean deleted = aiSettingsDao.deleteByTenantIdAndId(tenantId, aiSettingsId);
if (deleted) {
publishDeleteEvent(aiSettingsOpt.get());
publishEvictEvent(AiSettingsCacheEvictEvent.of(tenantId, aiSettingsId));
}
return deleted;
}
@Override
@ -121,8 +145,16 @@ class AiSettingsServiceImpl implements AiSettingsService {
if (deletedSettings.isEmpty()) {
return;
}
aiSettingsDao.deleteByTenantId(tenantId);
deletedSettings.forEach(this::publishDeleteEvent);
Set<AiSettingsCacheKey> cacheKeys = Sets.newHashSetWithExpectedSize(deletedSettings.size());
deletedSettings.forEach(settings -> {
publishDeleteEvent(settings);
cacheKeys.add(AiSettingsCacheKey.of(settings.getTenantId(), settings.getId()));
});
publishEvictEvent(new AiSettingsCacheEvictEvent(cacheKeys));
}
private void publishDeleteEvent(AiSettings settings) {

Loading…
Cancel
Save