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

org.apache.cassandra.db.compaction.WrappingCompactionStrategy 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.cassandra.db.compaction;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;

import com.google.common.collect.ImmutableMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.lifecycle.LifecycleTransaction;
import org.apache.cassandra.dht.Range;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.io.sstable.Component;
import org.apache.cassandra.io.sstable.ISSTableScanner;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.notifications.INotification;
import org.apache.cassandra.notifications.INotificationConsumer;
import org.apache.cassandra.notifications.SSTableAddedNotification;
import org.apache.cassandra.notifications.SSTableDeletingNotification;
import org.apache.cassandra.notifications.SSTableListChangedNotification;
import org.apache.cassandra.notifications.SSTableRepairStatusChanged;
import org.apache.cassandra.service.ActiveRepairService;
import org.apache.cassandra.utils.Pair;

public final class WrappingCompactionStrategy extends AbstractCompactionStrategy implements INotificationConsumer
{
    private static final Logger logger = LoggerFactory.getLogger(WrappingCompactionStrategy.class);
    private static final boolean UNREPAIR_ON_STARTUP = Boolean.parseBoolean(System.getProperty("palantir_cassandra.unrepair_on_startup", "true"));
    private static final int MAX_SSTABLES_TO_UNREPAIR_PER_CF = Integer.parseInt(System.getProperty("palantir_cassandra.max_sstables_to_unrepair_per_cf", "100"));

    private volatile AbstractCompactionStrategy repaired;
    private volatile AbstractCompactionStrategy unrepaired;
    /*
        We keep a copy of the schema compaction options and class here to be able to decide if we
        should update the compaction strategy in maybeReloadCompactionStrategy() due to an ALTER.

        If a user changes the local compaction strategy and then later ALTERs a compaction option,
        we will use the new compaction options.
     */
    private Map schemaCompactionOptions;
    private Class schemaCompactionStrategyClass;

    public WrappingCompactionStrategy(ColumnFamilyStore cfs)
    {
        super(cfs, cfs.metadata.compactionStrategyOptions);
        reloadCompactionStrategy(cfs.metadata);
        cfs.getTracker().subscribe(this);
        logger.trace("{} subscribed to the data tracker.", this);
    }

    @Override
    public synchronized AbstractCompactionTask getNextBackgroundTask(int gcBefore)
    {
        if (!isEnabled())
            return null;

        if (repaired.getEstimatedRemainingTasks() > unrepaired.getEstimatedRemainingTasks())
        {
            AbstractCompactionTask repairedTask = repaired.getNextBackgroundTask(gcBefore);
            if (repairedTask != null)
                return repairedTask;
            return unrepaired.getNextBackgroundTask(gcBefore);
        }
        else
        {
            AbstractCompactionTask unrepairedTask = unrepaired.getNextBackgroundTask(gcBefore);
            if (unrepairedTask != null)
                return unrepairedTask;
            return repaired.getNextBackgroundTask(gcBefore);
        }

    }

    @Override
    public Collection getMaximalTask(final int gcBefore, final boolean splitOutput)
    {
        // runWithCompactionsDisabled cancels active compactions and disables them, then we are able
        // to make the repaired/unrepaired strategies mark their own sstables as compacting. Once the
        // sstables are marked the compactions are re-enabled
        return cfs.runWithCompactionsDisabled(new Callable>()
        {
            @Override
            public Collection call() throws Exception
            {
                synchronized (WrappingCompactionStrategy.this)
                {
                    Collection repairedTasks = repaired.getMaximalTask(gcBefore, splitOutput);
                    Collection unrepairedTasks = unrepaired.getMaximalTask(gcBefore, splitOutput);

                    if (repairedTasks == null && unrepairedTasks == null)
                        return null;

                    if (repairedTasks == null)
                        return unrepairedTasks;
                    if (unrepairedTasks == null)
                        return repairedTasks;

                    List tasks = new ArrayList<>();
                    tasks.addAll(repairedTasks);
                    tasks.addAll(unrepairedTasks);
                    return tasks;
                }
            }
        }, false);
    }

