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

org.apache.bookkeeper.bookie.GarbageCollectorThread Maven / Gradle / Ivy

There is a newer version: 4.17.1
Show 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.bookkeeper.bookie;

import static org.apache.bookkeeper.util.BookKeeperConstants.METADATA_CACHE;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import io.netty.util.concurrent.DefaultThreadFactory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import lombok.Getter;
import org.apache.bookkeeper.bookie.BookieException.EntryLogMetadataMapException;
import org.apache.bookkeeper.bookie.GarbageCollector.GarbageCleaner;
import org.apache.bookkeeper.bookie.stats.GarbageCollectorStats;
import org.apache.bookkeeper.bookie.storage.EntryLogger;
import org.apache.bookkeeper.bookie.storage.ldb.PersistentEntryLogMetadataMap;
import org.apache.bookkeeper.conf.ServerConfiguration;
import org.apache.bookkeeper.meta.LedgerManager;
import org.apache.bookkeeper.stats.StatsLogger;
import org.apache.bookkeeper.util.MathUtils;
import org.apache.commons.lang3.mutable.MutableBoolean;
import org.apache.commons.lang3.mutable.MutableLong;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This is the garbage collector thread that runs in the background to
 * remove any entry log files that no longer contains any active ledger.
 */
public class GarbageCollectorThread implements Runnable {
    private static final Logger LOG = LoggerFactory.getLogger(GarbageCollectorThread.class);
    private static final int SECOND = 1000;
    private static final long MINUTE = TimeUnit.MINUTES.toMillis(1);

    // Maps entry log files to the set of ledgers that comprise the file and the size usage per ledger
    private final EntryLogMetadataMap entryLogMetaMap;

    private final ScheduledExecutorService gcExecutor;
    Future scheduledFuture = null;

    // This is the fixed delay in milliseconds before running the Garbage Collector Thread again.
    final long gcWaitTime;

    // Compaction parameters
    boolean isForceMinorCompactionAllow = false;
    boolean enableMinorCompaction = false;
    final double minorCompactionThreshold;
    final long minorCompactionInterval;
    final long minorCompactionMaxTimeMillis;
    long lastMinorCompactionTime;

    boolean isForceMajorCompactionAllow = false;
    boolean enableMajorCompaction = false;
    final double majorCompactionThreshold;
    final long majorCompactionInterval;
    long majorCompactionMaxTimeMillis;
    long lastMajorCompactionTime;

    @Getter
    final boolean isForceGCAllowWhenNoSpace;

    // Entry Logger Handle
    final EntryLogger entryLogger;
    final AbstractLogCompactor compactor;

    // Stats loggers for garbage collection operations
    private final GarbageCollectorStats gcStats;

    private volatile long totalEntryLogSize;
    private volatile int numActiveEntryLogs;

    final CompactableLedgerStorage ledgerStorage;

    // flag to ensure gc thread will not be interrupted during compaction
    // to reduce the risk getting entry log corrupted
    final AtomicBoolean compacting = new AtomicBoolean(false);

    // use to get the compacting status
    final AtomicBoolean minorCompacting = new AtomicBoolean(false);
    final AtomicBoolean majorCompacting = new AtomicBoolean(false);

    volatile boolean running = true;

    // Boolean to trigger a forced GC.
    final AtomicBoolean forceGarbageCollection = new AtomicBoolean(false);
    // Boolean to disable major compaction, when disk is almost full
    final AtomicBoolean suspendMajorCompaction = new AtomicBoolean(false);
    // Boolean to disable minor compaction, when disk is full
    final AtomicBoolean suspendMinorCompaction = new AtomicBoolean(false);

    final ScanAndCompareGarbageCollector garbageCollector;
    final GarbageCleaner garbageCleaner;

    final ServerConfiguration conf;
    final LedgerDirsManager ledgerDirsManager;

    private static final AtomicLong threadNum = new AtomicLong(0);
    final AbstractLogCompactor.Throttler throttler;

