org.apache.aurora.scheduler.storage.db.DbTaskStore.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.aurora.scheduler.storage.db.DbTaskStore.java

Source

/**
 * Licensed 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.aurora.scheduler.storage.db;

import java.util.Set;

import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.inject.Inject;

import org.apache.aurora.common.inject.TimedInterceptor.Timed;
import org.apache.aurora.common.quantity.Amount;
import org.apache.aurora.common.quantity.Time;
import org.apache.aurora.common.util.Clock;
import org.apache.aurora.gen.ScheduledTask;
import org.apache.aurora.scheduler.base.Query;
import org.apache.aurora.scheduler.base.Query.Builder;
import org.apache.aurora.scheduler.base.Tasks;
import org.apache.aurora.scheduler.storage.TaskStore;
import org.apache.aurora.scheduler.storage.db.views.DbScheduledTask;
import org.apache.aurora.scheduler.storage.entities.IJobKey;
import org.apache.aurora.scheduler.storage.entities.IScheduledTask;
import org.apache.aurora.scheduler.storage.entities.ITaskConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static java.util.Objects.requireNonNull;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * A task store implementation based on a relational database.
 * <p>
 * TODO(wfarner): Consider modifying code generator to support directly producing ITaskConfig, etc
 * from myBatis (it will set private final fields just fine).  This would reduce memory and time
 * spent translating and copying objects.
 */
class DbTaskStore implements TaskStore.Mutable {

    private static final Logger LOG = LoggerFactory.getLogger(DbTaskStore.class);

    private final TaskMapper taskMapper;
    private final TaskConfigManager configManager;
    private final Clock clock;
    private final long slowQueryThresholdNanos;

    @Inject
    DbTaskStore(TaskMapper taskMapper, TaskConfigManager configManager, Clock clock,
            Amount<Long, Time> slowQueryThreshold) {

        LOG.warn("DbTaskStore is experimental, and should not be used in production clusters!");
        this.taskMapper = requireNonNull(taskMapper);
        this.configManager = requireNonNull(configManager);
        this.clock = requireNonNull(clock);
        this.slowQueryThresholdNanos = slowQueryThreshold.as(Time.NANOSECONDS);
    }

    @Timed("db_storage_fetch_task")
    @Override
    public Optional<IScheduledTask> fetchTask(String taskId) {
        requireNonNull(taskId);
        return Optional.fromNullable(taskMapper.selectById(taskId)).transform(DbScheduledTask::toImmutable);
    }

    @Timed("db_storage_fetch_tasks")
    @Override
    public Iterable<IScheduledTask> fetchTasks(Builder query) {
        requireNonNull(query);

        // TODO(wfarner): Consider making slow query logging more reusable, or pushing it down into the
        //                database.
        long start = clock.nowNanos();
        Iterable<IScheduledTask> result = matches(query);
        long durationNanos = clock.nowNanos() - start;
        boolean infoLevel = durationNanos >= slowQueryThresholdNanos;
        long time = Amount.of(durationNanos, Time.NANOSECONDS).as(Time.MILLISECONDS);
        String message = "Query took {} ms: {}";
        if (infoLevel) {
            LOG.info(message, time, query.get());
        } else if (LOG.isDebugEnabled()) {
            LOG.debug(message, time, query.get());
        }

        return result;
    }

    @Timed("db_storage_get_job_keys")
    @Override
    public ImmutableSet<IJobKey> getJobKeys() {
        return IJobKey.setFromBuilders(taskMapper.selectJobKeys());
    }

    @Timed("db_storage_save_tasks")
    @Override
    public void saveTasks(Set<IScheduledTask> tasks) {
        if (tasks.isEmpty()) {
            return;
        }

        // TODO(wfarner): Restrict the TaskStore.Mutable methods to more specific mutations.  It would
        //                simplify this code if we did not have to handle full object tree mutations.

        deleteTasks(Tasks.ids(tasks));

        // Maintain a cache of all task configs that exist for a job key so that identical entities
        LoadingCache<ITaskConfig, Long> configCache = CacheBuilder.newBuilder()
                .build(new CacheLoader<ITaskConfig, Long>() {
                    @Override
                    public Long load(ITaskConfig config) {
                        return configManager.insert(config);
                    }
                });

        for (IScheduledTask task : tasks) {
            InsertResult result = new InsertResult();
            taskMapper.insertScheduledTask(task, configCache.getUnchecked(task.getAssignedTask().getTask()),
                    result);

            if (!task.getTaskEvents().isEmpty()) {
                taskMapper.insertTaskEvents(result.getId(), task.getTaskEvents());
            }
            if (!task.getAssignedTask().getAssignedPorts().isEmpty()) {
                taskMapper.insertPorts(result.getId(), task.getAssignedTask().getAssignedPorts());
            }
        }
    }

    @Timed("db_storage_delete_all_tasks")
    @Override
    public void deleteAllTasks() {
        taskMapper.truncate();
    }

    @Timed("db_storage_delete_tasks")
    @Override
    public void deleteTasks(Set<String> taskIds) {
        if (!taskIds.isEmpty()) {
            taskMapper.deleteTasks(taskIds);
        }
    }

    @Timed("db_storage_mutate_task")
    @Override
    public Optional<IScheduledTask> mutateTask(String taskId, Function<IScheduledTask, IScheduledTask> mutator) {

        requireNonNull(taskId);
        requireNonNull(mutator);

        return fetchTask(taskId).transform(original -> {
            IScheduledTask maybeMutated = mutator.apply(original);
            requireNonNull(maybeMutated);
            if (!original.equals(maybeMutated)) {
                Preconditions.checkState(Tasks.id(original).equals(Tasks.id(maybeMutated)),
                        "A task's ID may not be mutated.");
                saveTasks(ImmutableSet.of(maybeMutated));
            }
            return maybeMutated;
        });
    }

    @Timed("db_storage_unsafe_modify_in_place")
    @Override
    public boolean unsafeModifyInPlace(String taskId, ITaskConfig taskConfiguration) {
        checkNotNull(taskId);
        checkNotNull(taskConfiguration);
        Optional<IScheduledTask> task = fetchTask(taskId);
        if (task.isPresent()) {
            deleteTasks(ImmutableSet.of(taskId));
            ScheduledTask builder = task.get().newBuilder();
            builder.getAssignedTask().setTask(taskConfiguration.newBuilder());
            saveTasks(ImmutableSet.of(IScheduledTask.build(builder)));
            return true;
        }
        return false;
    }

    private FluentIterable<IScheduledTask> matches(Query.Builder query) {
        Iterable<DbScheduledTask> results;
        Predicate<IScheduledTask> filter;
        if (query.get().getTaskIds().size() == 1) {
            // Optimize queries that are scoped to a single task, as the dynamic SQL used for arbitrary
            // queries comes with a performance penalty.
            results = Optional
                    .fromNullable(taskMapper.selectById(Iterables.getOnlyElement(query.get().getTaskIds())))
                    .asSet();
            filter = Util.queryFilter(query);
        } else {
            results = taskMapper.select(query.get());
            // Additional filtering is not necessary in this case, since the query does it for us.
            filter = Predicates.alwaysTrue();
        }

        return FluentIterable.from(results).transform(DbScheduledTask::toImmutable).filter(filter);
    }
}