Skip to content

Commit 0e2b308

Browse files
authored
HDFS-15683. Allow configuring DISK/ARCHIVE capacity for individual volumes. (apache#2625)
1 parent 19ae0fa commit 0e2b308

File tree

14 files changed

+427
-38
lines changed

14 files changed

+427
-38
lines changed

hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/DFSConfigKeys.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1557,6 +1557,12 @@ public class DFSConfigKeys extends CommonConfigurationKeys {
15571557
public static final double
15581558
DFS_DATANODE_RESERVE_FOR_ARCHIVE_DEFAULT_PERCENTAGE_DEFAULT = 0.0;
15591559

1560+
public static final String
1561+
DFS_DATANODE_SAME_DISK_TIERING_CAPACITY_RATIO_PERCENTAGE =
1562+
"dfs.datanode.same-disk-tiering.capacity-ratio.percentage";
1563+
public static final String
1564+
DFS_DATANODE_SAME_DISK_TIERING_CAPACITY_RATIO_PERCENTAGE_DEFAULT = "";
1565+
15601566
// dfs.client.retry confs are moved to HdfsClientConfigKeys.Retry
15611567
@Deprecated
15621568
public static final String DFS_CLIENT_RETRY_POLICY_ENABLED_KEY

hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/DataNode.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020

2121
import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_DATANODE_ADDRESS_DEFAULT;
2222
import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_DATANODE_ADDRESS_KEY;
23+
import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_DATANODE_ALLOW_SAME_DISK_TIERING;
24+
import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_DATANODE_ALLOW_SAME_DISK_TIERING_DEFAULT;
2325
import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_DATANODE_DATA_DIR_KEY;
2426
import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_DATANODE_DIRECTORYSCAN_INTERVAL_DEFAULT;
2527
import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_DATANODE_DIRECTORYSCAN_INTERVAL_KEY;
@@ -739,9 +741,51 @@ ChangedVolumes parseChangedVolumes(String newVolumes) throws IOException {
739741
}
740742
}
741743

744+
validateVolumesWithSameDiskTiering(results);
745+
742746
return results;
743747
}
744748

749+
/**
750+
* Check conflict with same disk tiering feature
751+
* and throws exception.
752+
*
753+
* TODO: We can add feature to
754+
* allow refreshing volume with capacity ratio,
755+
* and solve the case of replacing volume on same mount.
756+
*/
757+
private void validateVolumesWithSameDiskTiering(ChangedVolumes
758+
changedVolumes) throws IOException {
759+
if (dnConf.getConf().getBoolean(DFS_DATANODE_ALLOW_SAME_DISK_TIERING,
760+
DFS_DATANODE_ALLOW_SAME_DISK_TIERING_DEFAULT)
761+
&& data.getMountVolumeMap() != null) {
762+
// Check if mount already exist.
763+
for (StorageLocation location : changedVolumes.newLocations) {
764+
if (StorageType.allowSameDiskTiering(location.getStorageType())) {
765+
File dir = new File(location.getUri());
766+
// Get the first parent dir that exists to check disk mount point.
767+
while (!dir.exists()) {
768+
dir = dir.getParentFile();
769+
if (dir == null) {
770+
throw new IOException("Invalid path: "
771+
+ location + ": directory does not exist");
772+
}
773+
}
774+
DF df = new DF(dir, dnConf.getConf());
775+
String mount = df.getMount();
776+
if (data.getMountVolumeMap().hasMount(mount)) {
777+
String errMsg = "Disk mount " + mount
778+
+ " already has volume, when trying to add "
779+
+ location + ". Please try removing mounts first"
780+
+ " or restart datanode.";
781+
LOG.error(errMsg);
782+
throw new IOException(errMsg);
783+
}
784+
}
785+
}
786+
}
787+
}
788+
745789
/**
746790
* Attempts to reload data volumes with new configuration.
747791
* @param newVolumes a comma separated string that specifies the data volumes.

hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/StorageLocation.java

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
package org.apache.hadoop.hdfs.server.datanode;
2020

21+
import java.util.HashMap;
22+
import java.util.Map;
2123
import java.util.regex.Pattern;
2224

2325
import java.io.File;
@@ -58,7 +60,16 @@ public class StorageLocation
5860
/** Regular expression that describes a storage uri with a storage type.
5961
* e.g. [Disk]/storages/storage1/
6062
*/
61-
private static final Pattern regex = Pattern.compile("^\\[(\\w*)\\](.+)$");
63+
private static final Pattern STORAGE_LOCATION_REGEX =
64+
Pattern.compile("^\\[(\\w*)\\](.+)$");
65+
66+
/** Regular expression for the capacity ratio of a storage volume (uri).
67+
* This is useful when configuring multiple
68+
* storage types on same disk mount (same-disk-tiering).
69+
* e.g. [0.3]/disk1/archive/
70+
*/
71+
private static final Pattern CAPACITY_RATIO_REGEX =
72+
Pattern.compile("^\\[([0-9.]*)\\](.+)$");
6273

