mirror of https://github.com/halo-dev/halo
feat: caching optimization
Add a post-processor to enable asynchronous mode for Caffeine Add caching for roles Add caching for plugins Add caching for extension point definitions Add CacheConditionProvider for caching conditions Add CacheNames to store cache names Add a feature-toggle function for manually switching caching feature (disabled by default)pull/6009/head
parent
afabffc546
commit
3c97d06f88
|
@ -0,0 +1,100 @@
|
|||
package run.halo.app.cache;
|
||||
|
||||
import java.util.Objects;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.stereotype.Component;
|
||||
import run.halo.app.extension.ReactiveExtensionClientImpl;
|
||||
import run.halo.app.infra.properties.HaloProperties;
|
||||
|
||||
/**
|
||||
* Provides methods to determine if specific types of caches are evictable or enabled.
|
||||
*
|
||||
* <p>Uses {@link HaloProperties} to check the configuration for cache settings.
|
||||
*
|
||||
* @author sergei
|
||||
* @see HaloProperties#getCaches()
|
||||
* @see Cacheable#condition()
|
||||
* @see CacheEvict#condition()
|
||||
* @see ReactiveExtensionClientImpl
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class CacheConditionProvider {
|
||||
private static final String ROLE_KIND = "Role";
|
||||
private static final String PLUGIN_KIND = "Plugin";
|
||||
private static final String EXTENSION_POINT_DEFINITION_KIND = "ExtensionPointDefinition";
|
||||
|
||||
private final HaloProperties properties;
|
||||
|
||||
/**
|
||||
* Determines if the role dependencies cache is evictable based on the provided kind.
|
||||
*
|
||||
* @param kind the kind to check against.
|
||||
* @return {@code true} if the role dependencies cache is evictable, {@code false} otherwise.
|
||||
*/
|
||||
public boolean isRoleDependenciesCacheEvictableByKind(String kind) {
|
||||
return isRoleCacheEnabled() && Objects.equals(kind, ROLE_KIND);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the plugin extension cache is evictable based on the provided kind.
|
||||
*
|
||||
* @param kind the kind to check against.
|
||||
* @return {@code true} if the plugin extension cache is evictable, {@code false} otherwise.
|
||||
*/
|
||||
public boolean isPluginExtensionCacheEvictableByKind(String kind) {
|
||||
return isPluginExtensionCacheEnabled() && Objects.equals(kind, PLUGIN_KIND);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the extension point definition cache is evictable based on the provided kind.
|
||||
*
|
||||
* @param kind the kind to check against.
|
||||
* @return {@code true} if the extension point definition cache is evictable, {@code false}
|
||||
* otherwise.
|
||||
*/
|
||||
public boolean isExtensionPointDefinitionCacheEvictableByKind(String kind) {
|
||||
return isExtensionPointDefinitionCacheEnabled() && Objects.equals(kind,
|
||||
EXTENSION_POINT_DEFINITION_KIND);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the role dependencies cache is enabled.
|
||||
*
|
||||
* @return {@code true} if the role dependencies cache is enabled, {@code false} otherwise
|
||||
*/
|
||||
public boolean isRoleCacheEnabled() {
|
||||
return isCacheEnabled("role-dependencies");
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the plugin extension cache is enabled.
|
||||
*
|
||||
* @return {@code true} if the plugin extension cache is enabled, {@code false} otherwise.
|
||||
*/
|
||||
public boolean isPluginExtensionCacheEnabled() {
|
||||
return isCacheEnabled("plugin-extension");
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the extension point definition cache is enabled.
|
||||
*
|
||||
* @return {@code true} if the extension point definition cache is enabled, {@code false}
|
||||
* otherwise.
|
||||
*/
|
||||
public boolean isExtensionPointDefinitionCacheEnabled() {
|
||||
return isCacheEnabled("extension-point-definition");
|
||||
}
|
||||
|
||||
private boolean isCacheEnabled(String cache) {
|
||||
var cacheProperties = properties.getCaches().get(cache);
|
||||
|
||||
if (cacheProperties != null) {
|
||||
return !cacheProperties.isDisabled();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package run.halo.app.cache;
|
||||
|
||||
import java.util.Set;
|
||||
import run.halo.app.core.extension.service.DefaultRoleService;
|
||||
import run.halo.app.extension.ReactiveExtensionClientImpl;
|
||||
import run.halo.app.plugin.extensionpoint.DefaultExtensionGetter;
|
||||
|
||||
/**
|
||||
* Defines constant cache names used in the application.
|
||||
*
|
||||
* @author sergei
|
||||
*/
|
||||
public final class CacheNames {
|
||||
|
||||
/**
|
||||
* Cache name for dependencies roles.
|
||||
*
|
||||
* @see DefaultRoleService#listDependenciesFlux(Set)
|
||||
* @see ReactiveExtensionClientImpl
|
||||
*/
|
||||
public static final String ROLE_DEPENDENCIES = "ROLE_DEPENDENCIES";
|
||||
|
||||
/**
|
||||
* Cache name for plugin extensions.
|
||||
*
|
||||
* @see DefaultExtensionGetter#getExtensions(Class)
|
||||
* @see ReactiveExtensionClientImpl
|
||||
*/
|
||||
public static final String PLUGIN_EXTENSIONS = "PLUGIN_EXTENSIONS";
|
||||
|
||||
/**
|
||||
* Cache name for plugin extensions.
|
||||
*
|
||||
* @see DefaultExtensionGetter#getEnabledExtensionByDefinition(Class)
|
||||
* @see ReactiveExtensionClientImpl
|
||||
*/
|
||||
public static final String EXTENSION_POINT_DEFINITIONS = "EXTENSION_POINT_DEFINITIONS";
|
||||
|
||||
private CacheNames() {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package run.halo.app.config.postproccessor;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.cache.caffeine.CaffeineCacheManager;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Post-processes of {@link CaffeineCacheManager} beans to enabled async mode for them after
|
||||
* initialization.
|
||||
*
|
||||
* @author sergei
|
||||
* @see CaffeineCacheManager#setAsyncCacheMode(boolean)
|
||||
*/
|
||||
@Component
|
||||
public class CaffeineCacheManagerPostProcessor implements BeanPostProcessor {
|
||||
|
||||
@Override
|
||||
public Object postProcessAfterInitialization(@NonNull Object bean, @NonNull String beanName)
|
||||
throws BeansException {
|
||||
if (bean instanceof CaffeineCacheManager cacheManager) {
|
||||
configure(cacheManager);
|
||||
}
|
||||
|
||||
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
|
||||
}
|
||||
|
||||
private void configure(CaffeineCacheManager caffeineCacheManager) {
|
||||
caffeineCacheManager.setAsyncCacheMode(true);
|
||||
}
|
||||
}
|
|
@ -13,11 +13,13 @@ import java.util.function.Predicate;
|
|||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.cache.CacheNames;
|
||||
import run.halo.app.core.extension.Role;
|
||||
import run.halo.app.core.extension.RoleBinding;
|
||||
import run.halo.app.core.extension.RoleBinding.RoleRef;
|
||||
|
@ -72,6 +74,10 @@ public class DefaultRoleService implements RoleService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(
|
||||
value = CacheNames.ROLE_DEPENDENCIES,
|
||||
condition = "@cacheConditionProvider.isRoleCacheEnabled()"
|
||||
)
|
||||
public Flux<Role> listDependenciesFlux(Set<String> names) {
|
||||
return listDependencies(names, shouldFilterHidden(false));
|
||||
}
|
||||
|
|
|
@ -18,6 +18,8 @@ import java.util.function.Predicate;
|
|||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.cache.annotation.Caching;
|
||||
import org.springframework.context.event.ContextRefreshedEvent;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
|
@ -29,6 +31,7 @@ import org.springframework.transaction.reactive.TransactionalOperator;
|
|||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.retry.Retry;
|
||||
import run.halo.app.cache.CacheNames;
|
||||
import run.halo.app.extension.exception.ExtensionNotFoundException;
|
||||
import run.halo.app.extension.index.DefaultExtensionIterator;
|
||||
import run.halo.app.extension.index.ExtensionIterator;
|
||||
|
@ -170,6 +173,24 @@ public class ReactiveExtensionClientImpl implements ReactiveExtensionClient {
|
|||
}
|
||||
|
||||
@Override
|
||||
@Caching(evict = {
|
||||
@CacheEvict(
|
||||
value = CacheNames.ROLE_DEPENDENCIES,
|
||||
condition = "@cacheConditionProvider.isRoleDependenciesCacheEvictableByKind"
|
||||
+ "(#extension.kind)",
|
||||
allEntries = true),
|
||||
@CacheEvict(
|
||||
value = CacheNames.PLUGIN_EXTENSIONS,
|
||||
condition =
|
||||
"@cacheConditionProvider.isPluginExtensionCacheEvictableByKind"
|
||||
+ "(#extension.kind)",
|
||||
allEntries = true),
|
||||
@CacheEvict(
|
||||
value = CacheNames.EXTENSION_POINT_DEFINITIONS,
|
||||
condition = "@cacheConditionProvider.isExtensionPointDefinitionCacheEvictableByKind"
|
||||
+ "(#extension.kind)",
|
||||
allEntries = true)
|
||||
})
|
||||
public <E extends Extension> Mono<E> create(E extension) {
|
||||
checkClientWritable(extension);
|
||||
return Mono.just(extension)
|
||||
|
@ -203,6 +224,24 @@ public class ReactiveExtensionClientImpl implements ReactiveExtensionClient {
|
|||
}
|
||||
|
||||
@Override
|
||||
@Caching(evict = {
|
||||
@CacheEvict(
|
||||
value = CacheNames.ROLE_DEPENDENCIES,
|
||||
condition = "@cacheConditionProvider.isRoleDependenciesCacheEvictableByKind"
|
||||
+ "(#extension.kind)",
|
||||
allEntries = true),
|
||||
@CacheEvict(
|
||||
value = CacheNames.PLUGIN_EXTENSIONS,
|
||||
condition =
|
||||
"@cacheConditionProvider.isPluginExtensionCacheEvictableByKind"
|
||||
+ "(#extension.kind)",
|
||||
allEntries = true),
|
||||
@CacheEvict(
|
||||
value = CacheNames.EXTENSION_POINT_DEFINITIONS,
|
||||
condition = "@cacheConditionProvider.isExtensionPointDefinitionCacheEvictableByKind"
|
||||
+ "(#extension.kind)",
|
||||
allEntries = true)
|
||||
})
|
||||
public <E extends Extension> Mono<E> update(E extension) {
|
||||
checkClientWritable(extension);
|
||||
// Refactor the atomic reference if we have a better solution.
|
||||
|
@ -245,6 +284,24 @@ public class ReactiveExtensionClientImpl implements ReactiveExtensionClient {
|
|||
}
|
||||
|
||||
@Override
|
||||
@Caching(evict = {
|
||||
@CacheEvict(
|
||||
value = CacheNames.ROLE_DEPENDENCIES,
|
||||
condition = "@cacheConditionProvider.isRoleDependenciesCacheEvictableByKind"
|
||||
+ "(#extension.kind)",
|
||||
allEntries = true),
|
||||
@CacheEvict(
|
||||
value = CacheNames.PLUGIN_EXTENSIONS,
|
||||
condition =
|
||||
"@cacheConditionProvider.isPluginExtensionCacheEvictableByKind"
|
||||
+ "(#extension.kind)",
|
||||
allEntries = true),
|
||||
@CacheEvict(
|
||||
value = CacheNames.EXTENSION_POINT_DEFINITIONS,
|
||||
condition = "@cacheConditionProvider.isExtensionPointDefinitionCacheEvictableByKind"
|
||||
+ "(#extension.kind)",
|
||||
allEntries = true)
|
||||
})
|
||||
public <E extends Extension> Mono<E> delete(E extension) {
|
||||
checkClientWritable(extension);
|
||||
// set deletionTimestamp
|
||||
|
|
|
@ -8,12 +8,14 @@ import java.util.stream.Stream;
|
|||
import lombok.RequiredArgsConstructor;
|
||||
import org.pf4j.ExtensionPoint;
|
||||
import org.pf4j.PluginManager;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.stereotype.Component;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.cache.CacheNames;
|
||||
import run.halo.app.extension.ReactiveExtensionClient;
|
||||
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
|
||||
import run.halo.app.infra.SystemSetting.ExtensionPointEnabled;
|
||||
|
@ -72,6 +74,10 @@ public class DefaultExtensionGetter implements ExtensionGetter {
|
|||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(
|
||||
value = CacheNames.EXTENSION_POINT_DEFINITIONS,
|
||||
condition = "@cacheConditionProvider.isExtensionPointDefinitionCacheEnabled()"
|
||||
)
|
||||
public <T extends ExtensionPoint> Flux<T> getEnabledExtensionByDefinition(
|
||||
Class<T> extensionPoint) {
|
||||
return fetchExtensionPointDefinition(extensionPoint)
|
||||
|
@ -88,6 +94,10 @@ public class DefaultExtensionGetter implements ExtensionGetter {
|
|||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(
|
||||
value = CacheNames.PLUGIN_EXTENSIONS,
|
||||
condition = "@cacheConditionProvider.isPluginExtensionCacheEnabled()"
|
||||
)
|
||||
public <T extends ExtensionPoint> Flux<T> getExtensions(Class<T> extensionPointClass) {
|
||||
var extensions = new ArrayList<>(pluginManager.getExtensions(extensionPointClass));
|
||||
applicationContext.getBeanProvider(extensionPointClass)
|
||||
|
|
|
@ -37,6 +37,13 @@ halo:
|
|||
page:
|
||||
# Disable page cache by default due to experimental feature
|
||||
disabled: true
|
||||
role-dependencies:
|
||||
disabled: ${ROLE_DEPENDENCIES_CACHE_DISABLED:true}
|
||||
plugin-extension:
|
||||
disabled: ${PLUGIN_EXTENSION_CACHE_DISABLED:true}
|
||||
extension-point-definition:
|
||||
disabled: ${EXTENSION_POINT_DEFINITION_CACHE_DISABLED:true}
|
||||
|
||||
work-dir: ${user.home}/.halo2
|
||||
plugin:
|
||||
plugins-root: ${halo.work-dir}/plugins
|
||||
|
|
Loading…
Reference in New Issue