Skip to content

Commit 2ce469c

Browse files
authored
SOLR-15472: New shards.preference option for preferring replicas based on their leader status (apache#2515)
1 parent e09979c commit 2ce469c

File tree

6 files changed

+93
-5
lines changed

6 files changed

+93
-5
lines changed

solr/CHANGES.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ Consult the LUCENE_CHANGES.txt file for additional, low level, changes in this r
1010

1111
New Features
1212
---------------------
13-
(No changes)
13+
* SOLR-15472: New shards.preference option for preferring replicas based on their leader status, i.e.
14+
`shards.preference=replica.leader:false` would prefer non-leader replicas. (wei wang via Timothy Potter)
1415

1516
Improvements
1617
---------------------

solr/solr-ref-guide/src/distributed-requests.adoc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,11 @@ Applied after sorting by inherent replica attributes, this property defines a fa
192192
`node.sysprop`::
193193
Query will be routed to nodes with same defined system properties as the current one. For example, if you start Solr nodes on different racks, you'll want to identify those nodes by a <<configuring-solrconfig-xml.adoc#jvm-system-properties,system property>> (e.g., `-Drack=rack1`). Then, queries can contain `shards.preference=node.sysprop:sysprop.rack`, to make sure you always hit shards with the same value of `rack`.
194194

195+
`replica.leader`::
196+
Prefer replicas based on their leader status, set to either `true` or `false`. Consider a shard with two `TLOG` replicas and four `PULL` replicas (six replicas in total, one of which is the leader).
197+
With `shards.preference=replica.leader:false`, 5 out of 6 replicas will be preferred. Contrast this with `shards.preference=replica.type:PULL` where only 4 of 6 replicas will be preferred.
198+
Note that the non-leader `TLOG` replica behaves like a `PULL` replica from a search perspective; it pulls index updates from the leader just like a `PULL` replica and does not perform soft-commits.
199+
The difference is that the non-leader `TLOG` replica also captures updates in its TLOG, so that it is a candidate to replace the current leader if it is lost.
195200

196201
Examples:
197202

@@ -216,4 +221,7 @@ Examples:
216221
* Prefer local replicas, and among them PULL replicas when available TLOG otherwise:
217222
`shards.preference=replica.location:local,replica.type:PULL,replica.type:TLOG`
218223

224+
* Prefer any replica that is not a leader:
225+
`shards.preference=replica.leader:false`
226+
219227
Note that if you provide the settings in a query string, they need to be properly URL-encoded.

solr/solrj/src/java/org/apache/solr/client/solrj/routing/NodePreferenceRulesComparator.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,10 @@ public int compare(Object left, Object right) {
112112
lhs = hasCoreUrlPrefix(left, preferenceRule.value);
113113
rhs = hasCoreUrlPrefix(right, preferenceRule.value);
114114
break;
115+
case ShardParams.SHARDS_PREFERENCE_REPLICA_LEADER:
116+
lhs = hasLeaderStatus(left, preferenceRule.value);
117+
rhs = hasLeaderStatus(right, preferenceRule.value);
118+
break;
115119
case ShardParams.SHARDS_PREFERENCE_NODE_WITH_SAME_SYSPROP:
116120
if (sysPropsCache == null) {
117121
throw new IllegalArgumentException("Unable to get the NodesSysPropsCacher on sorting replicas by preference:"+ preferenceRule.value);
@@ -161,6 +165,7 @@ else if (o instanceof Replica) {
161165
return s.startsWith(prefix);
162166
}
163167
}
168+
164169
private static boolean hasReplicaType(Object o, String preferred) {
165170
if (!(o instanceof Replica)) {
166171
return false;
@@ -169,6 +174,14 @@ private static boolean hasReplicaType(Object o, String preferred) {
169174
return s.equalsIgnoreCase(preferred);
170175
}
171176

177+
private static boolean hasLeaderStatus(Object o, String status) {
178+
if (!(o instanceof Replica)) {
179+
return false;
180+
}
181+
final boolean leaderStatus = ((Replica) o).isLeader();
182+
return leaderStatus == Boolean.parseBoolean(status);
183+
}
184+
172185
public List<PreferenceRule> getSortRules() {
173186
return sortRules;
174187
}

solr/solrj/src/java/org/apache/solr/common/params/ShardParams.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ public interface ShardParams {
6666
/** Replica location sort rule */
6767
String SHARDS_PREFERENCE_REPLICA_LOCATION = "replica.location";
6868

69+
/** Replica leader status sort rule, value= true/false */
70+
String SHARDS_PREFERENCE_REPLICA_LEADER = "replica.leader";
71+
6972
/** Node with same system property sort rule */
7073
String SHARDS_PREFERENCE_NODE_WITH_SAME_SYSPROP = "node.sysprop";
7174

solr/solrj/src/test/org/apache/solr/client/solrj/routing/NodePreferenceRulesComparatorTest.java

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121
import java.util.List;
2222

2323
import org.apache.solr.SolrTestCaseJ4;
24-
import org.apache.solr.common.cloud.UrlScheme;
2524
import org.apache.solr.common.cloud.Replica;
25+
import org.apache.solr.common.cloud.UrlScheme;
2626
import org.apache.solr.common.cloud.ZkStateReader;
2727
import org.apache.solr.common.params.ShardParams;
2828
import org.junit.Test;
@@ -93,6 +93,67 @@ public void replicaTypeAndReplicaLocationTest() {
9393
assertEquals("node3", getHost(replicas.get(3).getNodeName()));
9494
}
9595

96+
@Test
97+
public void replicaLeaderTest() {
98+
List<Replica> replicas = getBasicReplicaList();
99+
// Add a non-leader NRT replica so that sorting by replica.type:NRT will cause a tie, order is decided by leader status
100+
replicas.add(
101+
new Replica(
102+
"node4",
103+
map(ZkStateReader.BASE_URL_PROP, "http://host2:8983/solr",
104+
ZkStateReader.NODE_NAME_PROP, "node4:8983_solr",
105+
ZkStateReader.CORE_NAME_PROP, "collection1",
106+
ZkStateReader.REPLICA_TYPE, "NRT"
107+
), "collection1", "shard1"
108+
)
109+
);
110+
// Prefer non-leader only, therefore node1 has lowest priority
111+
List<PreferenceRule> rules = PreferenceRule.from(ShardParams.SHARDS_PREFERENCE_REPLICA_LEADER + ":false");
112+
NodePreferenceRulesComparator comparator = new NodePreferenceRulesComparator(rules, null);
113+
replicas.sort(comparator);
114+
assertEquals("node1:8983_solr", replicas.get(3).getNodeName());
115+
116+
// Prefer NRT replica, followed by non-leader
117+
rules = PreferenceRule.from(
118+
ShardParams.SHARDS_PREFERENCE_REPLICA_TYPE + ":NRT," +
119+
ShardParams.SHARDS_PREFERENCE_REPLICA_LEADER + ":false");
120+
comparator = new NodePreferenceRulesComparator(rules, null);
121+
replicas.sort(comparator);
122+
assertEquals("node4", getHost(replicas.get(0).getNodeName()));
123+
assertEquals("node1", getHost(replicas.get(1).getNodeName()));
124+
125+
// Prefer non-leader first, followed by PULL replica and location host2
126+
rules = PreferenceRule.from(
127+
ShardParams.SHARDS_PREFERENCE_REPLICA_LEADER + ":false," +
128+
ShardParams.SHARDS_PREFERENCE_REPLICA_TYPE + ":PULL," +
129+
ShardParams.SHARDS_PREFERENCE_REPLICA_LOCATION + ":http://host2");
130+
comparator = new NodePreferenceRulesComparator(rules, null);
131+
132+
replicas.sort(comparator);
133+
assertEquals("node3", getHost(replicas.get(0).getNodeName()));
134+
assertEquals("node4", getHost(replicas.get(1).getNodeName()));
135+
assertEquals("node2", getHost(replicas.get(2).getNodeName()));
136+
assertEquals("node1", getHost(replicas.get(3).getNodeName()));
137+
138+
// make sure a single leader only still gets selected
139+
List<Replica> onlyLeader = new ArrayList<>();
140+
onlyLeader.add(
141+
new Replica(
142+
"node1",
143+
map(
144+
ZkStateReader.NODE_NAME_PROP, "node1:8983_solr",
145+
ZkStateReader.CORE_NAME_PROP, "collection1",
146+
ZkStateReader.REPLICA_TYPE, "NRT",
147+
ZkStateReader.LEADER_PROP, "true"
148+
),"collection1","shard1"
149+
)
150+
);
151+
rules = PreferenceRule.from(ShardParams.SHARDS_PREFERENCE_REPLICA_LEADER + ":false");
152+
comparator = new NodePreferenceRulesComparator(rules, null);
153+
replicas.sort(comparator);
154+
assertEquals("node1:8983_solr", onlyLeader.get(0).getNodeName());
155+
}
156+
96157
@Test(expected = IllegalArgumentException.class)
97158
public void badRuleTest() {
98159
try {
@@ -126,7 +187,8 @@ private static List<Replica> getBasicReplicaList() {
126187
map(
127188
ZkStateReader.NODE_NAME_PROP, "node1:8983_solr",
128189
ZkStateReader.CORE_NAME_PROP, "collection1",
129-
ZkStateReader.REPLICA_TYPE, "NRT"
190+
ZkStateReader.REPLICA_TYPE, "NRT",
191+
ZkStateReader.LEADER_PROP, "true"
130192
),"collection1","shard1"
131193
)
132194
);

solr/solrj/src/test/org/apache/solr/client/solrj/routing/RequestReplicaListTransformerGeneratorTest.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,9 @@ public void replicaTypeAndReplicaBase() {
104104
)
105105
);
106106

107-
// replicaType and replicaBase combined rule param
108-
String rulesParam = ShardParams.SHARDS_PREFERENCE_REPLICA_TYPE + ":NRT," +
107+
// replica leader status, replicaType and replicaBase combined rule param
108+
String rulesParam = ShardParams.SHARDS_PREFERENCE_REPLICA_LEADER + ":true," +
109+
ShardParams.SHARDS_PREFERENCE_REPLICA_TYPE + ":NRT," +
109110
ShardParams.SHARDS_PREFERENCE_REPLICA_TYPE + ":TLOG," +
110111
ShardParams.SHARDS_PREFERENCE_REPLICA_BASE + ":stable:dividend:routingPreference";
111112

0 commit comments

Comments
 (0)