0

I have a cache loader which caches the response( which is in form of a list) from APIs. Now my functions do not need this response data directly, so I made a provider which can process these raw response and act as an oracle which allows me to send the data required instead of whole list.

The issue with my current approach is that for each such response the provider has to iterate over the whole list even if the list was the same as before; this makes the complexity of each call O(n) instead of O(1) I wanted.

Potential solutions I can think of

  1. Cache the processed data instead. Now issue with this is that I would need two different caches in this case, and would be unnecessarily fetching from API same results in both caches per refresh.
  2. Iterate in provider only if cache value has changed I want do this but I can't find a way to know if cache has changed the values, all my ways would need to do a pass on the data which will again take O(n) time.

Can anyone suggest a good way to solve this issue?

Rule Info Cache Loader Class


import com.google.common.cache.CacheLoader;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.annotation.ParametersAreNonnullByDefault;

final class RuleInfoLoader extends CacheLoader<RuleType, List<RuleInfo>> {
  private final ConfigServiceGrpc.ConfigServiceBlockingStub configServiceBlockingStub;
  private final long callTimeout;

  RuleInfoLoader(ConfigServiceClientConfig clientConfig) {
    callTimeout = clientConfig.getCallTimeout();
    configServiceBlockingStub = ConfigServiceGrpc.newBlockingStub();
  }

  @Override
  @ParametersAreNonnullByDefault
  public List<RuleInfo> load(RuleType ruleType) {
    return fetchRuleInfo(ruleType);
  }

  private List<RuleInfo> fetchRuleInfo(RuleType ruleType) {
    return configServiceBlockingStub
        .withDeadlineAfter(callTimeout, TimeUnit.MILLISECONDS)
        .getAnomalyRuleInfos(
            GetRuleInfosRequest.newBuilder().setRuleType(ruleType).build())
        .getRuleInfosList();
  }
}

Rule Info Provider

import com.google.common.cache.CacheLoader;
import com.google.common.collect.ImmutableMap;
import com.typesafe.config.Config;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

public class RuleInfoProvider {
  private LoadingCache<RuleType, List<RuleInfo>> ruleInfoCache;

  public RuleInfoProvider(Config config) {
    initCache(config);
  }

  public Map<String, RuleInfo> getRuleMapping() {
    ImmutableMap.Builder<String, RuleInfo> mapBuilder = new ImmutableMap.Builder<>();
    getRuleInfo(RuleType.a)
        .forEach(rule -> mapBuilder.put(rule.getRuleId(), rule));
    getRuleInfo(RuleType.b)
        .forEach(rule -> mapBuilder.put(rule.getRuleId(), rule));
    getRuleInfo(RuleType.c)
        .forEach(rule -> mapBuilder.put(rule.getRuleId(), rule));
    return mapBuilder.build();
  }

  public Map<Class<? extends RuleInfo>, List<String>> getPathsMapping() {
    return new ImmutableMap.Builder<Class<? extends RuleInfo>, List<String>>()
        .put(
            Class_A.class,
            getRuleInfo(RuleType.a).stream()
                .map(RuleInfo::getPath)
                .collect(Collectors.toList()))
        .put(
            Class_B.class,
            getRuleInfo(RuleType.b).stream()
                .map(RuleInfo::getPath)
                .collect(Collectors.toList()))
        .put(
            Class_c.class,
            getRuleInfo(RuleType.c).stream()
                .map(RuleInfo::getPath)
                .collect(Collectors.toList()))
        .build();
  }

  private List<RuleInfo> getRuleInfo(RuleType ruleType) {
    try {
      return ruleInfoCache.get(ruleType);
    } catch (Exception e) {
      return Collections.emptyList();
    }
  }
  
  private void initCache(Config config) {
    ruleInfoCache =
        CacheBuilder.newBuilder()
            .refreshAfterWrite(config.getCacheConfig().getRefreshMs())
            .maximumSize(config.getCacheConfig().getMaxSize())
            .build(
                CacheLoader.asyncReloading(
                    new RuleInfoLoader(config.getConfigServiceClientConfig()),
                    Executors.newSingleThreadScheduledExecutor()));
  }
}
chrylis -cautiouslyoptimistic-
  • 75,269
  • 21
  • 115
  • 152
  • Note that Guava Cache has been replaced by Caffeine. I'm going to add the tag since the maintainer is active here. – chrylis -cautiouslyoptimistic- Jul 23 '21 at 14:53
  • 1
    It looks like you always fetch everything, rather than a subset of data, for the processed result. The key/value doesn't help if the universe is reloaded each time. It would seem that you need a single, processed result object that is proactively fully refreshed regularly. In that case an AtomicReference and ScheduledExecutor might be the simplest options? The cache seems to only make it confusing, if I understand the current logic correctly. – Ben Manes Jul 23 '21 at 18:00

0 Answers0