    /**
     * Create a garbage collector thread.
     *
     * @param conf
     *          Server Configuration Object.
     * @throws IOException
     */
    public GarbageCollectorThread(ServerConfiguration conf, LedgerManager ledgerManager,
                                  final LedgerDirsManager ledgerDirsManager,
                                  final CompactableLedgerStorage ledgerStorage,
                                  EntryLogger entryLogger,
                                  StatsLogger statsLogger) throws IOException {
        this(conf, ledgerManager, ledgerDirsManager, ledgerStorage, entryLogger, statsLogger,
                Executors.newSingleThreadScheduledExecutor(new DefaultThreadFactory("GarbageCollectorThread")));
    }

    /**
     * Create a garbage collector thread.
     *
     * @param conf
     *          Server Configuration Object.
     * @throws IOException
     */
    public GarbageCollectorThread(ServerConfiguration conf,
                                  LedgerManager ledgerManager,
                                  final LedgerDirsManager ledgerDirsManager,
                                  final CompactableLedgerStorage ledgerStorage,
                                  EntryLogger entryLogger,
                                  StatsLogger statsLogger,
                                  ScheduledExecutorService gcExecutor)
        throws IOException {
        this.gcExecutor = gcExecutor;
        this.conf = conf;

        this.ledgerDirsManager = ledgerDirsManager;
        this.entryLogger = entryLogger;
        this.entryLogMetaMap = createEntryLogMetadataMap();
        this.ledgerStorage = ledgerStorage;
        this.gcWaitTime = conf.getGcWaitTime();

        this.numActiveEntryLogs = 0;
        this.totalEntryLogSize = 0L;
        this.garbageCollector = new ScanAndCompareGarbageCollector(ledgerManager, ledgerStorage, conf, statsLogger);
        this.gcStats = new GarbageCollectorStats(
            statsLogger,
            () -> numActiveEntryLogs,
            () -> totalEntryLogSize,
            () -> garbageCollector.getNumActiveLedgers()
        );

        this.garbageCleaner = ledgerId -> {
            try {
                if (LOG.isDebugEnabled()) {
                    LOG.debug("delete ledger : " + ledgerId);
                }
                gcStats.getDeletedLedgerCounter().inc();
                ledgerStorage.deleteLedger(ledgerId);
            } catch (IOException e) {
                LOG.error("Exception when deleting the ledger index file on the Bookie: ", e);
            }
        };

        // compaction parameters
        minorCompactionThreshold = conf.getMinorCompactionThreshold();
        minorCompactionInterval = conf.getMinorCompactionInterval() * SECOND;
        majorCompactionThreshold = conf.getMajorCompactionThreshold();
        majorCompactionInterval = conf.getMajorCompactionInterval() * SECOND;
        isForceGCAllowWhenNoSpace = conf.getIsForceGCAllowWhenNoSpace();
        majorCompactionMaxTimeMillis = conf.getMajorCompactionMaxTimeMillis();
        minorCompactionMaxTimeMillis = conf.getMinorCompactionMaxTimeMillis();

        boolean isForceAllowCompaction = conf.isForceAllowCompaction();

        AbstractLogCompactor.LogRemovalListener remover = new AbstractLogCompactor.LogRemovalListener() {
            @Override
            public void removeEntryLog(long logToRemove) {
                try {
                    GarbageCollectorThread.this.removeEntryLog(logToRemove);
                } catch (EntryLogMetadataMapException e) {
                    // Ignore and continue because ledger will not be cleaned up
                    // from entry-logger in this pass and will be taken care in
                    // next schedule task
                    LOG.warn("Failed to remove entry-log metadata {}", logToRemove, e);
                }
            }
        };
        if (conf.getUseTransactionalCompaction()) {
            this.compactor = new TransactionalEntryLogCompactor(conf, entryLogger, ledgerStorage, remover);
        } else {
            this.compactor = new EntryLogCompactor(conf, entryLogger, ledgerStorage, remover);
        }

        this.throttler = new AbstractLogCompactor.Throttler(conf);
        if (minorCompactionInterval > 0 && minorCompactionThreshold > 0) {
            if (minorCompactionThreshold > 1.0d) {
                throw new IOException("Invalid minor compaction threshold "
                                    + minorCompactionThreshold);
            }
            if (minorCompactionInterval < gcWaitTime) {
                throw new IOException("Too short minor compaction interval : "
                                    + minorCompactionInterval);
            }
            enableMinorCompaction = true;
        }

        if (isForceAllowCompaction) {
            if (minorCompactionThreshold > 0 && minorCompactionThreshold < 1.0d) {
                isForceMinorCompactionAllow = true;
            }
            if (majorCompactionThreshold > 0 && majorCompactionThreshold < 1.0d) {
                isForceMajorCompactionAllow = true;
            }
        }

        if (majorCompactionInterval > 0 && majorCompactionThreshold > 0) {
            if (majorCompactionThreshold > 1.0d) {
                throw new IOException("Invalid major compaction threshold "
                                    + majorCompactionThreshold);
            }
            if (majorCompactionInterval < gcWaitTime) {
                throw new IOException("Too short major compaction interval : "
                                    + majorCompactionInterval);
            }
            enableMajorCompaction = true;
        }

        if (enableMinorCompaction && enableMajorCompaction) {
            if (minorCompactionInterval >= majorCompactionInterval
                || minorCompactionThreshold >= majorCompactionThreshold) {
                throw new IOException("Invalid minor/major compaction settings : minor ("
                                    + minorCompactionThreshold + ", " + minorCompactionInterval
                                    + "), major (" + majorCompactionThreshold + ", "
                                    + majorCompactionInterval + ")");
            }
        }

        LOG.info("Minor Compaction : enabled=" + enableMinorCompaction + ", threshold="
               + minorCompactionThreshold + ", interval=" + minorCompactionInterval);
        LOG.info("Major Compaction : enabled=" + enableMajorCompaction + ", threshold="
               + majorCompactionThreshold + ", interval=" + majorCompactionInterval);

        lastMinorCompactionTime = lastMajorCompactionTime = System.currentTimeMillis();
    }

