View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.regionserver;
19  
20  import java.io.FileNotFoundException;
21  import java.io.IOException;
22  import java.util.ArrayList;
23  import java.util.Date;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.NavigableSet;
27  import java.util.UUID;
28  import java.util.concurrent.ConcurrentHashMap;
29  
30  import org.apache.commons.logging.Log;
31  import org.apache.commons.logging.LogFactory;
32  import org.apache.hadoop.hbase.classification.InterfaceAudience;
33  import org.apache.hadoop.conf.Configuration;
34  import org.apache.hadoop.fs.FileSystem;
35  import org.apache.hadoop.fs.Path;
36  import org.apache.hadoop.hbase.Cell;
37  import org.apache.hadoop.hbase.CellComparator;
38  import org.apache.hadoop.hbase.HColumnDescriptor;
39  import org.apache.hadoop.hbase.HConstants;
40  import org.apache.hadoop.hbase.KeyValue;
41  import org.apache.hadoop.hbase.KeyValue.KVComparator;
42  import org.apache.hadoop.hbase.KeyValue.Type;
43  import org.apache.hadoop.hbase.TableName;
44  import org.apache.hadoop.hbase.Tag;
45  import org.apache.hadoop.hbase.client.Scan;
46  import org.apache.hadoop.hbase.filter.Filter;
47  import org.apache.hadoop.hbase.filter.FilterList;
48  import org.apache.hadoop.hbase.io.compress.Compression;
49  import org.apache.hadoop.hbase.io.hfile.CacheConfig;
50  import org.apache.hadoop.hbase.io.hfile.CorruptHFileException;
51  import org.apache.hadoop.hbase.io.hfile.HFileContext;
52  import org.apache.hadoop.hbase.io.hfile.HFileContextBuilder;
53  import org.apache.hadoop.hbase.master.TableLockManager;
54  import org.apache.hadoop.hbase.master.TableLockManager.TableLock;
55  import org.apache.hadoop.hbase.mob.MobCacheConfig;
56  import org.apache.hadoop.hbase.mob.MobConstants;
57  import org.apache.hadoop.hbase.mob.MobFile;
58  import org.apache.hadoop.hbase.mob.MobFileName;
59  import org.apache.hadoop.hbase.mob.MobStoreEngine;
60  import org.apache.hadoop.hbase.mob.MobUtils;
61  import org.apache.hadoop.hbase.regionserver.compactions.CompactionContext;
62  import org.apache.hadoop.hbase.regionserver.compactions.CompactionThroughputController;
63  import org.apache.hadoop.hbase.util.Bytes;
64  import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
65  import org.apache.hadoop.hbase.util.HFileArchiveUtil;
66  import org.apache.hadoop.hbase.util.IdLock;
67  
68  /**
69   * The store implementation to save MOBs (medium objects), it extends the HStore.
70   * When a descriptor of a column family has the value "IS_MOB", it means this column family
71   * is a mob one. When a HRegion instantiate a store for this column family, the HMobStore is
72   * created.
73   * HMobStore is almost the same with the HStore except using different types of scanners.
74   * In the method of getScanner, the MobStoreScanner and MobReversedStoreScanner are returned.
75   * In these scanners, a additional seeks in the mob files should be performed after the seek
76   * to HBase is done.
77   * The store implements how we save MOBs by extending HStore. When a descriptor
78   * of a column family has the value "IS_MOB", it means this column family is a mob one. When a
79   * HRegion instantiate a store for this column family, the HMobStore is created. HMobStore is
80   * almost the same with the HStore except using different types of scanners. In the method of
81   * getScanner, the MobStoreScanner and MobReversedStoreScanner are returned. In these scanners, a
82   * additional seeks in the mob files should be performed after the seek in HBase is done.
83   */
84  @InterfaceAudience.Private
85  public class HMobStore extends HStore {
86    private static final Log LOG = LogFactory.getLog(HMobStore.class);
87    private MobCacheConfig mobCacheConfig;
88    private Path homePath;
89    private Path mobFamilyPath;
90    private volatile long cellsCountCompactedToMob = 0;
91    private volatile long cellsCountCompactedFromMob = 0;
92    private volatile long cellsSizeCompactedToMob = 0;
93    private volatile long cellsSizeCompactedFromMob = 0;
94    private volatile long mobFlushCount = 0;
95    private volatile long mobFlushedCellsCount = 0;
96    private volatile long mobFlushedCellsSize = 0;
97    private volatile long mobScanCellsCount = 0;
98    private volatile long mobScanCellsSize = 0;
99    private HColumnDescriptor family;
100   private Map<String, List<Path>> map = new ConcurrentHashMap<String, List<Path>>();
101   private final IdLock keyLock = new IdLock();
102 
103   public HMobStore(final HRegion region, final HColumnDescriptor family,
104       final Configuration confParam) throws IOException {
105     super(region, family, confParam);
106     this.family = family;
107     this.mobCacheConfig = (MobCacheConfig) cacheConf;
108     this.homePath = MobUtils.getMobHome(conf);
109     this.mobFamilyPath = MobUtils.getMobFamilyPath(conf, this.getTableName(),
110         family.getNameAsString());
111     List<Path> locations = new ArrayList<Path>(2);
112     locations.add(mobFamilyPath);
113     TableName tn = region.getTableDesc().getTableName();
114     locations.add(HFileArchiveUtil.getStoreArchivePath(conf, tn, MobUtils.getMobRegionInfo(tn)
115         .getEncodedName(), family.getNameAsString()));
116     map.put(Bytes.toString(tn.getName()), locations);
117   }
118 
119   /**
120    * Creates the mob cache config.
121    */
122   @Override
123   protected void createCacheConf(HColumnDescriptor family) {
124     cacheConf = new MobCacheConfig(conf, family);
125   }
126 
127   /**
128    * Gets current config.
129    */
130   public Configuration getConfiguration() {
131     return this.conf;
132   }
133 
134   /**
135    * Gets the MobStoreScanner or MobReversedStoreScanner. In these scanners, a additional seeks in
136    * the mob files should be performed after the seek in HBase is done.
137    */
138   @Override
139   protected KeyValueScanner createScanner(Scan scan, final NavigableSet<byte[]> targetCols,
140       long readPt, KeyValueScanner scanner) throws IOException {
141     if (scanner == null) {
142       if (MobUtils.isRefOnlyScan(scan)) {
143         Filter refOnlyFilter = new MobReferenceOnlyFilter();
144         Filter filter = scan.getFilter();
145         if (filter != null) {
146           scan.setFilter(new FilterList(filter, refOnlyFilter));
147         } else {
148           scan.setFilter(refOnlyFilter);
149         }
150       }
151       scanner = scan.isReversed() ? new ReversedMobStoreScanner(this, getScanInfo(), scan,
152           targetCols, readPt) : new MobStoreScanner(this, getScanInfo(), scan, targetCols, readPt);
153     }
154     return scanner;
155   }
156 
157   /**
158    * Creates the mob store engine.
159    */
160   @Override
161   protected StoreEngine<?, ?, ?, ?> createStoreEngine(Store store, Configuration conf,
162       KVComparator cellComparator) throws IOException {
163     MobStoreEngine engine = new MobStoreEngine();
164     engine.createComponents(conf, store, cellComparator);
165     return engine;
166   }
167 
168   /**
169    * Gets the temp directory.
170    * @return The temp directory.
171    */
172   private Path getTempDir() {
173     return new Path(homePath, MobConstants.TEMP_DIR_NAME);
174   }
175 
176   /**
177    * Creates the writer for the mob file in temp directory.
178    * @param date The latest date of written cells.
179    * @param maxKeyCount The key count.
180    * @param compression The compression algorithm.
181    * @param startKey The start key.
182    * @return The writer for the mob file.
183    * @throws IOException
184    */
185   public StoreFile.Writer createWriterInTmp(Date date, long maxKeyCount,
186       Compression.Algorithm compression, byte[] startKey) throws IOException {
187     if (startKey == null) {
188       startKey = HConstants.EMPTY_START_ROW;
189     }
190     Path path = getTempDir();
191     return createWriterInTmp(MobUtils.formatDate(date), path, maxKeyCount, compression, startKey);
192   }
193 
194   /**
195    * Creates the writer for the del file in temp directory.
196    * The del file keeps tracking the delete markers. Its name has a suffix _del,
197    * the format is [0-9a-f]+(_del)?.
198    * @param date The latest date of written cells.
199    * @param maxKeyCount The key count.
200    * @param compression The compression algorithm.
201    * @param startKey The start key.
202    * @return The writer for the del file.
203    * @throws IOException
204    */
205   public StoreFile.Writer createDelFileWriterInTmp(Date date, long maxKeyCount,
206       Compression.Algorithm compression, byte[] startKey) throws IOException {
207     if (startKey == null) {
208       startKey = HConstants.EMPTY_START_ROW;
209     }
210     Path path = getTempDir();
211     String suffix = UUID
212         .randomUUID().toString().replaceAll("-", "") + "_del";
213     MobFileName mobFileName = MobFileName.create(startKey, MobUtils.formatDate(date), suffix);
214     return createWriterInTmp(mobFileName, path, maxKeyCount, compression);
215   }
216 
217   /**
218    * Creates the writer for the mob file in temp directory.
219    * @param date The date string, its format is yyyymmmdd.
220    * @param basePath The basic path for a temp directory.
221    * @param maxKeyCount The key count.
222    * @param compression The compression algorithm.
223    * @param startKey The start key.
224    * @return The writer for the mob file.
225    * @throws IOException
226    */
227   public StoreFile.Writer createWriterInTmp(String date, Path basePath, long maxKeyCount,
228       Compression.Algorithm compression, byte[] startKey) throws IOException {
229     MobFileName mobFileName = MobFileName.create(startKey, date, UUID.randomUUID()
230         .toString().replaceAll("-", ""));
231     return createWriterInTmp(mobFileName, basePath, maxKeyCount, compression);
232   }
233 
234   /**
235    * Creates the writer for the mob file in temp directory.
236    * @param mobFileName The mob file name.
237    * @param basePath The basic path for a temp directory.
238    * @param maxKeyCount The key count.
239    * @param compression The compression algorithm.
240    * @return The writer for the mob file.
241    * @throws IOException
242    */
243   public StoreFile.Writer createWriterInTmp(MobFileName mobFileName, Path basePath, long maxKeyCount,
244       Compression.Algorithm compression) throws IOException {
245     final CacheConfig writerCacheConf = mobCacheConfig;
246     HFileContext hFileContext = new HFileContextBuilder().withCompression(compression)
247         .withIncludesMvcc(true).withIncludesTags(true)
248         .withCompressTags(family.isCompressTags())
249         .withChecksumType(checksumType)
250         .withBytesPerCheckSum(bytesPerChecksum)
251         .withBlockSize(blocksize)
252         .withHBaseCheckSum(true).withDataBlockEncoding(getFamily().getDataBlockEncoding())
253         .withEncryptionContext(cryptoContext)
254         .withCreateTime(EnvironmentEdgeManager.currentTime()).build();
255 
256     StoreFile.Writer w = new StoreFile.WriterBuilder(conf, writerCacheConf, region.getFilesystem())
257         .withFilePath(new Path(basePath, mobFileName.getFileName()))
258         .withComparator(KeyValue.COMPARATOR).withBloomType(BloomType.NONE)
259         .withMaxKeyCount(maxKeyCount).withFileContext(hFileContext).build();
260     return w;
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     StoreFile storeFile = null;
293     try {
294       storeFile =
295           new StoreFile(region.getFilesystem(), path, conf, this.mobCacheConfig, BloomType.NONE);
296       storeFile.createReader();
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.closeReader(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         byte[] tableName = tableNameTag.getValue();
337         String tableNameString = Bytes.toString(tableName);
338         List<Path> locations = map.get(tableNameString);
339         if (locations == null) {
340           IdLock.Entry lockEntry = keyLock.getLockEntry(tableNameString.hashCode());
341           try {
342             locations = map.get(tableNameString);
343             if (locations == null) {
344               locations = new ArrayList<Path>(2);
345               TableName tn = TableName.valueOf(tableName);
346               locations.add(MobUtils.getMobFamilyPath(conf, tn, family.getNameAsString()));
347               locations.add(HFileArchiveUtil.getStoreArchivePath(conf, tn, MobUtils
348                   .getMobRegionInfo(tn).getEncodedName(), family.getNameAsString()));
349               map.put(tableNameString, locations);
350             }
351           } finally {
352             keyLock.releaseLockEntry(lockEntry);
353           }
354         }
355         result = readCell(locations, fileName, reference, cacheBlocks, readPt,
356           readEmptyValueOnMobCellMiss);
357       }
358     }
359     if (result == null) {
360       LOG.warn("The KeyValue result is null, assemble a new KeyValue with the same row,family,"
361           + "qualifier,timestamp,type and tags but with an empty value to return.");
362       result = new KeyValue(reference.getRowArray(), reference.getRowOffset(),
363           reference.getRowLength(), reference.getFamilyArray(), reference.getFamilyOffset(),
364           reference.getFamilyLength(), reference.getQualifierArray(),
365           reference.getQualifierOffset(), reference.getQualifierLength(), reference.getTimestamp(),
366           Type.codeToType(reference.getTypeByte()), HConstants.EMPTY_BYTE_ARRAY,
367           0, 0, reference.getTagsArray(), reference.getTagsOffset(),
368           reference.getTagsLength());
369     }
370     return result;
371   }
372 
373   /**
374    * Reads the cell from a mob file.
375    * The mob file might be located in different directories.
376    * 1. The working directory.
377    * 2. The archive directory.
378    * Reads the cell from the files located in both of the above directories.
379    * @param locations The possible locations where the mob files are saved.
380    * @param fileName The file to be read.
381    * @param search The cell to be searched.
382    * @param cacheMobBlocks Whether the scanner should cache blocks.
383    * @param readPt the read point.
384    * @param readEmptyValueOnMobCellMiss Whether return null value when the mob file is
385    *        missing or corrupt.
386    * @return The found cell. Null if there's no such a cell.
387    * @throws IOException
388    */
389   private Cell readCell(List<Path> locations, String fileName, Cell search, boolean cacheMobBlocks,
390     long readPt, boolean readEmptyValueOnMobCellMiss) throws IOException {
391     FileSystem fs = getFileSystem();
392     Throwable throwable = null;
393     for (Path location : locations) {
394       MobFile file = null;
395       Path path = new Path(location, fileName);
396       try {
397         file = mobCacheConfig.getMobFileCache().openFile(fs, path, mobCacheConfig);
398         return readPt != -1 ? file.readCell(search, cacheMobBlocks, readPt) : file.readCell(search,
399           cacheMobBlocks);
400       } catch (IOException e) {
401         mobCacheConfig.getMobFileCache().evictFile(fileName);
402         throwable = e;
403         if ((e instanceof FileNotFoundException) ||
404             (e.getCause() instanceof FileNotFoundException)) {
405           LOG.warn("Fail to read the cell, the mob file " + path + " doesn't exist", e);
406         } else if (e instanceof CorruptHFileException) {
407           LOG.error("The mob file " + path + " is corrupt", e);
408           break;
409         } else {
410           throw e;
411         }
412       } catch (NullPointerException e) { // HDFS 1.x - DFSInputStream.getBlockAt()
413         mobCacheConfig.getMobFileCache().evictFile(fileName);
414         LOG.warn("Fail to read the cell", e);
415         throwable = e;
416       } catch (AssertionError e) { // assert in HDFS 1.x - DFSInputStream.getBlockAt()
417         mobCacheConfig.getMobFileCache().evictFile(fileName);
418         LOG.warn("Fail to read the cell", e);
419         throwable = e;
420       } finally {
421         if (file != null) {
422           mobCacheConfig.getMobFileCache().closeFile(file);
423         }
424       }
425     }
426     LOG.error("The mob file " + fileName + " could not be found in the locations " + locations
427       + " or it is corrupt");
428     if (readEmptyValueOnMobCellMiss) {
429       return null;
430     } else if (throwable instanceof IOException) {
431       throw (IOException) throwable;
432     } else {
433       throw new IOException(throwable);
434     }
435   }
436 
437   /**
438    * Gets the mob file path.
439    * @return The mob file path.
440    */
441   public Path getPath() {
442     return mobFamilyPath;
443   }
444 
445   public void updateCellsCountCompactedToMob(long count) {
446     cellsCountCompactedToMob += count;
447   }
448 
449   public long getCellsCountCompactedToMob() {
450     return cellsCountCompactedToMob;
451   }
452 
453   public void updateCellsCountCompactedFromMob(long count) {
454     cellsCountCompactedFromMob += count;
455   }
456 
457   public long getCellsCountCompactedFromMob() {
458     return cellsCountCompactedFromMob;
459   }
460 
461   public void updateCellsSizeCompactedToMob(long size) {
462     cellsSizeCompactedToMob += size;
463   }
464 
465   public long getCellsSizeCompactedToMob() {
466     return cellsSizeCompactedToMob;
467   }
468 
469   public void updateCellsSizeCompactedFromMob(long size) {
470     cellsSizeCompactedFromMob += size;
471   }
472 
473   public long getCellsSizeCompactedFromMob() {
474     return cellsSizeCompactedFromMob;
475   }
476 
477   @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "VO_VOLATILE_INCREMENT")
478   public void updateMobFlushCount() {
479     mobFlushCount++;
480   }
481 
482   public long getMobFlushCount() {
483     return mobFlushCount;
484   }
485 
486   public void updateMobFlushedCellsCount(long count) {
487     mobFlushedCellsCount += count;
488   }
489 
490   public long getMobFlushedCellsCount() {
491     return mobFlushedCellsCount;
492   }
493 
494   public void updateMobFlushedCellsSize(long size) {
495     mobFlushedCellsSize += size;
496   }
497 
498   public long getMobFlushedCellsSize() {
499     return mobFlushedCellsSize;
500   }
501 
502   public void updateMobScanCellsCount(long count) {
503     mobScanCellsCount += count;
504   }
505 
506   public long getMobScanCellsCount() {
507     return mobScanCellsCount;
508   }
509 
510   public void updateMobScanCellsSize(long size) {
511     mobScanCellsSize += size;
512   }
513 
514   public long getMobScanCellsSize() {
515     return mobScanCellsSize;
516   }
517 }