From 0eefa8db0e0e2b68ec5dde005e37f22c758851c1 Mon Sep 17 00:00:00 2001 From: Carsten Otto Date: Fri, 12 Nov 2021 22:25:06 +0100 Subject: [PATCH] add cache for getChannels --- application/build.gradle | 2 +- .../lndmanagej/service/ChannelService.java | 27 ++----- .../cotto/lndmanagej/service/NodeService.java | 28 ++----- .../lndmanagej/service/OwnNodeService.java | 21 +---- caching/build.gradle | 7 ++ .../lndmanagej/caching/CacheBuilder.java | 76 +++++++++++++++++++ .../lndmanagej/caching/CacheBuilderTest.java | 60 +++++++++++++++ grpc-adapter/build.gradle | 1 + .../de/cotto/lndmanagej/grpc/GrpcService.java | 13 +++- settings.gradle | 3 +- 10 files changed, 177 insertions(+), 61 deletions(-) create mode 100644 caching/build.gradle create mode 100644 caching/src/main/java/de/cotto/lndmanagej/caching/CacheBuilder.java create mode 100644 caching/src/test/java/de/cotto/lndmanagej/caching/CacheBuilderTest.java diff --git a/application/build.gradle b/application/build.gradle index a12ac5d9..1758d772 100644 --- a/application/build.gradle +++ b/application/build.gradle @@ -4,9 +4,9 @@ plugins { dependencies { implementation('org.springframework.boot:spring-boot-starter-web') - implementation('com.google.guava:guava:31.0.1-jre') implementation project(':grpc-adapter') implementation project(':model') + implementation project(':caching') runtimeOnly 'org.postgresql:postgresql' testImplementation testFixtures(project(':model')) } diff --git a/application/src/main/java/de/cotto/lndmanagej/service/ChannelService.java b/application/src/main/java/de/cotto/lndmanagej/service/ChannelService.java index a84ec837..ab59848a 100644 --- a/application/src/main/java/de/cotto/lndmanagej/service/ChannelService.java +++ b/application/src/main/java/de/cotto/lndmanagej/service/ChannelService.java @@ -1,16 +1,13 @@ package de.cotto.lndmanagej.service; -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; +import de.cotto.lndmanagej.caching.CacheBuilder; import de.cotto.lndmanagej.grpc.GrpcChannels; import de.cotto.lndmanagej.model.LocalChannel; import de.cotto.lndmanagej.model.Pubkey; import org.springframework.stereotype.Component; -import javax.annotation.Nonnull; import java.util.Set; -import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @Component @@ -18,12 +15,13 @@ public class ChannelService { private static final int MAXIMUM_SIZE = 500; private static final int CACHE_EXPIRY_MINUTES = 1; - private final GrpcChannels grpcChannels; - private final LoadingCache> channelsCache; + private final LoadingCache> channelsCache; public ChannelService(GrpcChannels grpcChannels) { - this.grpcChannels = grpcChannels; - channelsCache = initializeChannelsCache(); + channelsCache = new CacheBuilder() + .withExpiryMinutes(CACHE_EXPIRY_MINUTES) + .withMaximumSize(MAXIMUM_SIZE) + .build(grpcChannels::getChannels); } public Set getOpenChannels() { @@ -36,17 +34,4 @@ public class ChannelService { .collect(Collectors.toSet()); } - private LoadingCache> initializeChannelsCache() { - CacheLoader> loader = new CacheLoader<>() { - @Nonnull - @Override - public Set load(@Nonnull String ignored) { - return grpcChannels.getChannels(); - } - }; - return CacheBuilder.newBuilder() - .expireAfterWrite(CACHE_EXPIRY_MINUTES, TimeUnit.MINUTES) - .maximumSize(MAXIMUM_SIZE) - .build(loader); - } } diff --git a/application/src/main/java/de/cotto/lndmanagej/service/NodeService.java b/application/src/main/java/de/cotto/lndmanagej/service/NodeService.java index 313fd102..f61e2757 100644 --- a/application/src/main/java/de/cotto/lndmanagej/service/NodeService.java +++ b/application/src/main/java/de/cotto/lndmanagej/service/NodeService.java @@ -1,44 +1,32 @@ package de.cotto.lndmanagej.service; -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; +import de.cotto.lndmanagej.caching.CacheBuilder; import de.cotto.lndmanagej.grpc.GrpcNodeInfo; -import de.cotto.lndmanagej.model.Node; import de.cotto.lndmanagej.model.Pubkey; import org.springframework.stereotype.Component; -import javax.annotation.Nonnull; -import java.util.concurrent.TimeUnit; - @Component public class NodeService { private static final int MAXIMUM_SIZE = 500; private static final int CACHE_EXPIRY_MINUTES = 30; private final GrpcNodeInfo grpcNodeInfo; - private final LoadingCache aliasCache; + private final LoadingCache aliasCache = new CacheBuilder() + .withExpiryMinutes(CACHE_EXPIRY_MINUTES) + .withMaximumSize(MAXIMUM_SIZE) + .build(this::getAliasWithoutCache); public NodeService(GrpcNodeInfo grpcNodeInfo) { this.grpcNodeInfo = grpcNodeInfo; - CacheLoader loader = new CacheLoader<>() { - @Nonnull - @Override - public String load(@Nonnull Pubkey pubkey) { - return getNode(pubkey).alias(); - } - }; - aliasCache = CacheBuilder.newBuilder() - .expireAfterWrite(CACHE_EXPIRY_MINUTES, TimeUnit.MINUTES) - .maximumSize(MAXIMUM_SIZE) - .build(loader); } public String getAlias(Pubkey pubkey) { return aliasCache.getUnchecked(pubkey); } - private Node getNode(Pubkey pubkey) { - return grpcNodeInfo.getNode(pubkey); + private String getAliasWithoutCache(Pubkey pubkey) { + return grpcNodeInfo.getNode(pubkey).alias(); } + } diff --git a/application/src/main/java/de/cotto/lndmanagej/service/OwnNodeService.java b/application/src/main/java/de/cotto/lndmanagej/service/OwnNodeService.java index 684d12a2..b7bb9449 100644 --- a/application/src/main/java/de/cotto/lndmanagej/service/OwnNodeService.java +++ b/application/src/main/java/de/cotto/lndmanagej/service/OwnNodeService.java @@ -1,34 +1,21 @@ package de.cotto.lndmanagej.service; -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; +import de.cotto.lndmanagej.caching.CacheBuilder; import de.cotto.lndmanagej.grpc.GrpcGetInfo; import org.springframework.stereotype.Component; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.util.concurrent.TimeUnit; - @Component public class OwnNodeService { private static final int CACHE_EXPIRY_SECONDS = 30; private final GrpcGetInfo grpcGetInfo; - private final LoadingCache syncedToChainCache; + private final LoadingCache syncedToChainCache = new CacheBuilder() + .withExpirySeconds(CACHE_EXPIRY_SECONDS) + .build(this::isSyncedToChainWithoutCache); public OwnNodeService(GrpcGetInfo grpcGetInfo) { this.grpcGetInfo = grpcGetInfo; - CacheLoader loader = new CacheLoader<>() { - @Nonnull - @Override - public Boolean load(@Nullable String ignored) { - return isSyncedToChainWithoutCache(); - } - }; - syncedToChainCache = CacheBuilder.newBuilder() - .expireAfterWrite(CACHE_EXPIRY_SECONDS, TimeUnit.SECONDS) - .build(loader); } public boolean isSyncedToChain() { diff --git a/caching/build.gradle b/caching/build.gradle new file mode 100644 index 00000000..facb7ec2 --- /dev/null +++ b/caching/build.gradle @@ -0,0 +1,7 @@ +plugins { + id 'lnd-manageJ.java-library-conventions' +} + +dependencies { + api('com.google.guava:guava:31.0.1-jre') +} \ No newline at end of file diff --git a/caching/src/main/java/de/cotto/lndmanagej/caching/CacheBuilder.java b/caching/src/main/java/de/cotto/lndmanagej/caching/CacheBuilder.java new file mode 100644 index 00000000..48ed1fe2 --- /dev/null +++ b/caching/src/main/java/de/cotto/lndmanagej/caching/CacheBuilder.java @@ -0,0 +1,76 @@ +package de.cotto.lndmanagej.caching; + +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Objects; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import java.util.function.Supplier; + +public class CacheBuilder { + private long duration; + + @Nullable + private TimeUnit timeUnit; + + @Nullable + private Integer maximumSize; + + public CacheBuilder() { + duration = 10; + timeUnit = TimeUnit.MINUTES; + } + + public CacheBuilder withExpiryMilliseconds(long milliseconds) { + timeUnit = TimeUnit.MILLISECONDS; + duration = milliseconds; + return this; + } + + public CacheBuilder withExpirySeconds(long seconds) { + timeUnit = TimeUnit.SECONDS; + duration = seconds; + return this; + } + + public CacheBuilder withExpiryMinutes(long minutes) { + timeUnit = TimeUnit.MINUTES; + duration = minutes; + return this; + } + + public CacheBuilder withMaximumSize(int maximumSize) { + this.maximumSize = maximumSize; + return this; + } + + public LoadingCache build(Function function) { + CacheLoader loader = getLoader(function); + com.google.common.cache.CacheBuilder builder = com.google.common.cache.CacheBuilder.newBuilder() + .expireAfterWrite(duration, Objects.requireNonNull(timeUnit)); + if (this.maximumSize != null) { + return builder + .maximumSize(maximumSize) + .build(loader); + } + return builder + .build(loader); + } + + public LoadingCache build(Supplier function) { + return build(ignored -> function.get()); + } + + private CacheLoader getLoader(Function function) { + return new CacheLoader<>() { + @Nonnull + @Override + public O load(@Nonnull I input) { + return function.apply(input); + } + }; + } +} diff --git a/caching/src/test/java/de/cotto/lndmanagej/caching/CacheBuilderTest.java b/caching/src/test/java/de/cotto/lndmanagej/caching/CacheBuilderTest.java new file mode 100644 index 00000000..5c7f56a8 --- /dev/null +++ b/caching/src/test/java/de/cotto/lndmanagej/caching/CacheBuilderTest.java @@ -0,0 +1,60 @@ +package de.cotto.lndmanagej.caching; + +import com.google.common.cache.LoadingCache; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class CacheBuilderTest { + + @Test + void withoutExpiry() { + LoadingCache cache = new CacheBuilder().build(System::nanoTime); + assertIsCached(cache); + } + + @Test + void expiryOneMillisecond() throws InterruptedException { + LoadingCache cache = new CacheBuilder() + .withExpiryMilliseconds(1) + .build(System::nanoTime); + Long first = cache.getUnchecked(""); + Thread.sleep(1); + Long second = cache.getUnchecked(""); + assertThat(first).isNotEqualTo(second); + } + + @Test + void expiryOneSecond() { + LoadingCache cache = new CacheBuilder() + .withExpirySeconds(1) + .build(System::nanoTime); + assertIsCached(cache); + } + + @Test + void expiryOneMinute() { + LoadingCache cache = new CacheBuilder() + .withExpiryMinutes(1) + .build(System::nanoTime); + assertIsCached(cache); + } + + @Test + void withMaximumSize() { + LoadingCache cache = new CacheBuilder() + .withExpiryMinutes(1) + .withMaximumSize(1) + .build(System::nanoTime); + Long first = cache.getUnchecked(""); + cache.getUnchecked("a"); + Long third = cache.getUnchecked(""); + assertThat(first).isNotEqualTo(third); + } + + private void assertIsCached(LoadingCache cache) { + Long first = cache.getUnchecked(""); + Long second = cache.getUnchecked(""); + assertThat(first).isEqualTo(second); + } +} \ No newline at end of file diff --git a/grpc-adapter/build.gradle b/grpc-adapter/build.gradle index 966c2eeb..1e59026a 100644 --- a/grpc-adapter/build.gradle +++ b/grpc-adapter/build.gradle @@ -5,6 +5,7 @@ plugins { dependencies { implementation project(':grpc-client') implementation project(':model') + implementation project(':caching') testImplementation testFixtures(project(':model')) } diff --git a/grpc-adapter/src/main/java/de/cotto/lndmanagej/grpc/GrpcService.java b/grpc-adapter/src/main/java/de/cotto/lndmanagej/grpc/GrpcService.java index 949ff522..34d12348 100644 --- a/grpc-adapter/src/main/java/de/cotto/lndmanagej/grpc/GrpcService.java +++ b/grpc-adapter/src/main/java/de/cotto/lndmanagej/grpc/GrpcService.java @@ -1,6 +1,8 @@ package de.cotto.lndmanagej.grpc; +import com.google.common.cache.LoadingCache; import de.cotto.lndmanagej.LndConfiguration; +import de.cotto.lndmanagej.caching.CacheBuilder; import de.cotto.lndmanagej.model.ChannelId; import de.cotto.lndmanagej.model.Pubkey; import io.grpc.StatusRuntimeException; @@ -29,11 +31,16 @@ import java.util.function.Supplier; @Component public class GrpcService { + private static final int CACHE_EXPIRY_MILLISECONDS = 200; + private final Logger logger = LoggerFactory.getLogger(getClass()); private final StubCreator stubCreator; private final LightningGrpc.LightningBlockingStub lightningStub; private final RouterGrpc.RouterBlockingStub routerStub; + private final LoadingCache> channelsCache = new CacheBuilder() + .withExpiryMilliseconds(CACHE_EXPIRY_MILLISECONDS) + .build(this::getChannelsWithoutCache); public GrpcService(LndConfiguration lndConfiguration) throws IOException { stubCreator = new StubCreator( @@ -56,7 +63,7 @@ public class GrpcService { } Iterator getHtlcEvents() { - return get(() -> routerStub.subscribeHtlcEvents(SubscribeHtlcEventsRequest.newBuilder().build())) + return get(() -> routerStub.subscribeHtlcEvents(SubscribeHtlcEventsRequest.getDefaultInstance())) .orElse(Collections.emptyIterator()); } @@ -70,6 +77,10 @@ public class GrpcService { } public List getChannels() { + return channelsCache.getUnchecked(""); + } + + private List getChannelsWithoutCache() { return get(() -> lightningStub.listChannels(ListChannelsRequest.getDefaultInstance()).getChannelsList()) .orElse(List.of()); } diff --git a/settings.gradle b/settings.gradle index de09a685..8d4bc08d 100644 --- a/settings.gradle +++ b/settings.gradle @@ -2,4 +2,5 @@ rootProject.name = 'lnd-manageJ' include 'application' include 'model' include 'grpc-client' -include 'grpc-adapter' \ No newline at end of file +include 'grpc-adapter' +include 'caching' \ No newline at end of file