    private EntryLogMetadataMap createEntryLogMetadataMap() throws IOException {
        if (conf.isGcEntryLogMetadataCacheEnabled()) {
            String baseDir = Strings.isNullOrEmpty(conf.getGcEntryLogMetadataCachePath())
                ? this.ledgerDirsManager.getAllLedgerDirs().get(0).getPath() : conf.getGcEntryLogMetadataCachePath();
            try {
                return new PersistentEntryLogMetadataMap(baseDir, conf);
            } catch (IOException e) {
                LOG.error("Failed to initialize persistent-metadata-map , clean up {}",
                    baseDir + "/" + METADATA_CACHE, e);
                throw e;
            }
        } else {
            return new InMemoryEntryLogMetadataMap();
        }
    }

    public void enableForceGC() {
        if (forceGarbageCollection.compareAndSet(false, true)) {
            LOG.info("Forced garbage collection triggered by thread: {}", Thread.currentThread().getName());
            triggerGC(true, suspendMajorCompaction.get(),
                      suspendMinorCompaction.get());
        }
    }

    public void enableForceGC(boolean forceMajor, boolean forceMinor) {
        if (forceGarbageCollection.compareAndSet(false, true)) {
            LOG.info("Forced garbage collection triggered by thread: {}, forceMajor: {}, forceMinor: {}",
                Thread.currentThread().getName(), forceMajor, forceMinor);
            triggerGC(true, !forceMajor, !forceMinor);
        }
    }

    public void disableForceGC() {
        if (forceGarbageCollection.compareAndSet(true, false)) {
            LOG.info("{} disabled force garbage collection since bookie has enough space now.", Thread
                    .currentThread().getName());
        }
    }

    Future triggerGC(final boolean force,
                        final boolean suspendMajor,
                        final boolean suspendMinor) {
        return gcExecutor.submit(() -> {
                runWithFlags(force, suspendMajor, suspendMinor);
            });
    }

    Future triggerGC() {
        final boolean force = forceGarbageCollection.get();
        final boolean suspendMajor = suspendMajorCompaction.get();
        final boolean suspendMinor = suspendMinorCompaction.get();

        return gcExecutor.submit(() -> {
                runWithFlags(force, suspendMajor, suspendMinor);
            });
    }

    public boolean isInForceGC() {
        return forceGarbageCollection.get();
    }

    public boolean isMajorGcSuspend() {
        return suspendMajorCompaction.get();
    }