    @Override
    public AbstractCompactionTask getCompactionTask(LifecycleTransaction txn, final int gcBefore, long maxSSTableBytes)
    {
        assert txn.originals().size() > 0;
        boolean repairedSSTables = txn.originals().iterator().next().isRepaired();
        for (SSTableReader sstable : txn.originals())
            if (repairedSSTables != sstable.isRepaired())
                throw new RuntimeException("Can't mix repaired and unrepaired sstables in a compaction");

        if (repairedSSTables)
            return repaired.getCompactionTask(txn, gcBefore, maxSSTableBytes);
        else
            return unrepaired.getCompactionTask(txn, gcBefore, maxSSTableBytes);
    }

    @Override
    public synchronized AbstractCompactionTask getUserDefinedTask(Collection sstables, int gcBefore)
    {
        assert !sstables.isEmpty();
        boolean userDefinedInRepaired = sstables.iterator().next().isRepaired();
        for (SSTableReader sstable : sstables)
        {
            if (userDefinedInRepaired != sstable.isRepaired())
            {
                logger.error("You can't mix repaired and unrepaired sstables in a user defined compaction");
                return null;
            }
        }
        if (userDefinedInRepaired)
            return repaired.getUserDefinedTask(sstables, gcBefore);
        else
            return unrepaired.getUserDefinedTask(sstables, gcBefore);
    }

    @Override
    public synchronized int getEstimatedRemainingTasks()
    {
        assert repaired.getClass().equals(unrepaired.getClass());
        return repaired.getEstimatedRemainingTasks() + unrepaired.getEstimatedRemainingTasks();
    }

    @Override
    public synchronized long getMaxSSTableBytes()
    {
        assert repaired.getClass().equals(unrepaired.getClass());
        return unrepaired.getMaxSSTableBytes();
    }

    public synchronized void maybeReloadCompactionStrategy(CFMetaData metadata)
    {
        // compare the old schema configuration to the new one, ignore any locally set changes.
        if (metadata.compactionStrategyClass.equals(schemaCompactionStrategyClass) &&
            metadata.compactionStrategyOptions.equals(schemaCompactionOptions))
            return;
        reloadCompactionStrategy(metadata);
    }

    public synchronized void reloadCompactionStrategy(CFMetaData metadata)
    {
        boolean disabledWithJMX = !enabled && shouldBeEnabled();
        setStrategy(metadata.compactionStrategyClass, metadata.compactionStrategyOptions);
        schemaCompactionOptions = ImmutableMap.copyOf(metadata.compactionStrategyOptions);
        schemaCompactionStrategyClass = repaired.getClass();

        if (disabledWithJMX || !shouldBeEnabled())
            disable();
        else
            enable();
        startup();
    }

    public synchronized int getUnleveledSSTables()
    {
        if (this.repaired instanceof LeveledCompactionStrategy && this.unrepaired instanceof LeveledCompactionStrategy)
        {
            return ((LeveledCompactionStrategy)repaired).getLevelSize(0) + ((LeveledCompactionStrategy)unrepaired).getLevelSize(0);
        }
        return 0;
    }

    public synchronized int[] getSSTableCountPerLevel()
    {
        if (this.repaired instanceof LeveledCompactionStrategy && this.unrepaired instanceof LeveledCompactionStrategy)
        {
            int [] repairedCountPerLevel = ((LeveledCompactionStrategy) repaired).getAllLevelSize();
            int [] unrepairedCountPerLevel = ((LeveledCompactionStrategy) unrepaired).getAllLevelSize();
            return sumArrays(repairedCountPerLevel, unrepairedCountPerLevel);
        }
        return null;
    }

    public static int [] sumArrays(int[] a, int [] b)
    {
        int [] res = new int[Math.max(a.length, b.length)];
        for (int i = 0; i < res.length; i++)
        {
            if (i < a.length && i < b.length)
                res[i] = a[i] + b[i];
            else if (i < a.length)
                res[i] = a[i];
            else
                res[i] = b[i];
        }
        return res;
    }

    @Override
    public boolean shouldDefragment()
    {
        assert repaired.getClass().equals(unrepaired.getClass());
        return repaired.shouldDefragment();
    }

    @Override
    public String getName()
    {
        assert repaired.getClass().equals(unrepaired.getClass());
        return repaired.getName();
    }

