001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.regionserver;
019
020import java.io.FileNotFoundException;
021import java.io.IOException;
022import java.util.ArrayList;
023import java.util.Date;
024import java.util.List;
025import java.util.Map;
026import java.util.NavigableSet;
027import java.util.UUID;
028import java.util.concurrent.ConcurrentHashMap;
029import java.util.concurrent.atomic.AtomicLong;
030
031import org.apache.hadoop.conf.Configuration;
032import org.apache.hadoop.fs.FileSystem;
033import org.apache.hadoop.fs.Path;
034import org.apache.hadoop.hbase.ArrayBackedTag;
035import org.apache.hadoop.hbase.Cell;
036import org.apache.hadoop.hbase.CellBuilderType;
037import org.apache.hadoop.hbase.CellComparator;
038import org.apache.hadoop.hbase.DoNotRetryIOException;
039import org.apache.hadoop.hbase.ExtendedCellBuilderFactory;
040import org.apache.hadoop.hbase.HConstants;
041import org.apache.hadoop.hbase.TableName;
042import org.apache.hadoop.hbase.Tag;
043import org.apache.hadoop.hbase.TagType;
044import org.apache.hadoop.hbase.TagUtil;
045import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
046import org.apache.hadoop.hbase.client.Scan;
047import org.apache.hadoop.hbase.filter.Filter;
048import org.apache.hadoop.hbase.filter.FilterList;
049import org.apache.hadoop.hbase.io.compress.Compression;
050import org.apache.hadoop.hbase.io.hfile.CorruptHFileException;
051import org.apache.hadoop.hbase.mob.MobCacheConfig;
052import org.apache.hadoop.hbase.mob.MobConstants;
053import org.apache.hadoop.hbase.mob.MobFile;
054import org.apache.hadoop.hbase.mob.MobFileName;
055import org.apache.hadoop.hbase.mob.MobStoreEngine;
056import org.apache.hadoop.hbase.mob.MobUtils;
057import org.apache.hadoop.hbase.util.Bytes;
058import org.apache.hadoop.hbase.util.HFileArchiveUtil;
059import org.apache.hadoop.hbase.util.IdLock;
060import org.apache.yetus.audience.InterfaceAudience;
061import org.slf4j.Logger;
062import org.slf4j.LoggerFactory;
063
064/**
065 * The store implementation to save MOBs (medium objects), it extends the HStore.
066 * When a descriptor of a column family has the value "IS_MOB", it means this column family
067 * is a mob one. When a HRegion instantiate a store for this column family, the HMobStore is
068 * created.
069 * HMobStore is almost the same with the HStore except using different types of scanners.
070 * In the method of getScanner, the MobStoreScanner and MobReversedStoreScanner are returned.
071 * In these scanners, a additional seeks in the mob files should be performed after the seek
072 * to HBase is done.
073 * The store implements how we save MOBs by extending HStore. When a descriptor
074 * of a column family has the value "IS_MOB", it means this column family is a mob one. When a
075 * HRegion instantiate a store for this column family, the HMobStore is created. HMobStore is
076 * almost the same with the HStore except using different types of scanners. In the method of
077 * getScanner, the MobStoreScanner and MobReversedStoreScanner are returned. In these scanners, a
078 * additional seeks in the mob files should be performed after the seek in HBase is done.
079 */
080@InterfaceAudience.Private
081public class HMobStore extends HStore {
082  private static final Logger LOG = LoggerFactory.getLogger(HMobStore.class);
083  private MobCacheConfig mobCacheConfig;
084  private Path homePath;
085  private Path mobFamilyPath;
086  private AtomicLong cellsCountCompactedToMob = new AtomicLong();
087  private AtomicLong cellsCountCompactedFromMob = new AtomicLong();
088  private AtomicLong cellsSizeCompactedToMob = new AtomicLong();
089  private AtomicLong cellsSizeCompactedFromMob = new AtomicLong();
090  private AtomicLong mobFlushCount = new AtomicLong();
091  private AtomicLong mobFlushedCellsCount = new AtomicLong();
092  private AtomicLong mobFlushedCellsSize = new AtomicLong();
093  private AtomicLong mobScanCellsCount = new AtomicLong();
094  private AtomicLong mobScanCellsSize = new AtomicLong();
095  private ColumnFamilyDescriptor family;
096  private Map<String, List<Path>> map = new ConcurrentHashMap<>();
097  private final IdLock keyLock = new IdLock();
098  // When we add a MOB reference cell to the HFile, we will add 2 tags along with it
099  // 1. A ref tag with type TagType.MOB_REFERENCE_TAG_TYPE. This just denote this this cell is not
100  // original one but a ref to another MOB Cell.
101  // 2. Table name tag. It's very useful in cloning the snapshot. When reading from the cloning
102  // table, we need to find the original mob files by this table name. For details please see
103  // cloning snapshot for mob files.
104  private final byte[] refCellTags;
105
106  public HMobStore(final HRegion region, final ColumnFamilyDescriptor family,
107      final Configuration confParam) throws IOException {
108    super(region, family, confParam);
109    this.family = family;
110    this.mobCacheConfig = (MobCacheConfig) cacheConf;
111    this.homePath = MobUtils.getMobHome(conf);
112    this.mobFamilyPath = MobUtils.getMobFamilyPath(conf, this.getTableName(),
113        family.getNameAsString());
114    List<Path> locations = new ArrayList<>(2);
115    locations.add(mobFamilyPath);
116    TableName tn = region.getTableDescriptor().getTableName();
117    locations.add(HFileArchiveUtil.getStoreArchivePath(conf, tn, MobUtils.getMobRegionInfo(tn)
118        .getEncodedName(), family.getNameAsString()));
119    map.put(Bytes.toString(tn.getName()), locations);
120    List<Tag> tags = new ArrayList<>(2);
121    tags.add(MobConstants.MOB_REF_TAG);
122    Tag tableNameTag = new ArrayBackedTag(TagType.MOB_TABLE_NAME_TAG_TYPE,
123        getTableName().getName());
124    tags.add(tableNameTag);
125    this.refCellTags = TagUtil.fromList(tags);
126  }
127
128  /**
129   * Creates the mob cache config.
130   */
131  @Override
132  protected void createCacheConf(ColumnFamilyDescriptor family) {
133    cacheConf = new MobCacheConfig(conf, family);
134  }
135
136  /**
137   * Gets current config.
138   */
139  public Configuration getConfiguration() {
140    return this.conf;
141  }
142
143  /**
144   * Gets the MobStoreScanner or MobReversedStoreScanner. In these scanners, a additional seeks in
145   * the mob files should be performed after the seek in HBase is done.
146   */
147  @Override
148  protected KeyValueScanner createScanner(Scan scan, ScanInfo scanInfo,
149      NavigableSet<byte[]> targetCols, long readPt) throws IOException {
150    if (MobUtils.isRefOnlyScan(scan)) {
151      Filter refOnlyFilter = new MobReferenceOnlyFilter();
152      Filter filter = scan.getFilter();
153      if (filter != null) {
154        scan.setFilter(new FilterList(filter, refOnlyFilter));
155      } else {
156        scan.setFilter(refOnlyFilter);
157      }
158    }
159    return scan.isReversed() ? new ReversedMobStoreScanner(this, scanInfo, scan, targetCols, readPt)
160        : new MobStoreScanner(this, scanInfo, scan, targetCols, readPt);
161  }
162
163  /**
164   * Creates the mob store engine.
165   */
166  @Override
167  protected StoreEngine<?, ?, ?, ?> createStoreEngine(HStore store, Configuration conf,
168      CellComparator cellComparator) throws IOException {
169    MobStoreEngine engine = new MobStoreEngine();
170    engine.createComponents(conf, store, cellComparator);
171    return engine;
172  }
173
174  /**
175   * Gets the temp directory.
176   * @return The temp directory.
177   */
178  private Path getTempDir() {
179    return new Path(homePath, MobConstants.TEMP_DIR_NAME);
180  }
181
182  /**
183   * Creates the writer for the mob file in temp directory.
184   * @param date The latest date of written cells.
185   * @param maxKeyCount The key count.
186   * @param compression The compression algorithm.
187   * @param startKey The start key.
188   * @param isCompaction If the writer is used in compaction.
189   * @return The writer for the mob file.
190   * @throws IOException
191   */
192  public StoreFileWriter createWriterInTmp(Date date, long maxKeyCount,
193      Compression.Algorithm compression, byte[] startKey,
194      boolean isCompaction) throws IOException {
195    if (startKey == null) {
196      startKey = HConstants.EMPTY_START_ROW;
197    }
198    Path path = getTempDir();
199    return createWriterInTmp(MobUtils.formatDate(date), path, maxKeyCount, compression, startKey,
200      isCompaction);
201  }
202
203  /**
204   * Creates the writer for the del file in temp directory.
205   * The del file keeps tracking the delete markers. Its name has a suffix _del,
206   * the format is [0-9a-f]+(_del)?.
207   * @param date The latest date of written cells.
208   * @param maxKeyCount The key count.
209   * @param compression The compression algorithm.
210   * @param startKey The start key.
211   * @return The writer for the del file.
212   * @throws IOException
213   */
214  public StoreFileWriter createDelFileWriterInTmp(Date date, long maxKeyCount,
215      Compression.Algorithm compression, byte[] startKey) throws IOException {
216    if (startKey == null) {
217      startKey = HConstants.EMPTY_START_ROW;
218    }
219    Path path = getTempDir();
220    String suffix = UUID
221        .randomUUID().toString().replaceAll("-", "") + "_del";
222    MobFileName mobFileName = MobFileName.create(startKey, MobUtils.formatDate(date), suffix);
223    return createWriterInTmp(mobFileName, path, maxKeyCount, compression, true);
224  }
225
226  /**
227   * Creates the writer for the mob file in temp directory.
228   * @param date The date string, its format is yyyymmmdd.
229   * @param basePath The basic path for a temp directory.
230   * @param maxKeyCount The key count.
231   * @param compression The compression algorithm.
232   * @param startKey The start key.
233   * @param isCompaction If the writer is used in compaction.
234   * @return The writer for the mob file.
235   * @throws IOException
236   */
237  public StoreFileWriter createWriterInTmp(String date, Path basePath, long maxKeyCount,
238      Compression.Algorithm compression, byte[] startKey,
239      boolean isCompaction) throws IOException {
240    MobFileName mobFileName = MobFileName.create(startKey, date, UUID.randomUUID()
241        .toString().replaceAll("-", ""));
242    return createWriterInTmp(mobFileName, basePath, maxKeyCount, compression, isCompaction);
243  }
244
245  /**
246   * Creates the writer for the mob file in temp directory.
247   * @param mobFileName The mob file name.
248   * @param basePath The basic path for a temp directory.
249   * @param maxKeyCount The key count.
250   * @param compression The compression algorithm.
251   * @param isCompaction If the writer is used in compaction.
252   * @return The writer for the mob file.
253   * @throws IOException
254   */
255  public StoreFileWriter createWriterInTmp(MobFileName mobFileName, Path basePath,
256      long maxKeyCount, Compression.Algorithm compression,
257      boolean isCompaction) throws IOException {
258    return MobUtils.createWriter(conf, region.getFilesystem(), family,
259      new Path(basePath, mobFileName.getFileName()), maxKeyCount, compression, mobCacheConfig,
260      cryptoContext, checksumType, bytesPerChecksum, blocksize, BloomType.NONE, isCompaction);
261  }
262
263  /**
264   * Commits the mob file.
265   * @param sourceFile The source file.
266   * @param targetPath The directory path where the source file is renamed to.
267   * @throws IOException
268   */
269  public void commitFile(final Path sourceFile, Path targetPath) throws IOException {
270    if (sourceFile == null) {
271      return;
272    }
273    Path dstPath = new Path(targetPath, sourceFile.getName());
274    validateMobFile(sourceFile);
275    String msg = "Renaming flushed file from " + sourceFile + " to " + dstPath;
276    LOG.info(msg);
277    Path parent = dstPath.getParent();
278    if (!region.getFilesystem().exists(parent)) {
279      region.getFilesystem().mkdirs(parent);
280    }
281    if (!region.getFilesystem().rename(sourceFile, dstPath)) {
282      throw new IOException("Failed rename of " + sourceFile + " to " + dstPath);
283    }
284  }
285
286  /**
287   * Validates a mob file by opening and closing it.
288   *
289   * @param path the path to the mob file
290   */
291  private void validateMobFile(Path path) throws IOException {
292    HStoreFile storeFile = null;
293    try {
294      storeFile = new HStoreFile(region.getFilesystem(), path, conf, this.mobCacheConfig,
295          BloomType.NONE, isPrimaryReplicaStore());
296      storeFile.initReader();
297    } catch (IOException e) {
298      LOG.error("Fail to open mob file[" + path + "], keep it in temp directory.", e);
299      throw e;
300    } finally {
301      if (storeFile != null) {
302        storeFile.closeStoreFile(false);
303      }
304    }
305  }
306
307  /**
308   * Reads the cell from the mob file, and the read point does not count.
309   * This is used for DefaultMobStoreCompactor where we can read empty value for the missing cell.
310   * @param reference The cell found in the HBase, its value is a path to a mob file.
311   * @param cacheBlocks Whether the scanner should cache blocks.
312   * @return The cell found in the mob file.
313   * @throws IOException
314   */
315  public Cell resolve(Cell reference, boolean cacheBlocks) throws IOException {
316    return resolve(reference, cacheBlocks, -1, true);
317  }
318
319  /**
320   * Reads the cell from the mob file.
321   * @param reference The cell found in the HBase, its value is a path to a mob file.
322   * @param cacheBlocks Whether the scanner should cache blocks.
323   * @param readPt the read point.
324   * @param readEmptyValueOnMobCellMiss Whether return null value when the mob file is
325   *        missing or corrupt.
326   * @return The cell found in the mob file.
327   * @throws IOException
328   */
329  public Cell resolve(Cell reference, boolean cacheBlocks, long readPt,
330    boolean readEmptyValueOnMobCellMiss) throws IOException {
331    Cell result = null;
332    if (MobUtils.hasValidMobRefCellValue(reference)) {
333      String fileName = MobUtils.getMobFileName(reference);
334      Tag tableNameTag = MobUtils.getTableNameTag(reference);
335      if (tableNameTag != null) {
336        String tableNameString = Tag.getValueAsString(tableNameTag);
337        List<Path> locations = map.get(tableNameString);
338        if (locations == null) {
339          IdLock.Entry lockEntry = keyLock.getLockEntry(tableNameString.hashCode());
340          try {
341            locations = map.get(tableNameString);
342            if (locations == null) {
343              locations = new ArrayList<>(2);
344              TableName tn = TableName.valueOf(tableNameString);
345              locations.add(MobUtils.getMobFamilyPath(conf, tn, family.getNameAsString()));
346              locations.add(HFileArchiveUtil.getStoreArchivePath(conf, tn, MobUtils
347                  .getMobRegionInfo(tn).getEncodedName(), family.getNameAsString()));
348              map.put(tableNameString, locations);
349            }
350          } finally {
351            keyLock.releaseLockEntry(lockEntry);
352          }
353        }
354        result = readCell(locations, fileName, reference, cacheBlocks, readPt,
355          readEmptyValueOnMobCellMiss);
356      }
357    }
358    if (result == null) {
359      LOG.warn("The Cell result is null, assemble a new Cell with the same row,family,"
360          + "qualifier,timestamp,type and tags but with an empty value to return.");
361      result = ExtendedCellBuilderFactory.create(CellBuilderType.DEEP_COPY)
362              .setRow(reference.getRowArray(), reference.getRowOffset(), reference.getRowLength())
363              .setFamily(reference.getFamilyArray(), reference.getFamilyOffset(),
364                reference.getFamilyLength())
365              .setQualifier(reference.getQualifierArray(),
366                reference.getQualifierOffset(), reference.getQualifierLength())
367              .setTimestamp(reference.getTimestamp())
368              .setType(reference.getTypeByte())
369              .setValue(HConstants.EMPTY_BYTE_ARRAY)
370              .setTags(reference.getTagsArray(), reference.getTagsOffset(),
371                reference.getTagsLength())
372              .build();
373    }
374    return result;
375  }
376
377  /**
378   * Reads the cell from a mob file.
379   * The mob file might be located in different directories.
380   * 1. The working directory.
381   * 2. The archive directory.
382   * Reads the cell from the files located in both of the above directories.
383   * @param locations The possible locations where the mob files are saved.
384   * @param fileName The file to be read.
385   * @param search The cell to be searched.
386   * @param cacheMobBlocks Whether the scanner should cache blocks.
387   * @param readPt the read point.
388   * @param readEmptyValueOnMobCellMiss Whether return null value when the mob file is
389   *        missing or corrupt.
390   * @return The found cell. Null if there's no such a cell.
391   * @throws IOException
392   */
393  private Cell readCell(List<Path> locations, String fileName, Cell search, boolean cacheMobBlocks,
394    long readPt, boolean readEmptyValueOnMobCellMiss) throws IOException {
395    FileSystem fs = getFileSystem();
396    Throwable throwable = null;
397    for (Path location : locations) {
398      MobFile file = null;
399      Path path = new Path(location, fileName);
400      try {
401        file = mobCacheConfig.getMobFileCache().openFile(fs, path, mobCacheConfig);
402        return readPt != -1 ? file.readCell(search, cacheMobBlocks, readPt) : file.readCell(search,
403          cacheMobBlocks);
404      } catch (IOException e) {
405        mobCacheConfig.getMobFileCache().evictFile(fileName);
406        throwable = e;
407        if ((e instanceof FileNotFoundException) ||
408            (e.getCause() instanceof FileNotFoundException)) {
409          LOG.debug("Fail to read the cell, the mob file " + path + " doesn't exist", e);
410        } else if (e instanceof CorruptHFileException) {
411          LOG.error("The mob file " + path + " is corrupt", e);
412          break;
413        } else {
414          throw e;
415        }
416      } catch (NullPointerException e) { // HDFS 1.x - DFSInputStream.getBlockAt()
417        mobCacheConfig.getMobFileCache().evictFile(fileName);
418        LOG.debug("Fail to read the cell", e);
419        throwable = e;
420      } catch (AssertionError e) { // assert in HDFS 1.x - DFSInputStream.getBlockAt()
421        mobCacheConfig.getMobFileCache().evictFile(fileName);
422        LOG.debug("Fail to read the cell", e);
423        throwable = e;
424      } finally {
425        if (file != null) {
426          mobCacheConfig.getMobFileCache().closeFile(file);
427        }
428      }
429    }
430    LOG.error("The mob file " + fileName + " could not be found in the locations " + locations
431      + " or it is corrupt");
432    if (readEmptyValueOnMobCellMiss) {
433      return null;
434    } else if ((throwable instanceof FileNotFoundException)
435        || (throwable.getCause() instanceof FileNotFoundException)) {
436      // The region is re-opened when FileNotFoundException is thrown.
437      // This is not necessary when MOB files cannot be found, because the store files
438      // in a region only contain the references to MOB files and a re-open on a region
439      // doesn't help fix the lost MOB files.
440      throw new DoNotRetryIOException(throwable);
441    } else if (throwable instanceof IOException) {
442      throw (IOException) throwable;
443    } else {
444      throw new IOException(throwable);
445    }
446  }
447
448  /**
449   * Gets the mob file path.
450   * @return The mob file path.
451   */
452  public Path getPath() {
453    return mobFamilyPath;
454  }
455
456  public void updateCellsCountCompactedToMob(long count) {
457    cellsCountCompactedToMob.addAndGet(count);
458  }
459
460  public long getCellsCountCompactedToMob() {
461    return cellsCountCompactedToMob.get();
462  }
463
464  public void updateCellsCountCompactedFromMob(long count) {
465    cellsCountCompactedFromMob.addAndGet(count);
466  }
467
468  public long getCellsCountCompactedFromMob() {
469    return cellsCountCompactedFromMob.get();
470  }
471
472  public void updateCellsSizeCompactedToMob(long size) {
473    cellsSizeCompactedToMob.addAndGet(size);
474  }
475
476  public long getCellsSizeCompactedToMob() {
477    return cellsSizeCompactedToMob.get();
478  }
479
480  public void updateCellsSizeCompactedFromMob(long size) {
481    cellsSizeCompactedFromMob.addAndGet(size);
482  }
483
484  public long getCellsSizeCompactedFromMob() {
485    return cellsSizeCompactedFromMob.get();
486  }
487
488  public void updateMobFlushCount() {
489    mobFlushCount.incrementAndGet();
490  }
491
492  public long getMobFlushCount() {
493    return mobFlushCount.get();
494  }
495
496  public void updateMobFlushedCellsCount(long count) {
497    mobFlushedCellsCount.addAndGet(count);
498  }
499
500  public long getMobFlushedCellsCount() {
501    return mobFlushedCellsCount.get();
502  }
503
504  public void updateMobFlushedCellsSize(long size) {
505    mobFlushedCellsSize.addAndGet(size);
506  }
507
508  public long getMobFlushedCellsSize() {
509    return mobFlushedCellsSize.get();
510  }
511
512  public void updateMobScanCellsCount(long count) {
513    mobScanCellsCount.addAndGet(count);
514  }
515
516  public long getMobScanCellsCount() {
517    return mobScanCellsCount.get();
518  }
519
520  public void updateMobScanCellsSize(long size) {
521    mobScanCellsSize.addAndGet(size);
522  }
523
524  public long getMobScanCellsSize() {
525    return mobScanCellsSize.get();
526  }
527
528  public byte[] getRefCellTags() {
529    return this.refCellTags;
530  }
531}