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

org.apache.phoenix.cache.TenantCacheImpl 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.phoenix.cache;

import java.io.Closeable;
import java.io.IOException;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Collections;
import java.util.concurrent.TimeUnit;

import org.apache.commons.codec.binary.Hex;
import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.phoenix.coprocessor.ServerCachingProtocol.ServerCacheFactory;
import org.apache.phoenix.hbase.index.util.ImmutableBytesPtr;
import org.apache.phoenix.memory.MemoryManager;
import org.apache.phoenix.memory.MemoryManager.MemoryChunk;
import org.apache.phoenix.util.Closeables;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Ticker;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;

/**
 * 
 * Cache per tenant on server side.  Tracks memory usage for each
 * tenat as well and rolling up usage to global memory manager.
 * 
 * 
 * @since 0.1
 */
public class TenantCacheImpl implements TenantCache {
    private static final Logger LOGGER = LoggerFactory.getLogger(TenantCacheImpl.class);
    private final int maxTimeToLiveMs;
    private final int maxPersistenceTimeToLiveMs;
    private final MemoryManager memoryManager;
    private final Ticker ticker;

    // Two caches exist: the "serverCaches" cache which is used for handling live
    // queries, and the "persistentServerCaches" cache which is used to store data
    // between queries. If we are out of memory, attempt to clear out entries from
    // the persistent cache before throwing an exception.
    private volatile Cache serverCaches;
    private volatile Cache persistentServerCaches;

    private final long EVICTION_MARGIN_BYTES = 10000000;

    private static class CacheEntry implements Comparable, Closeable {
        private ImmutableBytesPtr cacheId;
        private ImmutableBytesWritable cachePtr;
        private int hits;
        private int liveQueriesCount;
        private boolean usePersistentCache;
        private long size;
        private Closeable closeable;

        public CacheEntry(ImmutableBytesPtr cacheId, ImmutableBytesWritable cachePtr, 
                ServerCacheFactory cacheFactory, byte[] txState, MemoryChunk chunk,
                boolean usePersistentCache, boolean useProtoForIndexMaintainer,
                int clientVersion) throws SQLException {
            this.cacheId = cacheId;
            this.cachePtr = cachePtr;
            this.size = cachePtr.getLength();
            this.hits = 0;
            this.liveQueriesCount = 0;
            this.usePersistentCache = usePersistentCache;
            this.closeable = cacheFactory.newCache(cachePtr, txState, chunk, useProtoForIndexMaintainer, clientVersion);
        }

        public void close() throws IOException {
            this.closeable.close();
        }

        synchronized public void incrementLiveQueryCount() {
            liveQueriesCount++;
            hits++;
        }

        synchronized public void decrementLiveQueryCount() {
            liveQueriesCount--;
        }

        synchronized public boolean isLive() {
            return liveQueriesCount > 0;
        }

        public boolean getUsePersistentCache() {
            return usePersistentCache;
        }

        public ImmutableBytesPtr getCacheId() {
            return cacheId;
        }

        private Float rank() {
            return (float)hits;
        }

        @Override
        public int compareTo(CacheEntry o) {
            return rank().compareTo(o.rank());
        }
    }

    public TenantCacheImpl(MemoryManager memoryManager, int maxTimeToLiveMs, int maxPersistenceTimeToLiveMs) {
        this(memoryManager, maxTimeToLiveMs, maxPersistenceTimeToLiveMs, Ticker.systemTicker());
    }
    
    public TenantCacheImpl(MemoryManager memoryManager, int maxTimeToLiveMs, int maxPersistenceTimeToLiveMs, Ticker ticker) {
        this.memoryManager = memoryManager;
        this.maxTimeToLiveMs = maxTimeToLiveMs;
        this.maxPersistenceTimeToLiveMs = maxPersistenceTimeToLiveMs;
        this.ticker = ticker;
    }
    
    public Ticker getTicker() {
        return ticker;
    }
    
    // For testing
    public void cleanUp() {
        synchronized(this) {
            if (serverCaches != null) {
                serverCaches.cleanUp();
            }
            if (persistentServerCaches != null) {
                persistentServerCaches.cleanUp();
            }
        }
    }
    
    @Override
    public MemoryManager getMemoryManager() {
        return memoryManager;
    }

    private Cache getServerCaches() {
        /* Delay creation of this map until it's needed */
        if (serverCaches == null) {
            synchronized(this) {
                if (serverCaches == null) {
                    serverCaches = buildCache(maxTimeToLiveMs, false);
                }
            }
        }
        return serverCaches;
    }