    @Override
    public void replaceSSTables(Collection removed, Collection added)
    {
        throw new UnsupportedOperationException("Can't replace sstables in the wrapping compaction strategy");
    }

    @Override
    public void addSSTable(SSTableReader added)
    {
        throw new UnsupportedOperationException("Can't add sstables to the wrapping compaction strategy");
    }

    @Override
    public void removeSSTable(SSTableReader sstable)
    {
        throw new UnsupportedOperationException("Can't remove sstables from the wrapping compaction strategy");
    }

    public synchronized void handleNotification(INotification notification, Object sender)
    {
        if (notification instanceof SSTableAddedNotification)
        {
            SSTableAddedNotification flushedNotification = (SSTableAddedNotification) notification;
            if (flushedNotification.added.isRepaired())
                repaired.addSSTable(flushedNotification.added);
            else
                unrepaired.addSSTable(flushedNotification.added);
        }
        else if (notification instanceof SSTableListChangedNotification)
        {
            SSTableListChangedNotification listChangedNotification = (SSTableListChangedNotification) notification;
            Set repairedRemoved = new HashSet<>();
            Set repairedAdded = new HashSet<>();
            Set unrepairedRemoved = new HashSet<>();
            Set unrepairedAdded = new HashSet<>();

            for (SSTableReader sstable : listChangedNotification.removed)
            {
                if (sstable.isRepaired())
                    repairedRemoved.add(sstable);
                else
                    unrepairedRemoved.add(sstable);
            }
            for (SSTableReader sstable : listChangedNotification.added)
            {
                if (sstable.isRepaired())
                    repairedAdded.add(sstable);
                else
                    unrepairedAdded.add(sstable);
            }
            if (!repairedRemoved.isEmpty())
            {
                repaired.replaceSSTables(repairedRemoved, repairedAdded);
            }
            else
            {
                for (SSTableReader sstable : repairedAdded)
                    repaired.addSSTable(sstable);
            }

            if (!unrepairedRemoved.isEmpty())
            {
                unrepaired.replaceSSTables(unrepairedRemoved, unrepairedAdded);
            }
            else
            {
                for (SSTableReader sstable : unrepairedAdded)
                    unrepaired.addSSTable(sstable);
            }
        }
        else if (notification instanceof SSTableRepairStatusChanged)
        {
            for (SSTableReader sstable : ((SSTableRepairStatusChanged) notification).sstable)
            {
                if (sstable.isRepaired())
                {
                    unrepaired.removeSSTable(sstable);
                    repaired.addSSTable(sstable);
                }
                else
                {
                    repaired.removeSSTable(sstable);
                    unrepaired.addSSTable(sstable);
                }
            }
        }
        else if (notification instanceof SSTableDeletingNotification)
        {
            SSTableReader sstable = ((SSTableDeletingNotification)notification).deleting;
            if (sstable.isRepaired())
                repaired.removeSSTable(sstable);
            else
                unrepaired.removeSSTable(sstable);
        }
    }

    @Override
    public List filterSSTablesForReads(List sstables)
    {
        // todo: union of filtered sstables or intersection?
        return unrepaired.filterSSTablesForReads(repaired.filterSSTablesForReads(sstables));
    }

    @Override
    public synchronized void startup()
    {
        super.startup();

        Map, Integer> unrepairedTablesPerCf = new HashMap<>();
        for (SSTableReader sstable : cfs.getSSTables())
        {
            if (sstable.openReason != SSTableReader.OpenReason.EARLY)
            {
                Pair ksAndCFName = sstable.metadata.ksAndCFName;
                if (UNREPAIR_ON_STARTUP && sstable.isRepaired() && isWithinUnrepairThershold(unrepairedTablesPerCf, ksAndCFName))
                {
                    tryUnrepairingSSTable(sstable);
                    incrementUnrepairCount(unrepairedTablesPerCf, ksAndCFName);

                    if (unrepairedTablesPerCf.get(ksAndCFName) == MAX_SSTABLES_TO_UNREPAIR_PER_CF) {
                        logger.info("Unrepair threshold has been hit. Will not unrepair any more sstables for {}", ksAndCFName);
                    }
                }

                if (sstable.isRepaired())
                    repaired.addSSTable(sstable);
                else
                    unrepaired.addSSTable(sstable);
            }
        }
        repaired.startup();
        unrepaired.startup();
    }

