org.apache.hadoop.hbase.regionserver.MemcacheFlusher.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.hadoop.hbase.regionserver.MemcacheFlusher.java

Source

/**
 * Copyright 2008 The Apache Software Foundation
 *
 * 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.hadoop.hbase.regionserver;

import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.HashSet;
import java.util.SortedMap;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.hbase.DroppedSnapshotException;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.RemoteExceptionHandler;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.util.StringUtils;

/**
 * Thread that flushes cache on request
 *
 * NOTE: This class extends Thread rather than Chore because the sleep time
 * can be interrupted when there is something to do, rather than the Chore
 * sleep time which is invariant.
 * 
 * @see FlushRequester
 */
class MemcacheFlusher extends Thread implements FlushRequester {
    static final Log LOG = LogFactory.getLog(MemcacheFlusher.class);
    private final BlockingQueue<HRegion> flushQueue = new LinkedBlockingQueue<HRegion>();

    private final HashSet<HRegion> regionsInQueue = new HashSet<HRegion>();

    private final long threadWakeFrequency;
    private final HRegionServer server;
    private final ReentrantLock lock = new ReentrantLock();

    protected final long globalMemcacheLimit;
    protected final long globalMemcacheLimitLowMark;

    public static final float DEFAULT_UPPER = 0.4f;
    public static final float DEFAULT_LOWER = 0.25f;
    public static final String UPPER_KEY = "hbase.regionserver.globalMemcache.upperLimit";
    public static final String LOWER_KEY = "hbase.regionserver.globalMemcache.lowerLimit";
    private long blockingStoreFilesNumber;
    private long blockingWaitTime;

    /**
     * @param conf
     * @param server
     */
    public MemcacheFlusher(final HBaseConfiguration conf, final HRegionServer server) {
        super();
        this.server = server;
        this.threadWakeFrequency = conf.getLong(HConstants.THREAD_WAKE_FREQUENCY, 10 * 1000);
        long max = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getMax();
        this.globalMemcacheLimit = globalMemcacheLimit(max, DEFAULT_UPPER, UPPER_KEY, conf);
        long lower = globalMemcacheLimit(max, DEFAULT_LOWER, LOWER_KEY, conf);
        if (lower > this.globalMemcacheLimit) {
            lower = this.globalMemcacheLimit;
            LOG.info("Setting globalMemcacheLimitLowMark == globalMemcacheLimit " + "because supplied " + LOWER_KEY
                    + " was > " + UPPER_KEY);
        }
        this.globalMemcacheLimitLowMark = lower;
        this.blockingStoreFilesNumber = conf.getInt("hbase.hstore.blockingStoreFiles", -1);
        if (this.blockingStoreFilesNumber == -1) {
            this.blockingStoreFilesNumber = 1 + conf.getInt("hbase.hstore.compactionThreshold", 3);
        }
        this.blockingWaitTime = conf.getInt("hbase.hstore.blockingWaitTime", 90000); // default of 180 seconds
        LOG.info("globalMemcacheLimit=" + StringUtils.humanReadableInt(this.globalMemcacheLimit)
                + ", globalMemcacheLimitLowMark=" + StringUtils.humanReadableInt(this.globalMemcacheLimitLowMark)
                + ", maxHeap=" + StringUtils.humanReadableInt(max));
    }

    /**
     * Calculate size using passed <code>key</code> for configured
     * percentage of <code>max</code>.
     * @param max
     * @param defaultLimit
     * @param key
     * @param c
     * @return Limit.
     */
    static long globalMemcacheLimit(final long max, final float defaultLimit, final String key,
            final HBaseConfiguration c) {
        float limit = c.getFloat(key, defaultLimit);
        return getMemcacheLimit(max, limit, defaultLimit);
    }

    static long getMemcacheLimit(final long max, final float limit, final float defaultLimit) {
        if (limit >= 0.9f || limit < 0.1f) {
            LOG.warn("Setting global memcache limit to default of " + defaultLimit
                    + " because supplied value outside allowed range of 0.1 -> 0.9");
        }
        return (long) (max * limit);
    }

    @Override
    public void run() {
        while (!this.server.isStopRequested() && this.server.isInSafeMode()) {
            try {
                Thread.sleep(threadWakeFrequency);
            } catch (InterruptedException ex) {
                continue;
            }
        }
        while (!server.isStopRequested()) {
            HRegion r = null;
            try {
                r = flushQueue.poll(threadWakeFrequency, TimeUnit.MILLISECONDS);
                if (r == null) {
                    continue;
                }
                if (!flushRegion(r, false)) {
                    break;
                }
            } catch (InterruptedException ex) {
                continue;
            } catch (ConcurrentModificationException ex) {
                continue;
            } catch (Exception ex) {
                LOG.error("Cache flush failed"
                        + (r != null ? (" for region " + Bytes.toString(r.getRegionName())) : ""), ex);
                if (!server.checkFileSystem()) {
                    break;
                }
            }
        }
        regionsInQueue.clear();
        flushQueue.clear();
        LOG.info(getName() + " exiting");
    }

    public void request(HRegion r) {
        synchronized (regionsInQueue) {
            if (!regionsInQueue.contains(r)) {
                regionsInQueue.add(r);
                flushQueue.add(r);
            }
        }
    }

    /**
     * Only interrupt once it's done with a run through the work loop.
     */
    void interruptIfNecessary() {
        lock.lock();
        try {
            this.interrupt();
        } finally {
            lock.unlock();
        }
    }

