Skip to content

Commit cfcfa7d

Browse files
authored
Merge pull request #1361 from amvanbaren/caffeine-cache-fallback
Caffeine cache fallback
2 parents 0b5b657 + 803d791 commit cfcfa7d

File tree

5 files changed

+114
-57
lines changed

5 files changed

+114
-57
lines changed

server/build.gradle

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def versions = [
4242
jaxb_impl: '2.3.8',
4343
gatling: '3.9.5',
4444
loki4j: '1.4.2',
45-
embedded_redis: '1.4.3'
45+
jedis: '6.2.0'
4646
]
4747
ext['junit-jupiter.version'] = versions.junit
4848
sourceCompatibility = versions.java
@@ -92,6 +92,8 @@ dependencies {
9292
implementation "org.springframework.retry:spring-retry"
9393
implementation "org.bouncycastle:bcpkix-jdk18on:${versions.bouncycastle}"
9494
implementation "com.github.ben-manes.caffeine:caffeine"
95+
implementation 'com.github.ben-manes.caffeine:jcache'
96+
implementation "redis.clients:jedis:${versions.jedis}"
9597
implementation "com.giffing.bucket4j.spring.boot.starter:bucket4j-spring-boot-starter:${versions.bucket4j}"
9698
implementation "com.bucket4j:bucket4j-redis:${versions.bucket4j_redis}"
9799
implementation "org.jobrunr:jobrunr-spring-boot-3-starter:${versions.jobrunr}"
@@ -115,7 +117,6 @@ dependencies {
115117
implementation "io.micrometer:micrometer-tracing"
116118
implementation "io.micrometer:micrometer-tracing-bridge-otel"
117119
implementation "io.opentelemetry:opentelemetry-exporter-zipkin"
118-
implementation "com.github.codemonstur:embedded-redis:${versions.embedded_redis}"
119120
runtimeOnly "io.micrometer:micrometer-registry-prometheus"
120121
runtimeOnly "org.postgresql:postgresql"
121122
jooqGenerator "org.postgresql:postgresql"

server/src/dev/resources/application.yml

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,17 @@ spring:
1616
exclude: org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinAutoConfiguration
1717
profiles:
1818
include: ovsx
19-
data:
20-
redis:
21-
# redis standalone configuration
22-
host: localhost
23-
port: 6379
24-
# connect to redis cluster configured in docker-compose.yml
19+
# connect to redis cluster configured in docker-compose.yml
20+
# data:
21+
# redis:
2522
# cluster:
2623
# nodes: '127.0.0.1:7001,127.0.0.1:7002,127.0.0.1:7003,127.0.0.1:7004,127.0.0.1:7005,127.0.0.1:7006'
2724
# username: openvsx
2825
# password: openvsx
2926
datasource:
3027
url: jdbc:postgresql://localhost:5432/postgres
31-
username: gitpod
32-
password: gitpod
28+
username: openvsx
29+
password: openvsx
3330
flyway:
3431
baseline-on-migrate: true
3532
baseline-version: 0.1.0
@@ -108,7 +105,7 @@ org:
108105

109106
bucket4j:
110107
enabled: true
111-
cache-to-use: redis-jedis # use redis-cluster-jedis when running redis cluster
108+
cache-to-use: jcache # use redis-jedis for redis standalone or redis-cluster-jedis for redis cluster
112109
filters:
113110
- cache-name: buckets
114111
url: '/api/-/(namespace/create|publish)'
@@ -151,7 +148,7 @@ ovsx:
151148
enabled: true
152149
clear-on-start: true
153150
redis:
154-
embedded: true
151+
enabled: false
155152
eclipse:
156153
base-url: https://api.eclipse.org
157154
publisher-agreement:

server/src/main/java/org/eclipse/openvsx/cache/CacheConfig.java

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,25 @@
1212
import com.fasterxml.jackson.annotation.JsonInclude;
1313
import com.fasterxml.jackson.databind.json.JsonMapper;
1414
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
15-
import com.giffing.bucket4j.spring.boot.starter.context.properties.Bucket4JBootProperties;
1615
import com.github.benmanes.caffeine.cache.Cache;
1716
import com.github.benmanes.caffeine.cache.Caffeine;
1817
import com.github.benmanes.caffeine.cache.Scheduler;
18+
import com.github.benmanes.caffeine.jcache.CacheManagerImpl;
19+
import com.github.benmanes.caffeine.jcache.configuration.CaffeineConfiguration;
20+
import com.github.benmanes.caffeine.jcache.spi.CaffeineCachingProvider;
1921
import io.micrometer.common.util.StringUtils;
2022
import org.eclipse.openvsx.entities.ExtensionVersion;
2123
import org.eclipse.openvsx.json.ExtensionJson;
2224
import org.eclipse.openvsx.json.NamespaceDetailsJson;
2325
import org.eclipse.openvsx.search.SearchResult;
2426
import org.springframework.beans.factory.annotation.Value;
27+
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
2528
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
2629
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
2730
import org.springframework.cache.CacheManager;
2831
import org.springframework.cache.annotation.EnableCaching;
2932
import org.springframework.cache.caffeine.CaffeineCacheManager;
33+
import org.springframework.cache.jcache.JCacheCacheManager;
3034
import org.springframework.context.annotation.Bean;
3135
import org.springframework.context.annotation.Configuration;
3236
import org.springframework.context.annotation.Primary;
@@ -39,7 +43,10 @@
3943
import redis.clients.jedis.JedisCluster;
4044
import redis.clients.jedis.JedisPool;
4145

46+
import java.net.URI;
4247
import java.time.Duration;
48+
import java.util.OptionalLong;
49+
import java.util.Properties;
4350
import java.util.stream.Collectors;
4451

4552
import static org.eclipse.openvsx.cache.CacheService.*;
@@ -103,13 +110,13 @@ public CacheManager fileCacheManager(
103110
}
104111

105112
@Bean
106-
@ConditionalOnProperty(prefix = Bucket4JBootProperties.PROPERTY_PREFIX, name = "cache-to-use", havingValue = "redis-jedis")
113+
@ConditionalOnExpression("${bucket4j.enabled:false} && '${bucket4j.cache-to-use:}' == 'redis-jedis'")
107114
public JedisPool jedisPool(RedisProperties properties) {
108115
return new JedisPool(properties.getHost(), properties.getPort(), properties.getUsername(), properties.getPassword());
109116
}
110117

111118
@Bean
112-
@ConditionalOnProperty(prefix = Bucket4JBootProperties.PROPERTY_PREFIX, name = "cache-to-use", havingValue = "redis-cluster-jedis")
119+
@ConditionalOnExpression("${bucket4j.enabled:false} && '${bucket4j.cache-to-use:}' == 'redis-cluster-jedis'")
113120
public JedisCluster jedisCluster(RedisProperties properties) {
114121
var configBuilder = DefaultJedisClientConfig.builder();
115122
var username = properties.getUsername();
@@ -130,6 +137,69 @@ public JedisCluster jedisCluster(RedisProperties properties) {
130137

131138
@Bean
132139
@Primary
140+
@ConditionalOnProperty(value = "ovsx.redis.enabled", havingValue = "false", matchIfMissing = true)
141+
public JCacheCacheManager caffeineCacheManager(
142+
@Value("${ovsx.caching.average-review-rating.ttl:P3D}") Duration averageReviewRatingTtl,
143+
@Value("${ovsx.caching.average-review-rating.max-size:1}") long averageReviewRatingMaxSize,
144+
@Value("${ovsx.caching.namespace-details-json.ttl:PT1H}") Duration namespaceDetailsJsonTtl,
145+
@Value("${ovsx.caching.namespace-details-json.max-size:1024}") long namespaceDetailsJsonMaxSize,
146+
@Value("${ovsx.caching.database-search.ttl:PT1H}") Duration databaseSearchTtl,
147+
@Value("${ovsx.caching.database-search.max-size:1024}") long databaseSearchMaxSize,
148+
@Value("${ovsx.caching.extension-json.ttl:PT1H}") Duration extensionJsonTtl,
149+
@Value("${ovsx.caching.extension-json.max-size:1024}") long extensionJsonMaxSize,
150+
@Value("${ovsx.caching.latest-extension-version.ttl:PT1H}") Duration latestExtensionVersionTtl,
151+
@Value("${ovsx.caching.latest-extension-version.max-size:1024}") long latestExtensionVersionMaxSize,
152+
@Value("${ovsx.caching.sitemap.ttl:PT1H}") Duration sitemapTtl,
153+
@Value("${ovsx.caching.sitemap.max-size:1}") long sitemapMaxSize,
154+
@Value("${ovsx.caching.malicious-extensions.ttl:P3D}") Duration maliciousExtensionsTtl,
155+
@Value("${ovsx.caching.malicious-extensions.max-size:1}") long maliciousExtensionsMaxSize,
156+
@Value("${ovsx.caching.rate-limiting.name:buckets}") String rateLimitingCacheName,
157+
@Value("${ovsx.caching.rate-limiting.tti:PT1H}") Duration rateLimitingTti,
158+
@Value("${ovsx.caching.rate-limiting.max-size:1024}") long rateLimitingMaxSize
159+
) {
160+
var averageReviewRatingCache = createCaffeineConfiguration(averageReviewRatingTtl, averageReviewRatingMaxSize, false);
161+
var namespaceDetailsJsonCache = createCaffeineConfiguration(namespaceDetailsJsonTtl, namespaceDetailsJsonMaxSize, false);
162+
var databaseSearchCache = createCaffeineConfiguration(databaseSearchTtl, databaseSearchMaxSize, false);
163+
var extensionJsonCache = createCaffeineConfiguration(extensionJsonTtl, extensionJsonMaxSize, false);
164+
var latestExtensionVersionCache = createCaffeineConfiguration(latestExtensionVersionTtl, latestExtensionVersionMaxSize, false);
165+
var sitemapCache = createCaffeineConfiguration(sitemapTtl, sitemapMaxSize, false);
166+
var maliciousExtensionsCache = createCaffeineConfiguration(maliciousExtensionsTtl, maliciousExtensionsMaxSize, false);
167+
var rateLimitingCache = createCaffeineConfiguration(rateLimitingTti, rateLimitingMaxSize, true);
168+
169+
var cacheManager = new CacheManagerImpl(
170+
new CaffeineCachingProvider(),
171+
false,
172+
URI.create("com.github.benmanes.caffeine.jcache.spi.CaffeineCachingProvider"),
173+
Thread.currentThread().getContextClassLoader(),
174+
new Properties()
175+
);
176+
177+
cacheManager.createCache(CACHE_AVERAGE_REVIEW_RATING, averageReviewRatingCache);
178+
cacheManager.createCache(CACHE_NAMESPACE_DETAILS_JSON, namespaceDetailsJsonCache);
179+
cacheManager.createCache(CACHE_DATABASE_SEARCH, databaseSearchCache);
180+
cacheManager.createCache(CACHE_EXTENSION_JSON, extensionJsonCache);
181+
cacheManager.createCache(CACHE_LATEST_EXTENSION_VERSION, latestExtensionVersionCache);
182+
cacheManager.createCache(CACHE_SITEMAP, sitemapCache);
183+
cacheManager.createCache(CACHE_MALICIOUS_EXTENSIONS, maliciousExtensionsCache);
184+
cacheManager.createCache(rateLimitingCacheName, rateLimitingCache);
185+
return new JCacheCacheManager(cacheManager);
186+
}
187+
188+
private CaffeineConfiguration<Object, Object> createCaffeineConfiguration(Duration duration, long maxSize, boolean tti) {
189+
var configuration = new CaffeineConfiguration<>();
190+
configuration.setMaximumSize(OptionalLong.of(maxSize));
191+
if(tti) {
192+
configuration.setExpireAfterAccess(OptionalLong.of(duration.toNanos()));
193+
} else {
194+
configuration.setExpireAfterWrite(OptionalLong.of(duration.toNanos()));
195+
}
196+
197+
return configuration;
198+
}
199+
200+
@Bean
201+
@Primary
202+
@ConditionalOnProperty(value = "ovsx.redis.enabled", havingValue = "true")
133203
public CacheManager redisCacheManager(
134204
RedisConnectionFactory redisConnectionFactory,
135205
@Value("${ovsx.caching.average-review-rating.ttl:P3D}") Duration averageReviewRatingTtl,

server/src/main/java/org/eclipse/openvsx/cache/EmbeddedRedisServer.java

Lines changed: 0 additions & 42 deletions
This file was deleted.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/** ******************************************************************************
2+
* Copyright (c) 2025 Precies. Software OU and others
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License v. 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
* ****************************************************************************** */
10+
package org.eclipse.openvsx.cache;
11+
12+
import com.giffing.bucket4j.spring.boot.starter.config.cache.SyncCacheResolver;
13+
import com.giffing.bucket4j.spring.boot.starter.config.cache.jcache.JCacheCacheResolver;
14+
import com.giffing.bucket4j.spring.boot.starter.config.condition.ConditionalOnCache;
15+
import com.giffing.bucket4j.spring.boot.starter.config.condition.ConditionalOnSynchronousPropertyCondition;
16+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
17+
import org.springframework.cache.jcache.JCacheCacheManager;
18+
import org.springframework.context.annotation.Bean;
19+
import org.springframework.context.annotation.Configuration;
20+
21+
@Configuration
22+
@ConditionalOnSynchronousPropertyCondition
23+
@ConditionalOnCache("jcache")
24+
public class JCacheBucket4jConfiguration {
25+
26+
@Bean
27+
@ConditionalOnMissingBean(SyncCacheResolver.class)
28+
public SyncCacheResolver bucket4JCacheResolver(JCacheCacheManager cacheManager) {
29+
return new JCacheCacheResolver(cacheManager.getCacheManager());
30+
}
31+
}

0 commit comments

Comments
 (0)