Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: add caching for extension getter to enhance performance #7102

Merged
merged 1 commit into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package run.halo.app.plugin.extensionpoint;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.DisposableBean;
import run.halo.app.extension.Extension;
import run.halo.app.extension.ExtensionClient;
import run.halo.app.extension.controller.Controller;
import run.halo.app.extension.controller.ControllerBuilder;
import run.halo.app.extension.controller.Reconciler;

@RequiredArgsConstructor
abstract class AbstractDefinitionGetter<E extends Extension>
implements Reconciler<Reconciler.Request>, DisposableBean {

protected final ConcurrentMap<String, E> cache = new ConcurrentHashMap<>();

private final ExtensionClient client;

private final E watchType;

abstract void putCache(E definition);

@Override
@SuppressWarnings("unchecked")
public Result reconcile(Request request) {
client.fetch((Class<E>) watchType.getClass(), request.name())
.ifPresent(this::putCache);
return Result.doNotRetry();
}

@Override
public Controller setupWith(ControllerBuilder builder) {
return builder.extension(watchType)
.syncAllOnStart(true)
.build();
}

@Override
public void destroy() throws Exception {
cache.clear();
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package run.halo.app.plugin.extensionpoint;

import static run.halo.app.extension.index.query.QueryFactory.equal;

import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
Expand All @@ -11,15 +9,9 @@
import org.pf4j.PluginManager;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import run.halo.app.extension.ListOptions;
import run.halo.app.extension.ListResult;
import run.halo.app.extension.PageRequestImpl;
import run.halo.app.extension.ReactiveExtensionClient;
import run.halo.app.extension.router.selector.FieldSelector;
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
import run.halo.app.infra.SystemSetting.ExtensionPointEnabled;

Expand All @@ -33,7 +25,9 @@ public class DefaultExtensionGetter implements ExtensionGetter {

private final BeanFactory beanFactory;

private final ReactiveExtensionClient client;
private final ExtensionDefinitionGetter extensionDefinitionGetter;

private final ExtensionPointDefinitionGetter extensionPointDefinitionGetter;

@Override
public <T extends ExtensionPoint> Flux<T> getExtensions(Class<T> extensionPoint) {
Expand Down Expand Up @@ -86,9 +80,7 @@ private <T extends ExtensionPoint> Flux<T> getEnabledExtensions(String epdName,
}
var extensions = getExtensions(extensionPoint).cache();
return Flux.fromIterable(extensionDefNames)
.flatMapSequential(extensionDefName ->
client.fetch(ExtensionDefinition.class, extensionDefName)
)
.flatMapSequential(extensionDefinitionGetter::get)
.flatMapSequential(extensionDef -> {
var className = extensionDef.getSpec().getClassName();
return extensions.filter(
Expand All @@ -101,15 +93,7 @@ private <T extends ExtensionPoint> Flux<T> getEnabledExtensions(String epdName,

private Mono<ExtensionPointDefinition> fetchExtensionPointDefinition(
Class<? extends ExtensionPoint> extensionPoint) {
var listOptions = new ListOptions();
listOptions.setFieldSelector(FieldSelector.of(
equal("spec.className", extensionPoint.getName())
));
var sort = Sort.by("metadata.creationTimestamp", "metadata.name").ascending();
return client.listBy(ExtensionPointDefinition.class, listOptions,
PageRequestImpl.ofSize(1).withSort(sort)
)
.flatMap(list -> Mono.justOrEmpty(ListResult.first(list)));
return extensionPointDefinitionGetter.getByClassName(extensionPoint.getName());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package run.halo.app.plugin.extensionpoint;

import reactor.core.publisher.Mono;

public interface ExtensionDefinitionGetter {

/**
* Gets extension definition by extension definition name.
*
* @param name extension definition name
*/
Mono<ExtensionDefinition> get(String name);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package run.halo.app.plugin.extensionpoint;

import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import run.halo.app.extension.ExtensionClient;

@Component
public class ExtensionDefinitionGetterImpl
extends AbstractDefinitionGetter<ExtensionDefinition>
implements ExtensionDefinitionGetter {

public ExtensionDefinitionGetterImpl(ExtensionClient client) {
super(client, new ExtensionDefinition());
}

@Override
public Mono<ExtensionDefinition> get(String name) {
return Mono.fromSupplier(() -> cache.get(name));
}

@Override
void putCache(ExtensionDefinition definition) {
cache.put(definition.getMetadata().getName(), definition);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package run.halo.app.plugin.extensionpoint;

import reactor.core.publisher.Mono;

public interface ExtensionPointDefinitionGetter {

/**
* Gets extension point definition by extension point class.
* <p>Retrieve by filedSelector: <code>spec.className</code></p>
*
* @param className extension point class name
*/
Mono<ExtensionPointDefinition> getByClassName(String className);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package run.halo.app.plugin.extensionpoint;

import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import run.halo.app.extension.ExtensionClient;

@Component
public class ExtensionPointDefinitionGetterImpl
extends AbstractDefinitionGetter<ExtensionPointDefinition>
implements ExtensionPointDefinitionGetter {

public ExtensionPointDefinitionGetterImpl(ExtensionClient client) {
super(client, new ExtensionPointDefinition());
}

@Override
public Mono<ExtensionPointDefinition> getByClassName(String className) {
return Mono.fromSupplier(() -> cache.get(className));
}

@Override
void putCache(ExtensionPointDefinition definition) {
var className = definition.getSpec().getClassName();
cache.put(className, definition);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static run.halo.app.infra.SystemSetting.ExtensionPointEnabled.GROUP;
Expand All @@ -22,10 +22,7 @@
import org.springframework.core.annotation.Order;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import run.halo.app.extension.ListOptions;
import run.halo.app.extension.ListResult;
import run.halo.app.extension.Metadata;
import run.halo.app.extension.ReactiveExtensionClient;
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
import run.halo.app.infra.SystemSetting.ExtensionPointEnabled;
import run.halo.app.plugin.extensionpoint.ExtensionPointDefinition.ExtensionPointType;
Expand All @@ -34,7 +31,10 @@
class DefaultExtensionGetterTest {

@Mock
ReactiveExtensionClient client;
ExtensionPointDefinitionGetter extensionPointDefinitionGetter;

@Mock
ExtensionDefinitionGetter extensionDefinitionGetter;

@Mock
PluginManager pluginManager;
Expand All @@ -54,15 +54,14 @@ class DefaultExtensionGetterTest {
@Test
void shouldGetExtensionBySingletonDefinitionWhenExtensionPointEnabledSet() {
// prepare extension point definition
when(client.listBy(same(ExtensionPointDefinition.class), any(ListOptions.class), any()))
.thenReturn(Mono.fromSupplier(() -> {
var epd = createExtensionPointDefinition("fake-extension-point",
when(extensionPointDefinitionGetter.getByClassName(any()))
.thenReturn(Mono.fromSupplier(
() -> createExtensionPointDefinition("fake-extension-point",
FakeExtensionPoint.class,
ExtensionPointType.SINGLETON);
return new ListResult<>(List.of(epd));
}));
ExtensionPointType.SINGLETON))
);

when(client.fetch(ExtensionDefinition.class, "fake-extension"))
when(extensionDefinitionGetter.get(eq("fake-extension")))
.thenReturn(Mono.fromSupplier(() -> createExtensionDefinition(
"fake-extension",
FakeExtensionPointImpl.class,
Expand Down Expand Up @@ -94,13 +93,12 @@ void shouldGetExtensionBySingletonDefinitionWhenExtensionPointEnabledSet() {

@Test
void shouldGetDefaultSingletonDefinitionWhileExtensionPointEnabledNotSet() {
when(client.listBy(same(ExtensionPointDefinition.class), any(ListOptions.class), any()))
.thenReturn(Mono.fromSupplier(() -> {
var epd = createExtensionPointDefinition("fake-extension-point",
when(extensionPointDefinitionGetter.getByClassName(any()))
.thenReturn(Mono.fromSupplier(
() -> createExtensionPointDefinition("fake-extension-point",
FakeExtensionPoint.class,
ExtensionPointType.SINGLETON);
return new ListResult<>(List.of(epd));
}));
ExtensionPointType.SINGLETON))
);

when(configFetcher.fetch(GROUP, ExtensionPointEnabled.class))
.thenReturn(Mono.empty());
Expand All @@ -124,21 +122,20 @@ void shouldGetDefaultSingletonDefinitionWhileExtensionPointEnabledNotSet() {
@Test
void shouldGetMultiInstanceExtensionWhileExtensionPointEnabledSet() {
// prepare extension point definition
when(client.listBy(same(ExtensionPointDefinition.class), any(ListOptions.class), any()))
.thenReturn(Mono.fromSupplier(() -> {
var epd = createExtensionPointDefinition("fake-extension-point",
when(extensionPointDefinitionGetter.getByClassName(any()))
.thenReturn(Mono.fromSupplier(
() -> createExtensionPointDefinition("fake-extension-point",
FakeExtensionPoint.class,
ExtensionPointType.MULTI_INSTANCE);
return new ListResult<>(List.of(epd));
}));
ExtensionPointType.MULTI_INSTANCE))
);

when(client.fetch(ExtensionDefinition.class, "fake-extension"))
when(extensionDefinitionGetter.get(eq("fake-extension")))
.thenReturn(Mono.fromSupplier(() -> createExtensionDefinition(
"fake-extension",
FakeExtensionPointImpl.class,
"fake-extension-point")));

when(client.fetch(ExtensionDefinition.class, "default-fake-extension"))
when(extensionDefinitionGetter.get(eq("default-fake-extension")))
.thenReturn(Mono.fromSupplier(() -> createExtensionDefinition(
"default-fake-extension",
FakeExtensionPointDefaultImpl.class,
Expand Down Expand Up @@ -177,13 +174,12 @@ void shouldGetMultiInstanceExtensionWhileExtensionPointEnabledSet() {
@Test
void shouldGetMultiInstanceExtensionWhileExtensionPointEnabledNotSet() {
// prepare extension point definition
when(client.listBy(same(ExtensionPointDefinition.class), any(ListOptions.class), any()))
.thenReturn(Mono.fromSupplier(() -> {
var epd = createExtensionPointDefinition("fake-extension-point",
when(extensionPointDefinitionGetter.getByClassName(any()))
.thenReturn(Mono.fromSupplier(
() -> createExtensionPointDefinition("fake-extension-point",
FakeExtensionPoint.class,
ExtensionPointType.MULTI_INSTANCE);
return new ListResult<>(List.of(epd));
}));
ExtensionPointType.MULTI_INSTANCE))
);

when(configFetcher.fetch(GROUP, ExtensionPointEnabled.class))
.thenReturn(Mono.empty());
Expand Down
Loading