Skip to content

Commit d13a28c

Browse files
committed
Fixed review comments and move to range based sharding
Signed-off-by: Atri Sharma <atri.jiit@gmail.com>
1 parent 8095046 commit d13a28c

File tree

3 files changed

+37
-25
lines changed

3 files changed

+37
-25
lines changed

server/src/main/java/org/opensearch/cluster/metadata/IndexMetadata.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,14 @@ static Setting<Integer> buildNumberOfShardsSetting() {
268268
static final String DEFAULT_NUMBER_OF_SHARDS = "opensearch.index.default_number_of_shards";
269269
static final String MAX_NUMBER_OF_SHARDS = "opensearch.index.max_number_of_shards";
270270
public static final Setting<Integer> INDEX_NUMBER_OF_SHARDS_SETTING = buildNumberOfShardsSetting();
271+
/**
272+
* Settings for configuring the number of virtual shards on an index.
273+
* <p>
274+
* Note: Virtual Shards uses range-based mapping (e.g., {@code vShardId / (V / P)}) to route to
275+
* physical shards instead of default modulo hashing. This ensures contiguous bucket mapping
276+
* and safe physical shard merging operations later in the lifecycle. The number of virtual shards
277+
* must be a multiple of the number of physical shards {@code (V % P == 0)}.
278+
*/
271279
public static final Setting<Integer> INDEX_NUMBER_OF_VIRTUAL_SHARDS_SETTING = Setting.intSetting(
272280
SETTING_NUMBER_OF_VIRTUAL_SHARDS,
273281
-1,

server/src/main/java/org/opensearch/cluster/metadata/VirtualShardRoutingHelper.java

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,14 @@
1010

1111
import org.apache.logging.log4j.LogManager;
1212
import org.apache.logging.log4j.Logger;
13+
import org.opensearch.common.annotation.PublicApi;
1314

1415
import java.util.Map;
1516

1617
/**
1718
* Resolves virtual shard routing to physical shard IDs.
1819
*/
19-
20+
@PublicApi(since = "3.6.0")
2021
public final class VirtualShardRoutingHelper {
2122

2223
private VirtualShardRoutingHelper() {}
@@ -29,20 +30,27 @@ private VirtualShardRoutingHelper() {}
2930
public static final String VIRTUAL_SHARDS_CUSTOM_METADATA_KEY = "virtual_shards_routing";
3031

3132
/**
32-
* Resolves the Physical Shard ID for a given Virtual Shard ID.
33-
*
34-
* @param indexMetadata The index metadata.
35-
* @param vShardId The virtual shard ID.
36-
* @return The physical shard ID.
33+
* Resolves the physical shard for a virtual shard id.
3734
*/
3835
public static int resolvePhysicalShardId(IndexMetadata indexMetadata, int vShardId) {
36+
int numVirtualShards = indexMetadata.getNumberOfVirtualShards();
37+
int numPhysicalShards = indexMetadata.getNumberOfShards();
38+
39+
if (numVirtualShards < numPhysicalShards || numVirtualShards % numPhysicalShards != 0) {
40+
throw new IllegalArgumentException(
41+
"Virtual shards must be enabled and be a multiple of the number of physical shards to resolve routing."
42+
);
43+
}
44+
45+
vShardId = Math.floorMod(vShardId, numVirtualShards);
46+
3947
Map<String, String> overrides = indexMetadata.getCustomData(VIRTUAL_SHARDS_CUSTOM_METADATA_KEY);
4048
if (overrides != null) {
4149
String pShardIdStr = overrides.get(String.valueOf(vShardId));
4250
if (pShardIdStr != null) {
4351
try {
4452
int pShardId = Integer.parseInt(pShardIdStr);
45-
if (pShardId >= 0 && pShardId < indexMetadata.getNumberOfShards()) {
53+
if (pShardId >= 0 && pShardId < numPhysicalShards) {
4654
return pShardId;
4755
}
4856
logger.trace("Invalid override value [{}] for vShard [{}]: out of bounds", pShardId, vShardId);
@@ -52,15 +60,6 @@ public static int resolvePhysicalShardId(IndexMetadata indexMetadata, int vShard
5260
}
5361
}
5462

55-
int numVirtualShards = indexMetadata.getNumberOfVirtualShards();
56-
int numPhysicalShards = indexMetadata.getNumberOfShards();
57-
58-
if (numVirtualShards < numPhysicalShards || numVirtualShards % numPhysicalShards != 0) {
59-
throw new IllegalArgumentException(
60-
"Virtual shards must be enabled and be a multiple of the number of physical shards to resolve routing."
61-
);
62-
}
63-
6463
int virtualShardsPerPhysical = numVirtualShards / numPhysicalShards;
6564
return vShardId / virtualShardsPerPhysical;
6665
}

server/src/test/java/org/opensearch/cluster/metadata/VirtualShardRoutingHelperTests.java

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ public void testResolvePhysicalShardIdDefaultRangeBased() {
3939
public void testResolvePhysicalShardIdWithOverrides() {
4040
int numPhysicalShards = 5;
4141
Map<String, String> overrides = new HashMap<>();
42-
overrides.put("7", "1"); // mapped out of standard routing
43-
overrides.put("8", "2"); // mapped out of standard routing
42+
overrides.put("7", "1");
43+
overrides.put("8", "2");
4444

4545
IndexMetadata.Builder builder = IndexMetadata.builder("test")
4646
.settings(
@@ -57,7 +57,6 @@ public void testResolvePhysicalShardIdWithOverrides() {
5757
assertEquals(1, VirtualShardRoutingHelper.resolvePhysicalShardId(metadata, 7));
5858
assertEquals(2, VirtualShardRoutingHelper.resolvePhysicalShardId(metadata, 8));
5959

60-
// Default falls back to range-based formula (20 / 5 = 4 vshards per pshard)
6160
assertEquals(0, VirtualShardRoutingHelper.resolvePhysicalShardId(metadata, 0));
6261
assertEquals(2, VirtualShardRoutingHelper.resolvePhysicalShardId(metadata, 9));
6362
}
@@ -81,19 +80,14 @@ public void testInvalidOverridesFallBackToRangeBased() {
8180

8281
IndexMetadata metadata = builder.build();
8382

84-
// Standard range-based routing expects 4 vshards per physical shard
85-
// vShard 7 -> 7 / 4 = 1
8683
assertEquals(1, VirtualShardRoutingHelper.resolvePhysicalShardId(metadata, 7));
87-
// vShard 8 -> 8 / 4 = 2
8884
assertEquals(2, VirtualShardRoutingHelper.resolvePhysicalShardId(metadata, 8));
89-
// vShard 19 -> 19 / 4 = 4
9085
assertEquals(4, VirtualShardRoutingHelper.resolvePhysicalShardId(metadata, 19));
9186
}
9287

9388
public void testResolvePhysicalShardIdInvalidConfigurations() {
9489
int numPhysicalShards = 5;
9590

96-
// Disabled virtual shards
9791
IndexMetadata metadataDisabled = org.mockito.Mockito.mock(IndexMetadata.class);
9892
org.mockito.Mockito.when(metadataDisabled.getNumberOfVirtualShards()).thenReturn(-1);
9993
org.mockito.Mockito.when(metadataDisabled.getNumberOfShards()).thenReturn(numPhysicalShards);
@@ -104,7 +98,6 @@ public void testResolvePhysicalShardIdInvalidConfigurations() {
10498
);
10599
assertTrue(e1.getMessage().contains("must be enabled and be a multiple"));
106100

107-
// Invalid multiple
108101
IndexMetadata metadataInvalid = org.mockito.Mockito.mock(IndexMetadata.class);
109102
org.mockito.Mockito.when(metadataInvalid.getNumberOfVirtualShards()).thenReturn(13);
110103
org.mockito.Mockito.when(metadataInvalid.getNumberOfShards()).thenReturn(numPhysicalShards);
@@ -115,4 +108,16 @@ public void testResolvePhysicalShardIdInvalidConfigurations() {
115108
);
116109
assertTrue(e2.getMessage().contains("must be enabled and be a multiple"));
117110
}
111+
112+
public void testResolvePhysicalShardIdOutOfBoundsNormalization() {
113+
int numPhysicalShards = 5;
114+
IndexMetadata metadata = org.mockito.Mockito.mock(IndexMetadata.class);
115+
org.mockito.Mockito.when(metadata.getNumberOfVirtualShards()).thenReturn(20);
116+
org.mockito.Mockito.when(metadata.getNumberOfShards()).thenReturn(numPhysicalShards);
117+
118+
assertEquals(4, VirtualShardRoutingHelper.resolvePhysicalShardId(metadata, -1));
119+
120+
assertEquals(0, VirtualShardRoutingHelper.resolvePhysicalShardId(metadata, 20));
121+
assertEquals(1, VirtualShardRoutingHelper.resolvePhysicalShardId(metadata, 25));
122+
}
118123
}

0 commit comments

Comments
 (0)