From 630067d71984b14dcf6cc8b2dba666984ebf3a05 Mon Sep 17 00:00:00 2001 From: Dubyk Danylo <45672370+CTMBNara@users.noreply.github.com> Date: Thu, 2 May 2024 16:58:06 +0300 Subject: [PATCH] Core: Add jitter for cache (#3135) --- docs/config-app.md | 1 + .../USCustomLogicModuleCreator.java | 2 +- .../settings/CachingApplicationSettings.java | 15 ++-- .../prebid/server/settings/SettingsCache.java | 82 +++++++++++++++++-- .../spring/config/SettingsConfiguration.java | 20 ++++- .../CachingApplicationSettingsTest.java | 9 +- .../server/settings/SettingsCacheTest.java | 2 +- 7 files changed, 109 insertions(+), 22 deletions(-) diff --git a/docs/config-app.md b/docs/config-app.md index 9e9eed3ed2a..a2f3b7a891b 100644 --- a/docs/config-app.md +++ b/docs/config-app.md @@ -340,6 +340,7 @@ See [application settings](application-settings.md) for full reference of availa For caching available next options: - `settings.in-memory-cache.ttl-seconds` - how long (in seconds) data will be available in LRU cache. - `settings.in-memory-cache.cache-size` - the size of LRU cache. +- `settings.in-memory-cache.jitter-seconds` - jitter (in seconds) for `settings.in-memory-cache.ttl-seconds` parameter. - `settings.in-memory-cache.notification-endpoints-enabled` - if equals to `true` two additional endpoints will be available: [/storedrequests/openrtb2](endpoints/storedrequests/openrtb2.md) and [/storedrequests/amp](endpoints/storedrequests/amp.md). - `settings.in-memory-cache.account-invalidation-enabled` - if equals to `true` additional admin protected endpoints will be diff --git a/src/main/java/org/prebid/server/activity/infrastructure/creator/privacy/uscustomlogic/USCustomLogicModuleCreator.java b/src/main/java/org/prebid/server/activity/infrastructure/creator/privacy/uscustomlogic/USCustomLogicModuleCreator.java index 6d8a308fc1a..026e856a585 100644 --- a/src/main/java/org/prebid/server/activity/infrastructure/creator/privacy/uscustomlogic/USCustomLogicModuleCreator.java +++ b/src/main/java/org/prebid/server/activity/infrastructure/creator/privacy/uscustomlogic/USCustomLogicModuleCreator.java @@ -55,7 +55,7 @@ public USCustomLogicModuleCreator(USCustomLogicGppReaderFactory gppReaderFactory this.metrics = Objects.requireNonNull(metrics); jsonLogicNodesCache = cacheTtl != null && cacheSize != null - ? SettingsCache.createCache(cacheTtl, cacheSize) + ? SettingsCache.createCache(cacheTtl, cacheSize, 0) : null; } diff --git a/src/main/java/org/prebid/server/settings/CachingApplicationSettings.java b/src/main/java/org/prebid/server/settings/CachingApplicationSettings.java index 7fb363a0ff0..f6e8d1f4868 100644 --- a/src/main/java/org/prebid/server/settings/CachingApplicationSettings.java +++ b/src/main/java/org/prebid/server/settings/CachingApplicationSettings.java @@ -48,16 +48,21 @@ public CachingApplicationSettings(ApplicationSettings delegate, SettingsCache videoCache, Metrics metrics, int ttl, - int size) { + int size, + int jitter) { if (ttl <= 0 || size <= 0) { throw new IllegalArgumentException("ttl and size must be positive"); } + if (jitter < 0 || jitter >= ttl) { + throw new IllegalArgumentException("jitter must match the inequality: 0 <= jitter < ttl"); + } + this.delegate = Objects.requireNonNull(delegate); - this.accountCache = SettingsCache.createCache(ttl, size); - this.accountToErrorCache = SettingsCache.createCache(ttl, size); - this.adServerPublisherToErrorCache = SettingsCache.createCache(ttl, size); - this.categoryConfigCache = SettingsCache.createCache(ttl, size); + this.accountCache = SettingsCache.createCache(ttl, size, jitter); + this.accountToErrorCache = SettingsCache.createCache(ttl, size, jitter); + this.adServerPublisherToErrorCache = SettingsCache.createCache(ttl, size, jitter); + this.categoryConfigCache = SettingsCache.createCache(ttl, size, jitter); this.cache = Objects.requireNonNull(cache); this.ampCache = Objects.requireNonNull(ampCache); this.videoCache = Objects.requireNonNull(videoCache); diff --git a/src/main/java/org/prebid/server/settings/SettingsCache.java b/src/main/java/org/prebid/server/settings/SettingsCache.java index 1bba204db36..3e2446742b1 100644 --- a/src/main/java/org/prebid/server/settings/SettingsCache.java +++ b/src/main/java/org/prebid/server/settings/SettingsCache.java @@ -1,8 +1,10 @@ package org.prebid.server.settings; import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.benmanes.caffeine.cache.Expiry; import org.apache.commons.collections4.MapUtils; import org.apache.commons.lang3.ObjectUtils; +import org.checkerframework.checker.index.qual.NonNegative; import org.prebid.server.settings.model.StoredItem; import java.util.Collections; @@ -10,7 +12,7 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.ThreadLocalRandom; /** * Just a simple wrapper over in-memory caches for requests and imps. @@ -20,17 +22,26 @@ public class SettingsCache implements CacheNotificationListener { private final Map> requestCache; private final Map> impCache; - public SettingsCache(int ttl, int size) { + public SettingsCache(int ttl, int size, int jitter) { if (ttl <= 0 || size <= 0) { throw new IllegalArgumentException("ttl and size must be positive"); } - requestCache = createCache(ttl, size); - impCache = createCache(ttl, size); + if (jitter < 0 || jitter >= ttl) { + throw new IllegalArgumentException("jitter must match the inequality: 0 <= jitter < ttl"); + } + + requestCache = createCache(ttl, size, jitter); + impCache = createCache(ttl, size, jitter); } - public static Map createCache(int ttl, int size) { + public static Map createCache(int ttlSeconds, int size, int jitterSeconds) { + final long expireAfterNanos = (long) (ttlSeconds * 1e9); + final long jitterNanos = jitterSeconds == 0 ? 0L : (long) (jitterSeconds * 1e9); + return Caffeine.newBuilder() - .expireAfterWrite(ttl, TimeUnit.SECONDS) + .expireAfter(jitterNanos == 0L + ? new StaticExpiry<>(expireAfterNanos) + : new ExpiryWithJitter<>(expireAfterNanos, jitterNanos)) .maximumSize(size) .build() .asMap(); @@ -53,7 +64,10 @@ void saveImpCache(String accountId, String impId, String impValue) { } private static void saveCachedValue(Map> cache, - String accountId, String id, String value) { + String accountId, + String id, + String value) { + final Set values = ObjectUtils.defaultIfNull(cache.get(id), new HashSet<>()); values.add(StoredItem.of(accountId, value)); cache.put(id, values); @@ -79,4 +93,58 @@ public void invalidate(List requests, List imps) { requests.forEach(requestCache.keySet()::remove); imps.forEach(impCache.keySet()::remove); } + + private static class StaticExpiry implements Expiry { + + private final long expireAfterNanos; + + private StaticExpiry(long expireAfterNanos) { + this.expireAfterNanos = expireAfterNanos; + } + + @Override + public long expireAfterCreate(K key, V value, long currentTime) { + return expireAfterNanos; + } + + @Override + public long expireAfterUpdate(K key, V value, long currentTime, @NonNegative long currentDuration) { + return expireAfterNanos; + } + + @Override + public long expireAfterRead(K key, V value, long currentTime, @NonNegative long currentDuration) { + return currentDuration; + } + } + + private static class ExpiryWithJitter implements Expiry { + + private final Expiry baseExpiry; + private final long jitterNanos; + + private ExpiryWithJitter(long baseExpireAfterNanos, long jitterNanos) { + this.baseExpiry = new StaticExpiry<>(baseExpireAfterNanos); + this.jitterNanos = jitterNanos; + } + + @Override + public long expireAfterCreate(K key, V value, long currentTime) { + return baseExpiry.expireAfterCreate(key, value, currentTime) + jitter(); + } + + @Override + public long expireAfterUpdate(K key, V value, long currentTime, @NonNegative long currentDuration) { + return baseExpiry.expireAfterUpdate(key, value, currentTime, currentDuration) + jitter(); + } + + @Override + public long expireAfterRead(K key, V value, long currentTime, @NonNegative long currentDuration) { + return baseExpiry.expireAfterRead(key, value, currentTime, currentDuration); + } + + private long jitter() { + return ThreadLocalRandom.current().nextLong(-jitterNanos, jitterNanos); + } + } } diff --git a/src/main/java/org/prebid/server/spring/config/SettingsConfiguration.java b/src/main/java/org/prebid/server/spring/config/SettingsConfiguration.java index 538eaeb3159..1006403c9c4 100644 --- a/src/main/java/org/prebid/server/spring/config/SettingsConfiguration.java +++ b/src/main/java/org/prebid/server/spring/config/SettingsConfiguration.java @@ -284,7 +284,8 @@ CachingApplicationSettings cachingApplicationSettings( videoCache, metrics, cacheProperties.getTtlSeconds(), - cacheProperties.getCacheSize()); + cacheProperties.getCacheSize(), + cacheProperties.getJitterSeconds()); } } @@ -306,19 +307,28 @@ static class CacheConfiguration { @Bean @Qualifier("settingsCache") SettingsCache settingsCache(ApplicationSettingsCacheProperties cacheProperties) { - return new SettingsCache(cacheProperties.getTtlSeconds(), cacheProperties.getCacheSize()); + return new SettingsCache( + cacheProperties.getTtlSeconds(), + cacheProperties.getCacheSize(), + cacheProperties.getJitterSeconds()); } @Bean @Qualifier("ampSettingsCache") SettingsCache ampSettingsCache(ApplicationSettingsCacheProperties cacheProperties) { - return new SettingsCache(cacheProperties.getTtlSeconds(), cacheProperties.getCacheSize()); + return new SettingsCache( + cacheProperties.getTtlSeconds(), + cacheProperties.getCacheSize(), + cacheProperties.getJitterSeconds()); } @Bean @Qualifier("videoSettingCache") SettingsCache videoSettingCache(ApplicationSettingsCacheProperties cacheProperties) { - return new SettingsCache(cacheProperties.getTtlSeconds(), cacheProperties.getCacheSize()); + return new SettingsCache( + cacheProperties.getTtlSeconds(), + cacheProperties.getCacheSize(), + cacheProperties.getJitterSeconds()); } } @@ -336,5 +346,7 @@ private static class ApplicationSettingsCacheProperties { @NotNull @Min(1) private Integer cacheSize; + @Min(0) + private int jitterSeconds; } } diff --git a/src/test/java/org/prebid/server/settings/CachingApplicationSettingsTest.java b/src/test/java/org/prebid/server/settings/CachingApplicationSettingsTest.java index a51d3fbfef7..9bc9cb27157 100644 --- a/src/test/java/org/prebid/server/settings/CachingApplicationSettingsTest.java +++ b/src/test/java/org/prebid/server/settings/CachingApplicationSettingsTest.java @@ -61,12 +61,13 @@ public void setUp() { target = new CachingApplicationSettings( delegateSettings, - new SettingsCache(360, 100), - new SettingsCache(360, 100), - new SettingsCache(360, 100), + new SettingsCache(360, 100, 0), + new SettingsCache(360, 100, 0), + new SettingsCache(360, 100, 0), metrics, 360, - 100); + 100, + 0); } @Test diff --git a/src/test/java/org/prebid/server/settings/SettingsCacheTest.java b/src/test/java/org/prebid/server/settings/SettingsCacheTest.java index cdf602d21fe..f185d40edd7 100644 --- a/src/test/java/org/prebid/server/settings/SettingsCacheTest.java +++ b/src/test/java/org/prebid/server/settings/SettingsCacheTest.java @@ -15,7 +15,7 @@ public class SettingsCacheTest { @Before public void setUp() { - settingsCache = new SettingsCache(10, 10); + settingsCache = new SettingsCache(10, 10, 0); } @Test