/* * The Alluxio Open Foundation licenses this work under the Apache License, version 2.0 * (the "License"). You may not use this work except in compliance with the License, which is * available at www.apache.org/licenses/LICENSE-2.0 * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied, as more fully set forth in the License. * * See the NOTICE file distributed with this work for information regarding copyright ownership. */ package alluxio.master.journal.ufs; import alluxio.Configuration; import alluxio.Constants; import alluxio.PropertyKey; import alluxio.underfs.UnderFileSystem; import alluxio.util.ThreadFactoryUtils; import com.google.common.base.Preconditions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.Closeable; import java.io.IOException; import java.net.URI; import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import javax.annotation.concurrent.ThreadSafe; /** * A garbage collector that periodically snapshots the journal and deletes files that are not * necessary anymore. The implementation guarantees that the journal contains all the information * required to recover the master full state. */ @ThreadSafe final class UfsJournalGarbageCollector implements Closeable { private static final Logger LOG = LoggerFactory.getLogger(UfsJournalGarbageCollector.class); private final ScheduledExecutorService mExecutor = Executors.newSingleThreadScheduledExecutor( ThreadFactoryUtils.build("UfsJournalGarbageCollector-%d", true)); private final UfsJournal mJournal; private final UnderFileSystem mUfs; private ScheduledFuture<?> mGc; /** * Creates the {@link UfsJournalGarbageCollector} instance. * * @param journal the UFS journal handle */ UfsJournalGarbageCollector(UfsJournal journal) { mJournal = Preconditions.checkNotNull(journal); mUfs = mJournal.getUfs(); mGc = mExecutor.scheduleAtFixedRate(new Runnable() { @Override public void run() { gc(); } }, Constants.SECOND_MS, Configuration.getLong(PropertyKey.MASTER_JOURNAL_GC_PERIOD_MS), TimeUnit.MILLISECONDS); } @Override public void close() { if (mGc != null) { mGc.cancel(true); mGc = null; } } /** * Snapshots the journal and deletes files that are not necessary. */ void gc() { UfsJournalSnapshot snapshot; try { snapshot = UfsJournalSnapshot.getSnapshot(mJournal); } catch (IOException e) { LOG.warn("Failed to get journal snapshot with error {}.", e.getMessage()); return; } long checkpointSequenceNumber = 0; // Checkpoint. List<UfsJournalFile> checkpoints = snapshot.getCheckpoints(); if (!checkpoints.isEmpty()) { checkpointSequenceNumber = checkpoints.get(checkpoints.size() - 1).getEnd(); } for (int i = 0; i < checkpoints.size() - 1; i++) { // Only keep at most 2 checkpoints. if (i < checkpoints.size() - 2) { deleteNoException(checkpoints.get(i).getLocation()); } // For the the second last checkpoint. Check whether it has been there for a long time. gcFileIfStale(checkpoints.get(i), checkpointSequenceNumber); } for (UfsJournalFile log : snapshot.getLogs()) { gcFileIfStale(log, checkpointSequenceNumber); } for (UfsJournalFile tmpCheckpoint : snapshot.getTemporaryCheckpoints()) { gcFileIfStale(tmpCheckpoint, checkpointSequenceNumber); } } /** * Garbage collects a file if necessary. * * @param file the file * @param checkpointSequenceNumber the first sequence number that has not been checkpointed */ private void gcFileIfStale(UfsJournalFile file, long checkpointSequenceNumber) { if (file.getEnd() > checkpointSequenceNumber && !file.isTmpCheckpoint()) { return; } long lastModifiedTimeMs; try { lastModifiedTimeMs = mUfs.getFileStatus(file.getLocation().toString()).getLastModifiedTime(); } catch (IOException e) { LOG.warn("Failed to get the last modified time for {}.", file.getLocation()); return; } long thresholdMs = file.isTmpCheckpoint() ? Configuration.getLong(PropertyKey.MASTER_JOURNAL_TEMPORARY_FILE_GC_THRESHOLD_MS) : Configuration.getLong(PropertyKey.MASTER_JOURNAL_GC_THRESHOLD_MS); if (System.currentTimeMillis() - lastModifiedTimeMs > thresholdMs) { deleteNoException(file.getLocation()); } } /** * Deletes a file and swallows the exception by logging it. * * @param location the file location */ private void deleteNoException(URI location) { try { mUfs.deleteFile(location.toString()); LOG.info("Garbage collected journal file {}.", location); } catch (IOException e) { LOG.warn("Failed to garbage collect journal file {}.", location); } } }