    private Cache getPersistentServerCaches() {
        /* Delay creation of this map until it's needed */
        if (persistentServerCaches == null) {
            synchronized(this) {
                if (persistentServerCaches == null) {
                    persistentServerCaches = buildCache(maxPersistenceTimeToLiveMs, true);
                }
            }
        }
        return persistentServerCaches;
    }

    private Cache buildCache(final int ttl, final boolean isPersistent) {
        CacheBuilder builder = CacheBuilder.newBuilder();
        if (isPersistent) {
            builder.expireAfterWrite(ttl, TimeUnit.MILLISECONDS);
        } else {
            builder.expireAfterAccess(ttl, TimeUnit.MILLISECONDS);
        }
        return builder
            .ticker(getTicker())
            .removalListener(new RemovalListener(){
                @Override
                public void onRemoval(RemovalNotification notification) {
                    if (isPersistent || !notification.getValue().getUsePersistentCache()) {
                        Closeables.closeAllQuietly(Collections.singletonList(notification.getValue()));
                    }
                }
            })
            .build();
    }

    synchronized private void evictInactiveEntries(long bytesNeeded) {
        LOGGER.debug("Trying to evict inactive cache entries to free up " + bytesNeeded + " bytes");
        CacheEntry[] entries = getPersistentServerCaches().asMap().values().toArray(new CacheEntry[]{});
        Arrays.sort(entries);
        long available = this.getMemoryManager().getAvailableMemory();
        for (int i = 0; i < entries.length && available < bytesNeeded; i++) {
            CacheEntry entry = entries[i];
            ImmutableBytesPtr cacheId = entry.getCacheId();
            getPersistentServerCaches().invalidate(cacheId);
            available = this.getMemoryManager().getAvailableMemory();
            LOGGER.debug("Evicted cache ID " + Bytes.toLong(cacheId.get()) + ", we now have "
                    + available + " bytes available");
        }
    }

    private CacheEntry getIfPresent(ImmutableBytesPtr cacheId) {
        CacheEntry entry = getPersistentServerCaches().getIfPresent(cacheId);
        if (entry != null) {
            return entry;
        }
        return getServerCaches().getIfPresent(cacheId);
    }

	@Override
    public Closeable getServerCache(ImmutableBytesPtr cacheId) {
        getServerCaches().cleanUp();
        CacheEntry entry = getIfPresent(cacheId);
        if (entry == null) {
            return null;
        }
        return entry.closeable;
    }

    @Override
    public Closeable addServerCache(ImmutableBytesPtr cacheId, ImmutableBytesWritable cachePtr, byte[] txState, ServerCacheFactory cacheFactory, boolean useProtoForIndexMaintainer, boolean usePersistentCache, int clientVersion) throws SQLException {
        getServerCaches().cleanUp();
        long available = this.getMemoryManager().getAvailableMemory();
        int size = cachePtr.getLength() + txState.length;
        if (size > available) {
            evictInactiveEntries(size - available + EVICTION_MARGIN_BYTES);
        }
        MemoryChunk chunk = this.getMemoryManager().allocate(size);
        boolean success = false;
        try {
            CacheEntry entry;
            synchronized(this) {
                entry = getIfPresent(cacheId);
                if (entry == null) {
                    entry = new CacheEntry(
                        cacheId, cachePtr, cacheFactory, txState, chunk,
                        usePersistentCache, useProtoForIndexMaintainer,
                        clientVersion);
                    getServerCaches().put(cacheId, entry);
                    if (usePersistentCache) {
                        getPersistentServerCaches().put(cacheId, entry);
                    }
                }
                entry.incrementLiveQueryCount();
            }
            success = true;
            return entry;
        } finally {
            if (!success) {
                Closeables.closeAllQuietly(Collections.singletonList(chunk));
            }
        }
    }

    @Override
    synchronized public void removeServerCache(ImmutableBytesPtr cacheId) {
        CacheEntry entry = getServerCaches().getIfPresent(cacheId);
        if (entry == null) {
            return;
        }
        entry.decrementLiveQueryCount();
        if (!entry.isLive()) {
            LOGGER.debug("Cache ID " + Bytes.toLong(cacheId.get())
                    + " is no longer live, invalidate it");
            getServerCaches().invalidate(cacheId);
        }
    }

    @Override
    public void removeAllServerCache() {
        getServerCaches().invalidateAll();
        getPersistentServerCaches().invalidateAll();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy