From fab500b68f5d64a52eec0a4137333c74705a95b5 Mon Sep 17 00:00:00 2001 From: Fabian Kovacs Date: Tue, 1 Jul 2025 17:19:19 +0200 Subject: [PATCH 1/4] Implements two stage Caching for ExecutionManager. Hard references, but timed. And untimed SoftReferences. This should help us fail earlier in congested scenarios, but also keep new results around for longer in contended situations. --- .../models/query/ExecutionManager.java | 132 ++++++++++++------ .../conquery/util/support/TestConquery.java | 5 +- 2 files changed, 91 insertions(+), 46 deletions(-) diff --git a/backend/src/main/java/com/bakdata/conquery/models/query/ExecutionManager.java b/backend/src/main/java/com/bakdata/conquery/models/query/ExecutionManager.java index 0ebaaba7ca..0c4d75cb6e 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/query/ExecutionManager.java +++ b/backend/src/main/java/com/bakdata/conquery/models/query/ExecutionManager.java @@ -1,7 +1,8 @@ package com.bakdata.conquery.models.query; +import static com.bakdata.conquery.models.execution.ExecutionState.RUNNING; + import java.util.NoSuchElementException; -import java.util.Objects; import java.util.Optional; import java.util.UUID; import java.util.concurrent.CountDownLatch; @@ -28,7 +29,9 @@ import com.google.common.cache.CacheBuilder; import com.google.common.cache.RemovalNotification; import com.google.common.util.concurrent.Uninterruptibles; +import lombok.AccessLevel; import lombok.Data; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; @@ -40,15 +43,40 @@ public abstract class ExecutionManager { private final DatasetRegistry datasetRegistry; private final ConqueryConfig config; - /** - * Cache for running and recent execution infos. - */ - private final Cache executionInfos = + @Getter(AccessLevel.NONE) + private final Cache executionInfosSoft = CacheBuilder.newBuilder() .softValues() .removalListener(this::executionRemoved) .build(); + @Getter(AccessLevel.NONE) + private final Cache executionInfosHard = + CacheBuilder.newBuilder() + .expireAfterAccess(10, TimeUnit.SECONDS) // TODO from config! + .removalListener(this::executionTimedOut) + .build(); + + private void executionTimedOut(RemovalNotification removalNotification) { + // If removal was done manually we assume it was also handled properly + if (!removalNotification.wasEvicted()) { + return; + } + + log.trace("Query {} timed out, demoting to soft cache.", removalNotification.getKey()); + + if (getExecution(removalNotification.getKey()) == null) { + // Execution already deleted. + return; + } + + executionInfosSoft.put(removalNotification.getKey(), removalNotification.getValue()); + } + + public ManagedExecution getExecution(ManagedExecutionId execution) { + return storage.getExecution(execution); + } + /** * Manage state of evicted Queries, setting them to NEW. */ @@ -70,10 +98,6 @@ private void executionRemoved(RemovalNotification Optional tryGetExecutionInfo(ManagedExecutionId id) { + ExecutionInfo maybeInfo = executionInfosSoft.getIfPresent(id); + + if (maybeInfo != null) { + return Optional.of((R) maybeInfo); + } + + maybeInfo = executionInfosHard.getIfPresent(id); + + if (maybeInfo != null) { + return Optional.of((R) maybeInfo); + } + + return Optional.empty(); } /** * Returns the state or throws an NoSuchElementException if no state was found. */ public R getExecutionInfo(ManagedExecutionId id) { - ExecutionInfo executionInfo = executionInfos.getIfPresent(id); - if (executionInfo == null) { - throw new NoSuchElementException("No execution found for %s".formatted(id)); + Optional maybeInfo = tryGetExecutionInfo(id); + + if (maybeInfo.isPresent()) { + return maybeInfo.get(); } - return (R) executionInfo; - } - public Optional tryGetExecutionInfo(ManagedExecutionId id) { - return Optional.ofNullable((R) executionInfos.getIfPresent(id)); + throw new NoSuchElementException("Could not find Execution %s".formatted(id)); } + public void addState(ManagedExecutionId id, ExecutionInfo result) { - executionInfos.put(id, result); + clearQueryResults(id); + + executionInfosHard.put(id, result); } public final ManagedExecution runQuery(Namespace namespace, QueryDescription query, UserId user, boolean system) { @@ -182,35 +224,34 @@ public final void cancelExecution(final ManagedExecution execution) { externalExecution.cancel(); return; } - executionInfos.invalidate(execution.getId()); + + clearQueryResults(execution.getId()); + doCancelQuery(execution.getId()); } public abstract void doCancelQuery(ManagedExecutionId managedExecutionId); public void updateState(ManagedExecutionId id, ExecutionState execState) { - ExecutionInfo executionInfo = executionInfos.getIfPresent(id); - if (executionInfo != null) { - executionInfo.setExecutionState(execState); - return; - } + Optional executionInfo = tryGetExecutionInfo(id); - log.warn("Could not update execution executionInfo of {} to {}, because it had no executionInfo.", id, execState); + executionInfo.ifPresentOrElse( + info -> info.setExecutionState(execState), + () -> log.warn("Could not update execution executionInfo of {} to {}, because it had no executionInfo.", id, execState) + ); } public Stream streamQueryResults(E execution) { - final InternalExecutionInfo resultParts = (InternalExecutionInfo) executionInfos.getIfPresent(execution.getId()); - - return resultParts == null - ? Stream.empty() - : resultParts.streamQueryResults(); + Optional maybeInfo = tryGetExecutionInfo(execution.getId()); + return maybeInfo.map(InternalExecutionInfo::streamQueryResults) + .orElseGet(Stream::empty); } public void clearBarrier(ManagedExecutionId id) { - ExecutionInfo result = Objects.requireNonNull(executionInfos.getIfPresent(id), "Cannot clear lock on absent execution result"); + ExecutionInfo executionInfo = getExecutionInfo(id); - result.getExecutingLock().countDown(); + executionInfo.getExecutingLock().countDown(); } /** @@ -218,24 +259,33 @@ public void clearBarrier(ManagedExecutionId id) { */ public ExecutionState awaitDone(ManagedExecutionId id, int time, TimeUnit unit) { - ExecutionInfo executionInfo = executionInfos.getIfPresent(id); - if (executionInfo == null) { + Optional maybeExecutionInfo = tryGetExecutionInfo(id); + + if (maybeExecutionInfo.isEmpty()) { return ExecutionState.NEW; } + ExecutionInfo executionInfo = maybeExecutionInfo.get(); ExecutionState execState = executionInfo.getExecutionState(); - if (execState != ExecutionState.RUNNING) { + if (execState != RUNNING) { return execState; } Uninterruptibles.awaitUninterruptibly(executionInfo.getExecutingLock(), time, unit); - ExecutionInfo executionInfoAfterWait = executionInfos.getIfPresent(id); - if (executionInfoAfterWait == null) { - return ExecutionState.NEW; + Optional maybeExecutionInfoAfterWait = tryGetExecutionInfo(id); + return maybeExecutionInfoAfterWait + .map(ExecutionInfo::getExecutionState) + .orElse(ExecutionState.NEW); + } + + public boolean hasRunningQueries() { + if (executionInfosSoft.asMap().values().stream().map(ExecutionInfo::getExecutionState).anyMatch(RUNNING::equals)) { + return true; } - return executionInfoAfterWait.getExecutionState(); + + return executionInfosHard.asMap().values().stream().map(ExecutionInfo::getExecutionState).anyMatch(RUNNING::equals); } /** @@ -264,6 +314,4 @@ public interface InternalExecutionInfo extends ExecutionInfo { } - - } diff --git a/backend/src/test/java/com/bakdata/conquery/util/support/TestConquery.java b/backend/src/test/java/com/bakdata/conquery/util/support/TestConquery.java index 3b6cdf45ff..91a2c58dcf 100644 --- a/backend/src/test/java/com/bakdata/conquery/util/support/TestConquery.java +++ b/backend/src/test/java/com/bakdata/conquery/util/support/TestConquery.java @@ -29,7 +29,6 @@ import com.bakdata.conquery.models.auth.entities.User; import com.bakdata.conquery.models.config.ConqueryConfig; import com.bakdata.conquery.models.datasets.Dataset; -import com.bakdata.conquery.models.execution.ExecutionState; import com.bakdata.conquery.models.identifiable.ids.specific.DatasetId; import com.bakdata.conquery.models.identifiable.ids.specific.UserId; import com.bakdata.conquery.models.query.ExecutionManager; @@ -206,9 +205,7 @@ private boolean isBusy() { busy |= standaloneCommand.getManager().getDatasetRegistry().getNamespaces().stream() .map(Namespace::getExecutionManager) - .flatMap(e -> e.getExecutionInfos().asMap().values().stream()) - .map(ExecutionManager.ExecutionInfo::getExecutionState) - .anyMatch(ExecutionState.RUNNING::equals); + .anyMatch(ExecutionManager::hasRunningQueries); for (Namespace namespace : standaloneCommand.getManagerNode().getDatasetRegistry().getNamespaces()) { busy |= namespace.getJobManager().isSlowWorkerBusy(); From 8733cbf58905d958549dfd1630a43a6007e4fadb Mon Sep 17 00:00:00 2001 From: Fabian Kovacs Date: Tue, 1 Jul 2025 18:03:38 +0200 Subject: [PATCH 2/4] Pull CacheSpecs from config --- .../conquery/models/config/QueryConfig.java | 21 +++- .../models/query/ExecutionManager.java | 98 +++++++++---------- 2 files changed, 69 insertions(+), 50 deletions(-) diff --git a/backend/src/main/java/com/bakdata/conquery/models/config/QueryConfig.java b/backend/src/main/java/com/bakdata/conquery/models/config/QueryConfig.java index 1cb33d7dfa..0db33fab77 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/config/QueryConfig.java +++ b/backend/src/main/java/com/bakdata/conquery/models/config/QueryConfig.java @@ -1,11 +1,17 @@ package com.bakdata.conquery.models.config; +import jakarta.validation.constraints.NotEmpty; + +import com.fasterxml.jackson.annotation.JsonIgnore; import io.dropwizard.util.Duration; +import io.dropwizard.validation.ValidationMethod; import lombok.Getter; import lombok.Setter; import lombok.ToString; -@Getter @Setter @ToString +@Getter +@Setter +@ToString public class QueryConfig { private ThreadPoolDefinition executionPool = new ThreadPoolDefinition(); @@ -19,4 +25,17 @@ public class QueryConfig { * TODO Implement global limit of active secondaryId sub plans */ private int secondaryIdSubPlanRetention = 15; + + @NotEmpty + private String softResultCacheSpec = "softValues"; + @NotEmpty + private String hardResultCacheSpec = "expireAfterAccess=10m"; + + @JsonIgnore + @ValidationMethod(message = "SoftResultCache is required to be soft.") + public boolean isSoftSpecSoft() { + return softResultCacheSpec.contains("softValues"); + } + + } diff --git a/backend/src/main/java/com/bakdata/conquery/models/query/ExecutionManager.java b/backend/src/main/java/com/bakdata/conquery/models/query/ExecutionManager.java index 0c4d75cb6e..d1129349d3 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/query/ExecutionManager.java +++ b/backend/src/main/java/com/bakdata/conquery/models/query/ExecutionManager.java @@ -44,37 +44,23 @@ public abstract class ExecutionManager { private final ConqueryConfig config; @Getter(AccessLevel.NONE) - private final Cache executionInfosSoft = - CacheBuilder.newBuilder() - .softValues() - .removalListener(this::executionRemoved) - .build(); - + private final Cache executionInfosSoft; @Getter(AccessLevel.NONE) - private final Cache executionInfosHard = - CacheBuilder.newBuilder() - .expireAfterAccess(10, TimeUnit.SECONDS) // TODO from config! - .removalListener(this::executionTimedOut) - .build(); + private final Cache executionInfosHard; - private void executionTimedOut(RemovalNotification removalNotification) { - // If removal was done manually we assume it was also handled properly - if (!removalNotification.wasEvicted()) { - return; - } + public ExecutionManager(MetaStorage storage, DatasetRegistry datasetRegistry, ConqueryConfig config) { + this.storage = storage; + this.datasetRegistry = datasetRegistry; + this.config = config; - log.trace("Query {} timed out, demoting to soft cache.", removalNotification.getKey()); + // for some reason, the builder in field initialization causes formatters to go bananas. - if (getExecution(removalNotification.getKey()) == null) { - // Execution already deleted. - return; - } - executionInfosSoft.put(removalNotification.getKey(), removalNotification.getValue()); - } + executionInfosSoft = CacheBuilder.from(config.getQueries().getSoftResultCacheSpec()) + .removalListener(this::executionRemoved).build(); - public ManagedExecution getExecution(ManagedExecutionId execution) { - return storage.getExecution(execution); + executionInfosHard = CacheBuilder.from(config.getQueries().getHardResultCacheSpec()) + .removalListener(this::executionTimedOut).build(); } /** @@ -98,6 +84,26 @@ private void executionRemoved(RemovalNotification removalNotification) { + // If removal was done manually we assume it was also handled properly + if (!removalNotification.wasEvicted()) { + return; + } + + log.trace("Query {} timed out, demoting to soft cache.", removalNotification.getKey()); + + if (getExecution(removalNotification.getKey()) == null) { + // Execution already deleted. + return; + } + + executionInfosSoft.put(removalNotification.getKey(), removalNotification.getValue()); + } + + public ManagedExecution getExecution(ManagedExecutionId execution) { + return storage.getExecution(execution); + } + public void reset(ManagedExecutionId id) { // This avoids endless loops with already reset queries if (!isResultPresent(id)) { @@ -132,20 +138,6 @@ public Optional tryGetExecutionInfo(ManagedExecutio return Optional.empty(); } - /** - * Returns the state or throws an NoSuchElementException if no state was found. - */ - public R getExecutionInfo(ManagedExecutionId id) { - Optional maybeInfo = tryGetExecutionInfo(id); - - if (maybeInfo.isPresent()) { - return maybeInfo.get(); - } - - throw new NoSuchElementException("Could not find Execution %s".formatted(id)); - } - - public void addState(ManagedExecutionId id, ExecutionInfo result) { clearQueryResults(id); @@ -235,17 +227,15 @@ public final void cancelExecution(final ManagedExecution execution) { public void updateState(ManagedExecutionId id, ExecutionState execState) { Optional executionInfo = tryGetExecutionInfo(id); - executionInfo.ifPresentOrElse( - info -> info.setExecutionState(execState), - () -> log.warn("Could not update execution executionInfo of {} to {}, because it had no executionInfo.", id, execState) + executionInfo.ifPresentOrElse(info -> info.setExecutionState(execState), + () -> log.warn("Could not update execution executionInfo of {} to {}, because it had no executionInfo.", id, execState) ); } public Stream streamQueryResults(E execution) { Optional maybeInfo = tryGetExecutionInfo(execution.getId()); - return maybeInfo.map(InternalExecutionInfo::streamQueryResults) - .orElseGet(Stream::empty); + return maybeInfo.map(InternalExecutionInfo::streamQueryResults).orElseGet(Stream::empty); } public void clearBarrier(ManagedExecutionId id) { @@ -254,6 +244,19 @@ public void clearBarrier(ManagedExecutionId id) { executionInfo.getExecutingLock().countDown(); } + /** + * Returns the state or throws an NoSuchElementException if no state was found. + */ + public R getExecutionInfo(ManagedExecutionId id) { + Optional maybeInfo = tryGetExecutionInfo(id); + + if (maybeInfo.isPresent()) { + return maybeInfo.get(); + } + + throw new NoSuchElementException("Could not find Execution %s".formatted(id)); + } + /** * Blocks until an execution finished of the specified timeout is reached. Return immediately if the execution is not running */ @@ -275,9 +278,7 @@ public ExecutionState awaitDone(ManagedExecutionId id, int time, TimeUnit unit) Uninterruptibles.awaitUninterruptibly(executionInfo.getExecutingLock(), time, unit); Optional maybeExecutionInfoAfterWait = tryGetExecutionInfo(id); - return maybeExecutionInfoAfterWait - .map(ExecutionInfo::getExecutionState) - .orElse(ExecutionState.NEW); + return maybeExecutionInfoAfterWait.map(ExecutionInfo::getExecutionState).orElse(ExecutionState.NEW); } public boolean hasRunningQueries() { @@ -296,8 +297,7 @@ public interface ExecutionInfo { /** * The current {@link ExecutionState} of the execution. */ - @NotNull - ExecutionState getExecutionState(); + @NotNull ExecutionState getExecutionState(); void setExecutionState(ExecutionState state); From c8c656f7ed829c26161a9f9c02967df9ec5d9366 Mon Sep 17 00:00:00 2001 From: Fabian Kovacs Date: Wed, 2 Jul 2025 09:59:20 +0200 Subject: [PATCH 3/4] Use Caffeine Cache and rename Caches to L1/L2 --- .../conquery/models/config/QueryConfig.java | 13 ++-- .../models/query/ExecutionManager.java | 71 +++++++------------ 2 files changed, 33 insertions(+), 51 deletions(-) diff --git a/backend/src/main/java/com/bakdata/conquery/models/config/QueryConfig.java b/backend/src/main/java/com/bakdata/conquery/models/config/QueryConfig.java index 0db33fab77..91d6dacfac 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/config/QueryConfig.java +++ b/backend/src/main/java/com/bakdata/conquery/models/config/QueryConfig.java @@ -1,7 +1,6 @@ package com.bakdata.conquery.models.config; -import jakarta.validation.constraints.NotEmpty; - +import com.bakdata.conquery.util.validation.ValidCaffeineSpec; import com.fasterxml.jackson.annotation.JsonIgnore; import io.dropwizard.util.Duration; import io.dropwizard.validation.ValidationMethod; @@ -26,15 +25,15 @@ public class QueryConfig { */ private int secondaryIdSubPlanRetention = 15; - @NotEmpty - private String softResultCacheSpec = "softValues"; - @NotEmpty - private String hardResultCacheSpec = "expireAfterAccess=10m"; + @ValidCaffeineSpec + private String L2CacheSpec = "softValues"; + @ValidCaffeineSpec + private String L1CacheSpec = "expireAfterAccess=10m"; @JsonIgnore @ValidationMethod(message = "SoftResultCache is required to be soft.") public boolean isSoftSpecSoft() { - return softResultCacheSpec.contains("softValues"); + return L2CacheSpec.contains("softValues"); } diff --git a/backend/src/main/java/com/bakdata/conquery/models/query/ExecutionManager.java b/backend/src/main/java/com/bakdata/conquery/models/query/ExecutionManager.java index d1129349d3..d037494df1 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/query/ExecutionManager.java +++ b/backend/src/main/java/com/bakdata/conquery/models/query/ExecutionManager.java @@ -25,9 +25,9 @@ import com.bakdata.conquery.models.query.results.EntityResult; import com.bakdata.conquery.models.worker.DatasetRegistry; import com.bakdata.conquery.models.worker.Namespace; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.RemovalNotification; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.benmanes.caffeine.cache.RemovalCause; import com.google.common.util.concurrent.Uninterruptibles; import lombok.AccessLevel; import lombok.Data; @@ -44,60 +44,43 @@ public abstract class ExecutionManager { private final ConqueryConfig config; @Getter(AccessLevel.NONE) - private final Cache executionInfosSoft; + private final Cache executionInfosL2; @Getter(AccessLevel.NONE) - private final Cache executionInfosHard; + private final Cache executionInfosL1; public ExecutionManager(MetaStorage storage, DatasetRegistry datasetRegistry, ConqueryConfig config) { this.storage = storage; this.datasetRegistry = datasetRegistry; this.config = config; - // for some reason, the builder in field initialization causes formatters to go bananas. - - - executionInfosSoft = CacheBuilder.from(config.getQueries().getSoftResultCacheSpec()) - .removalListener(this::executionRemoved).build(); - - executionInfosHard = CacheBuilder.from(config.getQueries().getHardResultCacheSpec()) - .removalListener(this::executionTimedOut).build(); + executionInfosL2 = Caffeine.from(config.getQueries().getL2CacheSpec()) + .removalListener(this::evictionSoft) + .build(); + executionInfosL1 = Caffeine.from(config.getQueries().getL1CacheSpec()) + .removalListener(this::evictionHard) + .build(); } /** * Manage state of evicted Queries, setting them to NEW. */ - private void executionRemoved(RemovalNotification removalNotification) { + private void evictionSoft(ManagedExecutionId executionId, ExecutionInfo resultInfo, RemovalCause cause) { + // If removal was done manually we assume it was also handled properly - if (!removalNotification.wasEvicted()) { + if (!cause.wasEvicted()) { return; } - final ManagedExecutionId executionId = removalNotification.getKey(); - - log.trace("Evicted Results for Query[{}] (Reason: {})", executionId, removalNotification.getCause()); - - final ManagedExecution execution = getExecution(executionId); - - // The query might already be deleted - if (execution != null) { - reset(executionId); - } + log.trace("Evicted Query[{}] results from soft cache (Reason: {})", executionId, cause); } - private void executionTimedOut(RemovalNotification removalNotification) { + private void evictionHard(ManagedExecutionId executionId, ExecutionInfo resultInfo, RemovalCause cause) { // If removal was done manually we assume it was also handled properly - if (!removalNotification.wasEvicted()) { + if (!cause.wasEvicted()) { return; } - log.trace("Query {} timed out, demoting to soft cache.", removalNotification.getKey()); - - if (getExecution(removalNotification.getKey()) == null) { - // Execution already deleted. - return; - } - - executionInfosSoft.put(removalNotification.getKey(), removalNotification.getValue()); + log.trace("Evicted Query[{}] results from hard cache (Reason: {})", executionId, cause); } public ManagedExecution getExecution(ManagedExecutionId execution) { @@ -118,18 +101,19 @@ public boolean isResultPresent(ManagedExecutionId id) { } public void clearQueryResults(ManagedExecutionId execution) { - executionInfosSoft.invalidate(execution); - executionInfosSoft.invalidate(execution); + executionInfosL2.invalidate(execution); + executionInfosL1.invalidate(execution); } public Optional tryGetExecutionInfo(ManagedExecutionId id) { - ExecutionInfo maybeInfo = executionInfosSoft.getIfPresent(id); + // Access hard before soft, to keep things "touched" + ExecutionInfo maybeInfo = executionInfosL1.getIfPresent(id); if (maybeInfo != null) { return Optional.of((R) maybeInfo); } - maybeInfo = executionInfosHard.getIfPresent(id); + maybeInfo = executionInfosL2.getIfPresent(id); if (maybeInfo != null) { return Optional.of((R) maybeInfo); @@ -139,9 +123,8 @@ public Optional tryGetExecutionInfo(ManagedExecutio } public void addState(ManagedExecutionId id, ExecutionInfo result) { - clearQueryResults(id); - - executionInfosHard.put(id, result); + executionInfosL1.put(id, result); + executionInfosL2.put(id, result); } public final ManagedExecution runQuery(Namespace namespace, QueryDescription query, UserId user, boolean system) { @@ -282,11 +265,11 @@ public ExecutionState awaitDone(ManagedExecutionId id, int time, TimeUnit unit) } public boolean hasRunningQueries() { - if (executionInfosSoft.asMap().values().stream().map(ExecutionInfo::getExecutionState).anyMatch(RUNNING::equals)) { + if (executionInfosL2.asMap().values().stream().map(ExecutionInfo::getExecutionState).anyMatch(RUNNING::equals)) { return true; } - return executionInfosHard.asMap().values().stream().map(ExecutionInfo::getExecutionState).anyMatch(RUNNING::equals); + return executionInfosL1.asMap().values().stream().map(ExecutionInfo::getExecutionState).anyMatch(RUNNING::equals); } /** From 4d986b2226ff3eb53f4019ec649d07102bdd457d Mon Sep 17 00:00:00 2001 From: awildturtok <1553491+awildturtok@users.noreply.github.com> Date: Wed, 2 Jul 2025 14:48:37 +0200 Subject: [PATCH 4/4] Update backend/src/main/java/com/bakdata/conquery/models/config/QueryConfig.java Co-authored-by: MT <12283268+thoniTUB@users.noreply.github.com> --- .../com/bakdata/conquery/models/config/QueryConfig.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/backend/src/main/java/com/bakdata/conquery/models/config/QueryConfig.java b/backend/src/main/java/com/bakdata/conquery/models/config/QueryConfig.java index 91d6dacfac..9538ecfa3c 100644 --- a/backend/src/main/java/com/bakdata/conquery/models/config/QueryConfig.java +++ b/backend/src/main/java/com/bakdata/conquery/models/config/QueryConfig.java @@ -25,16 +25,10 @@ public class QueryConfig { */ private int secondaryIdSubPlanRetention = 15; - @ValidCaffeineSpec + @ValidCaffeineSpec(softValue=true) private String L2CacheSpec = "softValues"; @ValidCaffeineSpec private String L1CacheSpec = "expireAfterAccess=10m"; - @JsonIgnore - @ValidationMethod(message = "SoftResultCache is required to be soft.") - public boolean isSoftSpecSoft() { - return L2CacheSpec.contains("softValues"); - } - }