    public boolean isMinorGcSuspend() {
        return suspendMinorCompaction.get();
    }

    public void suspendMajorGC() {
        if (suspendMajorCompaction.compareAndSet(false, true)) {
            LOG.info("Suspend Major Compaction triggered by thread: {}", Thread.currentThread().getName());
        }
    }

    public void resumeMajorGC() {
        if (suspendMajorCompaction.compareAndSet(true, false)) {
            LOG.info("{} Major Compaction back to normal since bookie has enough space now.",
                    Thread.currentThread().getName());
        }
    }

    public void suspendMinorGC() {
        if (suspendMinorCompaction.compareAndSet(false, true)) {
            LOG.info("Suspend Minor Compaction triggered by thread: {}", Thread.currentThread().getName());
        }
    }

    public void resumeMinorGC() {
        if (suspendMinorCompaction.compareAndSet(true, false)) {
            LOG.info("{} Minor Compaction back to normal since bookie has enough space now.",
                    Thread.currentThread().getName());
        }
    }

    public void start() {
        if (scheduledFuture != null) {
            scheduledFuture.cancel(false);
        }
        long initialDelay = getModInitialDelay();
        scheduledFuture = gcExecutor.scheduleWithFixedDelay(this, initialDelay, gcWaitTime, TimeUnit.MILLISECONDS);
    }

    /**
     * when number of ledger's Dir are more than 1,the same of GarbageCollectorThread will do the same thing,
     * Especially
     * 1) deleting ledger, then SyncThread will be timed to do rocksDB compact
     * 2) compact: entry, cost cpu.
     * then get Mod initial Delay time to simply avoid GarbageCollectorThread working at the same time
     */
    public long getModInitialDelay() {
        int ledgerDirsNum = conf.getLedgerDirs().length;
        long splitTime = gcWaitTime / ledgerDirsNum;
        long currentThreadNum = threadNum.incrementAndGet();
        return gcWaitTime + currentThreadNum * splitTime;
    }

    @Override
    public void run() {
        boolean force = forceGarbageCollection.get();
        boolean suspendMajor = suspendMajorCompaction.get();
        boolean suspendMinor = suspendMinorCompaction.get();

        runWithFlags(force, suspendMajor, suspendMinor);

        if (force) {
            // only set force to false if it had been true when the garbage
            // collection cycle started
            forceGarbageCollection.set(false);
        }
    }

    public void runWithFlags(boolean force, boolean suspendMajor, boolean suspendMinor) {
        long threadStart = MathUtils.nowInNano();
        if (force) {
            LOG.info("Garbage collector thread forced to perform GC before expiry of wait time.");
        }
        // Recover and clean up previous state if using transactional compaction
        compactor.cleanUpAndRecover();

        try {
            // gc inactive/deleted ledgers
            // this is used in extractMetaFromEntryLogs to calculate the usage of entry log
            doGcLedgers();

            // Extract all of the ledger ID's that comprise all of the entry logs
            // (except for the current new one which is still being written to).
            extractMetaFromEntryLogs();

            // gc entry logs
            doGcEntryLogs();

            if (suspendMajor) {
                LOG.info("Disk almost full, suspend major compaction to slow down filling disk.");
            }
            if (suspendMinor) {
                LOG.info("Disk full, suspend minor compaction to slow down filling disk.");
            }

            long curTime = System.currentTimeMillis();
            if (((isForceMajorCompactionAllow && force) || (enableMajorCompaction
                    && (force || curTime - lastMajorCompactionTime > majorCompactionInterval)))
                    && (!suspendMajor)) {
                // enter major compaction
                LOG.info("Enter major compaction, suspendMajor {}", suspendMajor);
                majorCompacting.set(true);
                try {
                    doCompactEntryLogs(majorCompactionThreshold, majorCompactionMaxTimeMillis);
                } finally {
                    lastMajorCompactionTime = System.currentTimeMillis();
                    // and also move minor compaction time
                    lastMinorCompactionTime = lastMajorCompactionTime;
                    gcStats.getMajorCompactionCounter().inc();
                    majorCompacting.set(false);
                }
            } else if (((isForceMinorCompactionAllow && force) || (enableMinorCompaction
                    && (force || curTime - lastMinorCompactionTime > minorCompactionInterval)))
                    && (!suspendMinor)) {
                // enter minor compaction
                LOG.info("Enter minor compaction, suspendMinor {}", suspendMinor);
                minorCompacting.set(true);
                try {
                    doCompactEntryLogs(minorCompactionThreshold, minorCompactionMaxTimeMillis);
                } finally {
                    lastMinorCompactionTime = System.currentTimeMillis();
                    gcStats.getMinorCompactionCounter().inc();
                    minorCompacting.set(false);
                }
            }
            gcStats.getGcThreadRuntime().registerSuccessfulEvent(
                    MathUtils.nowInNano() - threadStart, TimeUnit.NANOSECONDS);
        } catch (EntryLogMetadataMapException e) {
            LOG.error("Error in entryLog-metadatamap, Failed to complete GC/Compaction due to entry-log {}",
                    e.getMessage(), e);
            gcStats.getGcThreadRuntime().registerFailedEvent(
                    MathUtils.nowInNano() - threadStart, TimeUnit.NANOSECONDS);
        } finally {
            if (force && forceGarbageCollection.compareAndSet(true, false)) {
                LOG.info("{} Set forceGarbageCollection to false after force GC to make it forceGC-able again.",
                        Thread.currentThread().getName());
            }
        }

    }

