Skip to content

Commit 4829806

Browse files
HBASE-26707: Reduce number of renames during bulkload (#4066)
Signed-off-by: Wellington Ramos Chevreuil <wchevreuil@apache.org>
1 parent 8dec499 commit 4829806

9 files changed

Lines changed: 580 additions & 34 deletions

File tree

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
/**
2+
*
3+
* Licensed to the Apache Software Foundation (ASF) under one
4+
* or more contributor license agreements. See the NOTICE file
5+
* distributed with this work for additional information
6+
* regarding copyright ownership. The ASF licenses this file
7+
* to you under the Apache License, Version 2.0 (the
8+
* "License"); you may not use this file except in compliance
9+
* with the License. You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
package org.apache.hadoop.hbase.mapreduce;
20+
21+
import org.apache.commons.lang3.RandomStringUtils;
22+
import org.apache.hadoop.conf.Configuration;
23+
import org.apache.hadoop.fs.Path;
24+
import org.apache.hadoop.hbase.Cell;
25+
import org.apache.hadoop.hbase.CellUtil;
26+
import org.apache.hadoop.hbase.HBaseConfiguration;
27+
import org.apache.hadoop.hbase.HBaseTestingUtil;
28+
import org.apache.hadoop.hbase.IntegrationTestBase;
29+
import org.apache.hadoop.hbase.IntegrationTestingUtility;
30+
import org.apache.hadoop.hbase.KeyValue;
31+
import org.apache.hadoop.hbase.TableName;
32+
import org.apache.hadoop.hbase.client.Admin;
33+
import org.apache.hadoop.hbase.client.Connection;
34+
import org.apache.hadoop.hbase.client.ConnectionFactory;
35+
import org.apache.hadoop.hbase.client.Consistency;
36+
import org.apache.hadoop.hbase.client.RegionLocator;
37+
import org.apache.hadoop.hbase.client.Result;
38+
import org.apache.hadoop.hbase.client.Scan;
39+
import org.apache.hadoop.hbase.client.TableDescriptor;
40+
import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
41+
import org.apache.hadoop.hbase.coprocessor.ObserverContext;
42+
import org.apache.hadoop.hbase.coprocessor.RegionCoprocessor;
43+
import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment;
44+
import org.apache.hadoop.hbase.coprocessor.RegionObserver;
45+
import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
46+
import org.apache.hadoop.hbase.regionserver.InternalScanner;
47+
import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTrackerFactory;
48+
import org.apache.hadoop.hbase.testclassification.IntegrationTests;
49+
import org.apache.hadoop.hbase.tool.BulkLoadHFiles;
50+
import org.apache.hadoop.hbase.util.Bytes;
51+
import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
52+
import org.apache.hadoop.hbase.util.RegionSplitter;
53+
import org.apache.hadoop.io.LongWritable;
54+
import org.apache.hadoop.io.NullWritable;
55+
import org.apache.hadoop.io.Writable;
56+
import org.apache.hadoop.io.WritableComparable;
57+
import org.apache.hadoop.io.WritableComparator;
58+
import org.apache.hadoop.io.WritableUtils;
59+
import org.apache.hadoop.mapreduce.InputFormat;
60+
import org.apache.hadoop.mapreduce.InputSplit;
61+
import org.apache.hadoop.mapreduce.Job;
62+
import org.apache.hadoop.mapreduce.JobContext;
63+
import org.apache.hadoop.mapreduce.Mapper;
64+
import org.apache.hadoop.mapreduce.Partitioner;
65+
import org.apache.hadoop.mapreduce.RecordReader;
66+
import org.apache.hadoop.mapreduce.Reducer;
67+
import org.apache.hadoop.mapreduce.TaskAttemptContext;
68+
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
69+
import org.apache.hadoop.util.StringUtils;
70+
import org.apache.hadoop.util.ToolRunner;
71+
import org.apache.hbase.thirdparty.com.google.common.base.Joiner;
72+
import org.apache.hbase.thirdparty.com.google.common.collect.Sets;
73+
import org.apache.hbase.thirdparty.org.apache.commons.cli.CommandLine;
74+
import org.junit.Test;
75+
import org.junit.experimental.categories.Category;
76+
import org.slf4j.Logger;
77+
import org.slf4j.LoggerFactory;
78+
import java.io.DataInput;
79+
import java.io.DataOutput;
80+
import java.io.IOException;
81+
import java.util.ArrayList;
82+
import java.util.List;
83+
import java.util.Map;
84+
import java.util.Optional;
85+
import java.util.Random;
86+
import java.util.Set;
87+
import java.util.concurrent.atomic.AtomicLong;
88+
import static org.junit.Assert.assertEquals;
89+
90+
/**
91+
* Test Bulk Load and MR on a distributed cluster.
92+
* With FileBased StorefileTracker enabled.
93+
* It starts an MR job that creates linked chains
94+
*
95+
* The format of rows is like this:
96+
* Row Key -> Long
97+
*
98+
* L:<< Chain Id >> -> Row Key of the next link in the chain
99+
* S:<< Chain Id >> -> The step in the chain that his link is.
100+
* D:<< Chain Id >> -> Random Data.
101+
*
102+
* All chains start on row 0.
103+
* All rk's are > 0.
104+
*
105+
* After creating the linked lists they are walked over using a TableMapper based Mapreduce Job.
106+
*
107+
* There are a few options exposed:
108+
*
109+
* hbase.IntegrationTestBulkLoad.chainLength
110+
* The number of rows that will be part of each and every chain.
111+
*
112+
* hbase.IntegrationTestBulkLoad.numMaps
113+
* The number of mappers that will be run. Each mapper creates on linked list chain.
114+
*
115+
* hbase.IntegrationTestBulkLoad.numImportRounds
116+
* How many jobs will be run to create linked lists.
117+
*
118+
* hbase.IntegrationTestBulkLoad.tableName
119+
* The name of the table.
120+
*
121+
* hbase.IntegrationTestBulkLoad.replicaCount
122+
* How many region replicas to configure for the table under test.
123+
*/
124+
@Category(IntegrationTests.class)
125+
public class IntegrationTestFileBasedSFTBulkLoad extends IntegrationTestBulkLoad {
126+
127+
private static final Logger LOG = LoggerFactory.getLogger(IntegrationTestFileBasedSFTBulkLoad.class);
128+
129+
private static String NUM_MAPS_KEY = "hbase.IntegrationTestBulkLoad.numMaps";
130+
private static String NUM_IMPORT_ROUNDS_KEY = "hbase.IntegrationTestBulkLoad.numImportRounds";
131+
private static String NUM_REPLICA_COUNT_KEY = "hbase.IntegrationTestBulkLoad.replicaCount";
132+
private static int NUM_REPLICA_COUNT_DEFAULT = 1;
133+
134+
@Test
135+
public void testFileBasedSFTBulkLoad() throws Exception {
136+
super.testBulkLoad();
137+
}
138+
139+
@Override
140+
public void setUpCluster() throws Exception {
141+
util = getTestingUtil(getConf());
142+
util.getConfiguration().set(StoreFileTrackerFactory.TRACKER_IMPL,
143+
"org.apache.hadoop.hbase.regionserver.storefiletracker.FileBasedStoreFileTracker");
144+
util.initializeCluster(1);
145+
int replicaCount = getConf().getInt(NUM_REPLICA_COUNT_KEY, NUM_REPLICA_COUNT_DEFAULT);
146+
if (LOG.isDebugEnabled() && replicaCount != NUM_REPLICA_COUNT_DEFAULT) {
147+
LOG.debug("Region Replicas enabled: " + replicaCount);
148+
}
149+
150+
// Scale this up on a real cluster
151+
if (util.isDistributedCluster()) {
152+
util.getConfiguration().setIfUnset(NUM_MAPS_KEY,
153+
Integer.toString(util.getAdmin().getRegionServers().size() * 10));
154+
util.getConfiguration().setIfUnset(NUM_IMPORT_ROUNDS_KEY, "5");
155+
} else {
156+
util.startMiniMapReduceCluster();
157+
}
158+
}
159+
160+
public static void main(String[] args) throws Exception {
161+
Configuration conf = HBaseConfiguration.create();
162+
IntegrationTestingUtility.setUseDistributedCluster(conf);
163+
int status = ToolRunner.run(conf, new IntegrationTestFileBasedSFTBulkLoad(), args);
164+
System.exit(status);
165+
}
166+
}

hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegion.java

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7128,7 +7128,7 @@ public interface BulkLoadListener {
71287128
* @return final path to be used for actual loading
71297129
* @throws IOException
71307130
*/
7131-
String prepareBulkLoad(byte[] family, String srcPath, boolean copyFile)
7131+
String prepareBulkLoad(byte[] family, String srcPath, boolean copyFile, String customStaging)
71327132
throws IOException;
71337133

71347134
/**
@@ -7250,12 +7250,21 @@ public Map<byte[], List<Path>> bulkLoadHFiles(Collection<Pair<byte[], String>> f
72507250
familyWithFinalPath.put(familyName, new ArrayList<>());
72517251
}
72527252
List<Pair<Path, Path>> lst = familyWithFinalPath.get(familyName);
7253+
String finalPath = path;
72537254
try {
7254-
String finalPath = path;
7255+
boolean reqTmp = store.storeEngine.requireWritingToTmpDirFirst();
72557256
if (bulkLoadListener != null) {
7256-
finalPath = bulkLoadListener.prepareBulkLoad(familyName, path, copyFile);
7257+
finalPath = bulkLoadListener.prepareBulkLoad(familyName, path, copyFile,
7258+
reqTmp ? null : regionDir.toString());
7259+
}
7260+
Pair<Path, Path> pair = null;
7261+
if (reqTmp) {
7262+
pair = store.preBulkLoadHFile(finalPath, seqId);
7263+
}
7264+
else {
7265+
Path livePath = new Path(finalPath);
7266+
pair = new Pair<>(livePath, livePath);
72577267
}
7258-
Pair<Path, Path> pair = store.preBulkLoadHFile(finalPath, seqId);
72597268
lst.add(pair);
72607269
} catch (IOException ioe) {
72617270
// A failure here can cause an atomicity violation that we currently
@@ -7265,7 +7274,7 @@ public Map<byte[], List<Path>> bulkLoadHFiles(Collection<Pair<byte[], String>> f
72657274
" load " + Bytes.toString(p.getFirst()) + " : " + p.getSecond(), ioe);
72667275
if (bulkLoadListener != null) {
72677276
try {
7268-
bulkLoadListener.failedBulkLoad(familyName, path);
7277+
bulkLoadListener.failedBulkLoad(familyName, finalPath);
72697278
} catch (Exception ex) {
72707279
LOG.error("Error while calling failedBulkLoad for family " +
72717280
Bytes.toString(familyName) + " with path " + path, ex);

hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionFileSystem.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,10 @@ private Path preCommitStoreFile(final String familyName, final Path buildPath,
508508
* @throws IOException
509509
*/
510510
Path commitStoreFile(final Path buildPath, Path dstPath) throws IOException {
511+
// rename is not necessary in case of direct-insert stores
512+
if(buildPath.equals(dstPath)){
513+
return dstPath;
514+
}
511515
// buildPath exists, therefore not doing an exists() check.
512516
if (!rename(buildPath, dstPath)) {
513517
throw new IOException("Failed rename of " + buildPath + " to " + dstPath);

hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/SecureBulkLoadManager.java

Lines changed: 41 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.util.Map;
2828
import java.util.concurrent.ConcurrentHashMap;
2929
import java.util.function.Consumer;
30+
import org.apache.commons.lang3.StringUtils;
3031
import org.apache.commons.lang3.mutable.MutableInt;
3132
import org.apache.hadoop.conf.Configuration;
3233
import org.apache.hadoop.fs.FileStatus;
@@ -341,27 +342,37 @@ private User getActiveUser() throws IOException {
341342
return user;
342343
}
343344

344-
private static class SecureBulkLoadListener implements BulkLoadListener {
345+
//package-private for test purpose only
346+
static class SecureBulkLoadListener implements BulkLoadListener {
345347
// Target filesystem
346348
private final FileSystem fs;
347349
private final String stagingDir;
348350
private final Configuration conf;
349351
// Source filesystem
350352
private FileSystem srcFs = null;
351353
private Map<String, FsPermission> origPermissions = null;
354+
private Map<String, String> origSources = null;
352355

353356
public SecureBulkLoadListener(FileSystem fs, String stagingDir, Configuration conf) {
354357
this.fs = fs;
355358
this.stagingDir = stagingDir;
356359
this.conf = conf;
357360
this.origPermissions = new HashMap<>();
361+
this.origSources = new HashMap<>();
358362
}
359363

360364
@Override
361-
public String prepareBulkLoad(final byte[] family, final String srcPath, boolean copyFile)
362-
throws IOException {
365+
public String prepareBulkLoad(final byte[] family, final String srcPath, boolean copyFile,
366+
String customStaging ) throws IOException {
363367
Path p = new Path(srcPath);
364-
Path stageP = new Path(stagingDir, new Path(Bytes.toString(family), p.getName()));
368+
369+
//store customStaging for failedBulkLoad
370+
String currentStaging = stagingDir;
371+
if(StringUtils.isNotEmpty(customStaging)){
372+
currentStaging = customStaging;
373+
}
374+
375+
Path stageP = new Path(currentStaging, new Path(Bytes.toString(family), p.getName()));
365376

366377
// In case of Replication for bulk load files, hfiles are already copied in staging directory
367378
if (p.equals(stageP)) {
@@ -390,11 +401,16 @@ public String prepareBulkLoad(final byte[] family, final String srcPath, boolean
390401
LOG.debug("Moving " + p + " to " + stageP);
391402
FileStatus origFileStatus = fs.getFileStatus(p);
392403
origPermissions.put(srcPath, origFileStatus.getPermission());
404+
origSources.put(stageP.toString(), srcPath);
393405
if(!fs.rename(p, stageP)) {
394406
throw new IOException("Failed to move HFile: " + p + " to " + stageP);
395407
}
396408
}
397-
fs.setPermission(stageP, PERM_ALL_ACCESS);
409+
410+
if(StringUtils.isNotEmpty(customStaging)) {
411+
fs.setPermission(stageP, PERM_ALL_ACCESS);
412+
}
413+
398414
return stageP.toString();
399415
}
400416

@@ -412,35 +428,37 @@ private void closeSrcFs() throws IOException {
412428
}
413429

414430
@Override
415-
public void failedBulkLoad(final byte[] family, final String srcPath) throws IOException {
431+
public void failedBulkLoad(final byte[] family, final String stagedPath) throws IOException {
416432
try {
417-
Path p = new Path(srcPath);
418-
if (srcFs == null) {
419-
srcFs = FileSystem.newInstance(p.toUri(), conf);
420-
}
421-
if (!FSUtils.isSameHdfs(conf, srcFs, fs)) {
422-
// files are copied so no need to move them back
433+
String src = origSources.get(stagedPath);
434+
if(StringUtils.isEmpty(src)){
435+
LOG.debug(stagedPath + " was not moved to staging. No need to move back");
423436
return;
424437
}
425-
Path stageP = new Path(stagingDir, new Path(Bytes.toString(family), p.getName()));
426438

427-
// In case of Replication for bulk load files, hfiles are not renamed by end point during
428-
// prepare stage, so no need of rename here again
429-
if (p.equals(stageP)) {
430-
LOG.debug(p.getName() + " is already available in source directory. Skipping rename.");
439+
Path stageP = new Path(stagedPath);
440+
if (!fs.exists(stageP)) {
441+
throw new IOException(
442+
"Missing HFile: " + stageP + ", can't be moved back to it's original place");
443+
}
444+
445+
//we should not move back files if the original exists
446+
Path srcPath = new Path(src);
447+
if(srcFs.exists(srcPath)) {
448+
LOG.debug(src + " is already at it's original place. No need to move.");
431449
return;
432450
}
433451

434-
LOG.debug("Moving " + stageP + " back to " + p);
435-
if (!fs.rename(stageP, p)) {
436-
throw new IOException("Failed to move HFile: " + stageP + " to " + p);
452+
LOG.debug("Moving " + stageP + " back to " + srcPath);
453+
if (!fs.rename(stageP, srcPath)) {
454+
throw new IOException("Failed to move HFile: " + stageP + " to " + srcPath);
437455
}
438456

439457
// restore original permission
440-
if (origPermissions.containsKey(srcPath)) {
441-
fs.setPermission(p, origPermissions.get(srcPath));
458+
if (origPermissions.containsKey(stagedPath)) {
459+
fs.setPermission(srcPath, origPermissions.get(src));
442460
} else {
443-
LOG.warn("Can't find previous permission for path=" + srcPath);
461+
LOG.warn("Can't find previous permission for path=" + stagedPath);
444462
}
445463
} finally {
446464
closeSrcFs();

hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestBulkLoad.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ public class TestBulkLoad extends TestBulkloadBase {
5454
public static final HBaseClassTestRule CLASS_RULE =
5555
HBaseClassTestRule.forClass(TestBulkLoad.class);
5656

57+
public TestBulkLoad(boolean useFileBasedSFT) {
58+
super(useFileBasedSFT);
59+
}
60+
5761
@Test
5862
public void verifyBulkLoadEvent() throws IOException {
5963
TableName tableName = TableName.valueOf("test", "test");

0 commit comments

Comments
 (0)