    /*
     * Flush a region.
     * 
     * @param region the region to be flushed
     * @param removeFromQueue True if the region needs to be removed from the
     * flush queue. False if called from the main flusher run loop and true if
     * called from flushSomeRegions to relieve memory pressure from the region
     * server.  If <code>true</code>, we are in a state of emergency; we are not
     * taking on updates regionserver-wide, not until memory is flushed.  In this
     * case, do not let a compaction run inline with blocked updates. Compactions
     * can take a long time. Stopping compactions, there is a danger that number
     * of flushes will overwhelm compaction on a busy server; we'll have to see.
     * That compactions do not run when called out of flushSomeRegions means that
     * compactions can be reported by the historian without danger of deadlock
     * (HBASE-670).
     * 
     * <p>In the main run loop, regions have already been removed from the flush
     * queue, and if this method is called for the relief of memory pressure,
     * this may not be necessarily true. We want to avoid trying to remove 
     * region from the queue because if it has already been removed, it requires a
     * sequential scan of the queue to determine that it is not in the queue.
     * 
     * <p>If called from flushSomeRegions, the region may be in the queue but
     * it may have been determined that the region had a significant amount of 
     * memory in use and needed to be flushed to relieve memory pressure. In this
     * case, its flush may preempt the pending request in the queue, and if so,
     * it needs to be removed from the queue to avoid flushing the region
     * multiple times.
     * 
     * @return true if the region was successfully flushed, false otherwise. If 
     * false, there will be accompanying log messages explaining why the log was
     * not flushed.
     */
    private boolean flushRegion(HRegion region, boolean removeFromQueue) {
        // Wait until it is safe to flush
        int count = 0;
        boolean triggered = false;
        while (count++ < (blockingWaitTime / 500)) {
            for (Store hstore : region.stores.values()) {
                if (hstore.getStorefilesCount() > this.blockingStoreFilesNumber) {
                    if (!triggered) {
                        server.compactSplitThread.compactionRequested(region, getName());
                        LOG.info("Too many store files for region " + region + ": " + hstore.getStorefilesCount()
                                + ", waiting");
                        triggered = true;
                    }
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        // ignore
                    }
                    continue;
                }
            }
            if (triggered) {
                LOG.info("Compaction completed on region " + region + ", proceeding");
            }
            break;
        }
        synchronized (regionsInQueue) {
            // See comment above for removeFromQueue on why we do not
            // take the region out of the set. If removeFromQueue is true, remove it
            // from the queue too if it is there. This didn't used to be a
            // constraint, but now that HBASE-512 is in play, we need to try and
            // limit double-flushing of regions.
            if (regionsInQueue.remove(region) && removeFromQueue) {
                flushQueue.remove(region);
            }
            lock.lock();
        }
        try {
            // See comment above for removeFromQueue on why we do not
            // compact if removeFromQueue is true. Note that region.flushCache()
            // only returns true if a flush is done and if a compaction is needed.
            if (region.flushcache() && !removeFromQueue) {
                server.compactSplitThread.compactionRequested(region, getName());
            }
        } catch (DroppedSnapshotException ex) {
            // Cache flush can fail in a few places. If it fails in a critical
            // section, we get a DroppedSnapshotException and a replay of hlog
            // is required. Currently the only way to do this is a restart of
            // the server. Abort because hdfs is probably bad (HBASE-644 is a case
            // where hdfs was bad but passed the hdfs check).
            LOG.fatal("Replay of hlog required. Forcing server shutdown", ex);
            server.abort();
            return false;
        } catch (IOException ex) {
            LOG.error(
                    "Cache flush failed"
                            + (region != null ? (" for region " + Bytes.toString(region.getRegionName())) : ""),
                    RemoteExceptionHandler.checkIOException(ex));
            if (!server.checkFileSystem()) {
                return false;
            }
        } finally {
            lock.unlock();
        }

        return true;
    }

    /**
     * Check if the regionserver's memcache memory usage is greater than the 
     * limit. If so, flush regions with the biggest memcaches until we're down
     * to the lower limit. This method blocks callers until we're down to a safe
     * amount of memcache consumption.
     */
    public synchronized void reclaimMemcacheMemory() {
        if (server.getGlobalMemcacheSize() >= globalMemcacheLimit) {
            flushSomeRegions();
        }
    }

    /*
     * Emergency!  Need to flush memory.
     */
    private synchronized void flushSomeRegions() {
        // keep flushing until we hit the low water mark
        long globalMemcacheSize = -1;
        ArrayList<HRegion> regionsToCompact = new ArrayList<HRegion>();
        for (SortedMap<Long, HRegion> m = this.server
                .getCopyOfOnlineRegionsSortedBySize(); (globalMemcacheSize = server
                        .getGlobalMemcacheSize()) >= this.globalMemcacheLimitLowMark;) {
            // flush the region with the biggest memcache
            if (m.size() <= 0) {
                LOG.info("No online regions to flush though we've been asked flush " + "some; globalMemcacheSize="
                        + StringUtils.humanReadableInt(globalMemcacheSize) + ", globalMemcacheLimitLowMark="
                        + StringUtils.humanReadableInt(this.globalMemcacheLimitLowMark));
                break;
            }
            HRegion biggestMemcacheRegion = m.remove(m.firstKey());
            LOG.info("Forced flushing of " + biggestMemcacheRegion.toString() + " because global memcache limit of "
                    + StringUtils.humanReadableInt(this.globalMemcacheLimit) + " exceeded; currently "
                    + StringUtils.humanReadableInt(globalMemcacheSize) + " and flushing till "
                    + StringUtils.humanReadableInt(this.globalMemcacheLimitLowMark));
            if (!flushRegion(biggestMemcacheRegion, true)) {
                LOG.warn("Flush failed");
                break;
            }
            regionsToCompact.add(biggestMemcacheRegion);
        }
        for (HRegion region : regionsToCompact) {
            server.compactSplitThread.compactionRequested(region, getName());
        }
    }
}