    private boolean isWithinUnrepairThershold(Map, Integer> unrepairedTablesPerCf, Pair ksAndCFName)
    {
        return !unrepairedTablesPerCf.containsKey(ksAndCFName)
               || unrepairedTablesPerCf.get(ksAndCFName) < MAX_SSTABLES_TO_UNREPAIR_PER_CF;
    }

    private void incrementUnrepairCount(Map, Integer> unrepairedTablesPerCf, Pair ksAndCFName)
    {
        Integer previousValue = unrepairedTablesPerCf.containsKey(ksAndCFName)
                                ? unrepairedTablesPerCf.get(ksAndCFName)
                                : 0;
        unrepairedTablesPerCf.put(ksAndCFName, previousValue + 1);
    }

    private void tryUnrepairingSSTable(SSTableReader sstable)
    {
        logger.info("Trying to unrepair sstable {}", sstable.descriptor.filenameFor(Component.DATA));
        try
        {
            sstable.descriptor.getMetadataSerializer().mutateRepairedAt(sstable.descriptor, ActiveRepairService.UNREPAIRED_SSTABLE);
            sstable.reloadSSTableMetadata();
        }
        catch (IOException e)
        {
            logger.error("Could not unrepair sstable {}, moving on...", sstable.descriptor.filenameFor(Component.DATA), e);
        }
    }

    @Override
    public synchronized void shutdown()
    {
        super.shutdown();
        repaired.shutdown();
        unrepaired.shutdown();
    }

    @Override
    public void enable()
    {
        if (repaired != null)
            repaired.enable();
        if (unrepaired != null)
            unrepaired.enable();
        // enable this last to make sure the strategies are ready to get calls.
        super.enable();
    }

    @Override
    public void disable()
    {
        // disable this first avoid asking disabled strategies for compaction tasks
        super.disable();
        if (repaired != null)
            repaired.disable();
        if (unrepaired != null)
            unrepaired.disable();
    }

    @Override
    @SuppressWarnings("resource")
    public synchronized ScannerList getScanners(Collection sstables, Range range)
    {
        List repairedSSTables = new ArrayList<>();
        List unrepairedSSTables = new ArrayList<>();
        for (SSTableReader sstable : sstables)
            if (sstable.isRepaired())
                repairedSSTables.add(sstable);
            else
                unrepairedSSTables.add(sstable);
        ScannerList repairedScanners = repaired.getScanners(repairedSSTables, range);
        ScannerList unrepairedScanners = unrepaired.getScanners(unrepairedSSTables, range);
        List scanners = new ArrayList<>(repairedScanners.scanners.size() + unrepairedScanners.scanners.size());
        scanners.addAll(repairedScanners.scanners);
        scanners.addAll(unrepairedScanners.scanners);
        return new ScannerList(scanners);
    }

    public Collection> groupSSTablesForAntiCompaction(Collection sstablesToGroup)
    {
        return unrepaired.groupSSTablesForAntiCompaction(sstablesToGroup);
    }

    public List getWrappedStrategies()
    {
        return Arrays.asList(repaired, unrepaired);
    }

    public synchronized void setNewLocalCompactionStrategy(Class compactionStrategyClass, Map options)
    {
        logger.info("Switching local compaction strategy from {} to {} with options={}", repaired == null ? "null" : repaired.getClass(), compactionStrategyClass, options);
        setStrategy(compactionStrategyClass, options);
        if (shouldBeEnabled())
            enable();
        else
            disable();
        startup();
    }

    private void setStrategy(Class compactionStrategyClass, Map options)
    {
        if (repaired != null)
            repaired.shutdown();
        if (unrepaired != null)
            unrepaired.shutdown();
        repaired = CFMetaData.createCompactionStrategyInstance(compactionStrategyClass, cfs, options);
        unrepaired = CFMetaData.createCompactionStrategyInstance(compactionStrategyClass, cfs, options);
        this.options = ImmutableMap.copyOf(options);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy