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.mob;
020
021import java.io.FileNotFoundException;
022import java.io.IOException;
023import java.text.ParseException;
024import java.text.SimpleDateFormat;
025import java.util.ArrayList;
026import java.util.Calendar;
027import java.util.Collection;
028import java.util.Date;
029import java.util.List;
030import java.util.Optional;
031import java.util.UUID;
032import java.util.concurrent.ExecutorService;
033import java.util.concurrent.RejectedExecutionException;
034import java.util.concurrent.RejectedExecutionHandler;
035import java.util.concurrent.SynchronousQueue;
036import java.util.concurrent.ThreadPoolExecutor;
037import java.util.concurrent.TimeUnit;
038
039import org.apache.hadoop.conf.Configuration;
040import org.apache.hadoop.fs.FileStatus;
041import org.apache.hadoop.fs.FileSystem;
042import org.apache.hadoop.fs.Path;
043import org.apache.hadoop.hbase.Cell;
044import org.apache.hadoop.hbase.CellComparator;
045import org.apache.hadoop.hbase.HConstants;
046import org.apache.hadoop.hbase.PrivateCellUtil;
047import org.apache.hadoop.hbase.TableName;
048import org.apache.hadoop.hbase.Tag;
049import org.apache.hadoop.hbase.TagType;
050import org.apache.hadoop.hbase.TagUtil;
051import org.apache.hadoop.hbase.backup.HFileArchiver;
052import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
053import org.apache.hadoop.hbase.client.MobCompactPartitionPolicy;
054import org.apache.hadoop.hbase.client.RegionInfo;
055import org.apache.hadoop.hbase.client.RegionInfoBuilder;
056import org.apache.hadoop.hbase.client.Scan;
057import org.apache.hadoop.hbase.client.TableDescriptor;
058import org.apache.hadoop.hbase.io.HFileLink;
059import org.apache.hadoop.hbase.io.compress.Compression;
060import org.apache.hadoop.hbase.io.crypto.Encryption;
061import org.apache.hadoop.hbase.io.hfile.CacheConfig;
062import org.apache.hadoop.hbase.io.hfile.HFile;
063import org.apache.hadoop.hbase.io.hfile.HFileContext;
064import org.apache.hadoop.hbase.io.hfile.HFileContextBuilder;
065import org.apache.hadoop.hbase.master.locking.LockManager;
066import org.apache.hadoop.hbase.mob.compactions.MobCompactor;
067import org.apache.hadoop.hbase.mob.compactions.PartitionedMobCompactionRequest.CompactionPartitionId;
068import org.apache.hadoop.hbase.mob.compactions.PartitionedMobCompactor;
069import org.apache.hadoop.hbase.regionserver.BloomType;
070import org.apache.hadoop.hbase.regionserver.HStore;
071import org.apache.hadoop.hbase.regionserver.HStoreFile;
072import org.apache.hadoop.hbase.regionserver.StoreFileWriter;
073import org.apache.hadoop.hbase.util.Bytes;
074import org.apache.hadoop.hbase.util.ChecksumType;
075import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
076import org.apache.hadoop.hbase.util.FSUtils;
077import org.apache.hadoop.hbase.util.ReflectionUtils;
078import org.apache.hadoop.hbase.util.Threads;
079import org.apache.yetus.audience.InterfaceAudience;
080import org.slf4j.Logger;
081import org.slf4j.LoggerFactory;
082
083/**
084 * The mob utilities
085 */
086@InterfaceAudience.Private
087public final class MobUtils {
088
089  private static final Logger LOG = LoggerFactory.getLogger(MobUtils.class);
090  private final static long WEEKLY_THRESHOLD_MULTIPLIER = 7;
091  private final static long MONTHLY_THRESHOLD_MULTIPLIER = 4 * WEEKLY_THRESHOLD_MULTIPLIER;
092
093  private static final ThreadLocal<SimpleDateFormat> LOCAL_FORMAT =
094      new ThreadLocal<SimpleDateFormat>() {
095    @Override
096    protected SimpleDateFormat initialValue() {
097      return new SimpleDateFormat("yyyyMMdd");
098    }
099  };
100
101  private static final byte[] REF_DELETE_MARKER_TAG_BYTES;
102  static {
103    List<Tag> tags = new ArrayList<>();
104    tags.add(MobConstants.MOB_REF_TAG);
105    REF_DELETE_MARKER_TAG_BYTES = TagUtil.fromList(tags);
106  }
107
108  /**
109   * Private constructor to keep this class from being instantiated.
110   */
111  private MobUtils() {
112  }
113
114  /**
115   * Formats a date to a string.
116   * @param date The date.
117   * @return The string format of the date, it's yyyymmdd.
118   */
119  public static String formatDate(Date date) {
120    return LOCAL_FORMAT.get().format(date);
121  }
122
123  /**
124   * Parses the string to a date.
125   * @param dateString The string format of a date, it's yyyymmdd.
126   * @return A date.
127   * @throws ParseException
128   */
129  public static Date parseDate(String dateString) throws ParseException {
130    return LOCAL_FORMAT.get().parse(dateString);
131  }
132
133  /**
134   * Get the first day of the input date's month
135   * @param calendar Calendar object
136   * @param date The date to find out its first day of that month
137   * @return The first day in the month
138   */
139  public static Date getFirstDayOfMonth(final Calendar calendar, final Date date) {
140
141    calendar.setTime(date);
142    calendar.set(Calendar.HOUR_OF_DAY, 0);
143    calendar.set(Calendar.MINUTE, 0);
144    calendar.set(Calendar.SECOND, 0);
145    calendar.set(Calendar.MILLISECOND, 0);
146    calendar.set(Calendar.DAY_OF_MONTH, 1);
147
148    Date firstDayInMonth = calendar.getTime();
149    return firstDayInMonth;
150  }
151
152  /**
153   * Get the first day of the input date's week
154   * @param calendar Calendar object
155   * @param date The date to find out its first day of that week
156   * @return The first day in the week
157   */
158  public static Date getFirstDayOfWeek(final Calendar calendar, final Date date) {
159
160    calendar.setTime(date);
161    calendar.set(Calendar.HOUR_OF_DAY, 0);
162    calendar.set(Calendar.MINUTE, 0);
163    calendar.set(Calendar.SECOND, 0);
164    calendar.set(Calendar.MILLISECOND, 0);
165    calendar.setFirstDayOfWeek(Calendar.MONDAY);
166    calendar.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
167
168    Date firstDayInWeek = calendar.getTime();
169    return firstDayInWeek;
170  }
171
172  /**
173   * Whether the current cell is a mob reference cell.
174   * @param cell The current cell.
175   * @return True if the cell has a mob reference tag, false if it doesn't.
176   */
177  public static boolean isMobReferenceCell(Cell cell) {
178    if (cell.getTagsLength() > 0) {
179      Optional<Tag> tag = PrivateCellUtil.getTag(cell, TagType.MOB_REFERENCE_TAG_TYPE);
180      if (tag.isPresent()) {
181        return true;
182      }
183    }
184    return false;
185  }
186
187  /**
188   * Gets the table name tag.
189   * @param cell The current cell.
190   * @return The table name tag.
191   */
192  public static Tag getTableNameTag(Cell cell) {
193    if (cell.getTagsLength() > 0) {
194      Optional<Tag> tag = PrivateCellUtil.getTag(cell, TagType.MOB_TABLE_NAME_TAG_TYPE);
195      if (tag.isPresent()) {
196        return tag.get();
197      }
198    }
199    return null;
200  }
201
202  /**
203   * Whether the tag list has a mob reference tag.
204   * @param tags The tag list.
205   * @return True if the list has a mob reference tag, false if it doesn't.
206   */
207  public static boolean hasMobReferenceTag(List<Tag> tags) {
208    if (!tags.isEmpty()) {
209      for (Tag tag : tags) {
210        if (tag.getType() == TagType.MOB_REFERENCE_TAG_TYPE) {
211          return true;
212        }
213      }
214    }
215    return false;
216  }
217
218  /**
219   * Indicates whether it's a raw scan.
220   * The information is set in the attribute "hbase.mob.scan.raw" of scan.
221   * For a mob cell, in a normal scan the scanners retrieves the mob cell from the mob file.
222   * In a raw scan, the scanner directly returns cell in HBase without retrieve the one in
223   * the mob file.
224   * @param scan The current scan.
225   * @return True if it's a raw scan.
226   */
227  public static boolean isRawMobScan(Scan scan) {
228    byte[] raw = scan.getAttribute(MobConstants.MOB_SCAN_RAW);
229    try {
230      return raw != null && Bytes.toBoolean(raw);
231    } catch (IllegalArgumentException e) {
232      return false;
233    }
234  }
235
236  /**
237   * Indicates whether it's a reference only scan.
238   * The information is set in the attribute "hbase.mob.scan.ref.only" of scan.
239   * If it's a ref only scan, only the cells with ref tag are returned.
240   * @param scan The current scan.
241   * @return True if it's a ref only scan.
242   */
243  public static boolean isRefOnlyScan(Scan scan) {
244    byte[] refOnly = scan.getAttribute(MobConstants.MOB_SCAN_REF_ONLY);
245    try {
246      return refOnly != null && Bytes.toBoolean(refOnly);
247    } catch (IllegalArgumentException e) {
248      return false;
249    }
250  }
251
252  /**
253   * Indicates whether the scan contains the information of caching blocks.
254   * The information is set in the attribute "hbase.mob.cache.blocks" of scan.
255   * @param scan The current scan.
256   * @return True when the Scan attribute specifies to cache the MOB blocks.
257   */
258  public static boolean isCacheMobBlocks(Scan scan) {
259    byte[] cache = scan.getAttribute(MobConstants.MOB_CACHE_BLOCKS);
260    try {
261      return cache != null && Bytes.toBoolean(cache);
262    } catch (IllegalArgumentException e) {
263      return false;
264    }
265  }
266
267  /**
268   * Sets the attribute of caching blocks in the scan.
269   *
270   * @param scan
271   *          The current scan.
272   * @param cacheBlocks
273   *          True, set the attribute of caching blocks into the scan, the scanner with this scan
274   *          caches blocks.
275   *          False, the scanner doesn't cache blocks for this scan.
276   */
277  public static void setCacheMobBlocks(Scan scan, boolean cacheBlocks) {
278    scan.setAttribute(MobConstants.MOB_CACHE_BLOCKS, Bytes.toBytes(cacheBlocks));
279  }
280
281  /**
282   * Cleans the expired mob files.
283   * Cleans the files whose creation date is older than (current - columnFamily.ttl), and
284   * the minVersions of that column family is 0.
285   * @param fs The current file system.
286   * @param conf The current configuration.
287   * @param tableName The current table name.
288   * @param columnDescriptor The descriptor of the current column family.
289   * @param cacheConfig The cacheConfig that disables the block cache.
290   * @param current The current time.
291   * @throws IOException
292   */
293  public static void cleanExpiredMobFiles(FileSystem fs, Configuration conf, TableName tableName,
294      ColumnFamilyDescriptor columnDescriptor, CacheConfig cacheConfig, long current)
295      throws IOException {
296    long timeToLive = columnDescriptor.getTimeToLive();
297    if (Integer.MAX_VALUE == timeToLive) {
298      // no need to clean, because the TTL is not set.
299      return;
300    }
301
302    Calendar calendar = Calendar.getInstance();
303    calendar.setTimeInMillis(current - timeToLive * 1000);
304    calendar.set(Calendar.HOUR_OF_DAY, 0);
305    calendar.set(Calendar.MINUTE, 0);
306    calendar.set(Calendar.SECOND, 0);
307
308    Date expireDate = calendar.getTime();
309
310    LOG.info("MOB HFiles older than " + expireDate.toGMTString() + " will be deleted!");
311
312    FileStatus[] stats = null;
313    Path mobTableDir = FSUtils.getTableDir(getMobHome(conf), tableName);
314    Path path = getMobFamilyPath(conf, tableName, columnDescriptor.getNameAsString());
315    try {
316      stats = fs.listStatus(path);
317    } catch (FileNotFoundException e) {
318      LOG.warn("Failed to find the mob file " + path, e);
319    }
320    if (null == stats) {
321      // no file found
322      return;
323    }
324    List<HStoreFile> filesToClean = new ArrayList<>();
325    int deletedFileCount = 0;
326    for (FileStatus file : stats) {
327      String fileName = file.getPath().getName();
328      try {
329        if (HFileLink.isHFileLink(file.getPath())) {
330          HFileLink hfileLink = HFileLink.buildFromHFileLinkPattern(conf, file.getPath());
331          fileName = hfileLink.getOriginPath().getName();
332        }
333
334        Date fileDate = parseDate(MobFileName.getDateFromName(fileName));
335
336        if (LOG.isDebugEnabled()) {
337          LOG.debug("Checking file " + fileName);
338        }
339        if (fileDate.getTime() < expireDate.getTime()) {
340          if (LOG.isDebugEnabled()) {
341            LOG.debug(fileName + " is an expired file");
342          }
343          filesToClean
344              .add(new HStoreFile(fs, file.getPath(), conf, cacheConfig, BloomType.NONE, true));
345        }
346      } catch (Exception e) {
347        LOG.error("Cannot parse the fileName " + fileName, e);
348      }
349    }
350    if (!filesToClean.isEmpty()) {
351      try {
352        removeMobFiles(conf, fs, tableName, mobTableDir, columnDescriptor.getName(),
353            filesToClean);
354        deletedFileCount = filesToClean.size();
355      } catch (IOException e) {
356        LOG.error("Failed to delete the mob files " + filesToClean, e);
357      }
358    }
359    LOG.info(deletedFileCount + " expired mob files are deleted");
360  }
361
362  /**
363   * Gets the root dir of the mob files.
364   * It's {HBASE_DIR}/mobdir.
365   * @param conf The current configuration.
366   * @return the root dir of the mob file.
367   */
368  public static Path getMobHome(Configuration conf) {
369    Path hbaseDir = new Path(conf.get(HConstants.HBASE_DIR));
370    return new Path(hbaseDir, MobConstants.MOB_DIR_NAME);
371  }
372
373  /**
374   * Gets the qualified root dir of the mob files.
375   * @param conf The current configuration.
376   * @return The qualified root dir.
377   * @throws IOException
378   */
379  public static Path getQualifiedMobRootDir(Configuration conf) throws IOException {
380    Path hbaseDir = new Path(conf.get(HConstants.HBASE_DIR));
381    Path mobRootDir = new Path(hbaseDir, MobConstants.MOB_DIR_NAME);
382    FileSystem fs = mobRootDir.getFileSystem(conf);
383    return mobRootDir.makeQualified(fs.getUri(), fs.getWorkingDirectory());
384  }
385
386  /**
387   * Gets the region dir of the mob files.
388   * It's {HBASE_DIR}/mobdir/{namespace}/{tableName}/{regionEncodedName}.
389   * @param conf The current configuration.
390   * @param tableName The current table name.
391   * @return The region dir of the mob files.
392   */
393  public static Path getMobRegionPath(Configuration conf, TableName tableName) {
394    Path tablePath = FSUtils.getTableDir(getMobHome(conf), tableName);
395    RegionInfo regionInfo = getMobRegionInfo(tableName);
396    return new Path(tablePath, regionInfo.getEncodedName());
397  }
398
399  /**
400   * Gets the family dir of the mob files.
401   * It's {HBASE_DIR}/mobdir/{namespace}/{tableName}/{regionEncodedName}/{columnFamilyName}.
402   * @param conf The current configuration.
403   * @param tableName The current table name.
404   * @param familyName The current family name.
405   * @return The family dir of the mob files.
406   */
407  public static Path getMobFamilyPath(Configuration conf, TableName tableName, String familyName) {
408    return new Path(getMobRegionPath(conf, tableName), familyName);
409  }
410
411  /**
412   * Gets the family dir of the mob files.
413   * It's {HBASE_DIR}/mobdir/{namespace}/{tableName}/{regionEncodedName}/{columnFamilyName}.
414   * @param regionPath The path of mob region which is a dummy one.
415   * @param familyName The current family name.
416   * @return The family dir of the mob files.
417   */
418  public static Path getMobFamilyPath(Path regionPath, String familyName) {
419    return new Path(regionPath, familyName);
420  }
421
422  /**
423   * Gets the RegionInfo of the mob files.
424   * This is a dummy region. The mob files are not saved in a region in HBase.
425   * This is only used in mob snapshot. It's internally used only.
426   * @param tableName
427   * @return A dummy mob region info.
428   */
429  public static RegionInfo getMobRegionInfo(TableName tableName) {
430    return RegionInfoBuilder.newBuilder(tableName)
431        .setStartKey(MobConstants.MOB_REGION_NAME_BYTES)
432        .setEndKey(HConstants.EMPTY_END_ROW)
433        .setSplit(false)
434        .setRegionId(0)
435        .build();
436  }
437
438  /**
439   * Gets whether the current RegionInfo is a mob one.
440   * @param regionInfo The current RegionInfo.
441   * @return If true, the current RegionInfo is a mob one.
442   */
443  public static boolean isMobRegionInfo(RegionInfo regionInfo) {
444    return regionInfo == null ? false : getMobRegionInfo(regionInfo.getTable()).getEncodedName()
445        .equals(regionInfo.getEncodedName());
446  }
447
448  /**
449   * Gets whether the current region name follows the pattern of a mob region name.
450   * @param tableName The current table name.
451   * @param regionName The current region name.
452   * @return True if the current region name follows the pattern of a mob region name.
453   */
454  public static boolean isMobRegionName(TableName tableName, byte[] regionName) {
455    return Bytes.equals(regionName, getMobRegionInfo(tableName).getRegionName());
456  }
457
458  /**
459   * Gets the working directory of the mob compaction.
460   * @param root The root directory of the mob compaction.
461   * @param jobName The current job name.
462   * @return The directory of the mob compaction for the current job.
463   */
464  public static Path getCompactionWorkingPath(Path root, String jobName) {
465    return new Path(root, jobName);
466  }
467
468  /**
469   * Archives the mob files.
470   * @param conf The current configuration.
471   * @param fs The current file system.
472   * @param tableName The table name.
473   * @param tableDir The table directory.
474   * @param family The name of the column family.
475   * @param storeFiles The files to be deleted.
476   * @throws IOException
477   */
478  public static void removeMobFiles(Configuration conf, FileSystem fs, TableName tableName,
479      Path tableDir, byte[] family, Collection<HStoreFile> storeFiles) throws IOException {
480    HFileArchiver.archiveStoreFiles(conf, fs, getMobRegionInfo(tableName), tableDir, family,
481        storeFiles);
482  }
483
484  /**
485   * Creates a mob reference KeyValue.
486   * The value of the mob reference KeyValue is mobCellValueSize + mobFileName.
487   * @param cell The original Cell.
488   * @param fileName The mob file name where the mob reference KeyValue is written.
489   * @param tableNameTag The tag of the current table name. It's very important in
490   *                        cloning the snapshot.
491   * @return The mob reference KeyValue.
492   */
493  public static Cell createMobRefCell(Cell cell, byte[] fileName, Tag tableNameTag) {
494    // Append the tags to the KeyValue.
495    // The key is same, the value is the filename of the mob file
496    List<Tag> tags = new ArrayList<>();
497    // Add the ref tag as the 1st one.
498    tags.add(MobConstants.MOB_REF_TAG);
499    // Add the tag of the source table name, this table is where this mob file is flushed
500    // from.
501    // It's very useful in cloning the snapshot. When reading from the cloning table, we need to
502    // find the original mob files by this table name. For details please see cloning
503    // snapshot for mob files.
504    tags.add(tableNameTag);
505    return createMobRefCell(cell, fileName, TagUtil.fromList(tags));
506  }
507
508  public static Cell createMobRefCell(Cell cell, byte[] fileName, byte[] refCellTags) {
509    byte[] refValue = Bytes.add(Bytes.toBytes(cell.getValueLength()), fileName);
510    return PrivateCellUtil.createCell(cell, refValue, TagUtil.concatTags(refCellTags, cell));
511  }
512
513  /**
514   * Creates a writer for the mob file in temp directory.
515   * @param conf The current configuration.
516   * @param fs The current file system.
517   * @param family The descriptor of the current column family.
518   * @param date The date string, its format is yyyymmmdd.
519   * @param basePath The basic path for a temp directory.
520   * @param maxKeyCount The key count.
521   * @param compression The compression algorithm.
522   * @param startKey The hex string of the start key.
523   * @param cacheConfig The current cache config.
524   * @param cryptoContext The encryption context.
525   * @param isCompaction If the writer is used in compaction.
526   * @return The writer for the mob file.
527   * @throws IOException
528   */
529  public static StoreFileWriter createWriter(Configuration conf, FileSystem fs,
530      ColumnFamilyDescriptor family, String date, Path basePath, long maxKeyCount,
531      Compression.Algorithm compression, String startKey, CacheConfig cacheConfig,
532      Encryption.Context cryptoContext, boolean isCompaction)
533      throws IOException {
534    MobFileName mobFileName = MobFileName.create(startKey, date,
535        UUID.randomUUID().toString().replaceAll("-", ""));
536    return createWriter(conf, fs, family, mobFileName, basePath, maxKeyCount, compression,
537        cacheConfig, cryptoContext, isCompaction);
538  }
539
540  /**
541   * Creates a writer for the ref file in temp directory.
542   * @param conf The current configuration.
543   * @param fs The current file system.
544   * @param family The descriptor of the current column family.
545   * @param basePath The basic path for a temp directory.
546   * @param maxKeyCount The key count.
547   * @param cacheConfig The current cache config.
548   * @param cryptoContext The encryption context.
549   * @param isCompaction If the writer is used in compaction.
550   * @return The writer for the mob file.
551   * @throws IOException
552   */
553  public static StoreFileWriter createRefFileWriter(Configuration conf, FileSystem fs,
554    ColumnFamilyDescriptor family, Path basePath, long maxKeyCount, CacheConfig cacheConfig,
555    Encryption.Context cryptoContext, boolean isCompaction)
556    throws IOException {
557    return createWriter(conf, fs, family,
558      new Path(basePath, UUID.randomUUID().toString().replaceAll("-", "")), maxKeyCount,
559      family.getCompactionCompressionType(), cacheConfig, cryptoContext,
560      HStore.getChecksumType(conf), HStore.getBytesPerChecksum(conf), family.getBlocksize(),
561      family.getBloomFilterType(), isCompaction);
562  }
563
564  /**
565   * Creates a writer for the mob file in temp directory.
566   * @param conf The current configuration.
567   * @param fs The current file system.
568   * @param family The descriptor of the current column family.
569   * @param date The date string, its format is yyyymmmdd.
570   * @param basePath The basic path for a temp directory.
571   * @param maxKeyCount The key count.
572   * @param compression The compression algorithm.
573   * @param startKey The start key.
574   * @param cacheConfig The current cache config.
575   * @param cryptoContext The encryption context.
576   * @param isCompaction If the writer is used in compaction.
577   * @return The writer for the mob file.
578   * @throws IOException
579   */
580  public static StoreFileWriter createWriter(Configuration conf, FileSystem fs,
581      ColumnFamilyDescriptor family, String date, Path basePath, long maxKeyCount,
582      Compression.Algorithm compression, byte[] startKey, CacheConfig cacheConfig,
583      Encryption.Context cryptoContext, boolean isCompaction)
584      throws IOException {
585    MobFileName mobFileName = MobFileName.create(startKey, date,
586        UUID.randomUUID().toString().replaceAll("-", ""));
587    return createWriter(conf, fs, family, mobFileName, basePath, maxKeyCount, compression,
588      cacheConfig, cryptoContext, isCompaction);
589  }
590
591  /**
592   * Creates a writer for the del file in temp directory.
593   * @param conf The current configuration.
594   * @param fs The current file system.
595   * @param family The descriptor of the current column family.
596   * @param date The date string, its format is yyyymmmdd.
597   * @param basePath The basic path for a temp directory.
598   * @param maxKeyCount The key count.
599   * @param compression The compression algorithm.
600   * @param startKey The start key.
601   * @param cacheConfig The current cache config.
602   * @param cryptoContext The encryption context.
603   * @return The writer for the del file.
604   * @throws IOException
605   */
606  public static StoreFileWriter createDelFileWriter(Configuration conf, FileSystem fs,
607      ColumnFamilyDescriptor family, String date, Path basePath, long maxKeyCount,
608      Compression.Algorithm compression, byte[] startKey, CacheConfig cacheConfig,
609      Encryption.Context cryptoContext)
610      throws IOException {
611    String suffix = UUID
612      .randomUUID().toString().replaceAll("-", "") + "_del";
613    MobFileName mobFileName = MobFileName.create(startKey, date, suffix);
614    return createWriter(conf, fs, family, mobFileName, basePath, maxKeyCount, compression,
615      cacheConfig, cryptoContext, true);
616  }
617
618  /**
619   * Creates a writer for the mob file in temp directory.
620   * @param conf The current configuration.
621   * @param fs The current file system.
622   * @param family The descriptor of the current column family.
623   * @param mobFileName The mob file name.
624   * @param basePath The basic path for a temp directory.
625   * @param maxKeyCount The key count.
626   * @param compression The compression algorithm.
627   * @param cacheConfig The current cache config.
628   * @param cryptoContext The encryption context.
629   * @param isCompaction If the writer is used in compaction.
630   * @return The writer for the mob file.
631   * @throws IOException
632   */
633  public static StoreFileWriter createWriter(Configuration conf, FileSystem fs,
634                                             ColumnFamilyDescriptor family, MobFileName mobFileName, Path basePath, long maxKeyCount,
635      Compression.Algorithm compression, CacheConfig cacheConfig, Encryption.Context cryptoContext,
636      boolean isCompaction)
637      throws IOException {
638    return createWriter(conf, fs, family,
639      new Path(basePath, mobFileName.getFileName()), maxKeyCount, compression, cacheConfig,
640      cryptoContext, HStore.getChecksumType(conf), HStore.getBytesPerChecksum(conf),
641      family.getBlocksize(), BloomType.NONE, isCompaction);
642  }
643
644  /**
645   * Creates a writer for the mob file in temp directory.
646   * @param conf The current configuration.
647   * @param fs The current file system.
648   * @param family The descriptor of the current column family.
649   * @param path The path for a temp directory.
650   * @param maxKeyCount The key count.
651   * @param compression The compression algorithm.
652   * @param cacheConfig The current cache config.
653   * @param cryptoContext The encryption context.
654   * @param checksumType The checksum type.
655   * @param bytesPerChecksum The bytes per checksum.
656   * @param blocksize The HFile block size.
657   * @param bloomType The bloom filter type.
658   * @param isCompaction If the writer is used in compaction.
659   * @return The writer for the mob file.
660   * @throws IOException
661   */
662  public static StoreFileWriter createWriter(Configuration conf, FileSystem fs,
663      ColumnFamilyDescriptor family, Path path, long maxKeyCount,
664      Compression.Algorithm compression, CacheConfig cacheConfig, Encryption.Context cryptoContext,
665      ChecksumType checksumType, int bytesPerChecksum, int blocksize, BloomType bloomType,
666      boolean isCompaction)
667      throws IOException {
668    if (compression == null) {
669      compression = HFile.DEFAULT_COMPRESSION_ALGORITHM;
670    }
671    final CacheConfig writerCacheConf;
672    if (isCompaction) {
673      writerCacheConf = new CacheConfig(cacheConfig);
674      writerCacheConf.setCacheDataOnWrite(false);
675    } else {
676      writerCacheConf = cacheConfig;
677    }
678    HFileContext hFileContext = new HFileContextBuilder().withCompression(compression)
679        .withIncludesMvcc(true).withIncludesTags(true)
680        .withCompressTags(family.isCompressTags())
681        .withChecksumType(checksumType)
682        .withBytesPerCheckSum(bytesPerChecksum)
683        .withBlockSize(blocksize)
684        .withHBaseCheckSum(true).withDataBlockEncoding(family.getDataBlockEncoding())
685        .withEncryptionContext(cryptoContext)
686        .withCreateTime(EnvironmentEdgeManager.currentTime()).build();
687
688    StoreFileWriter w = new StoreFileWriter.Builder(conf, writerCacheConf, fs)
689        .withFilePath(path)
690        .withComparator(CellComparator.getInstance()).withBloomType(bloomType)
691        .withMaxKeyCount(maxKeyCount).withFileContext(hFileContext).build();
692    return w;
693  }
694
695  /**
696   * Commits the mob file.
697   * @param conf The current configuration.
698   * @param fs The current file system.
699   * @param sourceFile The path where the mob file is saved.
700   * @param targetPath The directory path where the source file is renamed to.
701   * @param cacheConfig The current cache config.
702   * @return The target file path the source file is renamed to.
703   * @throws IOException
704   */
705  public static Path commitFile(Configuration conf, FileSystem fs, final Path sourceFile,
706      Path targetPath, CacheConfig cacheConfig) throws IOException {
707    if (sourceFile == null) {
708      return null;
709    }
710    Path dstPath = new Path(targetPath, sourceFile.getName());
711    validateMobFile(conf, fs, sourceFile, cacheConfig, true);
712    String msg = "Renaming flushed file from " + sourceFile + " to " + dstPath;
713    LOG.info(msg);
714    Path parent = dstPath.getParent();
715    if (!fs.exists(parent)) {
716      fs.mkdirs(parent);
717    }
718    if (!fs.rename(sourceFile, dstPath)) {
719      throw new IOException("Failed rename of " + sourceFile + " to " + dstPath);
720    }
721    return dstPath;
722  }
723
724  /**
725   * Validates a mob file by opening and closing it.
726   * @param conf The current configuration.
727   * @param fs The current file system.
728   * @param path The path where the mob file is saved.
729   * @param cacheConfig The current cache config.
730   */
731  private static void validateMobFile(Configuration conf, FileSystem fs, Path path,
732      CacheConfig cacheConfig, boolean primaryReplica) throws IOException {
733    HStoreFile storeFile = null;
734    try {
735      storeFile = new HStoreFile(fs, path, conf, cacheConfig, BloomType.NONE, primaryReplica);
736      storeFile.initReader();
737    } catch (IOException e) {
738      LOG.error("Failed to open mob file[" + path + "], keep it in temp directory.", e);
739      throw e;
740    } finally {
741      if (storeFile != null) {
742        storeFile.closeStoreFile(false);
743      }
744    }
745  }
746
747  /**
748   * Indicates whether the current mob ref cell has a valid value.
749   * A mob ref cell has a mob reference tag.
750   * The value of a mob ref cell consists of two parts, real mob value length and mob file name.
751   * The real mob value length takes 4 bytes.
752   * The remaining part is the mob file name.
753   * @param cell The mob ref cell.
754   * @return True if the cell has a valid value.
755   */
756  public static boolean hasValidMobRefCellValue(Cell cell) {
757    return cell.getValueLength() > Bytes.SIZEOF_INT;
758  }
759
760  /**
761   * Gets the mob value length from the mob ref cell.
762   * A mob ref cell has a mob reference tag.
763   * The value of a mob ref cell consists of two parts, real mob value length and mob file name.
764   * The real mob value length takes 4 bytes.
765   * The remaining part is the mob file name.
766   * @param cell The mob ref cell.
767   * @return The real mob value length.
768   */
769  public static int getMobValueLength(Cell cell) {
770    return PrivateCellUtil.getValueAsInt(cell);
771  }
772
773  /**
774   * Gets the mob file name from the mob ref cell.
775   * A mob ref cell has a mob reference tag.
776   * The value of a mob ref cell consists of two parts, real mob value length and mob file name.
777   * The real mob value length takes 4 bytes.
778   * The remaining part is the mob file name.
779   * @param cell The mob ref cell.
780   * @return The mob file name.
781   */
782  public static String getMobFileName(Cell cell) {
783    return Bytes.toString(cell.getValueArray(), cell.getValueOffset() + Bytes.SIZEOF_INT,
784        cell.getValueLength() - Bytes.SIZEOF_INT);
785  }
786
787  /**
788   * Gets the table name used in the table lock.
789   * The table lock name is a dummy one, it's not a table name. It's tableName + ".mobLock".
790   * @param tn The table name.
791   * @return The table name used in table lock.
792   */
793  public static TableName getTableLockName(TableName tn) {
794    byte[] tableName = tn.getName();
795    return TableName.valueOf(Bytes.add(tableName, MobConstants.MOB_TABLE_LOCK_SUFFIX));
796  }
797
798  /**
799   * Performs the mob compaction.
800   * @param conf the Configuration
801   * @param fs the file system
802   * @param tableName the table the compact
803   * @param hcd the column descriptor
804   * @param pool the thread pool
805   * @param allFiles Whether add all mob files into the compaction.
806   */
807  public static void doMobCompaction(Configuration conf, FileSystem fs, TableName tableName,
808                                     ColumnFamilyDescriptor hcd, ExecutorService pool, boolean allFiles, LockManager.MasterLock lock)
809      throws IOException {
810    String className = conf.get(MobConstants.MOB_COMPACTOR_CLASS_KEY,
811        PartitionedMobCompactor.class.getName());
812    // instantiate the mob compactor.
813    MobCompactor compactor = null;
814    try {
815      compactor = ReflectionUtils.instantiateWithCustomCtor(className, new Class[] {
816        Configuration.class, FileSystem.class, TableName.class, ColumnFamilyDescriptor.class,
817        ExecutorService.class }, new Object[] { conf, fs, tableName, hcd, pool });
818    } catch (Exception e) {
819      throw new IOException("Unable to load configured mob file compactor '" + className + "'", e);
820    }
821    // compact only for mob-enabled column.
822    // obtain a write table lock before performing compaction to avoid race condition
823    // with major compaction in mob-enabled column.
824    try {
825      lock.acquire();
826      compactor.compact(allFiles);
827    } catch (Exception e) {
828      LOG.error("Failed to compact the mob files for the column " + hcd.getNameAsString()
829          + " in the table " + tableName.getNameAsString(), e);
830    } finally {
831      lock.release();
832    }
833  }
834
835  /**
836   * Creates a thread pool.
837   * @param conf the Configuration
838   * @return A thread pool.
839   */
840  public static ExecutorService createMobCompactorThreadPool(Configuration conf) {
841    int maxThreads = conf.getInt(MobConstants.MOB_COMPACTION_THREADS_MAX,
842        MobConstants.DEFAULT_MOB_COMPACTION_THREADS_MAX);
843    if (maxThreads == 0) {
844      maxThreads = 1;
845    }
846    final SynchronousQueue<Runnable> queue = new SynchronousQueue<>();
847    ThreadPoolExecutor pool = new ThreadPoolExecutor(1, maxThreads, 60, TimeUnit.SECONDS, queue,
848      Threads.newDaemonThreadFactory("MobCompactor"), new RejectedExecutionHandler() {
849        @Override
850        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
851          try {
852            // waiting for a thread to pick up instead of throwing exceptions.
853            queue.put(r);
854          } catch (InterruptedException e) {
855            throw new RejectedExecutionException(e);
856          }
857        }
858      });
859    ((ThreadPoolExecutor) pool).allowCoreThreadTimeOut(true);
860    return pool;
861  }
862
863  /**
864   * Checks whether this table has mob-enabled columns.
865   * @param htd The current table descriptor.
866   * @return Whether this table has mob-enabled columns.
867   */
868  public static boolean hasMobColumns(TableDescriptor htd) {
869    ColumnFamilyDescriptor[] hcds = htd.getColumnFamilies();
870    for (ColumnFamilyDescriptor hcd : hcds) {
871      if (hcd.isMobEnabled()) {
872        return true;
873      }
874    }
875    return false;
876  }
877
878  /**
879   * Indicates whether return null value when the mob file is missing or corrupt.
880   * The information is set in the attribute "empty.value.on.mobcell.miss" of scan.
881   * @param scan The current scan.
882   * @return True if the readEmptyValueOnMobCellMiss is enabled.
883   */
884  public static boolean isReadEmptyValueOnMobCellMiss(Scan scan) {
885    byte[] readEmptyValueOnMobCellMiss =
886      scan.getAttribute(MobConstants.EMPTY_VALUE_ON_MOBCELL_MISS);
887    try {
888      return readEmptyValueOnMobCellMiss != null && Bytes.toBoolean(readEmptyValueOnMobCellMiss);
889    } catch (IllegalArgumentException e) {
890      return false;
891    }
892  }
893
894  /**
895   * Creates a mob ref delete marker.
896   * @param cell The current delete marker.
897   * @return A delete marker with the ref tag.
898   */
899  public static Cell createMobRefDeleteMarker(Cell cell) {
900    return PrivateCellUtil.createCell(cell, TagUtil.concatTags(REF_DELETE_MARKER_TAG_BYTES, cell));
901  }
902
903  /**
904   * Checks if the mob file is expired.
905   * @param column The descriptor of the current column family.
906   * @param current The current time.
907   * @param fileDate The date string parsed from the mob file name.
908   * @return True if the mob file is expired.
909   */
910  public static boolean isMobFileExpired(ColumnFamilyDescriptor column, long current, String fileDate) {
911    if (column.getMinVersions() > 0) {
912      return false;
913    }
914    long timeToLive = column.getTimeToLive();
915    if (Integer.MAX_VALUE == timeToLive) {
916      return false;
917    }
918
919    Date expireDate = new Date(current - timeToLive * 1000);
920    expireDate = new Date(expireDate.getYear(), expireDate.getMonth(), expireDate.getDate());
921    try {
922      Date date = parseDate(fileDate);
923      if (date.getTime() < expireDate.getTime()) {
924        return true;
925      }
926    } catch (ParseException e) {
927      LOG.warn("Failed to parse the date " + fileDate, e);
928      return false;
929    }
930    return false;
931  }
932
933  /**
934   * fill out partition id based on compaction policy and date, threshold...
935   * @param id Partition id to be filled out
936   * @param firstDayOfCurrentMonth The first day in the current month
937   * @param firstDayOfCurrentWeek The first day in the current week
938   * @param dateStr Date string from the mob file
939   * @param policy Mob compaction policy
940   * @param calendar Calendar object
941   * @param threshold Mob compaciton threshold configured
942   * @return true if the file needs to be excluded from compaction
943   */
944  public static boolean fillPartitionId(final CompactionPartitionId id,
945      final Date firstDayOfCurrentMonth, final Date firstDayOfCurrentWeek, final String dateStr,
946      final MobCompactPartitionPolicy policy, final Calendar calendar, final long threshold) {
947
948    boolean skipCompcation = false;
949    id.setThreshold(threshold);
950    if (threshold <= 0) {
951      id.setDate(dateStr);
952      return skipCompcation;
953    }
954
955    long finalThreshold;
956    Date date;
957    try {
958      date = MobUtils.parseDate(dateStr);
959    } catch (ParseException e)  {
960      LOG.warn("Failed to parse date " + dateStr, e);
961      id.setDate(dateStr);
962      return true;
963    }
964
965    /* The algorithm works as follows:
966     *    For monthly policy:
967     *       1). If the file's date is in past months, apply 4 * 7 * threshold
968     *       2). If the file's date is in past weeks, apply 7 * threshold
969     *       3). If the file's date is in current week, exclude it from the compaction
970     *    For weekly policy:
971     *       1). If the file's date is in past weeks, apply 7 * threshold
972     *       2). If the file's date in currently, apply threshold
973     *    For daily policy:
974     *       1). apply threshold
975     */
976    if (policy == MobCompactPartitionPolicy.MONTHLY) {
977      if (date.before(firstDayOfCurrentMonth)) {
978        // Check overflow
979        if (threshold < (Long.MAX_VALUE / MONTHLY_THRESHOLD_MULTIPLIER)) {
980          finalThreshold = MONTHLY_THRESHOLD_MULTIPLIER * threshold;
981        } else {
982          finalThreshold = Long.MAX_VALUE;
983        }
984        id.setThreshold(finalThreshold);
985
986        // set to the date for the first day of that month
987        id.setDate(MobUtils.formatDate(MobUtils.getFirstDayOfMonth(calendar, date)));
988        return skipCompcation;
989      }
990    }
991
992    if ((policy == MobCompactPartitionPolicy.MONTHLY) ||
993        (policy == MobCompactPartitionPolicy.WEEKLY)) {
994      // Check if it needs to apply weekly multiplier
995      if (date.before(firstDayOfCurrentWeek)) {
996        // Check overflow
997        if (threshold < (Long.MAX_VALUE / WEEKLY_THRESHOLD_MULTIPLIER)) {
998          finalThreshold = WEEKLY_THRESHOLD_MULTIPLIER * threshold;
999        } else {
1000          finalThreshold = Long.MAX_VALUE;
1001        }
1002        id.setThreshold(finalThreshold);
1003
1004        id.setDate(MobUtils.formatDate(MobUtils.getFirstDayOfWeek(calendar, date)));
1005        return skipCompcation;
1006      } else if (policy == MobCompactPartitionPolicy.MONTHLY) {
1007        skipCompcation = true;
1008      }
1009    }
1010
1011    // Rest is daily
1012    id.setDate(dateStr);
1013    return skipCompcation;
1014  }
1015}