1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.hadoop.hbase.mapreduce;
20
21 import com.google.common.annotations.VisibleForTesting;
22 import static java.lang.String.format;
23
24 import java.io.FileNotFoundException;
25 import java.io.IOException;
26 import java.io.InterruptedIOException;
27 import java.nio.ByteBuffer;
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.Collection;
31 import java.util.Deque;
32 import java.util.HashMap;
33 import java.util.HashSet;
34 import java.util.Iterator;
35 import java.util.LinkedList;
36 import java.util.List;
37 import java.util.Map;
38 import java.util.Map.Entry;
39 import java.util.Set;
40 import java.util.TreeMap;
41 import java.util.UUID;
42 import java.util.concurrent.Callable;
43 import java.util.concurrent.ExecutionException;
44 import java.util.concurrent.ExecutorService;
45 import java.util.concurrent.Future;
46 import java.util.concurrent.LinkedBlockingQueue;
47 import java.util.concurrent.ThreadPoolExecutor;
48 import java.util.concurrent.TimeUnit;
49
50 import org.apache.commons.lang.mutable.MutableInt;
51 import org.apache.commons.logging.Log;
52 import org.apache.commons.logging.LogFactory;
53 import org.apache.hadoop.conf.Configuration;
54 import org.apache.hadoop.conf.Configured;
55 import org.apache.hadoop.fs.FileStatus;
56 import org.apache.hadoop.fs.FileSystem;
57 import org.apache.hadoop.fs.Path;
58 import org.apache.hadoop.fs.permission.FsPermission;
59 import org.apache.hadoop.hbase.HBaseConfiguration;
60 import org.apache.hadoop.hbase.HColumnDescriptor;
61 import org.apache.hadoop.hbase.HConstants;
62 import org.apache.hadoop.hbase.HTableDescriptor;
63 import org.apache.hadoop.hbase.TableName;
64 import org.apache.hadoop.hbase.TableNotFoundException;
65 import org.apache.hadoop.hbase.classification.InterfaceAudience;
66 import org.apache.hadoop.hbase.classification.InterfaceStability;
67 import org.apache.hadoop.hbase.client.Admin;
68 import org.apache.hadoop.hbase.client.ClusterConnection;
69 import org.apache.hadoop.hbase.client.Connection;
70 import org.apache.hadoop.hbase.client.ConnectionFactory;
71 import org.apache.hadoop.hbase.client.HBaseAdmin;
72 import org.apache.hadoop.hbase.client.HConnection;
73 import org.apache.hadoop.hbase.client.HTable;
74 import org.apache.hadoop.hbase.client.RegionLocator;
75 import org.apache.hadoop.hbase.client.RegionServerCallable;
76 import org.apache.hadoop.hbase.client.RpcRetryingCallerFactory;
77 import org.apache.hadoop.hbase.client.Table;
78 import org.apache.hadoop.hbase.client.coprocessor.SecureBulkLoadClient;
79 import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
80 import org.apache.hadoop.hbase.io.HFileLink;
81 import org.apache.hadoop.hbase.io.HalfStoreFileReader;
82 import org.apache.hadoop.hbase.io.Reference;
83 import org.apache.hadoop.hbase.io.compress.Compression.Algorithm;
84 import org.apache.hadoop.hbase.io.hfile.CacheConfig;
85 import org.apache.hadoop.hbase.io.hfile.HFile;
86 import org.apache.hadoop.hbase.io.hfile.HFileContext;
87 import org.apache.hadoop.hbase.io.hfile.HFileContextBuilder;
88 import org.apache.hadoop.hbase.io.hfile.HFileDataBlockEncoder;
89 import org.apache.hadoop.hbase.io.hfile.HFileScanner;
90 import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
91 import org.apache.hadoop.hbase.regionserver.BloomType;
92 import org.apache.hadoop.hbase.regionserver.HStore;
93 import org.apache.hadoop.hbase.regionserver.StoreFile;
94 import org.apache.hadoop.hbase.regionserver.StoreFileInfo;
95 import org.apache.hadoop.hbase.security.UserProvider;
96 import org.apache.hadoop.hbase.security.access.SecureBulkLoadEndpoint;
97 import org.apache.hadoop.hbase.security.token.FsDelegationToken;
98 import org.apache.hadoop.hbase.util.Bytes;
99 import org.apache.hadoop.hbase.util.FSHDFSUtils;
100 import org.apache.hadoop.hbase.util.FSUtils;
101 import org.apache.hadoop.hbase.util.Pair;
102 import org.apache.hadoop.util.Tool;
103 import org.apache.hadoop.util.ToolRunner;
104
105 import com.google.common.collect.HashMultimap;
106 import com.google.common.collect.Multimap;
107 import com.google.common.collect.Multimaps;
108 import com.google.common.util.concurrent.ThreadFactoryBuilder;
109
110
111
112
113
114 @InterfaceAudience.Public
115 @InterfaceStability.Stable
116 public class LoadIncrementalHFiles extends Configured implements Tool {
117 private static final Log LOG = LogFactory.getLog(LoadIncrementalHFiles.class);
118 private Admin hbAdmin;
119
120 public static final String NAME = "completebulkload";
121 public static final String MAX_FILES_PER_REGION_PER_FAMILY
122 = "hbase.mapreduce.bulkload.max.hfiles.perRegion.perFamily";
123 private static final String ASSIGN_SEQ_IDS = "hbase.mapreduce.bulkload.assign.sequenceNumbers";
124 public final static String CREATE_TABLE_CONF_KEY = "create.table";
125
126
127
128 final static String TMP_DIR = ".tmp";
129
130 private int maxFilesPerRegionPerFamily;
131 private boolean assignSeqIds;
132
133
134 private FileSystem fs;
135
136 private FsDelegationToken fsDelegationToken;
137 private String bulkToken;
138 private UserProvider userProvider;
139 private int nrThreads;
140 private int depth = 2;
141
142 private LoadIncrementalHFiles() {}
143
144 public LoadIncrementalHFiles(Configuration conf) throws Exception {
145 super(conf);
146 initialize();
147 }
148
149 public void setDepth(int depth) {
150 this.depth = depth;
151 }
152
153 private void initialize() throws Exception {
154 if (hbAdmin == null) {
155
156 setConf(HBaseConfiguration.create(getConf()));
157 Configuration conf = getConf();
158
159 conf.setFloat(HConstants.HFILE_BLOCK_CACHE_SIZE_KEY, 0);
160 this.hbAdmin = new HBaseAdmin(conf);
161 this.userProvider = UserProvider.instantiate(conf);
162 this.fsDelegationToken = new FsDelegationToken(userProvider, "renewer");
163 assignSeqIds = conf.getBoolean(ASSIGN_SEQ_IDS, true);
164 maxFilesPerRegionPerFamily = conf.getInt(MAX_FILES_PER_REGION_PER_FAMILY, 32);
165 nrThreads = conf.getInt("hbase.loadincremental.threads.max",
166 Runtime.getRuntime().availableProcessors());
167 }
168 }
169
170 private void usage() {
171 System.err.println("usage: " + NAME + " /path/to/hfileoutputformat-output tablename -loadTable"
172 + "\n -D" + CREATE_TABLE_CONF_KEY + "=no - can be used to avoid creation of table by "
173 + "this tool\n Note: if you set this to 'no', then the target table must already exist "
174 + "in HBase\n -loadTable implies your baseDirectory to store file has a depth of 3 ,you"
175 + " must have an existing table"
176 + "\n");
177 }
178
179 private static interface BulkHFileVisitor<TFamily> {
180 TFamily bulkFamily(final byte[] familyName)
181 throws IOException;
182 void bulkHFile(final TFamily family, final FileStatus hfileStatus)
183 throws IOException;
184 }
185
186
187
188
189
190 private static <TFamily> void visitBulkHFiles(final FileSystem fs, final Path bulkDir,
191 final BulkHFileVisitor<TFamily> visitor) throws IOException {
192 visitBulkHFiles(fs, bulkDir, visitor, true);
193 }
194
195
196
197
198
199
200
201 private static <TFamily> void visitBulkHFiles(final FileSystem fs, final Path bulkDir,
202 final BulkHFileVisitor<TFamily> visitor, final boolean validateHFile) throws IOException {
203 if (!fs.exists(bulkDir)) {
204 throw new FileNotFoundException("Bulkload dir " + bulkDir + " not found");
205 }
206
207 FileStatus[] familyDirStatuses = fs.listStatus(bulkDir);
208 if (familyDirStatuses == null) {
209 throw new FileNotFoundException("No families found in " + bulkDir);
210 }
211
212 for (FileStatus familyStat : familyDirStatuses) {
213 if (!familyStat.isDirectory()) {
214 LOG.warn("Skipping non-directory " + familyStat.getPath());
215 continue;
216 }
217 Path familyDir = familyStat.getPath();
218 byte[] familyName = familyDir.getName().getBytes();
219
220 try {
221 HColumnDescriptor.isLegalFamilyName(familyName);
222 }
223 catch (IllegalArgumentException e) {
224 LOG.warn("Skipping invalid " + familyStat.getPath());
225 continue;
226 }
227 TFamily family = visitor.bulkFamily(familyName);
228
229 FileStatus[] hfileStatuses = fs.listStatus(familyDir);
230 for (FileStatus hfileStatus : hfileStatuses) {
231 if (!fs.isFile(hfileStatus.getPath())) {
232 LOG.warn("Skipping non-file " + hfileStatus);
233 continue;
234 }
235
236 Path hfile = hfileStatus.getPath();
237
238 String fileName = hfile.getName();
239 if (fileName.startsWith("_")) {
240 continue;
241 }
242 if (StoreFileInfo.isReference(fileName)) {
243 LOG.warn("Skipping reference " + fileName);
244 continue;
245 }
246 if (HFileLink.isHFileLink(fileName)) {
247 LOG.warn("Skipping HFileLink " + fileName);
248 continue;
249 }
250
251
252 if (validateHFile) {
253 try {
254 if (!HFile.isHFileFormat(fs, hfile)) {
255 LOG.warn("the file " + hfile + " doesn't seems to be an hfile. skipping");
256 continue;
257 }
258 } catch (FileNotFoundException e) {
259 LOG.warn("the file " + hfile + " was removed");
260 continue;
261 }
262 }
263
264 visitor.bulkHFile(family, hfileStatus);
265 }
266 }
267 }
268
269
270
271
272
273
274
275
276
277 public static class LoadQueueItem {
278 final byte[] family;
279 final Path hfilePath;
280
281 public LoadQueueItem(byte[] family, Path hfilePath) {
282 this.family = family;
283 this.hfilePath = hfilePath;
284 }
285
286 @Override
287 public String toString() {
288 return "family:"+ Bytes.toString(family) + " path:" + hfilePath.toString();
289 }
290 }
291
292
293
294
295
296 private void discoverLoadQueue(final Deque<LoadQueueItem> ret, final Path hfofDir,
297 final boolean validateHFile) throws IOException {
298 fs = hfofDir.getFileSystem(getConf());
299 BulkHFileVisitor<byte[]> visitor = new BulkHFileVisitor<byte[]>() {
300 @Override public byte[] bulkFamily(final byte[] familyName) {
301 return familyName;
302 }
303
304 @Override public void bulkHFile(final byte[] family, final FileStatus hfile)
305 throws IOException {
306 long length = hfile.getLen();
307 if (length > getConf()
308 .getLong(HConstants.HREGION_MAX_FILESIZE, HConstants.DEFAULT_MAX_FILE_SIZE)) {
309 LOG.warn("Trying to bulk load hfile " + hfile.getPath() + " with size: " + length
310 + " bytes can be problematic as it may lead to oversplitting.");
311 }
312 ret.add(new LoadQueueItem(family, hfile.getPath()));
313 }
314 };
315 if (depth == 2) {
316 visitBulkHFiles(fs, hfofDir, visitor, validateHFile);
317 } else if (depth == 3) {
318 for (FileStatus fileStatus : FSUtils.listStatus(fs, hfofDir)) {
319 visitBulkHFiles(fs, fileStatus.getPath(), visitor, validateHFile);
320 }
321 } else {
322 throw new IllegalArgumentException("Depth of HFiles from directory must be 2 or 3");
323 }
324
325 }
326
327
328
329
330
331
332
333
334
335
336
337
338
339 @SuppressWarnings("deprecation")
340 @Deprecated
341 public void doBulkLoad(Path hfofDir, final HTable table)
342 throws TableNotFoundException, IOException
343 {
344 Admin admin = null;
345 Table t = table;
346 Connection conn = table.getConnection();
347 boolean closeConnWhenFinished = false;
348 try {
349 if (conn instanceof ClusterConnection && ((ClusterConnection) conn).isManaged()) {
350 LOG.warn("managed connection cannot be used for bulkload. Creating unmanaged connection.");
351
352 conn = ConnectionFactory.createConnection(table.getConfiguration());
353 t = conn.getTable(table.getName());
354 closeConnWhenFinished = true;
355 if (conn instanceof ClusterConnection && ((ClusterConnection) conn).isManaged()) {
356 throw new RuntimeException("Failed to create unmanaged connection.");
357 }
358 admin = conn.getAdmin();
359 } else {
360 admin = conn.getAdmin();
361 }
362 try (RegionLocator rl = conn.getRegionLocator(t.getName())) {
363 doBulkLoad(hfofDir, admin, t, rl);
364 }
365 } finally {
366 if (admin != null) admin.close();
367 if (closeConnWhenFinished) {
368 t.close();
369 conn.close();
370 }
371 }
372 }
373
374
375
376
377
378
379
380
381
382
383 public void doBulkLoad(Path hfofDir, final Admin admin, Table table,
384 RegionLocator regionLocator) throws TableNotFoundException, IOException {
385
386 if (!admin.isTableAvailable(regionLocator.getName())) {
387 throw new TableNotFoundException("Table " + table.getName() + " is not currently available.");
388 }
389
390 ExecutorService pool = createExecutorService();
391
392
393
394 Deque<LoadQueueItem> queue = new LinkedList<LoadQueueItem>();
395 try {
396
397
398
399
400 boolean validateHFile = getConf().getBoolean("hbase.loadincremental.validate.hfile", true);
401 if(!validateHFile) {
402 LOG.warn("You are skipping HFiles validation, it might cause some data loss if files " +
403 "are not correct. If you fail to read data from your table after using this " +
404 "option, consider removing the files and bulkload again without this option. " +
405 "See HBASE-13985");
406 }
407 prepareHFileQueue(hfofDir, table, queue, validateHFile);
408
409 int count = 0;
410
411 if (queue.isEmpty()) {
412 LOG.warn("Bulk load operation did not find any files to load in " +
413 "directory " + hfofDir.toUri() + ". Does it contain files in " +
414 "subdirectories that correspond to column family names?");
415 return;
416 }
417
418
419
420
421 fsDelegationToken.acquireDelegationToken(fs);
422 if(isSecureBulkLoadEndpointAvailable()) {
423 bulkToken = new SecureBulkLoadClient(table).prepareBulkLoad(table.getName());
424 }
425
426
427 while (!queue.isEmpty()) {
428
429 final Pair<byte[][], byte[][]> startEndKeys = regionLocator.getStartEndKeys();
430 if (count != 0) {
431 LOG.info("Split occured while grouping HFiles, retry attempt " +
432 + count + " with " + queue.size() + " files remaining to group or split");
433 }
434
435 int maxRetries = getConf().getInt(HConstants.BULKLOAD_MAX_RETRIES_NUMBER, 10);
436 maxRetries = Math.max(maxRetries, startEndKeys.getFirst().length + 1);
437 if (maxRetries != 0 && count >= maxRetries) {
438 throw new IOException("Retry attempted " + count +
439 " times without completing, bailing out");
440 }
441 count++;
442
443
444 Multimap<ByteBuffer, LoadQueueItem> regionGroups = groupOrSplitPhase(table,
445 pool, queue, startEndKeys);
446
447 if (!checkHFilesCountPerRegionPerFamily(regionGroups)) {
448
449 throw new IOException("Trying to load more than " + maxFilesPerRegionPerFamily
450 + " hfiles to one family of one region");
451 }
452
453 bulkLoadPhase(table, admin.getConnection(), pool, queue, regionGroups);
454
455
456
457
458 }
459
460 } finally {
461 fsDelegationToken.releaseDelegationToken();
462 if(bulkToken != null) {
463 new SecureBulkLoadClient(table).cleanupBulkLoad(bulkToken);
464 }
465 pool.shutdown();
466 if (queue != null && !queue.isEmpty()) {
467 StringBuilder err = new StringBuilder();
468 err.append("-------------------------------------------------\n");
469 err.append("Bulk load aborted with some files not yet loaded:\n");
470 err.append("-------------------------------------------------\n");
471 for (LoadQueueItem q : queue) {
472 err.append(" ").append(q.hfilePath).append('\n');
473 }
474 LOG.error(err);
475 }
476 }
477
478 if (queue != null && !queue.isEmpty()) {
479 throw new RuntimeException("Bulk load aborted with some files not yet loaded."
480 + "Please check log for more details.");
481 }
482 }
483
484
485
486
487
488
489
490
491
492
493
494 public void prepareHFileQueue(Path hfilesDir, Table table, Deque<LoadQueueItem> queue,
495 boolean validateHFile) throws IOException {
496 discoverLoadQueue(queue, hfilesDir, validateHFile);
497 validateFamiliesInHFiles(table, queue);
498 }
499
500
501 private ExecutorService createExecutorService() {
502 ThreadFactoryBuilder builder = new ThreadFactoryBuilder();
503 builder.setNameFormat("LoadIncrementalHFiles-%1$d");
504 ExecutorService pool = new ThreadPoolExecutor(nrThreads, nrThreads, 60, TimeUnit.SECONDS,
505 new LinkedBlockingQueue<Runnable>(), builder.build());
506 ((ThreadPoolExecutor) pool).allowCoreThreadTimeOut(true);
507 return pool;
508 }
509
510
511
512
513 private void validateFamiliesInHFiles(Table table, Deque<LoadQueueItem> queue)
514 throws IOException {
515 Collection<HColumnDescriptor> families = table.getTableDescriptor().getFamilies();
516 List<String> familyNames = new ArrayList<String>(families.size());
517 for (HColumnDescriptor family : families) {
518 familyNames.add(family.getNameAsString());
519 }
520 List<String> unmatchedFamilies = new ArrayList<String>();
521 Iterator<LoadQueueItem> queueIter = queue.iterator();
522 while (queueIter.hasNext()) {
523 LoadQueueItem lqi = queueIter.next();
524 String familyNameInHFile = Bytes.toString(lqi.family);
525 if (!familyNames.contains(familyNameInHFile)) {
526 unmatchedFamilies.add(familyNameInHFile);
527 }
528 }
529 if (unmatchedFamilies.size() > 0) {
530 String msg =
531 "Unmatched family names found: unmatched family names in HFiles to be bulkloaded: "
532 + unmatchedFamilies + "; valid family names of table " + table.getName() + " are: "
533 + familyNames;
534 LOG.error(msg);
535 throw new IOException(msg);
536 }
537 }
538
539
540
541
542
543
544
545
546
547
548
549 public void loadHFileQueue(final Table table, final Connection conn, Deque<LoadQueueItem> queue,
550 Pair<byte[][], byte[][]> startEndKeys) throws IOException {
551 ExecutorService pool = null;
552 try {
553 pool = createExecutorService();
554 Multimap<ByteBuffer, LoadQueueItem> regionGroups =
555 groupOrSplitPhase(table, pool, queue, startEndKeys);
556 bulkLoadPhase(table, conn, pool, queue, regionGroups);
557 } finally {
558 if (pool != null) {
559 pool.shutdown();
560 }
561 }
562 }
563
564
565
566
567
568
569 protected void bulkLoadPhase(final Table table, final Connection conn,
570 ExecutorService pool, Deque<LoadQueueItem> queue,
571 final Multimap<ByteBuffer, LoadQueueItem> regionGroups) throws IOException {
572
573 Set<Future<List<LoadQueueItem>>> loadingFutures = new HashSet<Future<List<LoadQueueItem>>>();
574 for (Entry<ByteBuffer, ? extends Collection<LoadQueueItem>> e: regionGroups.asMap().entrySet()) {
575 final byte[] first = e.getKey().array();
576 final Collection<LoadQueueItem> lqis = e.getValue();
577
578 final Callable<List<LoadQueueItem>> call = new Callable<List<LoadQueueItem>>() {
579 @Override
580 public List<LoadQueueItem> call() throws Exception {
581 List<LoadQueueItem> toRetry =
582 tryAtomicRegionLoad(conn, table.getName(), first, lqis);
583 return toRetry;
584 }
585 };
586 loadingFutures.add(pool.submit(call));
587 }
588
589
590 for (Future<List<LoadQueueItem>> future : loadingFutures) {
591 try {
592 List<LoadQueueItem> toRetry = future.get();
593
594
595 queue.addAll(toRetry);
596
597 } catch (ExecutionException e1) {
598 Throwable t = e1.getCause();
599 if (t instanceof IOException) {
600
601
602 throw new IOException("BulkLoad encountered an unrecoverable problem", t);
603 }
604 LOG.error("Unexpected execution exception during bulk load", e1);
605 throw new IllegalStateException(t);
606 } catch (InterruptedException e1) {
607 LOG.error("Unexpected interrupted exception during bulk load", e1);
608 throw (InterruptedIOException)new InterruptedIOException().initCause(e1);
609 }
610 }
611 }
612
613 private boolean checkHFilesCountPerRegionPerFamily(
614 final Multimap<ByteBuffer, LoadQueueItem> regionGroups) {
615 for (Entry<ByteBuffer,
616 ? extends Collection<LoadQueueItem>> e: regionGroups.asMap().entrySet()) {
617 final Collection<LoadQueueItem> lqis = e.getValue();
618 HashMap<byte[], MutableInt> filesMap = new HashMap<byte[], MutableInt>();
619 for (LoadQueueItem lqi: lqis) {
620 MutableInt count = filesMap.get(lqi.family);
621 if (count == null) {
622 count = new MutableInt();
623 filesMap.put(lqi.family, count);
624 }
625 count.increment();
626 if (count.intValue() > maxFilesPerRegionPerFamily) {
627 LOG.error("Trying to load more than " + maxFilesPerRegionPerFamily
628 + " hfiles to family " + Bytes.toStringBinary(lqi.family)
629 + " of region with start key "
630 + Bytes.toStringBinary(e.getKey()));
631 return false;
632 }
633 }
634 }
635 return true;
636 }
637
638
639
640
641
642 private Multimap<ByteBuffer, LoadQueueItem> groupOrSplitPhase(final Table table,
643 ExecutorService pool, Deque<LoadQueueItem> queue,
644 final Pair<byte[][], byte[][]> startEndKeys) throws IOException {
645
646
647 Multimap<ByteBuffer, LoadQueueItem> rgs = HashMultimap.create();
648 final Multimap<ByteBuffer, LoadQueueItem> regionGroups = Multimaps.synchronizedMultimap(rgs);
649
650
651 Set<Future<List<LoadQueueItem>>> splittingFutures = new HashSet<Future<List<LoadQueueItem>>>();
652 while (!queue.isEmpty()) {
653 final LoadQueueItem item = queue.remove();
654
655 final Callable<List<LoadQueueItem>> call = new Callable<List<LoadQueueItem>>() {
656 @Override
657 public List<LoadQueueItem> call() throws Exception {
658 List<LoadQueueItem> splits = groupOrSplit(regionGroups, item, table, startEndKeys);
659 return splits;
660 }
661 };
662 splittingFutures.add(pool.submit(call));
663 }
664
665
666 for (Future<List<LoadQueueItem>> lqis : splittingFutures) {
667 try {
668 List<LoadQueueItem> splits = lqis.get();
669 if (splits != null) {
670 queue.addAll(splits);
671 }
672 } catch (ExecutionException e1) {
673 Throwable t = e1.getCause();
674 if (t instanceof IOException) {
675 LOG.error("IOException during splitting", e1);
676 throw (IOException)t;
677 }
678 LOG.error("Unexpected execution exception during splitting", e1);
679 throw new IllegalStateException(t);
680 } catch (InterruptedException e1) {
681 LOG.error("Unexpected interrupted exception during splitting", e1);
682 throw (InterruptedIOException)new InterruptedIOException().initCause(e1);
683 }
684 }
685 return regionGroups;
686 }
687
688
689 private String getUniqueName() {
690 return UUID.randomUUID().toString().replaceAll("-", "");
691 }
692
693 protected List<LoadQueueItem> splitStoreFile(final LoadQueueItem item,
694 final Table table, byte[] startKey,
695 byte[] splitKey) throws IOException {
696 final Path hfilePath = item.hfilePath;
697
698 Path tmpDir = item.hfilePath.getParent();
699 if (!tmpDir.getName().equals(TMP_DIR)) {
700 tmpDir = new Path(tmpDir, TMP_DIR);
701 }
702
703 LOG.info("HFile at " + hfilePath + " no longer fits inside a single " +
704 "region. Splitting...");
705
706 String uniqueName = getUniqueName();
707 HColumnDescriptor familyDesc = table.getTableDescriptor().getFamily(item.family);
708
709 Path botOut = new Path(tmpDir, uniqueName + ".bottom");
710 Path topOut = new Path(tmpDir, uniqueName + ".top");
711 splitStoreFile(getConf(), hfilePath, familyDesc, splitKey, botOut, topOut);
712
713 FileSystem fs = tmpDir.getFileSystem(getConf());
714 fs.setPermission(tmpDir, FsPermission.valueOf("-rwxrwxrwx"));
715 fs.setPermission(botOut, FsPermission.valueOf("-rwxrwxrwx"));
716 fs.setPermission(topOut, FsPermission.valueOf("-rwxrwxrwx"));
717
718
719
720 List<LoadQueueItem> lqis = new ArrayList<LoadQueueItem>(2);
721 lqis.add(new LoadQueueItem(item.family, botOut));
722 lqis.add(new LoadQueueItem(item.family, topOut));
723
724
725
726
727 try {
728 tmpDir = item.hfilePath.getParent();
729 if (tmpDir.getName().equals(TMP_DIR)) {
730 fs.delete(item.hfilePath, false);
731 }
732 } catch (IOException e) {
733 LOG.warn("Unable to delete temporary split file " + item.hfilePath);
734 }
735 LOG.info("Successfully split into new HFiles " + botOut + " and " + topOut);
736 return lqis;
737 }
738
739
740
741
742
743
744
745
746
747
748 protected List<LoadQueueItem> groupOrSplit(Multimap<ByteBuffer, LoadQueueItem> regionGroups,
749 final LoadQueueItem item, final Table table,
750 final Pair<byte[][], byte[][]> startEndKeys)
751 throws IOException {
752 final Path hfilePath = item.hfilePath;
753
754 if (fs == null) {
755 fs = hfilePath.getFileSystem(getConf());
756 }
757 HFile.Reader hfr = HFile.createReader(fs, hfilePath,
758 new CacheConfig(getConf()), getConf());
759 final byte[] first, last;
760 try {
761 hfr.loadFileInfo();
762 first = hfr.getFirstRowKey();
763 last = hfr.getLastRowKey();
764 } finally {
765 hfr.close();
766 }
767
768 LOG.info("Trying to load hfile=" + hfilePath +
769 " first=" + Bytes.toStringBinary(first) +
770 " last=" + Bytes.toStringBinary(last));
771 if (first == null || last == null) {
772 assert first == null && last == null;
773
774 LOG.info("hfile " + hfilePath + " has no entries, skipping");
775 return null;
776 }
777 if (Bytes.compareTo(first, last) > 0) {
778 throw new IllegalArgumentException(
779 "Invalid range: " + Bytes.toStringBinary(first) +
780 " > " + Bytes.toStringBinary(last));
781 }
782 int idx = Arrays.binarySearch(startEndKeys.getFirst(), first,
783 Bytes.BYTES_COMPARATOR);
784 if (idx < 0) {
785
786
787 idx = -(idx + 1) - 1;
788 }
789 final int indexForCallable = idx;
790
791
792
793
794
795
796 if (indexForCallable < 0) {
797 throw new IOException("The first region info for table "
798 + table.getName()
799 + " can't be found in hbase:meta.Please use hbck tool to fix it first.");
800 } else if ((indexForCallable == startEndKeys.getFirst().length - 1)
801 && !Bytes.equals(startEndKeys.getSecond()[indexForCallable], HConstants.EMPTY_BYTE_ARRAY)) {
802 throw new IOException("The last region info for table "
803 + table.getName()
804 + " can't be found in hbase:meta.Please use hbck tool to fix it first.");
805 } else if (indexForCallable + 1 < startEndKeys.getFirst().length
806 && !(Bytes.compareTo(startEndKeys.getSecond()[indexForCallable],
807 startEndKeys.getFirst()[indexForCallable + 1]) == 0)) {
808 throw new IOException("The endkey of one region for table "
809 + table.getName()
810 + " is not equal to the startkey of the next region in hbase:meta."
811 + "Please use hbck tool to fix it first.");
812 }
813
814 boolean lastKeyInRange =
815 Bytes.compareTo(last, startEndKeys.getSecond()[idx]) < 0 ||
816 Bytes.equals(startEndKeys.getSecond()[idx], HConstants.EMPTY_BYTE_ARRAY);
817 if (!lastKeyInRange) {
818 List<LoadQueueItem> lqis = splitStoreFile(item, table,
819 startEndKeys.getFirst()[indexForCallable],
820 startEndKeys.getSecond()[indexForCallable]);
821 return lqis;
822 }
823
824
825 regionGroups.put(ByteBuffer.wrap(startEndKeys.getFirst()[idx]), item);
826 return null;
827 }
828
829
830
831
832
833
834
835 @Deprecated
836 protected List<LoadQueueItem> tryAtomicRegionLoad(final HConnection conn,
837 final byte [] tableName, final byte[] first, Collection<LoadQueueItem> lqis)
838 throws IOException {
839 return tryAtomicRegionLoad(conn, TableName.valueOf(tableName), first, lqis);
840 }
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855 protected List<LoadQueueItem> tryAtomicRegionLoad(final Connection conn,
856 final TableName tableName, final byte[] first, final Collection<LoadQueueItem> lqis)
857 throws IOException {
858 final List<Pair<byte[], String>> famPaths =
859 new ArrayList<Pair<byte[], String>>(lqis.size());
860 for (LoadQueueItem lqi : lqis) {
861 famPaths.add(Pair.newPair(lqi.family, lqi.hfilePath.toString()));
862 }
863
864 final RegionServerCallable<Boolean> svrCallable =
865 new RegionServerCallable<Boolean>(conn, tableName, first) {
866 @Override
867 public Boolean call(int callTimeout) throws Exception {
868 SecureBulkLoadClient secureClient = null;
869 boolean success = false;
870
871 try {
872 LOG.debug("Going to connect to server " + getLocation() + " for row "
873 + Bytes.toStringBinary(getRow()) + " with hfile group " + famPaths);
874 byte[] regionName = getLocation().getRegionInfo().getRegionName();
875 if (!isSecureBulkLoadEndpointAvailable()) {
876 success = ProtobufUtil.bulkLoadHFile(getStub(), famPaths, regionName, assignSeqIds);
877 } else {
878 try (Table table = conn.getTable(getTableName())) {
879 secureClient = new SecureBulkLoadClient(table);
880 success = secureClient.bulkLoadHFiles(famPaths, fsDelegationToken.getUserToken(),
881 bulkToken, getLocation().getRegionInfo().getStartKey());
882 }
883 }
884 return success;
885 } finally {
886
887
888
889 if(secureClient != null && !success) {
890 FileSystem targetFs = FileSystem.get(getConf());
891
892 if(fs == null) {
893 fs = lqis.iterator().next().hfilePath.getFileSystem(getConf());
894 }
895
896
897
898 if (FSHDFSUtils.isSameHdfs(getConf(), fs, targetFs)) {
899 for(Pair<byte[], String> el : famPaths) {
900 Path hfileStagingPath = null;
901 Path hfileOrigPath = new Path(el.getSecond());
902 try {
903 hfileStagingPath= new Path(secureClient.getStagingPath(bulkToken, el.getFirst()),
904 hfileOrigPath.getName());
905 if(targetFs.rename(hfileStagingPath, hfileOrigPath)) {
906 LOG.debug("Moved back file " + hfileOrigPath + " from " +
907 hfileStagingPath);
908 } else if(targetFs.exists(hfileStagingPath)){
909 LOG.debug("Unable to move back file " + hfileOrigPath + " from " +
910 hfileStagingPath);
911 }
912 } catch(Exception ex) {
913 LOG.debug("Unable to move back file " + hfileOrigPath + " from " +
914 hfileStagingPath, ex);
915 }
916 }
917 }
918 }
919 }
920 }
921 };
922
923 try {
924 List<LoadQueueItem> toRetry = new ArrayList<LoadQueueItem>();
925 Configuration conf = getConf();
926 boolean success = RpcRetryingCallerFactory.instantiate(conf,
927 null).<Boolean> newCaller()
928 .callWithRetries(svrCallable, Integer.MAX_VALUE);
929 if (!success) {
930 LOG.warn("Attempt to bulk load region containing "
931 + Bytes.toStringBinary(first) + " into table "
932 + tableName + " with files " + lqis
933 + " failed. This is recoverable and they will be retried.");
934 toRetry.addAll(lqis);
935 }
936
937 return toRetry;
938 } catch (IOException e) {
939 LOG.error("Encountered unrecoverable error from region server, additional details: "
940 + svrCallable.getExceptionMessageAdditionalDetail(), e);
941 throw e;
942 }
943 }
944
945 private boolean isSecureBulkLoadEndpointAvailable() {
946 String classes = getConf().get(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY, "");
947 return classes.contains("org.apache.hadoop.hbase.security.access.SecureBulkLoadEndpoint");
948 }
949
950
951
952
953
954 static void splitStoreFile(
955 Configuration conf, Path inFile,
956 HColumnDescriptor familyDesc, byte[] splitKey,
957 Path bottomOut, Path topOut) throws IOException
958 {
959
960 Reference topReference = Reference.createTopReference(splitKey);
961 Reference bottomReference = Reference.createBottomReference(splitKey);
962
963 copyHFileHalf(conf, inFile, topOut, topReference, familyDesc);
964 copyHFileHalf(conf, inFile, bottomOut, bottomReference, familyDesc);
965 }
966
967
968
969
970 private static void copyHFileHalf(
971 Configuration conf, Path inFile, Path outFile, Reference reference,
972 HColumnDescriptor familyDescriptor)
973 throws IOException {
974 FileSystem fs = inFile.getFileSystem(conf);
975 CacheConfig cacheConf = new CacheConfig(conf);
976 HalfStoreFileReader halfReader = null;
977 StoreFile.Writer halfWriter = null;
978 try {
979 halfReader = new HalfStoreFileReader(fs, inFile, cacheConf, reference, conf);
980 Map<byte[], byte[]> fileInfo = halfReader.loadFileInfo();
981
982 int blocksize = familyDescriptor.getBlocksize();
983 Algorithm compression = familyDescriptor.getCompression();
984 BloomType bloomFilterType = familyDescriptor.getBloomFilterType();
985 HFileContext hFileContext = new HFileContextBuilder()
986 .withCompression(compression)
987 .withChecksumType(HStore.getChecksumType(conf))
988 .withBytesPerCheckSum(HStore.getBytesPerChecksum(conf))
989 .withBlockSize(blocksize)
990 .withDataBlockEncoding(familyDescriptor.getDataBlockEncoding())
991 .withIncludesTags(true)
992 .build();
993 halfWriter = new StoreFile.WriterBuilder(conf, cacheConf,
994 fs)
995 .withFilePath(outFile)
996 .withBloomType(bloomFilterType)
997 .withFileContext(hFileContext)
998 .build();
999 HFileScanner scanner = halfReader.getScanner(false, false, false);
1000 scanner.seekTo();
1001 do {
1002 halfWriter.append(scanner.getKeyValue());
1003 } while (scanner.next());
1004
1005 for (Map.Entry<byte[],byte[]> entry : fileInfo.entrySet()) {
1006 if (shouldCopyHFileMetaKey(entry.getKey())) {
1007 halfWriter.appendFileInfo(entry.getKey(), entry.getValue());
1008 }
1009 }
1010 } finally {
1011 if (halfWriter != null) halfWriter.close();
1012 if (halfReader != null) halfReader.close(cacheConf.shouldEvictOnClose());
1013 }
1014 }
1015
1016 private static boolean shouldCopyHFileMetaKey(byte[] key) {
1017
1018 if (Bytes.equals(key, HFileDataBlockEncoder.DATA_BLOCK_ENCODING)) {
1019 return false;
1020 }
1021
1022 return !HFile.isReservedFileInfoKey(key);
1023 }
1024
1025 private boolean doesTableExist(TableName tableName) throws Exception {
1026 return hbAdmin.tableExists(tableName);
1027 }
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041 public static byte[][] inferBoundaries(TreeMap<byte[], Integer> bdryMap) {
1042 ArrayList<byte[]> keysArray = new ArrayList<byte[]>();
1043 int runningValue = 0;
1044 byte[] currStartKey = null;
1045 boolean firstBoundary = true;
1046
1047 for (Map.Entry<byte[], Integer> item: bdryMap.entrySet()) {
1048 if (runningValue == 0) currStartKey = item.getKey();
1049 runningValue += item.getValue();
1050 if (runningValue == 0) {
1051 if (!firstBoundary) keysArray.add(currStartKey);
1052 firstBoundary = false;
1053 }
1054 }
1055
1056 return keysArray.toArray(new byte[0][0]);
1057 }
1058
1059
1060
1061
1062
1063 private void createTable(TableName tableName, String dirPath) throws Exception {
1064 final Path hfofDir = new Path(dirPath);
1065 final FileSystem fs = hfofDir.getFileSystem(getConf());
1066
1067
1068
1069 final HTableDescriptor htd = new HTableDescriptor(tableName);
1070 final TreeMap<byte[], Integer> map = new TreeMap<byte[], Integer>(Bytes.BYTES_COMPARATOR);
1071 visitBulkHFiles(fs, hfofDir, new BulkHFileVisitor<HColumnDescriptor>() {
1072 @Override
1073 public HColumnDescriptor bulkFamily(final byte[] familyName) {
1074 HColumnDescriptor hcd = new HColumnDescriptor(familyName);
1075 htd.addFamily(hcd);
1076 return hcd;
1077 }
1078 @Override
1079 public void bulkHFile(final HColumnDescriptor hcd, final FileStatus hfileStatus)
1080 throws IOException {
1081 Path hfile = hfileStatus.getPath();
1082 HFile.Reader reader = HFile.createReader(fs, hfile,
1083 new CacheConfig(getConf()), getConf());
1084 try {
1085 if (hcd.getCompressionType() != reader.getFileContext().getCompression()) {
1086 hcd.setCompressionType(reader.getFileContext().getCompression());
1087 LOG.info("Setting compression " + hcd.getCompressionType().name() +
1088 " for family " + hcd.toString());
1089 }
1090 reader.loadFileInfo();
1091 byte[] first = reader.getFirstRowKey();
1092 byte[] last = reader.getLastRowKey();
1093
1094 LOG.info("Trying to figure out region boundaries hfile=" + hfile +
1095 " first=" + Bytes.toStringBinary(first) +
1096 " last=" + Bytes.toStringBinary(last));
1097
1098
1099 Integer value = map.containsKey(first)? map.get(first):0;
1100 map.put(first, value+1);
1101
1102 value = map.containsKey(last)? map.get(last):0;
1103 map.put(last, value-1);
1104 } finally {
1105 reader.close();
1106 }
1107 }
1108 });
1109
1110 byte[][] keys = LoadIncrementalHFiles.inferBoundaries(map);
1111 this.hbAdmin.createTable(htd,keys);
1112
1113 LOG.info("Table "+ tableName +" is available!!");
1114 }
1115
1116 @Override
1117 public int run(String[] args) throws Exception {
1118 if (args.length != 2 && args.length != 3) {
1119 usage();
1120 return -1;
1121 }
1122
1123 initialize();
1124
1125 String dirPath = args[0];
1126 TableName tableName = TableName.valueOf(args[1]);
1127 if (args.length == 3) {
1128 this.setDepth(3);
1129 }
1130
1131 boolean tableExists = this.doesTableExist(tableName);
1132 if (!tableExists) {
1133 if ("yes".equalsIgnoreCase(getConf().get(CREATE_TABLE_CONF_KEY, "yes"))) {
1134 this.createTable(tableName, dirPath);
1135 } else {
1136 String errorMsg = format("Table '%s' does not exist.", tableName);
1137 LOG.error(errorMsg);
1138 throw new TableNotFoundException(errorMsg);
1139 }
1140 }
1141
1142 Path hfofDir = new Path(dirPath);
1143
1144 try (Connection connection = ConnectionFactory.createConnection(getConf());
1145 HTable table = (HTable) connection.getTable(tableName);) {
1146 doBulkLoad(hfofDir, table);
1147 }
1148 return 0;
1149 }
1150
1151 public static void main(String[] args) throws Exception {
1152 Configuration conf = HBaseConfiguration.create();
1153 int ret = ToolRunner.run(conf, new LoadIncrementalHFiles(), args);
1154 System.exit(ret);
1155 }
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166 public void setBulkToken(String stagingDir) {
1167 this.bulkToken = stagingDir;
1168 }
1169
1170 }