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

org.kairosdb.rollup.AssignmentManager Maven / Gradle / Ivy

Go to download

KairosDB is a fast distributed scalable time series abstraction on top of Cassandra.

There is a newer version: 1.2.28
Show newest version
package org.kairosdb.rollup;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.MapDifference;
import com.google.common.collect.MapDifference.ValueDifference;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.collect.Sets.SetView;
import com.google.inject.Inject;
import com.google.inject.name.Named;
import org.kairosdb.core.HostManager;
import org.kairosdb.core.KairosDBService;
import org.kairosdb.core.Main;
import org.kairosdb.core.datastore.Duration;
import org.kairosdb.core.datastore.ServiceKeyValue;
import org.kairosdb.core.datastore.TimeUnit;
import org.kairosdb.core.exception.KairosDBException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.locks.ReentrantLock;

import static com.google.common.base.Preconditions.checkNotNull;
import static org.kairosdb.util.Preconditions.checkNotNullOrEmpty;

/**
 * Manages Roll-up server assignments. Assignments identify which Kairos host executes what roll-ups.
 * Associates TaskId with Host guid
 */
public class AssignmentManager implements KairosDBService {
    public static final Logger logger = LoggerFactory.getLogger(AssignmentManager.class);
    private static final String DELAY = "kairosdb.rollups.server_assignment.check_update_delay_millseconds";

    private final RollUpAssignmentStore assignmentStore;
    private final RollUpTasksStore taskStore;
    private final RollupTaskStatusStore statusStore;
    private final ScheduledExecutorService executorService;
    private final BalancingAlgorithm balancing;
    private final HostManager hostManager;
    private final String guid;
    private final ReentrantLock lock = new ReentrantLock();

    private long assignmentsLastModified;
    private long rollupsLastModified;

    private Map assignmentsCache = new TreeMap<>();


    @Inject
    public AssignmentManager(@Named(Main.KAIROSDB_SERVER_GUID) final String guid,
                             final RollUpTasksStore taskStore,
                             final RollUpAssignmentStore assignmentStore,
                             final RollupTaskStatusStore statusStore,
                             @Named(RollUpModule.ROLLUP_EXECUTOR) final ScheduledExecutorService executorService, final HostManager hostManager,
                             final BalancingAlgorithm balancing, @Named(DELAY) final long delay) {
        this.guid = checkNotNullOrEmpty(guid, "guid cannot be null or empty");
        this.assignmentStore = checkNotNull(assignmentStore, "assignmentStore cannot be null");
        this.taskStore = checkNotNull(taskStore, "taskStore cannot be null");
        this.statusStore = checkNotNull(statusStore, "statusStore cannot be null");

        this.executorService = checkNotNull(executorService, "executorService cannot be null");
        this.balancing = checkNotNull(balancing, "balancing cannot be null");
        this.hostManager = checkNotNull(hostManager, "hostManager cannot be null");

        // Start thread that checks for rollup changes and rollup assignments
        executorService.scheduleWithFixedDelay(new updateAssignments(), 0, delay, java.util.concurrent.TimeUnit.MILLISECONDS);
    }

    private static Set getTasksForHostsNowInactive(final Map previousAssignments, final Map hosts) {
        final SetView inactiveHosts = Sets.difference(new HashSet<>(previousAssignments.values()), hosts.keySet());

        final Set removedTaskIds = new HashSet<>();
        for (final Entry assignment : previousAssignments.entrySet()) {
            if (inactiveHosts.contains(assignment.getValue())) {
                removedTaskIds.add(assignment.getKey());
            }
        }
        return removedTaskIds;
    }

    private static Set getMyAssignmentIds(final String guid, final Map assignments) {
        final Set myIds = new HashSet<>();
        for (final String host : assignments.values()) {
            if (host.equals(guid)) {
                myIds.add(host);
            }
        }
        return myIds;
    }

    private static Map getScores(final Map tasks) {
        final Map scores = new HashMap<>();
        for (final String id : tasks.keySet()) {
            scores.put(id, score(tasks.get(id)));
        }
        return scores;
    }