    /**
     * Do garbage collection ledger index files.
     */
    private void doGcLedgers() {
        garbageCollector.gc(garbageCleaner);
    }

    /**
     * Garbage collect those entry loggers which are not associated with any active ledgers.
     */
    private void doGcEntryLogs() throws EntryLogMetadataMapException {
        // Get a cumulative count, don't update until complete
        AtomicLong totalEntryLogSizeAcc = new AtomicLong(0L);

        // Loop through all of the entry logs and remove the non-active ledgers.
        entryLogMetaMap.forEach((entryLogId, meta) -> {
            try {
                boolean modified = removeIfLedgerNotExists(meta);
                if (meta.isEmpty()) {
                    // This means the entry log is not associated with any active
                    // ledgers anymore.
                    // We can remove this entry log file now.
                    LOG.info("Deleting entryLogId {} as it has no active ledgers!", entryLogId);
                    removeEntryLog(entryLogId);
                    gcStats.getReclaimedSpaceViaDeletes().addCount(meta.getTotalSize());
                } else if (modified) {
                    // update entryLogMetaMap only when the meta modified.
                    entryLogMetaMap.put(meta.getEntryLogId(), meta);
                }
            } catch (EntryLogMetadataMapException e) {
                // Ignore and continue because ledger will not be cleaned up
                // from entry-logger in this pass and will be taken care in next
                // schedule task
                LOG.warn("Failed to remove ledger from entry-log metadata {}", entryLogId, e);
            }
           totalEntryLogSizeAcc.getAndAdd(meta.getRemainingSize());
        });

        this.totalEntryLogSize = totalEntryLogSizeAcc.get();
        this.numActiveEntryLogs = entryLogMetaMap.size();
    }

    private boolean removeIfLedgerNotExists(EntryLogMetadata meta) throws EntryLogMetadataMapException {
        MutableBoolean modified = new MutableBoolean(false);
        meta.removeLedgerIf((entryLogLedger) -> {
            // Remove the entry log ledger from the set if it isn't active.
            try {
                boolean exist = ledgerStorage.ledgerExists(entryLogLedger);
                if (!exist) {
                    modified.setTrue();
                }
                return !exist;
            } catch (IOException e) {
                LOG.error("Error reading from ledger storage", e);
                return false;
            }
        });

        return modified.getValue();
    }