6374
private StorageLocation(StorageType storageType, URI uri) {
6475
this.storageType = storageType;
@@ -127,7 +138,7 @@ public boolean matchesStorageDirectory(StorageDirectory sd,
127138
*/
128139
public static StorageLocation parse(String rawLocation)
129140
throws IOException, SecurityException {
130-
Matcher matcher = regex.matcher(rawLocation);
141+
Matcher matcher = STORAGE_LOCATION_REGEX.matcher(rawLocation);
131142
StorageType storageType = StorageType.DEFAULT;
132143
String location = rawLocation;
133144

@@ -144,6 +155,44 @@ public static StorageLocation parse(String rawLocation)
144155
return new StorageLocation(storageType, new Path(location).toUri());
145156
}
146157

158+
/**
159+
* Attempt to parse the storage capacity ratio and related volume directory
160+
* out of the capacity ratio config string.
161+
*
162+
* @param capacityRatioConf Config string of the capacity ratio
163+
* @return Map of URI of the volume and capacity ratio.
164+
* @throws SecurityException when format is incorrect or ratio is not
165+
* between 0 - 1.
166+
*/
167+
public static Map<URI, Double> parseCapacityRatio(String capacityRatioConf)
168+
throws SecurityException {
169+
Map<URI, Double> result = new HashMap<>();
170+
capacityRatioConf = capacityRatioConf.replaceAll("\\s", "");
171+
if (capacityRatioConf.isEmpty()) {
172+
return result;
173+
}
174+
String[] capacityRatios = capacityRatioConf.split(",");
175+
for (String ratio : capacityRatios) {
176+
Matcher matcher = CAPACITY_RATIO_REGEX.matcher(ratio);
177+
if (matcher.matches()) {
178+
String capacityString = matcher.group(1).trim();
179+
String location = matcher.group(2).trim();
180+
double capacityRatio = Double.parseDouble(capacityString);
181+
if (capacityRatio > 1 || capacityRatio < 0) {
182+
throw new IllegalArgumentException("Capacity ratio" + capacityRatio
183+
+ " is not between 0 to 1: " + ratio);
184+
}
185+
result.put(new Path(location).toUri(), capacityRatio);
186+
} else {
187+
throw new IllegalArgumentException(
188+
"Capacity ratio config is not with correct format: "
189+
+ capacityRatioConf
190+
);
191+
}
192+
}
193+
return result;
194+
}
195+
147196
@Override
148197
public String toString() {
149198
return "[" + storageType + "]" + baseURI.normalize();

hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/fsdataset/FsDatasetSpi.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.apache.hadoop.classification.InterfaceAudience;
3636
import org.apache.hadoop.conf.Configuration;
3737
import org.apache.hadoop.fs.StorageType;
38+
import org.apache.hadoop.hdfs.server.datanode.fsdataset.impl.MountVolumeMap;
3839
import org.apache.hadoop.util.AutoCloseableLock;
3940
import org.apache.hadoop.hdfs.DFSConfigKeys;
4041
import org.apache.hadoop.hdfs.protocol.Block;
@@ -680,4 +681,11 @@ ReplicaInfo moveBlockAcrossVolumes(final ExtendedBlock block,
680681
* @throws IOException
681682
*/
682683
Set<? extends Replica> deepCopyReplica(String bpid) throws IOException;
684+
685+
/**
686+
* Get relationship between disk mount and FsVolume.
687+
* @return Disk mount and FsVolume relationship.
688+
* @throws IOException
689+
*/
690+
MountVolumeMap getMountVolumeMap() throws IOException;
683691
}

hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/fsdataset/impl/FsDatasetImpl.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -193,10 +193,6 @@ public FsVolumeImpl getVolume(final ExtendedBlock b) {
193193
}
194194
}
195195

196-
MountVolumeMap getMountVolumeMap() {
197-
return volumes.getMountVolumeMap();
198-
}
199-
200196
@Override // FsDatasetSpi
201197
public Block getStoredBlock(String bpid, long blkid)
202198
throws IOException {
@@ -249,7 +245,7 @@ public LengthInputStream getMetaDataInputStream(ExtendedBlock b)
249245
}
250246
return info.getMetadataInputStream(0);
251247
}
252-
248+
253249
final DataNode datanode;
254250
private final DataNodeMetrics dataNodeMetrics;
255251
final DataStorage dataStorage;
@@ -3524,7 +3520,12 @@ public boolean getPinning(ExtendedBlock block) throws IOException {
35243520
ReplicaInfo r = getBlockReplica(block);
35253521
return r.getPinning(localFS);
35263522
}
3527-
3523+
3524+
@Override
3525+
public MountVolumeMap getMountVolumeMap() {
3526+
return volumes.getMountVolumeMap();
3527+
}
3528+
35283529
@Override
35293530
public boolean isDeletingBlock(String bpid, long blockId) {
35303531
synchronized(deletingBlock) {

hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/fsdataset/impl/FsVolumeList.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package org.apache.hadoop.hdfs.server.datanode.fsdataset.impl;
1919

2020
import java.io.IOException;
21+
import java.net.URI;
2122
import java.nio.channels.ClosedChannelException;
2223
import java.util.ArrayList;
2324
import java.util.Collection;
@@ -65,6 +66,7 @@ class FsVolumeList {
6566

6667
private final boolean enableSameDiskTiering;
6768
private final MountVolumeMap mountVolumeMap;
69+
private Map<URI, Double> capacityRatioMap;
6870

6971
FsVolumeList(List<VolumeFailureInfo> initialVolumeFailureInfos,
7072
BlockScanner blockScanner,
@@ -82,6 +84,7 @@ class FsVolumeList {
8284
DFSConfigKeys.DFS_DATANODE_ALLOW_SAME_DISK_TIERING,
8385
DFSConfigKeys.DFS_DATANODE_ALLOW_SAME_DISK_TIERING_DEFAULT);
8486
mountVolumeMap = new MountVolumeMap(config);
87+
initializeCapacityRatio(config);
8588
}
8689

8790
MountVolumeMap getMountVolumeMap() {
@@ -135,6 +138,20 @@ FsVolumeReference getVolumeByMount(StorageType storageType,
135138
return null;
136139
}
137140

141+
private void initializeCapacityRatio(Configuration config) {
142+
if (capacityRatioMap == null) {
143+
String capacityRatioConfig = config.get(
144+
DFSConfigKeys
145+
.DFS_DATANODE_SAME_DISK_TIERING_CAPACITY_RATIO_PERCENTAGE,
146+
DFSConfigKeys
147+
.DFS_DATANODE_SAME_DISK_TIERING_CAPACITY_RATIO_PERCENTAGE_DEFAULT
148+
);
149+
150+
this.capacityRatioMap = StorageLocation
151+
.parseCapacityRatio(capacityRatioConfig);
152+
}
153+
}
154+
138155
/**
139156
* Get next volume.
140157
*
@@ -325,11 +342,15 @@ public String toString() {
325342
*
326343
* @param ref a reference to the new FsVolumeImpl instance.
327344
*/
328-
void addVolume(FsVolumeReference ref) {
345+
void addVolume(FsVolumeReference ref) throws IOException {
329346
FsVolumeImpl volume = (FsVolumeImpl) ref.getVolume();
330347
volumes.add(volume);
331348
if (isSameDiskTieringApplied(volume)) {
332349
mountVolumeMap.addVolume(volume);
350+
URI uri = volume.getStorageLocation().getUri();
351+
if (capacityRatioMap.containsKey(uri)) {
352+
mountVolumeMap.setCapacityRatio(volume, capacityRatioMap.get(uri));
353+
}
333354
}
334355
if (blockScanner != null) {
335356
blockScanner.addVolumeScanner(ref);

hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/fsdataset/impl/MountVolumeInfo.java

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,24 @@
2424
import org.apache.hadoop.hdfs.server.datanode.fsdataset.FsVolumeReference;
2525

2626
import java.nio.channels.ClosedChannelException;
27-
import java.util.concurrent.ConcurrentHashMap;
28-
import java.util.concurrent.ConcurrentMap;
27+
import java.util.EnumMap;
28+
import java.util.Map;
2929

3030
/**
3131
* MountVolumeInfo is a wrapper of
3232
* detailed volume information for MountVolumeMap.
3333
*/
3434
@InterfaceAudience.Private
3535
class MountVolumeInfo {
36-
private final ConcurrentMap<StorageType, FsVolumeImpl>
36+
private final EnumMap<StorageType, FsVolumeImpl>
3737
storageTypeVolumeMap;
38+
private final EnumMap<StorageType, Double>
39+
capacityRatioMap;
3840
private double reservedForArchiveDefault;
3941

4042
MountVolumeInfo(Configuration conf) {
41-
storageTypeVolumeMap = new ConcurrentHashMap<>();
43+
storageTypeVolumeMap = new EnumMap<>(StorageType.class);
44+
capacityRatioMap = new EnumMap<>(StorageType.class);
4245
reservedForArchiveDefault = conf.getDouble(
4346
DFSConfigKeys.DFS_DATANODE_RESERVE_FOR_ARCHIVE_DEFAULT_PERCENTAGE,
4447
DFSConfigKeys
@@ -71,12 +74,22 @@ FsVolumeReference getVolumeRef(StorageType storageType) {
7174

7275
/**
7376
* Return configured capacity ratio.
74-
* If the volume is the only one on the mount,
75-
* return 1 to avoid unnecessary allocation.
76-
*
77-
* TODO: We should support customized capacity ratio for volumes.
7877
*/
7978
double getCapacityRatio(StorageType storageType) {
79+
// If capacity ratio is set, return the val.
80+
if (capacityRatioMap.containsKey(storageType)) {
81+
return capacityRatioMap.get(storageType);
82+
}
83+
// If capacity ratio is set for counterpart,
84+
// use the rest of capacity of the mount for it.
85+
if (!capacityRatioMap.isEmpty()) {
86+
double leftOver = 1;
87+
for (Map.Entry<StorageType, Double> e : capacityRatioMap.entrySet()) {
88+
leftOver -= e.getValue();
89+
}
90+
return leftOver;
91+
}
92+
// Use reservedForArchiveDefault by default.
8093
if (storageTypeVolumeMap.containsKey(storageType)
8194
&& storageTypeVolumeMap.size() > 1) {
8295
if (storageType == StorageType.ARCHIVE) {
@@ -102,9 +115,28 @@ boolean addVolume(FsVolumeImpl volume) {
102115
return true;
103116
}
104117

105-
106118
void removeVolume(FsVolumeImpl target) {
107119
storageTypeVolumeMap.remove(target.getStorageType());
120+
capacityRatioMap.remove(target.getStorageType());
121+
}
122+
123+
/**
124+
* Set customize capacity ratio for a storage type.
125+
* Return false if the value is too big.
126+
*/
127+
boolean setCapacityRatio(StorageType storageType,
128+
double capacityRatio) {
129+
double leftover = 1;
130+
for (Map.Entry<StorageType, Double> e : capacityRatioMap.entrySet()) {
131+
if (e.getKey() != storageType) {
132+
leftover -= e.getValue();
133+
}
134+
}
135+
if (leftover < capacityRatio) {
136+
return false;
137+
}
138+
capacityRatioMap.put(storageType, capacityRatio);
139+
return true;
108140
}
109141

110142
int size() {

hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/fsdataset/impl/MountVolumeMap.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.apache.hadoop.fs.StorageType;
2323
import org.apache.hadoop.hdfs.server.datanode.fsdataset.FsVolumeReference;
2424

25+
import java.io.IOException;
2526
import java.util.concurrent.ConcurrentHashMap;
2627
import java.util.concurrent.ConcurrentMap;
2728

@@ -34,7 +35,7 @@
3435
* we don't configure multiple volumes with same storage type on one mount.
3536
*/
3637
@InterfaceAudience.Private
37-
class MountVolumeMap {
38+
public class MountVolumeMap {
3839
private final ConcurrentMap<String, MountVolumeInfo>
3940
mountVolumeMapping;
4041
private final Configuration conf;
@@ -89,4 +90,24 @@ void removeVolume(FsVolumeImpl target) {
8990
}
9091
}
9192
}
93+
94+
void setCapacityRatio(FsVolumeImpl target, double capacityRatio)
95+
throws IOException {
96+
String mount = target.getMount();
97+
if (!mount.isEmpty()) {
98+
MountVolumeInfo info = mountVolumeMapping.get(mount);
99+
if (!info.setCapacityRatio(
100+
target.getStorageType(), capacityRatio)) {
101+
throw new IOException(
102+
"Not enough capacity ratio left on mount: "
103+
+ mount + ", for " + target + ": capacity ratio: "
104+
+ capacityRatio + ". Sum of the capacity"
105+
+ " ratio of on same disk mount should be <= 1");
106+
}
107+
}
108+
}
109+
110+
public boolean hasMount(String mount) {
111+
return mountVolumeMapping.containsKey(mount);
112+
}
92113
}

0 commit comments

Comments
 (0)