    /**
     * Returns a score for the task based on the execution interval.
     * Score values are as follows:
     * 1 second -> 120
     * 1 minute -> 60
     * 1 hour -> 1
     * > 1 hour -> 1
     */
    @VisibleForTesting
    static long score(final RollupTask task) {
        final Duration executionInterval = task.getExecutionInterval();
        if (executionInterval.getUnit().ordinal() > 2) {
            // 1 hour or greater
            return 1;
        } else if (executionInterval.getUnit().equals(TimeUnit.MINUTES)) {
            return 61 - executionInterval.getValue();
        } else if (executionInterval.getUnit().equals(TimeUnit.SECONDS)) {
            return 121 - executionInterval.getValue();
        } else {
            //noinspection ConstantConditions
            throw new IllegalArgumentException("Invalid time unit " + executionInterval.getUnit());
        }
    }

    @VisibleForTesting
    void checkAssignmentChanges() {
        try {
            final long assignmentTime = assignmentStore.getLastModifiedTime();
            final long taskStoreTime = taskStore.getLastModifiedTime();

            if (haveRollupsOrAssignmentsChanged(assignmentTime, taskStoreTime)) {
                final Map previousAssignments = getAssignmentsCache();
                final Map assignments = assignmentStore.getAssignments();
                Map newAssignments = new HashMap<>(assignments);
                final Map tasks = taskStore.read();
                final Map hosts = hostManager.getActiveKairosHosts();

                if (getMyAssignmentIds(guid, newAssignments).isEmpty() && tasks.size() > hosts.size()) {
                    logger.info("Server starting up. Reblanacing roll-up assignments");
                    newAssignments = balancing.rebalance(hosts.keySet(), getScores(tasks));
                } else {
                    logger.debug("Checking for roll-up assignment changes...");
                    // Remove assignments for task that have been removed
                    final SetView removedTasks = Sets.difference(previousAssignments.keySet(), tasks.keySet());
                    for (final String taskToRemove : removedTasks) {
                        newAssignments.remove(taskToRemove);
                        statusStore.remove(taskToRemove);
                    }

                    // Remove assignments for hosts that are inactive
                    final Set tasksForHostsRemoved = getTasksForHostsNowInactive(previousAssignments, hosts);
                    for (final String assignmentToRemove : tasksForHostsRemoved) {
                        newAssignments.remove(assignmentToRemove);
                    }

                    // Add assignments to unassigned tasks
                    newAssignments.putAll(balancing.balance(hosts.keySet(), newAssignments, getScores(tasks)));
                }

                // Save changes to the assignments table
                saveChangesToAssignmentTable(assignments, newAssignments);

                // Update caches
                lock.lock();
                try {
                    assignmentsCache = newAssignments;
                    assignmentsLastModified = assignmentTime;
                    rollupsLastModified = taskStoreTime;
                } finally {
                    lock.unlock();
                }
            }
        } catch (final Throwable e) {
            logger.error("Failed to modify roll-up assignments", e);
        }
    }

    private void saveChangesToAssignmentTable(final Map assignments, final Map newAssignments)
            throws RollUpException {
        final MapDifference diff = Maps.difference(assignments, newAssignments);
        if (!diff.areEqual()) {
            final Map remove = diff.entriesOnlyOnLeft();
            final Map add = diff.entriesOnlyOnRight();
            final Map> entryDifferences = diff.entriesDiffering();

            if (!remove.isEmpty()) {
                assignmentStore.removeAssignments(remove.keySet());
            }

            for (final String id : add.keySet()) {
                assignmentStore.setAssignment(id, add.get(id));
            }

            for (final String id : entryDifferences.keySet()) {
                assignmentStore.removeAssignments(ImmutableSet.of(id));
                assignmentStore.setAssignment(id, entryDifferences.get(id).rightValue());
            }
        }
    }

    private Map getAssignmentsCache() {
        lock.lock();
        try {
            return ImmutableMap.copyOf(assignmentsCache);
        } finally {
            lock.unlock();
        }
    }

    private boolean haveRollupsOrAssignmentsChanged(final long assignmentTime, final long taskStoreTime)
            throws RollUpException {
        lock.lock();
        try {
            return assignmentsLastModified == 0 ||
                    rollupsLastModified == 0 ||
                    assignmentsLastModified != assignmentTime ||
                    rollupsLastModified != taskStoreTime;
        } finally {
            lock.unlock();
        }
    }

    @Override
    public void start()
            throws KairosDBException {
    }

    @Override
    public void stop() {
        executorService.shutdown();
    }

    private class updateAssignments implements Runnable {
        @Override
        public void run() {
            checkAssignmentChanges();
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy