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

org.apache.hadoop.hbase.regionserver.StoreEngine Maven / Gradle / Ivy

There is a newer version: 3.0.0-beta-1
Show 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.hadoop.hbase.regionserver;

import com.google.errorprone.annotations.RestrictedApi;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Function;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.CellComparator;
import org.apache.hadoop.hbase.io.hfile.BloomFilterMetrics;
import org.apache.hadoop.hbase.log.HBaseMarkers;
import org.apache.hadoop.hbase.regionserver.compactions.CompactionContext;
import org.apache.hadoop.hbase.regionserver.compactions.CompactionPolicy;
import org.apache.hadoop.hbase.regionserver.compactions.Compactor;
import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTracker;
import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTrackerFactory;
import org.apache.hadoop.hbase.util.ReflectionUtils;
import org.apache.yetus.audience.InterfaceAudience;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.apache.hbase.thirdparty.com.google.common.collect.Sets;
import org.apache.hbase.thirdparty.org.apache.commons.collections4.CollectionUtils;

/**
 * StoreEngine is a factory that can create the objects necessary for HStore to operate. Since not
 * all compaction policies, compactors and store file managers are compatible, they are tied
 * together and replaced together via StoreEngine-s.
 * 

* We expose read write lock methods to upper layer for store operations:
*

    *
  • Locked in shared mode when the list of component stores is looked at: *
      *
    • all reads/writes to table data
    • *
    • checking for split
    • *
    *
  • *
  • Locked in exclusive mode when the list of component stores is modified: *
      *
    • closing
    • *
    • completing a compaction
    • *
    *
  • *
*

* It is a bit confusing that we have a StoreFileManager(SFM) and then a StoreFileTracker(SFT). As * its name says, SFT is used to track the store files list. The reason why we have a SFT beside SFM * is that, when introducing stripe compaction, we introduced the StoreEngine and also the SFM, but * actually, the SFM here is not a general 'Manager', it is only designed to manage the in memory * 'stripes', so we can select different store files when scanning or compacting. The 'tracking' of * store files is actually done in {@link org.apache.hadoop.hbase.regionserver.HRegionFileSystem} * and {@link HStore} before we have SFT. And since SFM is designed to only holds in memory states, * we will hold write lock when updating it, the lock is also used to protect the normal read/write * requests. This means we'd better not add IO operations to SFM. And also, no matter what the in * memory state is, stripe or not, it does not effect how we track the store files. So consider all * these facts, here we introduce a separated SFT to track the store files. *

* Here, since we always need to update SFM and SFT almost at the same time, we introduce methods in * StoreEngine directly to update them both, so upper layer just need to update StoreEngine once, to * reduce the possible misuse. */ @InterfaceAudience.Private public abstract class StoreEngine, SFM extends StoreFileManager> { private static final Logger LOG = LoggerFactory.getLogger(StoreEngine.class); protected SF storeFlusher; protected CP compactionPolicy; protected C compactor; protected SFM storeFileManager; private final BloomFilterMetrics bloomFilterMetrics = new BloomFilterMetrics(); private Configuration conf; private StoreContext ctx; private RegionCoprocessorHost coprocessorHost; private Function openStoreFileThreadPoolCreator; private StoreFileTracker storeFileTracker; private final ReadWriteLock storeLock = new ReentrantReadWriteLock(); /** * The name of the configuration parameter that specifies the class of a store engine that is used * to manage and compact HBase store files. */ public static final String STORE_ENGINE_CLASS_KEY = "hbase.hstore.engine.class"; private static final Class> DEFAULT_STORE_ENGINE_CLASS = DefaultStoreEngine.class; /** * Acquire read lock of this store. */ public void readLock() { storeLock.readLock().lock(); } /** * Release read lock of this store. */ public void readUnlock() { storeLock.readLock().unlock(); } /** * Acquire write lock of this store. */ public void writeLock() { storeLock.writeLock().lock(); } /** * Release write lock of this store. */ public void writeUnlock() { storeLock.writeLock().unlock(); } /** Returns Compaction policy to use. */ public CompactionPolicy getCompactionPolicy() { return this.compactionPolicy; } /** Returns Compactor to use. */ public Compactor getCompactor() { return this.compactor; } /** Returns Store file manager to use. */ public StoreFileManager getStoreFileManager() { return this.storeFileManager; } /** Returns Store flusher to use. */ public StoreFlusher getStoreFlusher() { return this.storeFlusher; } private StoreFileTracker createStoreFileTracker(Configuration conf, HStore store) { return StoreFileTrackerFactory.create(conf, store.isPrimaryReplicaStore(), store.getStoreContext()); } /** * @param filesCompacting Files currently compacting * @return whether a compaction selection is possible */ public abstract boolean needsCompaction(List filesCompacting); /** * Creates an instance of a compaction context specific to this engine. Doesn't actually select or * start a compaction. See CompactionContext class comment. * @return New CompactionContext object. */ public abstract CompactionContext createCompaction() throws IOException; /** * Create the StoreEngine's components. */ protected abstract void createComponents(Configuration conf, HStore store, CellComparator cellComparator) throws IOException; protected final void createComponentsOnce(Configuration conf, HStore store, CellComparator cellComparator) throws IOException { assert compactor == null && compactionPolicy == null && storeFileManager == null && storeFlusher == null && storeFileTracker == null; createComponents(conf, store, cellComparator); this.conf = conf; this.ctx = store.getStoreContext(); this.coprocessorHost = store.getHRegion().getCoprocessorHost(); this.openStoreFileThreadPoolCreator = store.getHRegion()::getStoreFileOpenAndCloseThreadPool; this.storeFileTracker = createStoreFileTracker(conf, store); assert compactor != null && compactionPolicy != null && storeFileManager != null && storeFlusher != null && storeFileTracker != null; } /** * Create a writer for writing new store files. * @return Writer for a new StoreFile */ public StoreFileWriter createWriter(CreateStoreFileWriterParams params) throws IOException { return storeFileTracker.createWriter(params); } public HStoreFile createStoreFileAndReader(Path p) throws IOException { StoreFileInfo info = new StoreFileInfo(conf, ctx.getRegionFileSystem().getFileSystem(), p, ctx.isPrimaryReplicaStore()); return createStoreFileAndReader(info); } public HStoreFile createStoreFileAndReader(StoreFileInfo info) throws IOException { info.setRegionCoprocessorHost(coprocessorHost); HStoreFile storeFile = new HStoreFile(info, ctx.getFamily().getBloomFilterType(), ctx.getCacheConf(), bloomFilterMetrics); storeFile.initReader(); return storeFile; } /** * Validates a store file by opening and closing it. In HFileV2 this should not be an expensive * operation. * @param path the path to the store file */ public void validateStoreFile(Path path) throws IOException { HStoreFile storeFile = null; try { storeFile = createStoreFileAndReader(path); } catch (IOException e) { LOG.error("Failed to open store file : {}, keeping it in tmp location", path, e); throw e; } finally { if (storeFile != null) { storeFile.closeStoreFile(false); } } } private List openStoreFiles(Collection files, boolean warmup) throws IOException { if (CollectionUtils.isEmpty(files)) { return Collections.emptyList(); } // initialize the thread pool for opening store files in parallel.. ExecutorService storeFileOpenerThreadPool = openStoreFileThreadPoolCreator.apply("StoreFileOpener-" + ctx.getRegionInfo().getEncodedName() + "-" + ctx.getFamily().getNameAsString()); CompletionService completionService = new ExecutorCompletionService<>(storeFileOpenerThreadPool); int totalValidStoreFile = 0; for (StoreFileInfo storeFileInfo : files) { // The StoreFileInfo will carry store configuration down to HFile, we need to set it to // our store's CompoundConfiguration here. storeFileInfo.setConf(conf); // open each store file in parallel completionService.submit(() -> createStoreFileAndReader(storeFileInfo)); totalValidStoreFile++; } Set compactedStoreFiles = new HashSet<>(); ArrayList results = new ArrayList<>(files.size()); IOException ioe = null; try { for (int i = 0; i < totalValidStoreFile; i++) { try { HStoreFile storeFile = completionService.take().get(); if (storeFile != null) { LOG.debug("loaded {}", storeFile); results.add(storeFile); compactedStoreFiles.addAll(storeFile.getCompactedStoreFiles()); } } catch (InterruptedException e) { if (ioe == null) { ioe = new InterruptedIOException(e.getMessage()); } } catch (ExecutionException e) { if (ioe == null) { ioe = new IOException(e.getCause()); } } } } finally { storeFileOpenerThreadPool.shutdownNow(); } if (ioe != null) { // close StoreFile readers boolean evictOnClose = ctx.getCacheConf() != null ? ctx.getCacheConf().shouldEvictOnClose() : true; for (HStoreFile file : results) { try { if (file != null) { file.closeStoreFile(evictOnClose); } } catch (IOException e) { LOG.warn("Could not close store file {}", file, e); } } throw ioe; } // Should not archive the compacted store files when region warmup. See HBASE-22163. if (!warmup) { // Remove the compacted files from result List filesToRemove = new ArrayList<>(compactedStoreFiles.size()); for (HStoreFile storeFile : results) { if (compactedStoreFiles.contains(storeFile.getPath().getName())) { LOG.warn("Clearing the compacted storefile {} from {}", storeFile, this); storeFile.getReader() .close(storeFile.getCacheConf() != null ? storeFile.getCacheConf().shouldEvictOnClose() : true); filesToRemove.add(storeFile); } } results.removeAll(filesToRemove); if (!filesToRemove.isEmpty() && ctx.isPrimaryReplicaStore()) { LOG.debug("Moving the files {} to archive", filesToRemove); ctx.getRegionFileSystem().removeStoreFiles(ctx.getFamily().getNameAsString(), filesToRemove); } } return results; } public void initialize(boolean warmup) throws IOException { List fileInfos = storeFileTracker.load(); List files = openStoreFiles(fileInfos, warmup); storeFileManager.loadFiles(files); } public void refreshStoreFiles() throws IOException { List fileInfos = storeFileTracker.load(); refreshStoreFilesInternal(fileInfos); } public void refreshStoreFiles(Collection newFiles) throws IOException { List storeFiles = new ArrayList<>(newFiles.size()); for (String file : newFiles) { storeFiles .add(ctx.getRegionFileSystem().getStoreFileInfo(ctx.getFamily().getNameAsString(), file)); } refreshStoreFilesInternal(storeFiles); } /** * Checks the underlying store files, and opens the files that have not been opened, and removes * the store file readers for store files no longer available. Mainly used by secondary region * replicas to keep up to date with the primary region files. */ private void refreshStoreFilesInternal(Collection newFiles) throws IOException { Collection currentFiles = storeFileManager.getStorefiles(); Collection compactedFiles = storeFileManager.getCompactedfiles(); if (currentFiles == null) { currentFiles = Collections.emptySet(); } if (newFiles == null) { newFiles = Collections.emptySet(); } if (compactedFiles == null) { compactedFiles = Collections.emptySet(); } HashMap currentFilesSet = new HashMap<>(currentFiles.size()); for (HStoreFile sf : currentFiles) { currentFilesSet.put(sf.getFileInfo(), sf); } HashMap compactedFilesSet = new HashMap<>(compactedFiles.size()); for (HStoreFile sf : compactedFiles) { compactedFilesSet.put(sf.getFileInfo(), sf); } Set newFilesSet = new HashSet(newFiles); // Exclude the files that have already been compacted newFilesSet = Sets.difference(newFilesSet, compactedFilesSet.keySet()); Set toBeAddedFiles = Sets.difference(newFilesSet, currentFilesSet.keySet()); Set toBeRemovedFiles = Sets.difference(currentFilesSet.keySet(), newFilesSet); if (toBeAddedFiles.isEmpty() && toBeRemovedFiles.isEmpty()) { return; } LOG.info("Refreshing store files for " + this + " files to add: " + toBeAddedFiles + " files to remove: " + toBeRemovedFiles); Set toBeRemovedStoreFiles = new HashSet<>(toBeRemovedFiles.size()); for (StoreFileInfo sfi : toBeRemovedFiles) { toBeRemovedStoreFiles.add(currentFilesSet.get(sfi)); } // try to open the files List openedFiles = openStoreFiles(toBeAddedFiles, false); // propogate the file changes to the underlying store file manager replaceStoreFiles(toBeRemovedStoreFiles, openedFiles, () -> { }, () -> { }); // won't throw an exception } /** * Commit the given {@code files}. *

* We will move the file into data directory, and open it. * @param files the files want to commit * @param validate whether to validate the store files * @return the committed store files */ public List commitStoreFiles(List files, boolean validate) throws IOException { List committedFiles = new ArrayList<>(files.size()); HRegionFileSystem hfs = ctx.getRegionFileSystem(); String familyName = ctx.getFamily().getNameAsString(); Path storeDir = hfs.getStoreDir(familyName); for (Path file : files) { try { if (validate) { validateStoreFile(file); } Path committedPath; // As we want to support writing to data directory directly, here we need to check whether // the store file is already in the right place if (file.getParent() != null && file.getParent().equals(storeDir)) { // already in the right place, skip renmaing committedPath = file; } else { // Write-out finished successfully, move into the right spot committedPath = hfs.commitStoreFile(familyName, file); } HStoreFile sf = createStoreFileAndReader(committedPath); committedFiles.add(sf); } catch (IOException e) { LOG.error("Failed to commit store file {}", file, e); // Try to delete the files we have committed before. // It is OK to fail when deleting as leaving the file there does not cause any data // corruption problem. It just introduces some duplicated data which may impact read // performance a little when reading before compaction. for (HStoreFile sf : committedFiles) { Path pathToDelete = sf.getPath(); try { sf.deleteStoreFile(); } catch (IOException deleteEx) { LOG.warn(HBaseMarkers.FATAL, "Failed to delete committed store file {}", pathToDelete, deleteEx); } } throw new IOException("Failed to commit the flush", e); } } return committedFiles; } @FunctionalInterface public interface IOExceptionRunnable { void run() throws IOException; } /** * Add the store files to store file manager, and also record it in the store file tracker. *

* The {@code actionAfterAdding} will be executed after the insertion to store file manager, under * the lock protection. Usually this is for clear the memstore snapshot. */ public void addStoreFiles(Collection storeFiles, IOExceptionRunnable actionAfterAdding) throws IOException { storeFileTracker.add(StoreUtils.toStoreFileInfo(storeFiles)); writeLock(); try { storeFileManager.insertNewFiles(storeFiles); actionAfterAdding.run(); } finally { // We need the lock, as long as we are updating the storeFiles // or changing the memstore. Let us release it before calling // notifyChangeReadersObservers. See HBASE-4485 for a possible // deadlock scenario that could have happened if continue to hold // the lock. writeUnlock(); } } public void replaceStoreFiles(Collection compactedFiles, Collection newFiles, IOExceptionRunnable walMarkerWriter, Runnable actionUnderLock) throws IOException { storeFileTracker.replace(StoreUtils.toStoreFileInfo(compactedFiles), StoreUtils.toStoreFileInfo(newFiles)); walMarkerWriter.run(); writeLock(); try { storeFileManager.addCompactionResults(compactedFiles, newFiles); actionUnderLock.run(); } finally { writeUnlock(); } } public void removeCompactedFiles(Collection compactedFiles) { writeLock(); try { storeFileManager.removeCompactedFiles(compactedFiles); } finally { writeUnlock(); } } /** * Create the StoreEngine configured for the given Store. * @param store The store. An unfortunate dependency needed due to it being passed to * coprocessors via the compactor. * @param conf Store configuration. * @param cellComparator CellComparator for storeFileManager. * @return StoreEngine to use. */ public static StoreEngine create(HStore store, Configuration conf, CellComparator cellComparator) throws IOException { String className = conf.get(STORE_ENGINE_CLASS_KEY, DEFAULT_STORE_ENGINE_CLASS.getName()); try { StoreEngine se = ReflectionUtils.instantiateWithCustomCtor(className, new Class[] {}, new Object[] {}); se.createComponentsOnce(conf, store, cellComparator); return se; } catch (Exception e) { throw new IOException("Unable to load configured store engine '" + className + "'", e); } } /** * Whether the implementation of the used storefile tracker requires you to write to temp * directory first, i.e, does not allow broken store files under the actual data directory. */ public boolean requireWritingToTmpDirFirst() { return storeFileTracker.requireWritingToTmpDirFirst(); } @RestrictedApi(explanation = "Should only be called in TestHStore", link = "", allowedOnPath = ".*/TestHStore.java") ReadWriteLock getLock() { return storeLock; } public BloomFilterMetrics getBloomFilterMetrics() { return bloomFilterMetrics; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy