001/**
002 *
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements.  See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership.  The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License.  You may obtain a copy of the License at
010 *
011 *     http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 */
019package org.apache.hadoop.hbase.regionserver;
020
021import java.io.IOException;
022import java.io.InterruptedIOException;
023import java.net.InetSocketAddress;
024import java.util.ArrayList;
025import java.util.Collection;
026import java.util.Collections;
027import java.util.HashMap;
028import java.util.HashSet;
029import java.util.Iterator;
030import java.util.List;
031import java.util.Map;
032import java.util.NavigableSet;
033import java.util.Optional;
034import java.util.OptionalDouble;
035import java.util.OptionalLong;
036import java.util.Set;
037import java.util.concurrent.Callable;
038import java.util.concurrent.CompletionService;
039import java.util.concurrent.ConcurrentHashMap;
040import java.util.concurrent.ExecutionException;
041import java.util.concurrent.ExecutorCompletionService;
042import java.util.concurrent.Future;
043import java.util.concurrent.ThreadPoolExecutor;
044import java.util.concurrent.atomic.AtomicBoolean;
045import java.util.concurrent.atomic.AtomicLong;
046import java.util.concurrent.locks.ReentrantLock;
047import java.util.concurrent.locks.ReentrantReadWriteLock;
048import java.util.function.Predicate;
049import java.util.function.ToLongFunction;
050import java.util.stream.Collectors;
051import java.util.stream.LongStream;
052
053import org.apache.hadoop.conf.Configuration;
054import org.apache.hadoop.fs.FileSystem;
055import org.apache.hadoop.fs.Path;
056import org.apache.hadoop.hbase.Cell;
057import org.apache.hadoop.hbase.CellComparator;
058import org.apache.hadoop.hbase.CellUtil;
059import org.apache.hadoop.hbase.CompoundConfiguration;
060import org.apache.hadoop.hbase.HConstants;
061import org.apache.hadoop.hbase.MemoryCompactionPolicy;
062import org.apache.hadoop.hbase.TableName;
063import org.apache.hadoop.hbase.backup.FailedArchiveException;
064import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
065import org.apache.hadoop.hbase.client.RegionInfo;
066import org.apache.hadoop.hbase.client.Scan;
067import org.apache.hadoop.hbase.conf.ConfigurationManager;
068import org.apache.hadoop.hbase.conf.PropagatingConfigurationObserver;
069import org.apache.hadoop.hbase.io.HeapSize;
070import org.apache.hadoop.hbase.io.compress.Compression;
071import org.apache.hadoop.hbase.io.crypto.Encryption;
072import org.apache.hadoop.hbase.io.hfile.CacheConfig;
073import org.apache.hadoop.hbase.io.hfile.HFile;
074import org.apache.hadoop.hbase.io.hfile.HFileContext;
075import org.apache.hadoop.hbase.io.hfile.HFileContextBuilder;
076import org.apache.hadoop.hbase.io.hfile.HFileDataBlockEncoder;
077import org.apache.hadoop.hbase.io.hfile.HFileDataBlockEncoderImpl;
078import org.apache.hadoop.hbase.io.hfile.HFileScanner;
079import org.apache.hadoop.hbase.io.hfile.InvalidHFileException;
080import org.apache.hadoop.hbase.log.HBaseMarkers;
081import org.apache.hadoop.hbase.monitoring.MonitoredTask;
082import org.apache.hadoop.hbase.regionserver.compactions.CompactionContext;
083import org.apache.hadoop.hbase.regionserver.compactions.CompactionLifeCycleTracker;
084import org.apache.hadoop.hbase.regionserver.compactions.CompactionProgress;
085import org.apache.hadoop.hbase.regionserver.compactions.CompactionRequestImpl;
086import org.apache.hadoop.hbase.regionserver.compactions.DefaultCompactor;
087import org.apache.hadoop.hbase.regionserver.compactions.OffPeakHours;
088import org.apache.hadoop.hbase.regionserver.querymatcher.ScanQueryMatcher;
089import org.apache.hadoop.hbase.regionserver.throttle.ThroughputController;
090import org.apache.hadoop.hbase.regionserver.wal.WALUtil;
091import org.apache.hadoop.hbase.security.EncryptionUtil;
092import org.apache.hadoop.hbase.security.User;
093import org.apache.hadoop.hbase.util.Bytes;
094import org.apache.hadoop.hbase.util.ChecksumType;
095import org.apache.hadoop.hbase.util.ClassSize;
096import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
097import org.apache.hadoop.hbase.util.Pair;
098import org.apache.hadoop.hbase.util.ReflectionUtils;
099import org.apache.hadoop.util.StringUtils;
100import org.apache.hadoop.util.StringUtils.TraditionalBinaryPrefix;
101import org.apache.yetus.audience.InterfaceAudience;
102import org.slf4j.Logger;
103import org.slf4j.LoggerFactory;
104import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting;
105import org.apache.hbase.thirdparty.com.google.common.base.Preconditions;
106import org.apache.hbase.thirdparty.com.google.common.collect.ImmutableCollection;
107import org.apache.hbase.thirdparty.com.google.common.collect.ImmutableList;
108import org.apache.hbase.thirdparty.com.google.common.collect.Lists;
109import org.apache.hbase.thirdparty.com.google.common.collect.Sets;
110import org.apache.hbase.thirdparty.org.apache.commons.collections4.CollectionUtils;
111import org.apache.hbase.thirdparty.org.apache.commons.collections4.IterableUtils;
112import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
113import org.apache.hadoop.hbase.shaded.protobuf.generated.WALProtos.CompactionDescriptor;
114
115/**
116 * A Store holds a column family in a Region.  Its a memstore and a set of zero
117 * or more StoreFiles, which stretch backwards over time.
118 *
119 * <p>There's no reason to consider append-logging at this level; all logging
120 * and locking is handled at the HRegion level.  Store just provides
121 * services to manage sets of StoreFiles.  One of the most important of those
122 * services is compaction services where files are aggregated once they pass
123 * a configurable threshold.
124 *
125 * <p>Locking and transactions are handled at a higher level.  This API should
126 * not be called directly but by an HRegion manager.
127 */
128@InterfaceAudience.Private
129public class HStore implements Store, HeapSize, StoreConfigInformation, PropagatingConfigurationObserver {
130  public static final String MEMSTORE_CLASS_NAME = "hbase.regionserver.memstore.class";
131  public static final String COMPACTCHECKER_INTERVAL_MULTIPLIER_KEY =
132      "hbase.server.compactchecker.interval.multiplier";
133  public static final String BLOCKING_STOREFILES_KEY = "hbase.hstore.blockingStoreFiles";
134  public static final String BLOCK_STORAGE_POLICY_KEY = "hbase.hstore.block.storage.policy";
135  // keep in accordance with HDFS default storage policy
136  public static final String DEFAULT_BLOCK_STORAGE_POLICY = "HOT";
137  public static final int DEFAULT_COMPACTCHECKER_INTERVAL_MULTIPLIER = 1000;
138  public static final int DEFAULT_BLOCKING_STOREFILE_COUNT = 16;
139
140  private static final Logger LOG = LoggerFactory.getLogger(HStore.class);
141
142  protected final MemStore memstore;
143  // This stores directory in the filesystem.
144  protected final HRegion region;
145  private final ColumnFamilyDescriptor family;
146  private final HRegionFileSystem fs;
147  protected Configuration conf;
148  protected CacheConfig cacheConf;
149  private long lastCompactSize = 0;
150  volatile boolean forceMajor = false;
151  /* how many bytes to write between status checks */
152  static int closeCheckInterval = 0;
153  private AtomicLong storeSize = new AtomicLong();
154  private AtomicLong totalUncompressedBytes = new AtomicLong();
155
156  /**
157   * RWLock for store operations.
158   * Locked in shared mode when the list of component stores is looked at:
159   *   - all reads/writes to table data
160   *   - checking for split
161   * Locked in exclusive mode when the list of component stores is modified:
162   *   - closing
163   *   - completing a compaction
164   */
165  final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
166  /**
167   * Lock specific to archiving compacted store files.  This avoids races around
168   * the combination of retrieving the list of compacted files and moving them to
169   * the archive directory.  Since this is usually a background process (other than
170   * on close), we don't want to handle this with the store write lock, which would
171   * block readers and degrade performance.
172   *
173   * Locked by:
174   *   - CompactedHFilesDispatchHandler via closeAndArchiveCompactedFiles()
175   *   - close()
176   */
177  final ReentrantLock archiveLock = new ReentrantLock();
178
179  private final boolean verifyBulkLoads;
180
181  private ScanInfo scanInfo;
182
183  // All access must be synchronized.
184  // TODO: ideally, this should be part of storeFileManager, as we keep passing this to it.
185  private final List<HStoreFile> filesCompacting = Lists.newArrayList();
186
187  // All access must be synchronized.
188  private final Set<ChangedReadersObserver> changedReaderObservers =
189    Collections.newSetFromMap(new ConcurrentHashMap<ChangedReadersObserver, Boolean>());
190
191  protected final int blocksize;
192  private HFileDataBlockEncoder dataBlockEncoder;
193
194  /** Checksum configuration */
195  protected ChecksumType checksumType;
196  protected int bytesPerChecksum;
197
198  // Comparing KeyValues
199  protected final CellComparator comparator;
200
201  final StoreEngine<?, ?, ?, ?> storeEngine;
202
203  private static final AtomicBoolean offPeakCompactionTracker = new AtomicBoolean();
204  private volatile OffPeakHours offPeakHours;
205
206  private static final int DEFAULT_FLUSH_RETRIES_NUMBER = 10;
207  private int flushRetriesNumber;
208  private int pauseTime;
209
210  private long blockingFileCount;
211  private int compactionCheckMultiplier;
212  protected Encryption.Context cryptoContext = Encryption.Context.NONE;
213
214  private AtomicLong flushedCellsCount = new AtomicLong();
215  private AtomicLong compactedCellsCount = new AtomicLong();
216  private AtomicLong majorCompactedCellsCount = new AtomicLong();
217  private AtomicLong flushedCellsSize = new AtomicLong();
218  private AtomicLong flushedOutputFileSize = new AtomicLong();
219  private AtomicLong compactedCellsSize = new AtomicLong();
220  private AtomicLong majorCompactedCellsSize = new AtomicLong();
221
222  /**
223   * Constructor
224   * @param region
225   * @param family HColumnDescriptor for this column
226   * @param confParam configuration object
227   * failed.  Can be null.
228   * @throws IOException
229   */
230  protected HStore(final HRegion region, final ColumnFamilyDescriptor family,
231      final Configuration confParam) throws IOException {
232
233    this.fs = region.getRegionFileSystem();
234
235    // Assemble the store's home directory and Ensure it exists.
236    fs.createStoreDir(family.getNameAsString());
237    this.region = region;
238    this.family = family;
239    // 'conf' renamed to 'confParam' b/c we use this.conf in the constructor
240    // CompoundConfiguration will look for keys in reverse order of addition, so we'd
241    // add global config first, then table and cf overrides, then cf metadata.
242    this.conf = new CompoundConfiguration()
243      .add(confParam)
244      .addBytesMap(region.getTableDescriptor().getValues())
245      .addStringMap(family.getConfiguration())
246      .addBytesMap(family.getValues());
247    this.blocksize = family.getBlocksize();
248
249    // set block storage policy for store directory
250    String policyName = family.getStoragePolicy();
251    if (null == policyName) {
252      policyName = this.conf.get(BLOCK_STORAGE_POLICY_KEY, DEFAULT_BLOCK_STORAGE_POLICY);
253    }
254    this.fs.setStoragePolicy(family.getNameAsString(), policyName.trim());
255
256    this.dataBlockEncoder = new HFileDataBlockEncoderImpl(family.getDataBlockEncoding());
257
258    this.comparator = region.getCellComparator();
259    // used by ScanQueryMatcher
260    long timeToPurgeDeletes =
261        Math.max(conf.getLong("hbase.hstore.time.to.purge.deletes", 0), 0);
262    LOG.trace("Time to purge deletes set to {}ms in store {}", timeToPurgeDeletes, this);
263    // Get TTL
264    long ttl = determineTTLFromFamily(family);
265    // Why not just pass a HColumnDescriptor in here altogether?  Even if have
266    // to clone it?
267    scanInfo = new ScanInfo(conf, family, ttl, timeToPurgeDeletes, this.comparator);
268    this.memstore = getMemstore();
269    this.offPeakHours = OffPeakHours.getInstance(conf);
270
271    // Setting up cache configuration for this family
272    createCacheConf(family);
273
274    this.verifyBulkLoads = conf.getBoolean("hbase.hstore.bulkload.verify", false);
275
276    this.blockingFileCount =
277        conf.getInt(BLOCKING_STOREFILES_KEY, DEFAULT_BLOCKING_STOREFILE_COUNT);
278    this.compactionCheckMultiplier = conf.getInt(
279        COMPACTCHECKER_INTERVAL_MULTIPLIER_KEY, DEFAULT_COMPACTCHECKER_INTERVAL_MULTIPLIER);
280    if (this.compactionCheckMultiplier <= 0) {
281      LOG.error("Compaction check period multiplier must be positive, setting default: {}",
282          DEFAULT_COMPACTCHECKER_INTERVAL_MULTIPLIER);
283      this.compactionCheckMultiplier = DEFAULT_COMPACTCHECKER_INTERVAL_MULTIPLIER;
284    }
285
286    if (HStore.closeCheckInterval == 0) {
287      HStore.closeCheckInterval = conf.getInt(
288          "hbase.hstore.close.check.interval", 10*1000*1000 /* 10 MB */);
289    }
290
291    this.storeEngine = createStoreEngine(this, this.conf, this.comparator);
292    this.storeEngine.getStoreFileManager().loadFiles(loadStoreFiles());
293
294    // Initialize checksum type from name. The names are CRC32, CRC32C, etc.
295    this.checksumType = getChecksumType(conf);
296    // Initialize bytes per checksum
297    this.bytesPerChecksum = getBytesPerChecksum(conf);
298    flushRetriesNumber = conf.getInt(
299        "hbase.hstore.flush.retries.number", DEFAULT_FLUSH_RETRIES_NUMBER);
300    pauseTime = conf.getInt(HConstants.HBASE_SERVER_PAUSE, HConstants.DEFAULT_HBASE_SERVER_PAUSE);
301    if (flushRetriesNumber <= 0) {
302      throw new IllegalArgumentException(
303          "hbase.hstore.flush.retries.number must be > 0, not "
304              + flushRetriesNumber);
305    }
306    cryptoContext = EncryptionUtil.createEncryptionContext(conf, family);
307
308    LOG.info("Store={},  memstore type={}, storagePolicy={}, verifyBulkLoads={}",
309        getColumnFamilyName(), this.memstore.getClass().getSimpleName(), policyName,
310        this.verifyBulkLoads);
311  }
312
313  /**
314   * @return MemStore Instance to use in this store.
315   */
316  private MemStore getMemstore() {
317    MemStore ms = null;
318    // Check if in-memory-compaction configured. Note MemoryCompactionPolicy is an enum!
319    MemoryCompactionPolicy inMemoryCompaction = null;
320    if (this.getTableName().isSystemTable()) {
321      inMemoryCompaction = MemoryCompactionPolicy.valueOf(
322          conf.get("hbase.systemtables.compacting.memstore.type", "NONE"));
323    } else {
324      inMemoryCompaction = family.getInMemoryCompaction();
325    }
326    if (inMemoryCompaction == null) {
327      inMemoryCompaction =
328          MemoryCompactionPolicy.valueOf(conf.get(CompactingMemStore.COMPACTING_MEMSTORE_TYPE_KEY,
329              CompactingMemStore.COMPACTING_MEMSTORE_TYPE_DEFAULT));
330    }
331    switch (inMemoryCompaction) {
332      case NONE:
333        ms = ReflectionUtils.newInstance(DefaultMemStore.class,
334            new Object[] { conf, this.comparator,
335                this.getHRegion().getRegionServicesForStores()});
336        break;
337      default:
338        Class<? extends CompactingMemStore> clz = conf.getClass(MEMSTORE_CLASS_NAME,
339            CompactingMemStore.class, CompactingMemStore.class);
340        ms = ReflectionUtils.newInstance(clz, new Object[]{conf, this.comparator, this,
341            this.getHRegion().getRegionServicesForStores(), inMemoryCompaction});
342    }
343    return ms;
344  }
345
346  /**
347   * Creates the cache config.
348   * @param family The current column family.
349   */
350  protected void createCacheConf(final ColumnFamilyDescriptor family) {
351    this.cacheConf = new CacheConfig(conf, family);
352  }
353
354  /**
355   * Creates the store engine configured for the given Store.
356   * @param store The store. An unfortunate dependency needed due to it
357   *              being passed to coprocessors via the compactor.
358   * @param conf Store configuration.
359   * @param kvComparator KVComparator for storeFileManager.
360   * @return StoreEngine to use.
361   */
362  protected StoreEngine<?, ?, ?, ?> createStoreEngine(HStore store, Configuration conf,
363      CellComparator kvComparator) throws IOException {
364    return StoreEngine.create(store, conf, comparator);
365  }
366
367  /**
368   * @param family
369   * @return TTL in seconds of the specified family
370   */
371  public static long determineTTLFromFamily(final ColumnFamilyDescriptor family) {
372    // HCD.getTimeToLive returns ttl in seconds.  Convert to milliseconds.
373    long ttl = family.getTimeToLive();
374    if (ttl == HConstants.FOREVER) {
375      // Default is unlimited ttl.
376      ttl = Long.MAX_VALUE;
377    } else if (ttl == -1) {
378      ttl = Long.MAX_VALUE;
379    } else {
380      // Second -> ms adjust for user data
381      ttl *= 1000;
382    }
383    return ttl;
384  }
385
386  @Override
387  public String getColumnFamilyName() {
388    return this.family.getNameAsString();
389  }
390
391  @Override
392  public TableName getTableName() {
393    return this.getRegionInfo().getTable();
394  }
395
396  @Override
397  public FileSystem getFileSystem() {
398    return this.fs.getFileSystem();
399  }
400
401  public HRegionFileSystem getRegionFileSystem() {
402    return this.fs;
403  }
404
405  /* Implementation of StoreConfigInformation */
406  @Override
407  public long getStoreFileTtl() {
408    // TTL only applies if there's no MIN_VERSIONs setting on the column.
409    return (this.scanInfo.getMinVersions() == 0) ? this.scanInfo.getTtl() : Long.MAX_VALUE;
410  }
411
412  @Override
413  public long getMemStoreFlushSize() {
414    // TODO: Why is this in here?  The flushsize of the region rather than the store?  St.Ack
415    return this.region.memstoreFlushSize;
416  }
417
418  @Override
419  public MemStoreSize getFlushableSize() {
420    return this.memstore.getFlushableSize();
421  }
422
423  @Override
424  public MemStoreSize getSnapshotSize() {
425    return this.memstore.getSnapshotSize();
426  }
427
428  @Override
429  public long getCompactionCheckMultiplier() {
430    return this.compactionCheckMultiplier;
431  }
432
433  @Override
434  public long getBlockingFileCount() {
435    return blockingFileCount;
436  }
437  /* End implementation of StoreConfigInformation */
438
439  /**
440   * Returns the configured bytesPerChecksum value.
441   * @param conf The configuration
442   * @return The bytesPerChecksum that is set in the configuration
443   */
444  public static int getBytesPerChecksum(Configuration conf) {
445    return conf.getInt(HConstants.BYTES_PER_CHECKSUM,
446                       HFile.DEFAULT_BYTES_PER_CHECKSUM);
447  }
448
449  /**
450   * Returns the configured checksum algorithm.
451   * @param conf The configuration
452   * @return The checksum algorithm that is set in the configuration
453   */
454  public static ChecksumType getChecksumType(Configuration conf) {
455    String checksumName = conf.get(HConstants.CHECKSUM_TYPE_NAME);
456    if (checksumName == null) {
457      return ChecksumType.getDefaultChecksumType();
458    } else {
459      return ChecksumType.nameToType(checksumName);
460    }
461  }
462
463  /**
464   * @return how many bytes to write between status checks
465   */
466  public static int getCloseCheckInterval() {
467    return closeCheckInterval;
468  }
469
470  @Override
471  public ColumnFamilyDescriptor getColumnFamilyDescriptor() {
472    return this.family;
473  }
474
475  @Override
476  public OptionalLong getMaxSequenceId() {
477    return StoreUtils.getMaxSequenceIdInList(this.getStorefiles());
478  }
479
480  @Override
481  public OptionalLong getMaxMemStoreTS() {
482    return StoreUtils.getMaxMemStoreTSInList(this.getStorefiles());
483  }
484
485  /**
486   * @param tabledir {@link Path} to where the table is being stored
487   * @param hri {@link RegionInfo} for the region.
488   * @param family {@link ColumnFamilyDescriptor} describing the column family
489   * @return Path to family/Store home directory.
490   */
491  @Deprecated
492  public static Path getStoreHomedir(final Path tabledir,
493      final RegionInfo hri, final byte[] family) {
494    return getStoreHomedir(tabledir, hri.getEncodedName(), family);
495  }
496
497  /**
498   * @param tabledir {@link Path} to where the table is being stored
499   * @param encodedName Encoded region name.
500   * @param family {@link ColumnFamilyDescriptor} describing the column family
501   * @return Path to family/Store home directory.
502   */
503  @Deprecated
504  public static Path getStoreHomedir(final Path tabledir,
505      final String encodedName, final byte[] family) {
506    return new Path(tabledir, new Path(encodedName, Bytes.toString(family)));
507  }
508
509  /**
510   * @return the data block encoder
511   */
512  public HFileDataBlockEncoder getDataBlockEncoder() {
513    return dataBlockEncoder;
514  }
515
516  /**
517   * Should be used only in tests.
518   * @param blockEncoder the block delta encoder to use
519   */
520  void setDataBlockEncoderInTest(HFileDataBlockEncoder blockEncoder) {
521    this.dataBlockEncoder = blockEncoder;
522  }
523
524  /**
525   * Creates an unsorted list of StoreFile loaded in parallel
526   * from the given directory.
527   * @throws IOException
528   */
529  private List<HStoreFile> loadStoreFiles() throws IOException {
530    Collection<StoreFileInfo> files = fs.getStoreFiles(getColumnFamilyName());
531    return openStoreFiles(files);
532  }
533
534  private List<HStoreFile> openStoreFiles(Collection<StoreFileInfo> files) throws IOException {
535    if (CollectionUtils.isEmpty(files)) {
536      return Collections.emptyList();
537    }
538    // initialize the thread pool for opening store files in parallel..
539    ThreadPoolExecutor storeFileOpenerThreadPool =
540      this.region.getStoreFileOpenAndCloseThreadPool("StoreFileOpenerThread-" +
541          this.getColumnFamilyName());
542    CompletionService<HStoreFile> completionService = new ExecutorCompletionService<>(storeFileOpenerThreadPool);
543
544    int totalValidStoreFile = 0;
545    for (StoreFileInfo storeFileInfo : files) {
546      // open each store file in parallel
547      completionService.submit(() -> this.createStoreFileAndReader(storeFileInfo));
548      totalValidStoreFile++;
549    }
550
551    ArrayList<HStoreFile> results = new ArrayList<>(files.size());
552    IOException ioe = null;
553    try {
554      for (int i = 0; i < totalValidStoreFile; i++) {
555        try {
556          HStoreFile storeFile = completionService.take().get();
557          if (storeFile != null) {
558            long length = storeFile.getReader().length();
559            this.storeSize.addAndGet(length);
560            this.totalUncompressedBytes
561                .addAndGet(storeFile.getReader().getTotalUncompressedBytes());
562            LOG.debug("loaded {}", storeFile);
563            results.add(storeFile);
564          }
565        } catch (InterruptedException e) {
566          if (ioe == null) ioe = new InterruptedIOException(e.getMessage());
567        } catch (ExecutionException e) {
568          if (ioe == null) ioe = new IOException(e.getCause());
569        }
570      }
571    } finally {
572      storeFileOpenerThreadPool.shutdownNow();
573    }
574    if (ioe != null) {
575      // close StoreFile readers
576      boolean evictOnClose =
577          cacheConf != null? cacheConf.shouldEvictOnClose(): true;
578      for (HStoreFile file : results) {
579        try {
580          if (file != null) {
581            file.closeStoreFile(evictOnClose);
582          }
583        } catch (IOException e) {
584          LOG.warn("Could not close store file", e);
585        }
586      }
587      throw ioe;
588    }
589
590    return results;
591  }
592
593  @Override
594  public void refreshStoreFiles() throws IOException {
595    Collection<StoreFileInfo> newFiles = fs.getStoreFiles(getColumnFamilyName());
596    refreshStoreFilesInternal(newFiles);
597  }
598
599  /**
600   * Replaces the store files that the store has with the given files. Mainly used by secondary
601   * region replicas to keep up to date with the primary region files.
602   * @throws IOException
603   */
604  public void refreshStoreFiles(Collection<String> newFiles) throws IOException {
605    List<StoreFileInfo> storeFiles = new ArrayList<>(newFiles.size());
606    for (String file : newFiles) {
607      storeFiles.add(fs.getStoreFileInfo(getColumnFamilyName(), file));
608    }
609    refreshStoreFilesInternal(storeFiles);
610  }
611
612  /**
613   * Checks the underlying store files, and opens the files that  have not
614   * been opened, and removes the store file readers for store files no longer
615   * available. Mainly used by secondary region replicas to keep up to date with
616   * the primary region files.
617   * @throws IOException
618   */
619  private void refreshStoreFilesInternal(Collection<StoreFileInfo> newFiles) throws IOException {
620    StoreFileManager sfm = storeEngine.getStoreFileManager();
621    Collection<HStoreFile> currentFiles = sfm.getStorefiles();
622    Collection<HStoreFile> compactedFiles = sfm.getCompactedfiles();
623    if (currentFiles == null) currentFiles = Collections.emptySet();
624    if (newFiles == null) newFiles = Collections.emptySet();
625    if (compactedFiles == null) compactedFiles = Collections.emptySet();
626
627    HashMap<StoreFileInfo, HStoreFile> currentFilesSet = new HashMap<>(currentFiles.size());
628    for (HStoreFile sf : currentFiles) {
629      currentFilesSet.put(sf.getFileInfo(), sf);
630    }
631    HashMap<StoreFileInfo, HStoreFile> compactedFilesSet = new HashMap<>(compactedFiles.size());
632    for (HStoreFile sf : compactedFiles) {
633      compactedFilesSet.put(sf.getFileInfo(), sf);
634    }
635
636    Set<StoreFileInfo> newFilesSet = new HashSet<StoreFileInfo>(newFiles);
637    // Exclude the files that have already been compacted
638    newFilesSet = Sets.difference(newFilesSet, compactedFilesSet.keySet());
639    Set<StoreFileInfo> toBeAddedFiles = Sets.difference(newFilesSet, currentFilesSet.keySet());
640    Set<StoreFileInfo> toBeRemovedFiles = Sets.difference(currentFilesSet.keySet(), newFilesSet);
641
642    if (toBeAddedFiles.isEmpty() && toBeRemovedFiles.isEmpty()) {
643      return;
644    }
645
646    LOG.info("Refreshing store files for region " + this.getRegionInfo().getRegionNameAsString()
647      + " files to add: " + toBeAddedFiles + " files to remove: " + toBeRemovedFiles);
648
649    Set<HStoreFile> toBeRemovedStoreFiles = new HashSet<>(toBeRemovedFiles.size());
650    for (StoreFileInfo sfi : toBeRemovedFiles) {
651      toBeRemovedStoreFiles.add(currentFilesSet.get(sfi));
652    }
653
654    // try to open the files
655    List<HStoreFile> openedFiles = openStoreFiles(toBeAddedFiles);
656
657    // propogate the file changes to the underlying store file manager
658    replaceStoreFiles(toBeRemovedStoreFiles, openedFiles); //won't throw an exception
659
660    // Advance the memstore read point to be at least the new store files seqIds so that
661    // readers might pick it up. This assumes that the store is not getting any writes (otherwise
662    // in-flight transactions might be made visible)
663    if (!toBeAddedFiles.isEmpty()) {
664      // we must have the max sequence id here as we do have several store files
665      region.getMVCC().advanceTo(this.getMaxSequenceId().getAsLong());
666    }
667
668    completeCompaction(toBeRemovedStoreFiles);
669  }
670
671  @VisibleForTesting
672  protected HStoreFile createStoreFileAndReader(final Path p) throws IOException {
673    StoreFileInfo info = new StoreFileInfo(conf, this.getFileSystem(), p);
674    return createStoreFileAndReader(info);
675  }
676
677  private HStoreFile createStoreFileAndReader(StoreFileInfo info) throws IOException {
678    info.setRegionCoprocessorHost(this.region.getCoprocessorHost());
679    HStoreFile storeFile = new HStoreFile(this.getFileSystem(), info, this.conf, this.cacheConf,
680        this.family.getBloomFilterType(), isPrimaryReplicaStore());
681    storeFile.initReader();
682    return storeFile;
683  }
684
685  /**
686   * This message intends to inform the MemStore that next coming updates
687   * are going to be part of the replaying edits from WAL
688   */
689  public void startReplayingFromWAL(){
690    this.memstore.startReplayingFromWAL();
691  }
692
693  /**
694   * This message intends to inform the MemStore that the replaying edits from WAL
695   * are done
696   */
697  public void stopReplayingFromWAL(){
698    this.memstore.stopReplayingFromWAL();
699  }
700
701  /**
702   * Adds a value to the memstore
703   */
704  public void add(final Cell cell, MemStoreSizing memstoreSizing) {
705    lock.readLock().lock();
706    try {
707       this.memstore.add(cell, memstoreSizing);
708    } finally {
709      lock.readLock().unlock();
710    }
711  }
712
713  /**
714   * Adds the specified value to the memstore
715   */
716  public void add(final Iterable<Cell> cells, MemStoreSizing memstoreSizing) {
717    lock.readLock().lock();
718    try {
719      memstore.add(cells, memstoreSizing);
720    } finally {
721      lock.readLock().unlock();
722    }
723  }
724
725  @Override
726  public long timeOfOldestEdit() {
727    return memstore.timeOfOldestEdit();
728  }
729
730  /**
731   * @return All store files.
732   */
733  @Override
734  public Collection<HStoreFile> getStorefiles() {
735    return this.storeEngine.getStoreFileManager().getStorefiles();
736  }
737
738  @Override
739  public Collection<HStoreFile> getCompactedFiles() {
740    return this.storeEngine.getStoreFileManager().getCompactedfiles();
741  }
742
743  /**
744   * This throws a WrongRegionException if the HFile does not fit in this region, or an
745   * InvalidHFileException if the HFile is not valid.
746   */
747  public void assertBulkLoadHFileOk(Path srcPath) throws IOException {
748    HFile.Reader reader  = null;
749    try {
750      LOG.info("Validating hfile at " + srcPath + " for inclusion in "
751          + "store " + this + " region " + this.getRegionInfo().getRegionNameAsString());
752      reader = HFile.createReader(srcPath.getFileSystem(conf), srcPath, cacheConf,
753        isPrimaryReplicaStore(), conf);
754      reader.loadFileInfo();
755
756      Optional<byte[]> firstKey = reader.getFirstRowKey();
757      Preconditions.checkState(firstKey.isPresent(), "First key can not be null");
758      Optional<Cell> lk = reader.getLastKey();
759      Preconditions.checkState(lk.isPresent(), "Last key can not be null");
760      byte[] lastKey =  CellUtil.cloneRow(lk.get());
761
762      if (LOG.isDebugEnabled()) {
763        LOG.debug("HFile bounds: first=" + Bytes.toStringBinary(firstKey.get()) +
764            " last=" + Bytes.toStringBinary(lastKey));
765        LOG.debug("Region bounds: first=" +
766            Bytes.toStringBinary(getRegionInfo().getStartKey()) +
767            " last=" + Bytes.toStringBinary(getRegionInfo().getEndKey()));
768      }
769
770      if (!this.getRegionInfo().containsRange(firstKey.get(), lastKey)) {
771        throw new WrongRegionException(
772            "Bulk load file " + srcPath.toString() + " does not fit inside region "
773            + this.getRegionInfo().getRegionNameAsString());
774      }
775
776      if(reader.length() > conf.getLong(HConstants.HREGION_MAX_FILESIZE,
777          HConstants.DEFAULT_MAX_FILE_SIZE)) {
778        LOG.warn("Trying to bulk load hfile " + srcPath + " with size: " +
779            reader.length() + " bytes can be problematic as it may lead to oversplitting.");
780      }
781
782      if (verifyBulkLoads) {
783        long verificationStartTime = EnvironmentEdgeManager.currentTime();
784        LOG.info("Full verification started for bulk load hfile: {}", srcPath);
785        Cell prevCell = null;
786        HFileScanner scanner = reader.getScanner(false, false, false);
787        scanner.seekTo();
788        do {
789          Cell cell = scanner.getCell();
790          if (prevCell != null) {
791            if (comparator.compareRows(prevCell, cell) > 0) {
792              throw new InvalidHFileException("Previous row is greater than"
793                  + " current row: path=" + srcPath + " previous="
794                  + CellUtil.getCellKeyAsString(prevCell) + " current="
795                  + CellUtil.getCellKeyAsString(cell));
796            }
797            if (CellComparator.getInstance().compareFamilies(prevCell, cell) != 0) {
798              throw new InvalidHFileException("Previous key had different"
799                  + " family compared to current key: path=" + srcPath
800                  + " previous="
801                  + Bytes.toStringBinary(prevCell.getFamilyArray(), prevCell.getFamilyOffset(),
802                      prevCell.getFamilyLength())
803                  + " current="
804                  + Bytes.toStringBinary(cell.getFamilyArray(), cell.getFamilyOffset(),
805                      cell.getFamilyLength()));
806            }
807          }
808          prevCell = cell;
809        } while (scanner.next());
810      LOG.info("Full verification complete for bulk load hfile: " + srcPath.toString()
811         + " took " + (EnvironmentEdgeManager.currentTime() - verificationStartTime)
812         + " ms");
813      }
814    } finally {
815      if (reader != null) reader.close();
816    }
817  }
818
819  /**
820   * This method should only be called from Region. It is assumed that the ranges of values in the
821   * HFile fit within the stores assigned region. (assertBulkLoadHFileOk checks this)
822   *
823   * @param srcPathStr
824   * @param seqNum sequence Id associated with the HFile
825   */
826  public Pair<Path, Path> preBulkLoadHFile(String srcPathStr, long seqNum) throws IOException {
827    Path srcPath = new Path(srcPathStr);
828    return fs.bulkLoadStoreFile(getColumnFamilyName(), srcPath, seqNum);
829  }
830
831  public Path bulkLoadHFile(byte[] family, String srcPathStr, Path dstPath) throws IOException {
832    Path srcPath = new Path(srcPathStr);
833    try {
834      fs.commitStoreFile(srcPath, dstPath);
835    } finally {
836      if (this.getCoprocessorHost() != null) {
837        this.getCoprocessorHost().postCommitStoreFile(family, srcPath, dstPath);
838      }
839    }
840
841    LOG.info("Loaded HFile " + srcPath + " into store '" + getColumnFamilyName() + "' as "
842        + dstPath + " - updating store file list.");
843
844    HStoreFile sf = createStoreFileAndReader(dstPath);
845    bulkLoadHFile(sf);
846
847    LOG.info("Successfully loaded store file {} into store {} (new location: {})",
848        srcPath, this, dstPath);
849
850    return dstPath;
851  }
852
853  public void bulkLoadHFile(StoreFileInfo fileInfo) throws IOException {
854    HStoreFile sf = createStoreFileAndReader(fileInfo);
855    bulkLoadHFile(sf);
856  }
857
858  private void bulkLoadHFile(HStoreFile sf) throws IOException {
859    StoreFileReader r = sf.getReader();
860    this.storeSize.addAndGet(r.length());
861    this.totalUncompressedBytes.addAndGet(r.getTotalUncompressedBytes());
862
863    // Append the new storefile into the list
864    this.lock.writeLock().lock();
865    try {
866      this.storeEngine.getStoreFileManager().insertNewFiles(Lists.newArrayList(sf));
867    } finally {
868      // We need the lock, as long as we are updating the storeFiles
869      // or changing the memstore. Let us release it before calling
870      // notifyChangeReadersObservers. See HBASE-4485 for a possible
871      // deadlock scenario that could have happened if continue to hold
872      // the lock.
873      this.lock.writeLock().unlock();
874    }
875    LOG.info("Loaded HFile " + sf.getFileInfo() + " into store '" + getColumnFamilyName());
876    if (LOG.isTraceEnabled()) {
877      String traceMessage = "BULK LOAD time,size,store size,store files ["
878          + EnvironmentEdgeManager.currentTime() + "," + r.length() + "," + storeSize
879          + "," + storeEngine.getStoreFileManager().getStorefileCount() + "]";
880      LOG.trace(traceMessage);
881    }
882  }
883
884  /**
885   * Close all the readers We don't need to worry about subsequent requests because the Region holds
886   * a write lock that will prevent any more reads or writes.
887   * @return the {@link StoreFile StoreFiles} that were previously being used.
888   * @throws IOException on failure
889   */
890  public ImmutableCollection<HStoreFile> close() throws IOException {
891    this.archiveLock.lock();
892    this.lock.writeLock().lock();
893    try {
894      // Clear so metrics doesn't find them.
895      ImmutableCollection<HStoreFile> result = storeEngine.getStoreFileManager().clearFiles();
896      Collection<HStoreFile> compactedfiles =
897          storeEngine.getStoreFileManager().clearCompactedFiles();
898      // clear the compacted files
899      if (CollectionUtils.isNotEmpty(compactedfiles)) {
900        removeCompactedfiles(compactedfiles);
901      }
902      if (!result.isEmpty()) {
903        // initialize the thread pool for closing store files in parallel.
904        ThreadPoolExecutor storeFileCloserThreadPool = this.region
905            .getStoreFileOpenAndCloseThreadPool("StoreFileCloserThread-"
906                + this.getColumnFamilyName());
907
908        // close each store file in parallel
909        CompletionService<Void> completionService =
910          new ExecutorCompletionService<>(storeFileCloserThreadPool);
911        for (HStoreFile f : result) {
912          completionService.submit(new Callable<Void>() {
913            @Override
914            public Void call() throws IOException {
915              boolean evictOnClose =
916                  cacheConf != null? cacheConf.shouldEvictOnClose(): true;
917              f.closeStoreFile(evictOnClose);
918              return null;
919            }
920          });
921        }
922
923        IOException ioe = null;
924        try {
925          for (int i = 0; i < result.size(); i++) {
926            try {
927              Future<Void> future = completionService.take();
928              future.get();
929            } catch (InterruptedException e) {
930              if (ioe == null) {
931                ioe = new InterruptedIOException();
932                ioe.initCause(e);
933              }
934            } catch (ExecutionException e) {
935              if (ioe == null) ioe = new IOException(e.getCause());
936            }
937          }
938        } finally {
939          storeFileCloserThreadPool.shutdownNow();
940        }
941        if (ioe != null) throw ioe;
942      }
943      LOG.trace("Closed {}", this);
944      return result;
945    } finally {
946      this.lock.writeLock().unlock();
947      this.archiveLock.unlock();
948    }
949  }
950
951  /**
952   * Snapshot this stores memstore. Call before running
953   * {@link #flushCache(long, MemStoreSnapshot, MonitoredTask, ThroughputController,
954   * FlushLifeCycleTracker)}
955   *  so it has some work to do.
956   */
957  void snapshot() {
958    this.lock.writeLock().lock();
959    try {
960      this.memstore.snapshot();
961    } finally {
962      this.lock.writeLock().unlock();
963    }
964  }
965
966  /**
967   * Write out current snapshot. Presumes {@link #snapshot()} has been called previously.
968   * @param logCacheFlushId flush sequence number
969   * @param snapshot
970   * @param status
971   * @param throughputController
972   * @return The path name of the tmp file to which the store was flushed
973   * @throws IOException if exception occurs during process
974   */
975  protected List<Path> flushCache(final long logCacheFlushId, MemStoreSnapshot snapshot,
976      MonitoredTask status, ThroughputController throughputController,
977      FlushLifeCycleTracker tracker) throws IOException {
978    // If an exception happens flushing, we let it out without clearing
979    // the memstore snapshot.  The old snapshot will be returned when we say
980    // 'snapshot', the next time flush comes around.
981    // Retry after catching exception when flushing, otherwise server will abort
982    // itself
983    StoreFlusher flusher = storeEngine.getStoreFlusher();
984    IOException lastException = null;
985    for (int i = 0; i < flushRetriesNumber; i++) {
986      try {
987        List<Path> pathNames =
988            flusher.flushSnapshot(snapshot, logCacheFlushId, status, throughputController, tracker);
989        Path lastPathName = null;
990        try {
991          for (Path pathName : pathNames) {
992            lastPathName = pathName;
993            validateStoreFile(pathName);
994          }
995          return pathNames;
996        } catch (Exception e) {
997          LOG.warn("Failed validating store file {}, retrying num={}", lastPathName, i, e);
998          if (e instanceof IOException) {
999            lastException = (IOException) e;
1000          } else {
1001            lastException = new IOException(e);
1002          }
1003        }
1004      } catch (IOException e) {
1005        LOG.warn("Failed flushing store file, retrying num={}", i, e);
1006        lastException = e;
1007      }
1008      if (lastException != null && i < (flushRetriesNumber - 1)) {
1009        try {
1010          Thread.sleep(pauseTime);
1011        } catch (InterruptedException e) {
1012          IOException iie = new InterruptedIOException();
1013          iie.initCause(e);
1014          throw iie;
1015        }
1016      }
1017    }
1018    throw lastException;
1019  }
1020
1021  /**
1022   * @param path The pathname of the tmp file into which the store was flushed
1023   * @param logCacheFlushId
1024   * @param status
1025   * @return store file created.
1026   * @throws IOException
1027   */
1028  private HStoreFile commitFile(Path path, long logCacheFlushId, MonitoredTask status)
1029      throws IOException {
1030    // Write-out finished successfully, move into the right spot
1031    Path dstPath = fs.commitStoreFile(getColumnFamilyName(), path);
1032
1033    status.setStatus("Flushing " + this + ": reopening flushed file");
1034    HStoreFile sf = createStoreFileAndReader(dstPath);
1035
1036    StoreFileReader r = sf.getReader();
1037    this.storeSize.addAndGet(r.length());
1038    this.totalUncompressedBytes.addAndGet(r.getTotalUncompressedBytes());
1039
1040    if (LOG.isInfoEnabled()) {
1041      LOG.info("Added " + sf + ", entries=" + r.getEntries() +
1042        ", sequenceid=" + logCacheFlushId +
1043        ", filesize=" + TraditionalBinaryPrefix.long2String(r.length(), "", 1));
1044    }
1045    return sf;
1046  }
1047
1048  /**
1049   * @param maxKeyCount
1050   * @param compression Compression algorithm to use
1051   * @param isCompaction whether we are creating a new file in a compaction
1052   * @param includeMVCCReadpoint - whether to include MVCC or not
1053   * @param includesTag - includesTag or not
1054   * @return Writer for a new StoreFile in the tmp dir.
1055   */
1056  // TODO : allow the Writer factory to create Writers of ShipperListener type only in case of
1057  // compaction
1058  public StoreFileWriter createWriterInTmp(long maxKeyCount, Compression.Algorithm compression,
1059      boolean isCompaction, boolean includeMVCCReadpoint, boolean includesTag,
1060      boolean shouldDropBehind) throws IOException {
1061    final CacheConfig writerCacheConf;
1062    if (isCompaction) {
1063      // Don't cache data on write on compactions.
1064      writerCacheConf = new CacheConfig(cacheConf);
1065      writerCacheConf.setCacheDataOnWrite(false);
1066    } else {
1067      writerCacheConf = cacheConf;
1068    }
1069    InetSocketAddress[] favoredNodes = null;
1070    if (region.getRegionServerServices() != null) {
1071      favoredNodes = region.getRegionServerServices().getFavoredNodesForRegion(
1072          region.getRegionInfo().getEncodedName());
1073    }
1074    HFileContext hFileContext = createFileContext(compression, includeMVCCReadpoint, includesTag,
1075      cryptoContext);
1076    Path familyTempDir = new Path(fs.getTempDir(), family.getNameAsString());
1077    StoreFileWriter.Builder builder = new StoreFileWriter.Builder(conf, writerCacheConf,
1078        this.getFileSystem())
1079            .withOutputDir(familyTempDir)
1080            .withComparator(comparator)
1081            .withBloomType(family.getBloomFilterType())
1082            .withMaxKeyCount(maxKeyCount)
1083            .withFavoredNodes(favoredNodes)
1084            .withFileContext(hFileContext)
1085            .withShouldDropCacheBehind(shouldDropBehind);
1086    return builder.build();
1087  }
1088
1089  private HFileContext createFileContext(Compression.Algorithm compression,
1090      boolean includeMVCCReadpoint, boolean includesTag, Encryption.Context cryptoContext) {
1091    if (compression == null) {
1092      compression = HFile.DEFAULT_COMPRESSION_ALGORITHM;
1093    }
1094    HFileContext hFileContext = new HFileContextBuilder()
1095                                .withIncludesMvcc(includeMVCCReadpoint)
1096                                .withIncludesTags(includesTag)
1097                                .withCompression(compression)
1098                                .withCompressTags(family.isCompressTags())
1099                                .withChecksumType(checksumType)
1100                                .withBytesPerCheckSum(bytesPerChecksum)
1101                                .withBlockSize(blocksize)
1102                                .withHBaseCheckSum(true)
1103                                .withDataBlockEncoding(family.getDataBlockEncoding())
1104                                .withEncryptionContext(cryptoContext)
1105                                .withCreateTime(EnvironmentEdgeManager.currentTime())
1106                                .build();
1107    return hFileContext;
1108  }
1109
1110
1111  private long getTotalSize(Collection<HStoreFile> sfs) {
1112    return sfs.stream().mapToLong(sf -> sf.getReader().length()).sum();
1113  }
1114
1115  /**
1116   * Change storeFiles adding into place the Reader produced by this new flush.
1117   * @param sfs Store files
1118   * @param snapshotId
1119   * @throws IOException
1120   * @return Whether compaction is required.
1121   */
1122  private boolean updateStorefiles(List<HStoreFile> sfs, long snapshotId) throws IOException {
1123    this.lock.writeLock().lock();
1124    try {
1125      this.storeEngine.getStoreFileManager().insertNewFiles(sfs);
1126      if (snapshotId > 0) {
1127        this.memstore.clearSnapshot(snapshotId);
1128      }
1129    } finally {
1130      // We need the lock, as long as we are updating the storeFiles
1131      // or changing the memstore. Let us release it before calling
1132      // notifyChangeReadersObservers. See HBASE-4485 for a possible
1133      // deadlock scenario that could have happened if continue to hold
1134      // the lock.
1135      this.lock.writeLock().unlock();
1136    }
1137    // notify to be called here - only in case of flushes
1138    notifyChangedReadersObservers(sfs);
1139    if (LOG.isTraceEnabled()) {
1140      long totalSize = getTotalSize(sfs);
1141      String traceMessage = "FLUSH time,count,size,store size,store files ["
1142          + EnvironmentEdgeManager.currentTime() + "," + sfs.size() + "," + totalSize
1143          + "," + storeSize + "," + storeEngine.getStoreFileManager().getStorefileCount() + "]";
1144      LOG.trace(traceMessage);
1145    }
1146    return needsCompaction();
1147  }
1148
1149  /**
1150   * Notify all observers that set of Readers has changed.
1151   * @throws IOException
1152   */
1153  private void notifyChangedReadersObservers(List<HStoreFile> sfs) throws IOException {
1154    for (ChangedReadersObserver o : this.changedReaderObservers) {
1155      List<KeyValueScanner> memStoreScanners;
1156      this.lock.readLock().lock();
1157      try {
1158        memStoreScanners = this.memstore.getScanners(o.getReadPoint());
1159      } finally {
1160        this.lock.readLock().unlock();
1161      }
1162      o.updateReaders(sfs, memStoreScanners);
1163    }
1164  }
1165
1166  /**
1167   * Get all scanners with no filtering based on TTL (that happens further down the line).
1168   * @param cacheBlocks cache the blocks or not
1169   * @param usePread true to use pread, false if not
1170   * @param isCompaction true if the scanner is created for compaction
1171   * @param matcher the scan query matcher
1172   * @param startRow the start row
1173   * @param stopRow the stop row
1174   * @param readPt the read point of the current scan
1175   * @return all scanners for this store
1176   */
1177  public List<KeyValueScanner> getScanners(boolean cacheBlocks, boolean isGet, boolean usePread,
1178      boolean isCompaction, ScanQueryMatcher matcher, byte[] startRow, byte[] stopRow, long readPt)
1179      throws IOException {
1180    return getScanners(cacheBlocks, usePread, isCompaction, matcher, startRow, true, stopRow, false,
1181      readPt);
1182  }
1183
1184  /**
1185   * Get all scanners with no filtering based on TTL (that happens further down the line).
1186   * @param cacheBlocks cache the blocks or not
1187   * @param usePread true to use pread, false if not
1188   * @param isCompaction true if the scanner is created for compaction
1189   * @param matcher the scan query matcher
1190   * @param startRow the start row
1191   * @param includeStartRow true to include start row, false if not
1192   * @param stopRow the stop row
1193   * @param includeStopRow true to include stop row, false if not
1194   * @param readPt the read point of the current scan
1195   * @return all scanners for this store
1196   */
1197  public List<KeyValueScanner> getScanners(boolean cacheBlocks, boolean usePread,
1198      boolean isCompaction, ScanQueryMatcher matcher, byte[] startRow, boolean includeStartRow,
1199      byte[] stopRow, boolean includeStopRow, long readPt) throws IOException {
1200    Collection<HStoreFile> storeFilesToScan;
1201    List<KeyValueScanner> memStoreScanners;
1202    this.lock.readLock().lock();
1203    try {
1204      storeFilesToScan = this.storeEngine.getStoreFileManager().getFilesForScan(startRow,
1205        includeStartRow, stopRow, includeStopRow);
1206      memStoreScanners = this.memstore.getScanners(readPt);
1207    } finally {
1208      this.lock.readLock().unlock();
1209    }
1210
1211    // First the store file scanners
1212
1213    // TODO this used to get the store files in descending order,
1214    // but now we get them in ascending order, which I think is
1215    // actually more correct, since memstore get put at the end.
1216    List<StoreFileScanner> sfScanners = StoreFileScanner.getScannersForStoreFiles(storeFilesToScan,
1217      cacheBlocks, usePread, isCompaction, false, matcher, readPt);
1218    List<KeyValueScanner> scanners = new ArrayList<>(sfScanners.size() + 1);
1219    scanners.addAll(sfScanners);
1220    // Then the memstore scanners
1221    scanners.addAll(memStoreScanners);
1222    return scanners;
1223  }
1224
1225  /**
1226   * Create scanners on the given files and if needed on the memstore with no filtering based on TTL
1227   * (that happens further down the line).
1228   * @param files the list of files on which the scanners has to be created
1229   * @param cacheBlocks cache the blocks or not
1230   * @param usePread true to use pread, false if not
1231   * @param isCompaction true if the scanner is created for compaction
1232   * @param matcher the scan query matcher
1233   * @param startRow the start row
1234   * @param stopRow the stop row
1235   * @param readPt the read point of the current scan
1236   * @param includeMemstoreScanner true if memstore has to be included
1237   * @return scanners on the given files and on the memstore if specified
1238   */
1239  public List<KeyValueScanner> getScanners(List<HStoreFile> files, boolean cacheBlocks,
1240      boolean isGet, boolean usePread, boolean isCompaction, ScanQueryMatcher matcher,
1241      byte[] startRow, byte[] stopRow, long readPt, boolean includeMemstoreScanner)
1242      throws IOException {
1243    return getScanners(files, cacheBlocks, usePread, isCompaction, matcher, startRow, true, stopRow,
1244      false, readPt, includeMemstoreScanner);
1245  }
1246
1247  /**
1248   * Create scanners on the given files and if needed on the memstore with no filtering based on TTL
1249   * (that happens further down the line).
1250   * @param files the list of files on which the scanners has to be created
1251   * @param cacheBlocks ache the blocks or not
1252   * @param usePread true to use pread, false if not
1253   * @param isCompaction true if the scanner is created for compaction
1254   * @param matcher the scan query matcher
1255   * @param startRow the start row
1256   * @param includeStartRow true to include start row, false if not
1257   * @param stopRow the stop row
1258   * @param includeStopRow true to include stop row, false if not
1259   * @param readPt the read point of the current scan
1260   * @param includeMemstoreScanner true if memstore has to be included
1261   * @return scanners on the given files and on the memstore if specified
1262   */
1263  public List<KeyValueScanner> getScanners(List<HStoreFile> files, boolean cacheBlocks,
1264      boolean usePread, boolean isCompaction, ScanQueryMatcher matcher, byte[] startRow,
1265      boolean includeStartRow, byte[] stopRow, boolean includeStopRow, long readPt,
1266      boolean includeMemstoreScanner) throws IOException {
1267    List<KeyValueScanner> memStoreScanners = null;
1268    if (includeMemstoreScanner) {
1269      this.lock.readLock().lock();
1270      try {
1271        memStoreScanners = this.memstore.getScanners(readPt);
1272      } finally {
1273        this.lock.readLock().unlock();
1274      }
1275    }
1276    List<StoreFileScanner> sfScanners = StoreFileScanner.getScannersForStoreFiles(files,
1277      cacheBlocks, usePread, isCompaction, false, matcher, readPt);
1278    List<KeyValueScanner> scanners = new ArrayList<>(sfScanners.size() + 1);
1279    scanners.addAll(sfScanners);
1280    // Then the memstore scanners
1281    if (memStoreScanners != null) {
1282      scanners.addAll(memStoreScanners);
1283    }
1284    return scanners;
1285  }
1286
1287  /**
1288   * @param o Observer who wants to know about changes in set of Readers
1289   */
1290  public void addChangedReaderObserver(ChangedReadersObserver o) {
1291    this.changedReaderObservers.add(o);
1292  }
1293
1294  /**
1295   * @param o Observer no longer interested in changes in set of Readers.
1296   */
1297  public void deleteChangedReaderObserver(ChangedReadersObserver o) {
1298    // We don't check if observer present; it may not be (legitimately)
1299    this.changedReaderObservers.remove(o);
1300  }
1301
1302  //////////////////////////////////////////////////////////////////////////////
1303  // Compaction
1304  //////////////////////////////////////////////////////////////////////////////
1305
1306  /**
1307   * Compact the StoreFiles.  This method may take some time, so the calling
1308   * thread must be able to block for long periods.
1309   *
1310   * <p>During this time, the Store can work as usual, getting values from
1311   * StoreFiles and writing new StoreFiles from the memstore.
1312   *
1313   * Existing StoreFiles are not destroyed until the new compacted StoreFile is
1314   * completely written-out to disk.
1315   *
1316   * <p>The compactLock prevents multiple simultaneous compactions.
1317   * The structureLock prevents us from interfering with other write operations.
1318   *
1319   * <p>We don't want to hold the structureLock for the whole time, as a compact()
1320   * can be lengthy and we want to allow cache-flushes during this period.
1321   *
1322   * <p> Compaction event should be idempotent, since there is no IO Fencing for
1323   * the region directory in hdfs. A region server might still try to complete the
1324   * compaction after it lost the region. That is why the following events are carefully
1325   * ordered for a compaction:
1326   *  1. Compaction writes new files under region/.tmp directory (compaction output)
1327   *  2. Compaction atomically moves the temporary file under region directory
1328   *  3. Compaction appends a WAL edit containing the compaction input and output files.
1329   *  Forces sync on WAL.
1330   *  4. Compaction deletes the input files from the region directory.
1331   *
1332   * Failure conditions are handled like this:
1333   *  - If RS fails before 2, compaction wont complete. Even if RS lives on and finishes
1334   *  the compaction later, it will only write the new data file to the region directory.
1335   *  Since we already have this data, this will be idempotent but we will have a redundant
1336   *  copy of the data.
1337   *  - If RS fails between 2 and 3, the region will have a redundant copy of the data. The
1338   *  RS that failed won't be able to finish snyc() for WAL because of lease recovery in WAL.
1339   *  - If RS fails after 3, the region region server who opens the region will pick up the
1340   *  the compaction marker from the WAL and replay it by removing the compaction input files.
1341   *  Failed RS can also attempt to delete those files, but the operation will be idempotent
1342   *
1343   * See HBASE-2231 for details.
1344   *
1345   * @param compaction compaction details obtained from requestCompaction()
1346   * @throws IOException
1347   * @return Storefile we compacted into or null if we failed or opted out early.
1348   */
1349  public List<HStoreFile> compact(CompactionContext compaction,
1350    ThroughputController throughputController, User user) throws IOException {
1351    assert compaction != null;
1352    List<HStoreFile> sfs = null;
1353    CompactionRequestImpl cr = compaction.getRequest();
1354    try {
1355      // Do all sanity checking in here if we have a valid CompactionRequestImpl
1356      // because we need to clean up after it on the way out in a finally
1357      // block below
1358      long compactionStartTime = EnvironmentEdgeManager.currentTime();
1359      assert compaction.hasSelection();
1360      Collection<HStoreFile> filesToCompact = cr.getFiles();
1361      assert !filesToCompact.isEmpty();
1362      synchronized (filesCompacting) {
1363        // sanity check: we're compacting files that this store knows about
1364        // TODO: change this to LOG.error() after more debugging
1365        Preconditions.checkArgument(filesCompacting.containsAll(filesToCompact));
1366      }
1367
1368      // Ready to go. Have list of files to compact.
1369      LOG.info("Starting compaction of " + filesToCompact +
1370        " into tmpdir=" + fs.getTempDir() + ", totalSize=" +
1371          TraditionalBinaryPrefix.long2String(cr.getSize(), "", 1));
1372
1373      return doCompaction(cr, filesToCompact, user, compactionStartTime,
1374          compaction.compact(throughputController, user));
1375    } finally {
1376      finishCompactionRequest(cr);
1377    }
1378  }
1379
1380  @VisibleForTesting
1381  protected List<HStoreFile> doCompaction(CompactionRequestImpl cr,
1382      Collection<HStoreFile> filesToCompact, User user, long compactionStartTime,
1383      List<Path> newFiles) throws IOException {
1384    // Do the steps necessary to complete the compaction.
1385    List<HStoreFile> sfs = moveCompactedFilesIntoPlace(cr, newFiles, user);
1386    writeCompactionWalRecord(filesToCompact, sfs);
1387    replaceStoreFiles(filesToCompact, sfs);
1388    if (cr.isMajor()) {
1389      majorCompactedCellsCount.addAndGet(getCompactionProgress().getTotalCompactingKVs());
1390      majorCompactedCellsSize.addAndGet(getCompactionProgress().totalCompactedSize);
1391    } else {
1392      compactedCellsCount.addAndGet(getCompactionProgress().getTotalCompactingKVs());
1393      compactedCellsSize.addAndGet(getCompactionProgress().totalCompactedSize);
1394    }
1395    long outputBytes = getTotalSize(sfs);
1396
1397    // At this point the store will use new files for all new scanners.
1398    completeCompaction(filesToCompact); // update store size.
1399
1400    long now = EnvironmentEdgeManager.currentTime();
1401    if (region.getRegionServerServices() != null
1402        && region.getRegionServerServices().getMetrics() != null) {
1403      region.getRegionServerServices().getMetrics().updateCompaction(cr.isMajor(),
1404          now - compactionStartTime, cr.getFiles().size(), newFiles.size(), cr.getSize(),
1405          outputBytes);
1406    }
1407
1408    logCompactionEndMessage(cr, sfs, now, compactionStartTime);
1409    return sfs;
1410  }
1411
1412  private List<HStoreFile> moveCompactedFilesIntoPlace(CompactionRequestImpl cr, List<Path> newFiles,
1413      User user) throws IOException {
1414    List<HStoreFile> sfs = new ArrayList<>(newFiles.size());
1415    for (Path newFile : newFiles) {
1416      assert newFile != null;
1417      HStoreFile sf = moveFileIntoPlace(newFile);
1418      if (this.getCoprocessorHost() != null) {
1419        getCoprocessorHost().postCompact(this, sf, cr.getTracker(), cr, user);
1420      }
1421      assert sf != null;
1422      sfs.add(sf);
1423    }
1424    return sfs;
1425  }
1426
1427  // Package-visible for tests
1428  HStoreFile moveFileIntoPlace(Path newFile) throws IOException {
1429    validateStoreFile(newFile);
1430    // Move the file into the right spot
1431    Path destPath = fs.commitStoreFile(getColumnFamilyName(), newFile);
1432    return createStoreFileAndReader(destPath);
1433  }
1434
1435  /**
1436   * Writes the compaction WAL record.
1437   * @param filesCompacted Files compacted (input).
1438   * @param newFiles Files from compaction.
1439   */
1440  private void writeCompactionWalRecord(Collection<HStoreFile> filesCompacted,
1441      Collection<HStoreFile> newFiles) throws IOException {
1442    if (region.getWAL() == null) {
1443      return;
1444    }
1445    List<Path> inputPaths =
1446        filesCompacted.stream().map(HStoreFile::getPath).collect(Collectors.toList());
1447    List<Path> outputPaths =
1448        newFiles.stream().map(HStoreFile::getPath).collect(Collectors.toList());
1449    RegionInfo info = this.region.getRegionInfo();
1450    CompactionDescriptor compactionDescriptor = ProtobufUtil.toCompactionDescriptor(info,
1451        family.getName(), inputPaths, outputPaths, fs.getStoreDir(getColumnFamilyDescriptor().getNameAsString()));
1452    // Fix reaching into Region to get the maxWaitForSeqId.
1453    // Does this method belong in Region altogether given it is making so many references up there?
1454    // Could be Region#writeCompactionMarker(compactionDescriptor);
1455    WALUtil.writeCompactionMarker(this.region.getWAL(), this.region.getReplicationScope(),
1456        this.region.getRegionInfo(), compactionDescriptor, this.region.getMVCC());
1457  }
1458
1459  @VisibleForTesting
1460  void replaceStoreFiles(Collection<HStoreFile> compactedFiles, Collection<HStoreFile> result)
1461      throws IOException {
1462    this.lock.writeLock().lock();
1463    try {
1464      this.storeEngine.getStoreFileManager().addCompactionResults(compactedFiles, result);
1465      synchronized (filesCompacting) {
1466        filesCompacting.removeAll(compactedFiles);
1467      }
1468    } finally {
1469      this.lock.writeLock().unlock();
1470    }
1471  }
1472
1473  /**
1474   * Log a very elaborate compaction completion message.
1475   * @param cr Request.
1476   * @param sfs Resulting files.
1477   * @param compactionStartTime Start time.
1478   */
1479  private void logCompactionEndMessage(
1480      CompactionRequestImpl cr, List<HStoreFile> sfs, long now, long compactionStartTime) {
1481    StringBuilder message = new StringBuilder(
1482      "Completed" + (cr.isMajor() ? " major" : "") + " compaction of "
1483      + cr.getFiles().size() + (cr.isAllFiles() ? " (all)" : "") + " file(s) in "
1484      + this + " of " + this.getRegionInfo().getShortNameToLog() + " into ");
1485    if (sfs.isEmpty()) {
1486      message.append("none, ");
1487    } else {
1488      for (HStoreFile sf: sfs) {
1489        message.append(sf.getPath().getName());
1490        message.append("(size=");
1491        message.append(TraditionalBinaryPrefix.long2String(sf.getReader().length(), "", 1));
1492        message.append("), ");
1493      }
1494    }
1495    message.append("total size for store is ")
1496      .append(StringUtils.TraditionalBinaryPrefix.long2String(storeSize.get(), "", 1))
1497      .append(". This selection was in queue for ")
1498      .append(StringUtils.formatTimeDiff(compactionStartTime, cr.getSelectionTime()))
1499      .append(", and took ").append(StringUtils.formatTimeDiff(now, compactionStartTime))
1500      .append(" to execute.");
1501    LOG.info(message.toString());
1502    if (LOG.isTraceEnabled()) {
1503      int fileCount = storeEngine.getStoreFileManager().getStorefileCount();
1504      long resultSize = getTotalSize(sfs);
1505      String traceMessage = "COMPACTION start,end,size out,files in,files out,store size,"
1506        + "store files [" + compactionStartTime + "," + now + "," + resultSize + ","
1507          + cr.getFiles().size() + "," + sfs.size() + "," +  storeSize + "," + fileCount + "]";
1508      LOG.trace(traceMessage);
1509    }
1510  }
1511
1512  /**
1513   * Call to complete a compaction. Its for the case where we find in the WAL a compaction
1514   * that was not finished.  We could find one recovering a WAL after a regionserver crash.
1515   * See HBASE-2231.
1516   * @param compaction
1517   */
1518  public void replayCompactionMarker(CompactionDescriptor compaction, boolean pickCompactionFiles,
1519      boolean removeFiles) throws IOException {
1520    LOG.debug("Completing compaction from the WAL marker");
1521    List<String> compactionInputs = compaction.getCompactionInputList();
1522    List<String> compactionOutputs = Lists.newArrayList(compaction.getCompactionOutputList());
1523
1524    // The Compaction Marker is written after the compaction is completed,
1525    // and the files moved into the region/family folder.
1526    //
1527    // If we crash after the entry is written, we may not have removed the
1528    // input files, but the output file is present.
1529    // (The unremoved input files will be removed by this function)
1530    //
1531    // If we scan the directory and the file is not present, it can mean that:
1532    //   - The file was manually removed by the user
1533    //   - The file was removed as consequence of subsequent compaction
1534    // so, we can't do anything with the "compaction output list" because those
1535    // files have already been loaded when opening the region (by virtue of
1536    // being in the store's folder) or they may be missing due to a compaction.
1537
1538    String familyName = this.getColumnFamilyName();
1539    Set<String> inputFiles = new HashSet<>();
1540    for (String compactionInput : compactionInputs) {
1541      Path inputPath = fs.getStoreFilePath(familyName, compactionInput);
1542      inputFiles.add(inputPath.getName());
1543    }
1544
1545    //some of the input files might already be deleted
1546    List<HStoreFile> inputStoreFiles = new ArrayList<>(compactionInputs.size());
1547    for (HStoreFile sf : this.getStorefiles()) {
1548      if (inputFiles.contains(sf.getPath().getName())) {
1549        inputStoreFiles.add(sf);
1550      }
1551    }
1552
1553    // check whether we need to pick up the new files
1554    List<HStoreFile> outputStoreFiles = new ArrayList<>(compactionOutputs.size());
1555
1556    if (pickCompactionFiles) {
1557      for (HStoreFile sf : this.getStorefiles()) {
1558        compactionOutputs.remove(sf.getPath().getName());
1559      }
1560      for (String compactionOutput : compactionOutputs) {
1561        StoreFileInfo storeFileInfo = fs.getStoreFileInfo(getColumnFamilyName(), compactionOutput);
1562        HStoreFile storeFile = createStoreFileAndReader(storeFileInfo);
1563        outputStoreFiles.add(storeFile);
1564      }
1565    }
1566
1567    if (!inputStoreFiles.isEmpty() || !outputStoreFiles.isEmpty()) {
1568      LOG.info("Replaying compaction marker, replacing input files: " +
1569          inputStoreFiles + " with output files : " + outputStoreFiles);
1570      this.replaceStoreFiles(inputStoreFiles, outputStoreFiles);
1571      this.completeCompaction(inputStoreFiles);
1572    }
1573  }
1574
1575  /**
1576   * This method tries to compact N recent files for testing.
1577   * Note that because compacting "recent" files only makes sense for some policies,
1578   * e.g. the default one, it assumes default policy is used. It doesn't use policy,
1579   * but instead makes a compaction candidate list by itself.
1580   * @param N Number of files.
1581   */
1582  @VisibleForTesting
1583  public void compactRecentForTestingAssumingDefaultPolicy(int N) throws IOException {
1584    List<HStoreFile> filesToCompact;
1585    boolean isMajor;
1586
1587    this.lock.readLock().lock();
1588    try {
1589      synchronized (filesCompacting) {
1590        filesToCompact = Lists.newArrayList(storeEngine.getStoreFileManager().getStorefiles());
1591        if (!filesCompacting.isEmpty()) {
1592          // exclude all files older than the newest file we're currently
1593          // compacting. this allows us to preserve contiguity (HBASE-2856)
1594          HStoreFile last = filesCompacting.get(filesCompacting.size() - 1);
1595          int idx = filesToCompact.indexOf(last);
1596          Preconditions.checkArgument(idx != -1);
1597          filesToCompact.subList(0, idx + 1).clear();
1598        }
1599        int count = filesToCompact.size();
1600        if (N > count) {
1601          throw new RuntimeException("Not enough files");
1602        }
1603
1604        filesToCompact = filesToCompact.subList(count - N, count);
1605        isMajor = (filesToCompact.size() == storeEngine.getStoreFileManager().getStorefileCount());
1606        filesCompacting.addAll(filesToCompact);
1607        Collections.sort(filesCompacting, storeEngine.getStoreFileManager()
1608            .getStoreFileComparator());
1609      }
1610    } finally {
1611      this.lock.readLock().unlock();
1612    }
1613
1614    try {
1615      // Ready to go. Have list of files to compact.
1616      List<Path> newFiles = ((DefaultCompactor)this.storeEngine.getCompactor())
1617          .compactForTesting(filesToCompact, isMajor);
1618      for (Path newFile: newFiles) {
1619        // Move the compaction into place.
1620        HStoreFile sf = moveFileIntoPlace(newFile);
1621        if (this.getCoprocessorHost() != null) {
1622          this.getCoprocessorHost().postCompact(this, sf, null, null, null);
1623        }
1624        replaceStoreFiles(filesToCompact, Collections.singletonList(sf));
1625        completeCompaction(filesToCompact);
1626      }
1627    } finally {
1628      synchronized (filesCompacting) {
1629        filesCompacting.removeAll(filesToCompact);
1630      }
1631    }
1632  }
1633
1634  @Override
1635  public boolean hasReferences() {
1636    List<HStoreFile> reloadedStoreFiles = null;
1637    try {
1638      // Reloading the store files from file system due to HBASE-20940. As split can happen with an
1639      // region which has references
1640      reloadedStoreFiles = loadStoreFiles();
1641      return StoreUtils.hasReferences(reloadedStoreFiles);
1642    } catch (IOException ioe) {
1643      LOG.error("Error trying to determine if store has references, assuming references exists",
1644        ioe);
1645      return true;
1646    } finally {
1647      if (reloadedStoreFiles != null) {
1648        for (HStoreFile storeFile : reloadedStoreFiles) {
1649          try {
1650            storeFile.closeStoreFile(false);
1651          } catch (IOException ioe) {
1652            LOG.warn("Encountered exception closing " + storeFile + ": " + ioe.getMessage());
1653            // continue with closing the remaining store files
1654          }
1655        }
1656      }
1657    }
1658  }
1659
1660  /**
1661   * getter for CompactionProgress object
1662   * @return CompactionProgress object; can be null
1663   */
1664  public CompactionProgress getCompactionProgress() {
1665    return this.storeEngine.getCompactor().getProgress();
1666  }
1667
1668  @Override
1669  public boolean shouldPerformMajorCompaction() throws IOException {
1670    for (HStoreFile sf : this.storeEngine.getStoreFileManager().getStorefiles()) {
1671      // TODO: what are these reader checks all over the place?
1672      if (sf.getReader() == null) {
1673        LOG.debug("StoreFile {} has null Reader", sf);
1674        return false;
1675      }
1676    }
1677    return storeEngine.getCompactionPolicy().shouldPerformMajorCompaction(
1678        this.storeEngine.getStoreFileManager().getStorefiles());
1679  }
1680
1681  public Optional<CompactionContext> requestCompaction() throws IOException {
1682    return requestCompaction(NO_PRIORITY, CompactionLifeCycleTracker.DUMMY, null);
1683  }
1684
1685  public Optional<CompactionContext> requestCompaction(int priority,
1686      CompactionLifeCycleTracker tracker, User user) throws IOException {
1687    // don't even select for compaction if writes are disabled
1688    if (!this.areWritesEnabled()) {
1689      return Optional.empty();
1690    }
1691    // Before we do compaction, try to get rid of unneeded files to simplify things.
1692    removeUnneededFiles();
1693
1694    final CompactionContext compaction = storeEngine.createCompaction();
1695    CompactionRequestImpl request = null;
1696    this.lock.readLock().lock();
1697    try {
1698      synchronized (filesCompacting) {
1699        // First, see if coprocessor would want to override selection.
1700        if (this.getCoprocessorHost() != null) {
1701          final List<HStoreFile> candidatesForCoproc = compaction.preSelect(this.filesCompacting);
1702          boolean override = getCoprocessorHost().preCompactSelection(this,
1703              candidatesForCoproc, tracker, user);
1704          if (override) {
1705            // Coprocessor is overriding normal file selection.
1706            compaction.forceSelect(new CompactionRequestImpl(candidatesForCoproc));
1707          }
1708        }
1709
1710        // Normal case - coprocessor is not overriding file selection.
1711        if (!compaction.hasSelection()) {
1712          boolean isUserCompaction = priority == Store.PRIORITY_USER;
1713          boolean mayUseOffPeak = offPeakHours.isOffPeakHour() &&
1714              offPeakCompactionTracker.compareAndSet(false, true);
1715          try {
1716            compaction.select(this.filesCompacting, isUserCompaction,
1717              mayUseOffPeak, forceMajor && filesCompacting.isEmpty());
1718          } catch (IOException e) {
1719            if (mayUseOffPeak) {
1720              offPeakCompactionTracker.set(false);
1721            }
1722            throw e;
1723          }
1724          assert compaction.hasSelection();
1725          if (mayUseOffPeak && !compaction.getRequest().isOffPeak()) {
1726            // Compaction policy doesn't want to take advantage of off-peak.
1727            offPeakCompactionTracker.set(false);
1728          }
1729        }
1730        if (this.getCoprocessorHost() != null) {
1731          this.getCoprocessorHost().postCompactSelection(
1732              this, ImmutableList.copyOf(compaction.getRequest().getFiles()), tracker,
1733              compaction.getRequest(), user);
1734        }
1735        // Finally, we have the resulting files list. Check if we have any files at all.
1736        request = compaction.getRequest();
1737        Collection<HStoreFile> selectedFiles = request.getFiles();
1738        if (selectedFiles.isEmpty()) {
1739          return Optional.empty();
1740        }
1741
1742        addToCompactingFiles(selectedFiles);
1743
1744        // If we're enqueuing a major, clear the force flag.
1745        this.forceMajor = this.forceMajor && !request.isMajor();
1746
1747        // Set common request properties.
1748        // Set priority, either override value supplied by caller or from store.
1749        request.setPriority((priority != Store.NO_PRIORITY) ? priority : getCompactPriority());
1750        request.setDescription(getRegionInfo().getRegionNameAsString(), getColumnFamilyName());
1751        request.setTracker(tracker);
1752      }
1753    } finally {
1754      this.lock.readLock().unlock();
1755    }
1756
1757    if (LOG.isDebugEnabled()) {
1758      LOG.debug(getRegionInfo().getEncodedName() + " - " + getColumnFamilyName()
1759          + ": Initiating " + (request.isMajor() ? "major" : "minor") + " compaction"
1760          + (request.isAllFiles() ? " (all files)" : ""));
1761    }
1762    this.region.reportCompactionRequestStart(request.isMajor());
1763    return Optional.of(compaction);
1764  }
1765
1766  /** Adds the files to compacting files. filesCompacting must be locked. */
1767  private void addToCompactingFiles(Collection<HStoreFile> filesToAdd) {
1768    if (CollectionUtils.isEmpty(filesToAdd)) {
1769      return;
1770    }
1771    // Check that we do not try to compact the same StoreFile twice.
1772    if (!Collections.disjoint(filesCompacting, filesToAdd)) {
1773      Preconditions.checkArgument(false, "%s overlaps with %s", filesToAdd, filesCompacting);
1774    }
1775    filesCompacting.addAll(filesToAdd);
1776    Collections.sort(filesCompacting, storeEngine.getStoreFileManager().getStoreFileComparator());
1777  }
1778
1779  private void removeUnneededFiles() throws IOException {
1780    if (!conf.getBoolean("hbase.store.delete.expired.storefile", true)) return;
1781    if (getColumnFamilyDescriptor().getMinVersions() > 0) {
1782      LOG.debug("Skipping expired store file removal due to min version being {}",
1783          getColumnFamilyDescriptor().getMinVersions());
1784      return;
1785    }
1786    this.lock.readLock().lock();
1787    Collection<HStoreFile> delSfs = null;
1788    try {
1789      synchronized (filesCompacting) {
1790        long cfTtl = getStoreFileTtl();
1791        if (cfTtl != Long.MAX_VALUE) {
1792          delSfs = storeEngine.getStoreFileManager().getUnneededFiles(
1793              EnvironmentEdgeManager.currentTime() - cfTtl, filesCompacting);
1794          addToCompactingFiles(delSfs);
1795        }
1796      }
1797    } finally {
1798      this.lock.readLock().unlock();
1799    }
1800
1801    if (CollectionUtils.isEmpty(delSfs)) {
1802      return;
1803    }
1804
1805    Collection<HStoreFile> newFiles = Collections.emptyList(); // No new files.
1806    writeCompactionWalRecord(delSfs, newFiles);
1807    replaceStoreFiles(delSfs, newFiles);
1808    completeCompaction(delSfs);
1809    LOG.info("Completed removal of " + delSfs.size() + " unnecessary (expired) file(s) in "
1810        + this + " of " + this.getRegionInfo().getRegionNameAsString()
1811        + "; total size for store is "
1812        + TraditionalBinaryPrefix.long2String(storeSize.get(), "", 1));
1813  }
1814
1815  public void cancelRequestedCompaction(CompactionContext compaction) {
1816    finishCompactionRequest(compaction.getRequest());
1817  }
1818
1819  private void finishCompactionRequest(CompactionRequestImpl cr) {
1820    this.region.reportCompactionRequestEnd(cr.isMajor(), cr.getFiles().size(), cr.getSize());
1821    if (cr.isOffPeak()) {
1822      offPeakCompactionTracker.set(false);
1823      cr.setOffPeak(false);
1824    }
1825    synchronized (filesCompacting) {
1826      filesCompacting.removeAll(cr.getFiles());
1827    }
1828  }
1829
1830  /**
1831   * Validates a store file by opening and closing it. In HFileV2 this should not be an expensive
1832   * operation.
1833   * @param path the path to the store file
1834   */
1835  private void validateStoreFile(Path path) throws IOException {
1836    HStoreFile storeFile = null;
1837    try {
1838      storeFile = createStoreFileAndReader(path);
1839    } catch (IOException e) {
1840      LOG.error("Failed to open store file : {}, keeping it in tmp location", path, e);
1841      throw e;
1842    } finally {
1843      if (storeFile != null) {
1844        storeFile.closeStoreFile(false);
1845      }
1846    }
1847  }
1848
1849  /**
1850   * <p>It works by processing a compaction that's been written to disk.
1851   *
1852   * <p>It is usually invoked at the end of a compaction, but might also be
1853   * invoked at HStore startup, if the prior execution died midway through.
1854   *
1855   * <p>Moving the compacted TreeMap into place means:
1856   * <pre>
1857   * 1) Unload all replaced StoreFile, close and collect list to delete.
1858   * 2) Compute new store size
1859   * </pre>
1860   *
1861   * @param compactedFiles list of files that were compacted
1862   */
1863  @VisibleForTesting
1864  protected void completeCompaction(Collection<HStoreFile> compactedFiles)
1865    throws IOException {
1866    this.storeSize.set(0L);
1867    this.totalUncompressedBytes.set(0L);
1868    for (HStoreFile hsf : this.storeEngine.getStoreFileManager().getStorefiles()) {
1869      StoreFileReader r = hsf.getReader();
1870      if (r == null) {
1871        LOG.warn("StoreFile {} has a null Reader", hsf);
1872        continue;
1873      }
1874      this.storeSize.addAndGet(r.length());
1875      this.totalUncompressedBytes.addAndGet(r.getTotalUncompressedBytes());
1876    }
1877  }
1878
1879  /*
1880   * @param wantedVersions How many versions were asked for.
1881   * @return wantedVersions or this families' {@link HConstants#VERSIONS}.
1882   */
1883  int versionsToReturn(final int wantedVersions) {
1884    if (wantedVersions <= 0) {
1885      throw new IllegalArgumentException("Number of versions must be > 0");
1886    }
1887    // Make sure we do not return more than maximum versions for this store.
1888    int maxVersions = this.family.getMaxVersions();
1889    return wantedVersions > maxVersions ? maxVersions: wantedVersions;
1890  }
1891
1892  @Override
1893  public boolean canSplit() {
1894    this.lock.readLock().lock();
1895    try {
1896      // Not split-able if we find a reference store file present in the store.
1897      boolean result = !hasReferences();
1898      if (!result) {
1899        LOG.trace("Not splittable; has references: {}", this);
1900      }
1901      return result;
1902    } finally {
1903      this.lock.readLock().unlock();
1904    }
1905  }
1906
1907  /**
1908   * Determines if Store should be split.
1909   */
1910  public Optional<byte[]> getSplitPoint() {
1911    this.lock.readLock().lock();
1912    try {
1913      // Should already be enforced by the split policy!
1914      assert !this.getRegionInfo().isMetaRegion();
1915      // Not split-able if we find a reference store file present in the store.
1916      if (hasReferences()) {
1917        LOG.trace("Not splittable; has references: {}", this);
1918        return Optional.empty();
1919      }
1920      return this.storeEngine.getStoreFileManager().getSplitPoint();
1921    } catch(IOException e) {
1922      LOG.warn("Failed getting store size for {}", this, e);
1923    } finally {
1924      this.lock.readLock().unlock();
1925    }
1926    return Optional.empty();
1927  }
1928
1929  @Override
1930  public long getLastCompactSize() {
1931    return this.lastCompactSize;
1932  }
1933
1934  @Override
1935  public long getSize() {
1936    return storeSize.get();
1937  }
1938
1939  public void triggerMajorCompaction() {
1940    this.forceMajor = true;
1941  }
1942
1943  //////////////////////////////////////////////////////////////////////////////
1944  // File administration
1945  //////////////////////////////////////////////////////////////////////////////
1946
1947  /**
1948   * Return a scanner for both the memstore and the HStore files. Assumes we are not in a
1949   * compaction.
1950   * @param scan Scan to apply when scanning the stores
1951   * @param targetCols columns to scan
1952   * @return a scanner over the current key values
1953   * @throws IOException on failure
1954   */
1955  public KeyValueScanner getScanner(Scan scan, final NavigableSet<byte[]> targetCols, long readPt)
1956      throws IOException {
1957    lock.readLock().lock();
1958    try {
1959      ScanInfo scanInfo;
1960      if (this.getCoprocessorHost() != null) {
1961        scanInfo = this.getCoprocessorHost().preStoreScannerOpen(this);
1962      } else {
1963        scanInfo = getScanInfo();
1964      }
1965      return createScanner(scan, scanInfo, targetCols, readPt);
1966    } finally {
1967      lock.readLock().unlock();
1968    }
1969  }
1970
1971  // HMobStore will override this method to return its own implementation.
1972  protected KeyValueScanner createScanner(Scan scan, ScanInfo scanInfo,
1973      NavigableSet<byte[]> targetCols, long readPt) throws IOException {
1974    return scan.isReversed() ? new ReversedStoreScanner(this, scanInfo, scan, targetCols, readPt)
1975        : new StoreScanner(this, scanInfo, scan, targetCols, readPt);
1976  }
1977
1978  /**
1979   * Recreates the scanners on the current list of active store file scanners
1980   * @param currentFileScanners the current set of active store file scanners
1981   * @param cacheBlocks cache the blocks or not
1982   * @param usePread use pread or not
1983   * @param isCompaction is the scanner for compaction
1984   * @param matcher the scan query matcher
1985   * @param startRow the scan's start row
1986   * @param includeStartRow should the scan include the start row
1987   * @param stopRow the scan's stop row
1988   * @param includeStopRow should the scan include the stop row
1989   * @param readPt the read point of the current scane
1990   * @param includeMemstoreScanner whether the current scanner should include memstorescanner
1991   * @return list of scanners recreated on the current Scanners
1992   * @throws IOException
1993   */
1994  public List<KeyValueScanner> recreateScanners(List<KeyValueScanner> currentFileScanners,
1995      boolean cacheBlocks, boolean usePread, boolean isCompaction, ScanQueryMatcher matcher,
1996      byte[] startRow, boolean includeStartRow, byte[] stopRow, boolean includeStopRow, long readPt,
1997      boolean includeMemstoreScanner) throws IOException {
1998    this.lock.readLock().lock();
1999    try {
2000      Map<String, HStoreFile> name2File =
2001          new HashMap<>(getStorefilesCount() + getCompactedFilesCount());
2002      for (HStoreFile file : getStorefiles()) {
2003        name2File.put(file.getFileInfo().getActiveFileName(), file);
2004      }
2005      Collection<HStoreFile> compactedFiles = getCompactedFiles();
2006      for (HStoreFile file : IterableUtils.emptyIfNull(compactedFiles)) {
2007        name2File.put(file.getFileInfo().getActiveFileName(), file);
2008      }
2009      List<HStoreFile> filesToReopen = new ArrayList<>();
2010      for (KeyValueScanner kvs : currentFileScanners) {
2011        assert kvs.isFileScanner();
2012        if (kvs.peek() == null) {
2013          continue;
2014        }
2015        filesToReopen.add(name2File.get(kvs.getFilePath().getName()));
2016      }
2017      if (filesToReopen.isEmpty()) {
2018        return null;
2019      }
2020      return getScanners(filesToReopen, cacheBlocks, false, false, matcher, startRow,
2021        includeStartRow, stopRow, includeStopRow, readPt, false);
2022    } finally {
2023      this.lock.readLock().unlock();
2024    }
2025  }
2026
2027  @Override
2028  public String toString() {
2029    return this.getColumnFamilyName();
2030  }
2031
2032  @Override
2033  public int getStorefilesCount() {
2034    return this.storeEngine.getStoreFileManager().getStorefileCount();
2035  }
2036
2037  @Override
2038  public int getCompactedFilesCount() {
2039    return this.storeEngine.getStoreFileManager().getCompactedFilesCount();
2040  }
2041
2042  private LongStream getStoreFileAgeStream() {
2043    return this.storeEngine.getStoreFileManager().getStorefiles().stream().filter(sf -> {
2044      if (sf.getReader() == null) {
2045        LOG.warn("StoreFile {} has a null Reader", sf);
2046        return false;
2047      } else {
2048        return true;
2049      }
2050    }).filter(HStoreFile::isHFile).mapToLong(sf -> sf.getFileInfo().getCreatedTimestamp())
2051        .map(t -> EnvironmentEdgeManager.currentTime() - t);
2052  }
2053
2054  @Override
2055  public OptionalLong getMaxStoreFileAge() {
2056    return getStoreFileAgeStream().max();
2057  }
2058
2059  @Override
2060  public OptionalLong getMinStoreFileAge() {
2061    return getStoreFileAgeStream().min();
2062  }
2063
2064  @Override
2065  public OptionalDouble getAvgStoreFileAge() {
2066    return getStoreFileAgeStream().average();
2067  }
2068
2069  @Override
2070  public long getNumReferenceFiles() {
2071    return this.storeEngine.getStoreFileManager().getStorefiles().stream()
2072        .filter(HStoreFile::isReference).count();
2073  }
2074
2075  @Override
2076  public long getNumHFiles() {
2077    return this.storeEngine.getStoreFileManager().getStorefiles().stream()
2078        .filter(HStoreFile::isHFile).count();
2079  }
2080
2081  @Override
2082  public long getStoreSizeUncompressed() {
2083    return this.totalUncompressedBytes.get();
2084  }
2085
2086  @Override
2087  public long getStorefilesSize() {
2088    // Include all StoreFiles
2089    return getStorefilesSize(storeFile -> true);
2090  }
2091
2092  @Override
2093  public long getHFilesSize() {
2094    // Include only StoreFiles which are HFiles
2095    return getStorefilesSize(storeFile -> storeFile.isHFile());
2096  }
2097
2098  private long getStorefilesSize(Predicate<HStoreFile> predicate) {
2099    return this.storeEngine.getStoreFileManager().getStorefiles().stream().filter(sf -> {
2100      if (sf.getReader() == null) {
2101        LOG.warn("StoreFile {} has a null Reader", sf);
2102        return false;
2103      } else {
2104        return true;
2105      }
2106    }).filter(predicate).mapToLong(sf -> sf.getReader().length()).sum();
2107  }
2108
2109  private long getStoreFileFieldSize(ToLongFunction<StoreFileReader> f) {
2110    return this.storeEngine.getStoreFileManager().getStorefiles().stream().filter(sf -> {
2111      if (sf.getReader() == null) {
2112        LOG.warn("StoreFile {} has a null Reader", sf);
2113        return false;
2114      } else {
2115        return true;
2116      }
2117    }).map(HStoreFile::getReader).mapToLong(f).sum();
2118  }
2119
2120  @Override
2121  public long getStorefilesRootLevelIndexSize() {
2122    return getStoreFileFieldSize(StoreFileReader::indexSize);
2123  }
2124
2125  @Override
2126  public long getTotalStaticIndexSize() {
2127    return getStoreFileFieldSize(StoreFileReader::getUncompressedDataIndexSize);
2128  }
2129
2130  @Override
2131  public long getTotalStaticBloomSize() {
2132    return getStoreFileFieldSize(StoreFileReader::getTotalBloomSize);
2133  }
2134
2135  @Override
2136  public MemStoreSize getMemStoreSize() {
2137    return this.memstore.size();
2138  }
2139
2140  @Override
2141  public int getCompactPriority() {
2142    int priority = this.storeEngine.getStoreFileManager().getStoreCompactionPriority();
2143    if (priority == PRIORITY_USER) {
2144      LOG.warn("Compaction priority is USER despite there being no user compaction");
2145    }
2146    return priority;
2147  }
2148
2149  public boolean throttleCompaction(long compactionSize) {
2150    return storeEngine.getCompactionPolicy().throttleCompaction(compactionSize);
2151  }
2152
2153  public HRegion getHRegion() {
2154    return this.region;
2155  }
2156
2157  public RegionCoprocessorHost getCoprocessorHost() {
2158    return this.region.getCoprocessorHost();
2159  }
2160
2161  @Override
2162  public RegionInfo getRegionInfo() {
2163    return this.fs.getRegionInfo();
2164  }
2165
2166  @Override
2167  public boolean areWritesEnabled() {
2168    return this.region.areWritesEnabled();
2169  }
2170
2171  @Override
2172  public long getSmallestReadPoint() {
2173    return this.region.getSmallestReadPoint();
2174  }
2175
2176  /**
2177   * Adds or replaces the specified KeyValues.
2178   * <p>
2179   * For each KeyValue specified, if a cell with the same row, family, and qualifier exists in
2180   * MemStore, it will be replaced. Otherwise, it will just be inserted to MemStore.
2181   * <p>
2182   * This operation is atomic on each KeyValue (row/family/qualifier) but not necessarily atomic
2183   * across all of them.
2184   * @param readpoint readpoint below which we can safely remove duplicate KVs
2185   * @throws IOException
2186   */
2187  public void upsert(Iterable<Cell> cells, long readpoint, MemStoreSizing memstoreSizing)
2188      throws IOException {
2189    this.lock.readLock().lock();
2190    try {
2191      this.memstore.upsert(cells, readpoint, memstoreSizing);
2192    } finally {
2193      this.lock.readLock().unlock();
2194    }
2195  }
2196
2197  public StoreFlushContext createFlushContext(long cacheFlushId, FlushLifeCycleTracker tracker) {
2198    return new StoreFlusherImpl(cacheFlushId, tracker);
2199  }
2200
2201  private final class StoreFlusherImpl implements StoreFlushContext {
2202
2203    private final FlushLifeCycleTracker tracker;
2204    private final long cacheFlushSeqNum;
2205    private MemStoreSnapshot snapshot;
2206    private List<Path> tempFiles;
2207    private List<Path> committedFiles;
2208    private long cacheFlushCount;
2209    private long cacheFlushSize;
2210    private long outputFileSize;
2211
2212    private StoreFlusherImpl(long cacheFlushSeqNum, FlushLifeCycleTracker tracker) {
2213      this.cacheFlushSeqNum = cacheFlushSeqNum;
2214      this.tracker = tracker;
2215    }
2216
2217    /**
2218     * This is not thread safe. The caller should have a lock on the region or the store.
2219     * If necessary, the lock can be added with the patch provided in HBASE-10087
2220     */
2221    @Override
2222    public MemStoreSize prepare() {
2223      // passing the current sequence number of the wal - to allow bookkeeping in the memstore
2224      this.snapshot = memstore.snapshot();
2225      this.cacheFlushCount = snapshot.getCellsCount();
2226      this.cacheFlushSize = snapshot.getDataSize();
2227      committedFiles = new ArrayList<>(1);
2228      return snapshot.getMemStoreSize();
2229    }
2230
2231    @Override
2232    public void flushCache(MonitoredTask status) throws IOException {
2233      RegionServerServices rsService = region.getRegionServerServices();
2234      ThroughputController throughputController =
2235          rsService == null ? null : rsService.getFlushThroughputController();
2236      tempFiles =
2237          HStore.this.flushCache(cacheFlushSeqNum, snapshot, status, throughputController, tracker);
2238    }
2239
2240    @Override
2241    public boolean commit(MonitoredTask status) throws IOException {
2242      if (CollectionUtils.isEmpty(this.tempFiles)) {
2243        return false;
2244      }
2245      List<HStoreFile> storeFiles = new ArrayList<>(this.tempFiles.size());
2246      for (Path storeFilePath : tempFiles) {
2247        try {
2248          HStoreFile sf = HStore.this.commitFile(storeFilePath, cacheFlushSeqNum, status);
2249          outputFileSize += sf.getReader().length();
2250          storeFiles.add(sf);
2251        } catch (IOException ex) {
2252          LOG.error("Failed to commit store file {}", storeFilePath, ex);
2253          // Try to delete the files we have committed before.
2254          for (HStoreFile sf : storeFiles) {
2255            Path pathToDelete = sf.getPath();
2256            try {
2257              sf.deleteStoreFile();
2258            } catch (IOException deleteEx) {
2259              LOG.error(HBaseMarkers.FATAL, "Failed to delete store file we committed, "
2260                  + "halting {}", pathToDelete, ex);
2261              Runtime.getRuntime().halt(1);
2262            }
2263          }
2264          throw new IOException("Failed to commit the flush", ex);
2265        }
2266      }
2267
2268      for (HStoreFile sf : storeFiles) {
2269        if (HStore.this.getCoprocessorHost() != null) {
2270          HStore.this.getCoprocessorHost().postFlush(HStore.this, sf, tracker);
2271        }
2272        committedFiles.add(sf.getPath());
2273      }
2274
2275      HStore.this.flushedCellsCount.addAndGet(cacheFlushCount);
2276      HStore.this.flushedCellsSize.addAndGet(cacheFlushSize);
2277      HStore.this.flushedOutputFileSize.addAndGet(outputFileSize);
2278
2279      // Add new file to store files.  Clear snapshot too while we have the Store write lock.
2280      return HStore.this.updateStorefiles(storeFiles, snapshot.getId());
2281    }
2282
2283    @Override
2284    public long getOutputFileSize() {
2285      return outputFileSize;
2286    }
2287
2288    @Override
2289    public List<Path> getCommittedFiles() {
2290      return committedFiles;
2291    }
2292
2293    /**
2294     * Similar to commit, but called in secondary region replicas for replaying the
2295     * flush cache from primary region. Adds the new files to the store, and drops the
2296     * snapshot depending on dropMemstoreSnapshot argument.
2297     * @param fileNames names of the flushed files
2298     * @param dropMemstoreSnapshot whether to drop the prepared memstore snapshot
2299     * @throws IOException
2300     */
2301    @Override
2302    public void replayFlush(List<String> fileNames, boolean dropMemstoreSnapshot)
2303        throws IOException {
2304      List<HStoreFile> storeFiles = new ArrayList<>(fileNames.size());
2305      for (String file : fileNames) {
2306        // open the file as a store file (hfile link, etc)
2307        StoreFileInfo storeFileInfo = fs.getStoreFileInfo(getColumnFamilyName(), file);
2308        HStoreFile storeFile = createStoreFileAndReader(storeFileInfo);
2309        storeFiles.add(storeFile);
2310        HStore.this.storeSize.addAndGet(storeFile.getReader().length());
2311        HStore.this.totalUncompressedBytes
2312            .addAndGet(storeFile.getReader().getTotalUncompressedBytes());
2313        if (LOG.isInfoEnabled()) {
2314          LOG.info("Region: " + HStore.this.getRegionInfo().getEncodedName() +
2315            " added " + storeFile + ", entries=" + storeFile.getReader().getEntries() +
2316              ", sequenceid=" + +storeFile.getReader().getSequenceID() + ", filesize="
2317              + TraditionalBinaryPrefix.long2String(storeFile.getReader().length(), "", 1));
2318        }
2319      }
2320
2321      long snapshotId = -1; // -1 means do not drop
2322      if (dropMemstoreSnapshot && snapshot != null) {
2323        snapshotId = snapshot.getId();
2324        snapshot.close();
2325      }
2326      HStore.this.updateStorefiles(storeFiles, snapshotId);
2327    }
2328
2329    /**
2330     * Abort the snapshot preparation. Drops the snapshot if any.
2331     * @throws IOException
2332     */
2333    @Override
2334    public void abort() throws IOException {
2335      if (snapshot != null) {
2336        //We need to close the snapshot when aborting, otherwise, the segment scanner
2337        //won't be closed. If we are using MSLAB, the chunk referenced by those scanners
2338        //can't be released, thus memory leak
2339        snapshot.close();
2340        HStore.this.updateStorefiles(Collections.emptyList(), snapshot.getId());
2341      }
2342    }
2343  }
2344
2345  @Override
2346  public boolean needsCompaction() {
2347    List<HStoreFile> filesCompactingClone = null;
2348    synchronized (filesCompacting) {
2349      filesCompactingClone = Lists.newArrayList(filesCompacting);
2350    }
2351    return this.storeEngine.needsCompaction(filesCompactingClone);
2352  }
2353
2354  /**
2355   * Used for tests.
2356   * @return cache configuration for this Store.
2357   */
2358  @VisibleForTesting
2359  public CacheConfig getCacheConfig() {
2360    return this.cacheConf;
2361  }
2362
2363  public static final long FIXED_OVERHEAD =
2364      ClassSize.align(ClassSize.OBJECT + (26 * ClassSize.REFERENCE) + (2 * Bytes.SIZEOF_LONG)
2365              + (5 * Bytes.SIZEOF_INT) + (2 * Bytes.SIZEOF_BOOLEAN));
2366
2367  public static final long DEEP_OVERHEAD = ClassSize.align(FIXED_OVERHEAD
2368      + ClassSize.OBJECT + ClassSize.REENTRANT_LOCK
2369      + ClassSize.CONCURRENT_SKIPLISTMAP
2370      + ClassSize.CONCURRENT_SKIPLISTMAP_ENTRY + ClassSize.OBJECT
2371      + ScanInfo.FIXED_OVERHEAD);
2372
2373  @Override
2374  public long heapSize() {
2375    MemStoreSize memstoreSize = this.memstore.size();
2376    return DEEP_OVERHEAD + memstoreSize.getHeapSize();
2377  }
2378
2379  @Override
2380  public CellComparator getComparator() {
2381    return comparator;
2382  }
2383
2384  public ScanInfo getScanInfo() {
2385    return scanInfo;
2386  }
2387
2388  /**
2389   * Set scan info, used by test
2390   * @param scanInfo new scan info to use for test
2391   */
2392  void setScanInfo(ScanInfo scanInfo) {
2393    this.scanInfo = scanInfo;
2394  }
2395
2396  @Override
2397  public boolean hasTooManyStoreFiles() {
2398    return getStorefilesCount() > this.blockingFileCount;
2399  }
2400
2401  @Override
2402  public long getFlushedCellsCount() {
2403    return flushedCellsCount.get();
2404  }
2405
2406  @Override
2407  public long getFlushedCellsSize() {
2408    return flushedCellsSize.get();
2409  }
2410
2411  @Override
2412  public long getFlushedOutputFileSize() {
2413    return flushedOutputFileSize.get();
2414  }
2415
2416  @Override
2417  public long getCompactedCellsCount() {
2418    return compactedCellsCount.get();
2419  }
2420
2421  @Override
2422  public long getCompactedCellsSize() {
2423    return compactedCellsSize.get();
2424  }
2425
2426  @Override
2427  public long getMajorCompactedCellsCount() {
2428    return majorCompactedCellsCount.get();
2429  }
2430
2431  @Override
2432  public long getMajorCompactedCellsSize() {
2433    return majorCompactedCellsSize.get();
2434  }
2435
2436  /**
2437   * Returns the StoreEngine that is backing this concrete implementation of Store.
2438   * @return Returns the {@link StoreEngine} object used internally inside this HStore object.
2439   */
2440  @VisibleForTesting
2441  public StoreEngine<?, ?, ?, ?> getStoreEngine() {
2442    return this.storeEngine;
2443  }
2444
2445  protected OffPeakHours getOffPeakHours() {
2446    return this.offPeakHours;
2447  }
2448
2449  /**
2450   * {@inheritDoc}
2451   */
2452  @Override
2453  public void onConfigurationChange(Configuration conf) {
2454    this.conf = new CompoundConfiguration()
2455            .add(conf)
2456            .addBytesMap(family.getValues());
2457    this.storeEngine.compactionPolicy.setConf(conf);
2458    this.offPeakHours = OffPeakHours.getInstance(conf);
2459  }
2460
2461  /**
2462   * {@inheritDoc}
2463   */
2464  @Override
2465  public void registerChildren(ConfigurationManager manager) {
2466    // No children to register
2467  }
2468
2469  /**
2470   * {@inheritDoc}
2471   */
2472  @Override
2473  public void deregisterChildren(ConfigurationManager manager) {
2474    // No children to deregister
2475  }
2476
2477  @Override
2478  public double getCompactionPressure() {
2479    return storeEngine.getStoreFileManager().getCompactionPressure();
2480  }
2481
2482  @Override
2483  public boolean isPrimaryReplicaStore() {
2484    return getRegionInfo().getReplicaId() == RegionInfo.DEFAULT_REPLICA_ID;
2485  }
2486
2487  /**
2488   * Sets the store up for a region level snapshot operation.
2489   * @see #postSnapshotOperation()
2490   */
2491  public void preSnapshotOperation() {
2492    archiveLock.lock();
2493  }
2494
2495  /**
2496   * Perform tasks needed after the completion of snapshot operation.
2497   * @see #preSnapshotOperation()
2498   */
2499  public void postSnapshotOperation() {
2500    archiveLock.unlock();
2501  }
2502
2503  /**
2504   * Closes and archives the compacted files under this store
2505   */
2506  public synchronized void closeAndArchiveCompactedFiles() throws IOException {
2507    // ensure other threads do not attempt to archive the same files on close()
2508    archiveLock.lock();
2509    try {
2510      lock.readLock().lock();
2511      Collection<HStoreFile> copyCompactedfiles = null;
2512      try {
2513        Collection<HStoreFile> compactedfiles =
2514            this.getStoreEngine().getStoreFileManager().getCompactedfiles();
2515        if (CollectionUtils.isNotEmpty(compactedfiles)) {
2516          // Do a copy under read lock
2517          copyCompactedfiles = new ArrayList<>(compactedfiles);
2518        } else {
2519          LOG.trace("No compacted files to archive");
2520        }
2521      } finally {
2522        lock.readLock().unlock();
2523      }
2524      if (CollectionUtils.isNotEmpty(copyCompactedfiles)) {
2525        removeCompactedfiles(copyCompactedfiles);
2526      }
2527    } finally {
2528      archiveLock.unlock();
2529    }
2530  }
2531
2532  /**
2533   * Archives and removes the compacted files
2534   * @param compactedfiles The compacted files in this store that are not active in reads
2535   * @throws IOException
2536   */
2537  private void removeCompactedfiles(Collection<HStoreFile> compactedfiles)
2538      throws IOException {
2539    final List<HStoreFile> filesToRemove = new ArrayList<>(compactedfiles.size());
2540    for (final HStoreFile file : compactedfiles) {
2541      synchronized (file) {
2542        try {
2543          StoreFileReader r = file.getReader();
2544          if (r == null) {
2545            LOG.debug("The file {} was closed but still not archived", file);
2546            filesToRemove.add(file);
2547            continue;
2548          }
2549          if (file.isCompactedAway() && !file.isReferencedInReads()) {
2550            // Even if deleting fails we need not bother as any new scanners won't be
2551            // able to use the compacted file as the status is already compactedAway
2552            LOG.trace("Closing and archiving the file {}", file);
2553            r.close(true);
2554            // Just close and return
2555            filesToRemove.add(file);
2556          } else {
2557            LOG.info("Can't archive compacted file " + file.getPath()
2558                + " because of either isCompactedAway = " + file.isCompactedAway()
2559                + " or file has reference, isReferencedInReads = " + file.isReferencedInReads()
2560                + ", skipping for now.");
2561          }
2562        } catch (Exception e) {
2563          LOG.error("Exception while trying to close the compacted store file {}",
2564            file.getPath(), e);
2565        }
2566      }
2567    }
2568    if (this.isPrimaryReplicaStore()) {
2569      // Only the primary region is allowed to move the file to archive.
2570      // The secondary region does not move the files to archive. Any active reads from
2571      // the secondary region will still work because the file as such has active readers on it.
2572      if (!filesToRemove.isEmpty()) {
2573        LOG.debug("Moving the files {} to archive", filesToRemove);
2574        // Only if this is successful it has to be removed
2575        try {
2576          this.fs.removeStoreFiles(this.getColumnFamilyDescriptor().getNameAsString(), filesToRemove);
2577        } catch (FailedArchiveException fae) {
2578          // Even if archiving some files failed, we still need to clear out any of the
2579          // files which were successfully archived.  Otherwise we will receive a
2580          // FileNotFoundException when we attempt to re-archive them in the next go around.
2581          Collection<Path> failedFiles = fae.getFailedFiles();
2582          Iterator<HStoreFile> iter = filesToRemove.iterator();
2583          while (iter.hasNext()) {
2584            if (failedFiles.contains(iter.next().getPath())) {
2585              iter.remove();
2586            }
2587          }
2588          if (!filesToRemove.isEmpty()) {
2589            clearCompactedfiles(filesToRemove);
2590          }
2591          throw fae;
2592        }
2593      }
2594    }
2595    if (!filesToRemove.isEmpty()) {
2596      // Clear the compactedfiles from the store file manager
2597      clearCompactedfiles(filesToRemove);
2598    }
2599  }
2600
2601  public Long preFlushSeqIDEstimation() {
2602    return memstore.preFlushSeqIDEstimation();
2603  }
2604
2605  @Override
2606  public boolean isSloppyMemStore() {
2607    return this.memstore.isSloppy();
2608  }
2609
2610  private void clearCompactedfiles(List<HStoreFile> filesToRemove) throws IOException {
2611    LOG.trace("Clearing the compacted file {} from this store", filesToRemove);
2612    try {
2613      lock.writeLock().lock();
2614      this.getStoreEngine().getStoreFileManager().removeCompactedFiles(filesToRemove);
2615    } finally {
2616      lock.writeLock().unlock();
2617    }
2618  }
2619}