    /**
     * Compact entry logs if necessary.
     *
     * 

* Compaction will be executed from low unused space to high unused space. * Those entry log files whose remaining size percentage is higher than threshold * would not be compacted. *

*/ @VisibleForTesting void doCompactEntryLogs(double threshold, long maxTimeMillis) throws EntryLogMetadataMapException { LOG.info("Do compaction to compact those files lower than {}", threshold); final int numBuckets = 10; int[] entryLogUsageBuckets = new int[numBuckets]; int[] compactedBuckets = new int[numBuckets]; ArrayList> compactableBuckets = new ArrayList<>(numBuckets); for (int i = 0; i < numBuckets; i++) { compactableBuckets.add(new LinkedList<>()); } long start = System.currentTimeMillis(); MutableLong end = new MutableLong(start); MutableLong timeDiff = new MutableLong(0); entryLogMetaMap.forEach((entryLogId, meta) -> { double usage = meta.getUsage(); if (conf.isUseTargetEntryLogSizeForGc() && usage < 1.0d) { usage = (double) meta.getRemainingSize() / Math.max(meta.getTotalSize(), conf.getEntryLogSizeLimit()); } int bucketIndex = calculateUsageIndex(numBuckets, usage); entryLogUsageBuckets[bucketIndex]++; if (timeDiff.getValue() < maxTimeMillis) { end.setValue(System.currentTimeMillis()); timeDiff.setValue(end.getValue() - start); } if ((usage >= threshold || (maxTimeMillis > 0 && timeDiff.getValue() >= maxTimeMillis) || !running)) { // We allow the usage limit calculation to continue so that we get an accurate // report of where the usage was prior to running compaction. return; } compactableBuckets.get(bucketIndex).add(meta.getEntryLogId()); }); LOG.info( "Compaction: entry log usage buckets before compaction [10% 20% 30% 40% 50% 60% 70% 80% 90% 100%] = {}", entryLogUsageBuckets); final int maxBucket = calculateUsageIndex(numBuckets, threshold); int totalEntryLogIds = 0; for (int currBucket = 0; currBucket <= maxBucket; currBucket++) { totalEntryLogIds += compactableBuckets.get(currBucket).size(); } long lastPrintTimestamp = 0; AtomicInteger processedEntryLogCnt = new AtomicInteger(0); stopCompaction: for (int currBucket = 0; currBucket <= maxBucket; currBucket++) { LinkedList entryLogIds = compactableBuckets.get(currBucket); while (!entryLogIds.isEmpty()) { if (timeDiff.getValue() < maxTimeMillis) { end.setValue(System.currentTimeMillis()); timeDiff.setValue(end.getValue() - start); } if ((maxTimeMillis > 0 && timeDiff.getValue() >= maxTimeMillis) || !running) { // We allow the usage limit calculation to continue so that we get an accurate // report of where the usage was prior to running compaction. break stopCompaction; } final int bucketIndex = currBucket; final long logId = entryLogIds.remove(); if (System.currentTimeMillis() - lastPrintTimestamp >= MINUTE) { lastPrintTimestamp = System.currentTimeMillis(); LOG.info("Compaction progress {} / {}, current compaction entryLogId: {}", processedEntryLogCnt.get(), totalEntryLogIds, logId); } entryLogMetaMap.forKey(logId, (entryLogId, meta) -> { if (meta == null) { if (LOG.isDebugEnabled()) { LOG.debug("Metadata for entry log {} already deleted", logId); } return; } if (LOG.isDebugEnabled()) { LOG.debug("Compacting entry log {} with usage {} below threshold {}", meta.getEntryLogId(), meta.getUsage(), threshold); } long priorRemainingSize = meta.getRemainingSize(); compactEntryLog(meta); gcStats.getReclaimedSpaceViaCompaction().addCount(meta.getTotalSize() - priorRemainingSize); compactedBuckets[bucketIndex]++; processedEntryLogCnt.getAndIncrement(); }); } } if (LOG.isDebugEnabled()) { if (!running) { LOG.debug("Compaction exited due to gc not running"); } if (maxTimeMillis > 0 && timeDiff.getValue() > maxTimeMillis) { LOG.debug("Compaction ran for {}ms but was limited by {}ms", timeDiff, maxTimeMillis); } } LOG.info( "Compaction: entry log usage buckets[10% 20% 30% 40% 50% 60% 70% 80% 90% 100%] = {}, compacted {}", entryLogUsageBuckets, compactedBuckets); } /** * Calculate the index for the batch based on the usage between 0 and 1. * * @param numBuckets Number of reporting buckets. * @param usage 0.0 - 1.0 value representing the usage of the entry log. * @return index based on the number of buckets The last bucket will have the 1.0 if added. */ int calculateUsageIndex(int numBuckets, double usage) { return Math.min( numBuckets - 1, (int) Math.floor(usage * numBuckets)); } /** * Shutdown the garbage collector thread. * * @throws InterruptedException if there is an exception stopping gc thread. */ @SuppressFBWarnings("SWL_SLEEP_WITH_LOCK_HELD") public synchronized void shutdown() throws InterruptedException { if (!this.running) { return; } this.running = false; LOG.info("Shutting down GarbageCollectorThread"); throttler.cancelledAcquire(); compactor.throttler.cancelledAcquire(); while (!compacting.compareAndSet(false, true)) { // Wait till the thread stops compacting Thread.sleep(100); } // Interrupt GC executor thread gcExecutor.shutdownNow(); try { entryLogMetaMap.close(); } catch (Exception e) { LOG.warn("Failed to close entryLog metadata-map", e); } } /** * Remove entry log. * * @param entryLogId * Entry Log File Id * @throws EntryLogMetadataMapException */ protected void removeEntryLog(long entryLogId) throws EntryLogMetadataMapException { // remove entry log file successfully if (entryLogger.removeEntryLog(entryLogId)) { LOG.info("Removing entry log metadata for {}", entryLogId); entryLogMetaMap.remove(entryLogId); } } /** * Compact an entry log. * * @param entryLogMeta */ protected void compactEntryLog(EntryLogMetadata entryLogMeta) { // Similar with Sync Thread // try to mark compacting flag to make sure it would not be interrupted // by shutdown during compaction. otherwise it will receive // ClosedByInterruptException which may cause index file & entry logger // closed and corrupted. if (!compacting.compareAndSet(false, true)) { // set compacting flag failed, means compacting is true now // indicates that compaction is in progress for this EntryLogId. return; } try { // Do the actual compaction compactor.compact(entryLogMeta); } catch (Exception e) { LOG.error("Failed to compact entry log {} due to unexpected error", entryLogMeta.getEntryLogId(), e); } finally { // Mark compaction done compacting.set(false); } } /** * Method to read in all of the entry logs (those that we haven't done so yet), * and find the set of ledger ID's that make up each entry log file. * * @throws EntryLogMetadataMapException */ protected void extractMetaFromEntryLogs() throws EntryLogMetadataMapException { for (long entryLogId : entryLogger.getFlushedLogIds()) { // Comb the current entry log file if it has not already been extracted. if (entryLogMetaMap.containsKey(entryLogId)) { continue; } // check whether log file exists or not // if it doesn't exist, this log file might have been garbage collected. if (!entryLogger.logExists(entryLogId)) { continue; } LOG.info("Extracting entry log meta from entryLogId: {}", entryLogId); try { // Read through the entry log file and extract the entry log meta EntryLogMetadata entryLogMeta = entryLogger.getEntryLogMetadata(entryLogId, throttler); removeIfLedgerNotExists(entryLogMeta); if (entryLogMeta.isEmpty()) { // This means the entry log is not associated with any active // ledgers anymore. // We can remove this entry log file now. LOG.info("Deleting entryLogId {} as it has no active ledgers!", entryLogId); removeEntryLog(entryLogId); gcStats.getReclaimedSpaceViaDeletes().addCount(entryLogMeta.getTotalSize()); } else { entryLogMetaMap.put(entryLogId, entryLogMeta); } } catch (IOException | RuntimeException e) { LOG.warn("Premature exception when processing " + entryLogId + " recovery will take care of the problem", e); } } } CompactableLedgerStorage getLedgerStorage() { return ledgerStorage; } @VisibleForTesting EntryLogMetadataMap getEntryLogMetaMap() { return entryLogMetaMap; } public GarbageCollectionStatus getGarbageCollectionStatus() { return GarbageCollectionStatus.builder() .forceCompacting(forceGarbageCollection.get()) .majorCompacting(majorCompacting.get()) .minorCompacting(minorCompacting.get()) .lastMajorCompactionTime(lastMajorCompactionTime) .lastMinorCompactionTime(lastMinorCompactionTime) .majorCompactionCounter(gcStats.getMajorCompactionCounter().get()) .minorCompactionCounter(gcStats.getMinorCompactionCounter().get()) .build(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy