All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.druid.client.cache.HybridCache Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.apache.druid.client.cache;

import com.google.common.collect.Sets;
import org.apache.druid.java.util.common.lifecycle.LifecycleStop;
import org.apache.druid.java.util.common.logger.Logger;
import org.apache.druid.java.util.emitter.service.ServiceEmitter;
import org.apache.druid.utils.CloseableUtils;

import javax.annotation.Nullable;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;

public class HybridCache implements Cache
{
  private static final Logger log = new Logger(HybridCache.class);

  private final HybridCacheConfig config;
  private final Cache level1;
  private final Cache level2;

  private final AtomicLong hitCount = new AtomicLong(0);
  private final AtomicLong missCount = new AtomicLong(0);

  public HybridCache(HybridCacheConfig config, Cache level1, Cache level2)
  {
    this.config = config;
    log.info("Config: %s", config);
    this.level1 = level1;
    this.level2 = level2;
  }

  @Nullable
  @Override
  public byte[] get(NamedKey key)
  {
    byte[] res = level1.get(key);
    if (res == null) {
      res = getL2(key);
      if (res != null) {
        level1.put(key, res);
      }
    }
    if (res != null) {
      hitCount.incrementAndGet();
      return res;
    } else {
      missCount.incrementAndGet();
      return null;
    }
  }

  @Nullable
  private byte[] getL2(NamedKey key)
  {
    if (config.getUseL2()) {
      return level2.get(key);
    } else {
      return null;
    }
  }

  @Override
  public void put(NamedKey key, byte[] value)
  {
    level1.put(key, value);
    if (config.getPopulateL2()) {
      level2.put(key, value);
    }
  }

  @Override
  public Map getBulk(Iterable keys)
  {
    Set remaining = Sets.newHashSet(keys);
    Map res = level1.getBulk(keys);
    hitCount.addAndGet(res.size());

    remaining = Sets.difference(remaining, res.keySet());

    if (!remaining.isEmpty()) {
      Map res2 = getBulkL2(remaining);
      for (Map.Entry entry : res2.entrySet()) {
        level1.put(entry.getKey(), entry.getValue());
      }

      int size = res2.size();
      hitCount.addAndGet(size);
      missCount.addAndGet(remaining.size() - size);

      if (size != 0) {
        res = new HashMap<>(res);
        res.putAll(res2);
      }
    }
    return res;
  }

  private Map getBulkL2(Iterable keys)
  {
    if (config.getUseL2()) {
      return level2.getBulk(keys);
    } else {
      return Collections.emptyMap();
    }
  }

  @Override
  public void close(String namespace)
  {
    Throwable t = null;
    try {
      level1.close(namespace);
    }
    catch (Throwable t1) {
      t = t1;
      throw t1;
    }
    finally {
      if (t != null) {
        try {
          level2.close(namespace);
        }
        catch (Throwable t2) {
          t.addSuppressed(t2);
        }
      } else {
        level2.close(namespace);
      }
    }
  }

  @Override
  @LifecycleStop
  public void close() throws IOException
  {
    CloseableUtils.closeAll(level1, level2);
  }

  @Override
  public CacheStats getStats()
  {
    CacheStats stats1 = level1.getStats();
    CacheStats stats2 = level2.getStats();
    return new CacheStats(
        hitCount.get(),
        missCount.get(),
        stats1.getNumEntries() + stats2.getNumEntries(),
        stats1.getSizeInBytes() + stats2.getSizeInBytes(),
        stats1.getNumEvictions() + stats2.getNumEvictions(),
        stats1.getNumTimeouts() + stats2.getNumTimeouts(),
        stats1.getNumErrors() + stats2.getNumErrors()
    );
  }

  @Override
  public boolean isLocal()
  {
    return level1.isLocal() && level2.isLocal();
  }

  @Override
  public void doMonitor(ServiceEmitter emitter)
  {
    level1.doMonitor(emitter);
    level2.doMonitor(emitter);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy