/* * Copyright 2016 LinkedIn Corp. All rights reserved. * * 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. */ package com.github.ambry.store; import com.github.ambry.clustermap.DiskId; import com.github.ambry.clustermap.PartitionId; import com.github.ambry.clustermap.ReplicaId; import com.github.ambry.config.StoreConfig; import com.github.ambry.utils.Throttler; import com.github.ambry.utils.Time; import com.github.ambry.utils.Utils; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.atomic.AtomicInteger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Manages all the stores on a disk. */ class DiskManager { private final Map<PartitionId, BlobStore> stores = new HashMap<>(); private final DiskId disk; private final StorageManagerMetrics metrics; private final Time time; private final DiskIOScheduler diskIOScheduler; private final CompactionManager compactionManager; private static final Logger logger = LoggerFactory.getLogger(DiskManager.class); /** * Constructs a {@link DiskManager} * @param disk representation of the disk. * @param replicas all the replicas on this disk. * @param config the settings for store configuration. * @param scheduler the {@link ScheduledExecutorService} for executing background tasks. * @param metrics the {@link StorageManagerMetrics} object used for store-related metrics. * @param keyFactory the {@link StoreKeyFactory} for parsing store keys. * @param recovery the {@link MessageStoreRecovery} instance to use. * @param hardDelete the {@link MessageStoreHardDelete} instance to use. * @param time the {@link Time} instance to use. */ DiskManager(DiskId disk, List<ReplicaId> replicas, StoreConfig config, ScheduledExecutorService scheduler, StorageManagerMetrics metrics, StoreKeyFactory keyFactory, MessageStoreRecovery recovery, MessageStoreHardDelete hardDelete, Time time) { this.disk = disk; this.metrics = metrics; this.time = time; diskIOScheduler = new DiskIOScheduler(getThrottlers(config, time)); for (ReplicaId replica : replicas) { if (disk.equals(replica.getDiskId())) { String storeId = replica.getPartitionId().toString(); BlobStore store = new BlobStore(storeId, config, scheduler, diskIOScheduler, metrics, replica.getReplicaPath(), replica.getCapacityInBytes(), keyFactory, recovery, hardDelete, time); stores.put(replica.getPartitionId(), store); } } compactionManager = new CompactionManager(disk.getMountPath(), config, stores.values(), metrics, time); } /** * Starts all the stores on this disk. * @throws InterruptedException */ void start() throws InterruptedException { long startTimeMs = time.milliseconds(); try { File mountPath = new File(disk.getMountPath()); if (mountPath.exists()) { List<Thread> startupThreads = new ArrayList<>(); final AtomicInteger numFailures = new AtomicInteger(0); for (final Map.Entry<PartitionId, BlobStore> partitionAndStore : stores.entrySet()) { Thread thread = Utils.newThread("store-startup-" + partitionAndStore.getKey(), new Runnable() { @Override public void run() { try { partitionAndStore.getValue().start(); } catch (Exception e) { numFailures.incrementAndGet(); metrics.totalStoreStartFailures.inc(); logger.error("Exception while starting store for the partition" + partitionAndStore.getKey(), e); } } }, false); thread.start(); startupThreads.add(thread); } for (Thread startupThread : startupThreads) { startupThread.join(); } if (numFailures.get() > 0) { logger.error( "Could not start " + numFailures.get() + " out of " + stores.size() + " stores on the disk " + disk); } compactionManager.enable(); } else { metrics.diskMountPathFailures.inc(); metrics.totalStoreStartFailures.inc(stores.size()); logger.error("Mount path does not exist: " + mountPath + " ; cannot start stores on this disk"); } } finally { metrics.diskStartTimeMs.update(time.milliseconds() - startTimeMs); } } /** * Shuts down all the stores on this disk. * @throws InterruptedException */ void shutdown() throws InterruptedException { long startTimeMs = time.milliseconds(); try { compactionManager.disable(); final AtomicInteger numFailures = new AtomicInteger(0); List<Thread> shutdownThreads = new ArrayList<>(); for (final Map.Entry<PartitionId, BlobStore> partitionAndStore : stores.entrySet()) { Thread thread = Utils.newThread("store-shutdown-" + partitionAndStore.getKey(), new Runnable() { @Override public void run() { try { partitionAndStore.getValue().shutdown(); } catch (Exception e) { numFailures.incrementAndGet(); metrics.totalStoreShutdownFailures.inc(); logger.error("Exception while shutting down store {} on disk {}", partitionAndStore.getKey(), disk, e); } } }, false); thread.start(); shutdownThreads.add(thread); } for (Thread shutdownThread : shutdownThreads) { shutdownThread.join(); } if (numFailures.get() > 0) { logger.error( "Could not shutdown " + numFailures.get() + " out of " + stores.size() + " stores on the disk " + disk); } compactionManager.awaitTermination(); diskIOScheduler.close(); } finally { metrics.diskShutdownTimeMs.update(time.milliseconds() - startTimeMs); } } /** * @param id the {@link PartitionId} to find the store for. * @return the associated {@link Store}, or {@code null} if the partition is not on this disk, or the store is not * started. */ Store getStore(PartitionId id) { BlobStore store = stores.get(id); return (store != null && store.isStarted()) ? store : null; } /** * @return {@code true} if the compaction thread is running. {@code false} otherwise. */ boolean isCompactionExecutorRunning() { return compactionManager.isCompactionExecutorRunning(); } /** * @return the {@link DiskId} that is managed by this {@link DiskManager}. */ DiskId getDisk() { return disk; } /** * Schedules the {@link PartitionId} {@code id} for compaction next. * @param id the {@link PartitionId} of the {@link BlobStore} to compact. * @return {@code true} if the scheduling was successful. {@code false} if not. */ boolean scheduleNextForCompaction(PartitionId id) { BlobStore store = (BlobStore) getStore(id); return store != null && compactionManager.scheduleNextForCompaction(store); } /** * Gets all the throttlers that the {@link DiskIOScheduler} will be constructed with. * @param config the {@link StoreConfig} with configuration values. * @param time the {@link Time} instance to use in the throttlers * @return the throttlers that the {@link DiskIOScheduler} will be constructed with. */ private Map<String, Throttler> getThrottlers(StoreConfig config, Time time) { Map<String, Throttler> throttlers = new HashMap<>(); // compaction Throttler compactionCopyThrottler = new Throttler(config.storeCleanupOperationsBytesPerSec, 1000, true, time); throttlers.put(BlobStoreCompactor.LOG_SEGMENT_COPY_JOB_NAME, compactionCopyThrottler); return throttlers; } }