Skip to content

Commit 283ec1d

Browse files
committed
JAVA-2389: Make minimum value for max staleness either 90 seconds or the heartbeat frequency plus the idle write period, whichever is greatest
1 parent f7a2779 commit 283ec1d

40 files changed

+180
-681
lines changed

driver-core/src/main/com/mongodb/ConnectionString.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,9 @@
158158
* <li>{@code maxStalenessSeconds=seconds}. The maximum staleness in seconds. For use with any non-primary read preference, the driver
159159
* estimates the staleness of each secondary, based on lastWriteDate values provided in server isMaster responses, and selects only those
160160
* secondaries whose staleness is less than or equal to maxStalenessSeconds. Not providing the parameter or explicitly setting it to -1
161-
* indicates that there should be no max staleness check.
161+
* indicates that there should be no max staleness check. The maximum staleness feature is designed to prevent badly-lagging servers from
162+
* being selected. The staleness estimate is imprecise and shouldn't be used to try to select "up-to-date" secondaries. The minimum value
163+
* is either 90 seconds, or the heartbeat frequency plus 10 seconds, whichever is greatest.
162164
* </li>
163165
* </ul>
164166
* <p>Authentication configuration:</p>
@@ -448,7 +450,7 @@ private WriteConcern createWriteConcern(final Map<String, List<String>> optionsM
448450
private ReadPreference createReadPreference(final Map<String, List<String>> optionsMap) {
449451
String readPreferenceType = null;
450452
List<TagSet> tagSetList = new ArrayList<TagSet>();
451-
double maxStalenessSeconds = -1;
453+
long maxStalenessSeconds = -1;
452454

453455
for (final String key : READ_PREFERENCE_KEYS) {
454456
String value = getLastValue(optionsMap, key);
@@ -459,7 +461,7 @@ private ReadPreference createReadPreference(final Map<String, List<String>> opti
459461
if (key.equals("readpreference")) {
460462
readPreferenceType = value;
461463
} else if (key.equals("maxstalenessseconds")) {
462-
maxStalenessSeconds = Double.parseDouble(value);
464+
maxStalenessSeconds = parseInteger(value, "maxstalenessseconds");
463465
} else if (key.equals("readpreferencetags")) {
464466
for (final String cur : optionsMap.get(key)) {
465467
TagSet tagSet = getTags(cur.trim());
@@ -601,15 +603,14 @@ private Map<String, List<String>> parseOptions(final String optionsPart) {
601603
}
602604

603605
private ReadPreference buildReadPreference(final String readPreferenceType,
604-
final List<TagSet> tagSetList, final double maxStalenessSeconds) {
606+
final List<TagSet> tagSetList, final long maxStalenessSeconds) {
605607
if (readPreferenceType != null) {
606608
if (tagSetList.isEmpty() && maxStalenessSeconds == -1) {
607609
return ReadPreference.valueOf(readPreferenceType);
608610
} else if (maxStalenessSeconds == -1) {
609611
return ReadPreference.valueOf(readPreferenceType, tagSetList);
610612
} else {
611-
return ReadPreference.valueOf(readPreferenceType, tagSetList,
612-
Math.round(maxStalenessSeconds * 1000), TimeUnit.MILLISECONDS);
613+
return ReadPreference.valueOf(readPreferenceType, tagSetList, maxStalenessSeconds, TimeUnit.SECONDS);
613614
}
614615
} else if (!(tagSetList.isEmpty() && maxStalenessSeconds == -1)) {
615616
throw new IllegalArgumentException("Read preference mode must be specified if "

driver-core/src/main/com/mongodb/ReadPreference.java

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -138,10 +138,12 @@ public static ReadPreference nearest() {
138138
/**
139139
* Gets a read preference that forces reads to the primary if available, otherwise to a secondary.
140140
*
141-
* @param maxStaleness the max allowable staleness of secondaries.
141+
* @param maxStaleness the max allowable staleness of secondaries. The minimum value is either 90 seconds, or the heartbeat frequency
142+
* plus 10 seconds, whichever is greatest.
142143
* @param timeUnit the time unit of maxStaleness
143144
* @return ReadPreference which reads primary if available.
144145
* @since 3.4
146+
* @see TaggableReadPreference#getMaxStaleness(TimeUnit)
145147
*/
146148
public static ReadPreference primaryPreferred(final long maxStaleness, final TimeUnit timeUnit) {
147149
return new PrimaryPreferredReadPreference(Collections.<TagSet>emptyList(), maxStaleness, timeUnit);
@@ -155,10 +157,12 @@ public static ReadPreference primaryPreferred(final long maxStaleness, final Tim
155157
* and selects only those secondaries whose staleness is less than or equal to maxStaleness.
156158
* </p>
157159
*
158-
* @param maxStaleness the max allowable staleness of secondaries.
160+
* @param maxStaleness the max allowable staleness of secondaries. The minimum value is either 90 seconds, or the heartbeat frequency
161+
* plus 10 seconds, whichever is greatest.
159162
* @param timeUnit the time unit of maxStaleness
160163
* @return ReadPreference which reads secondary.
161164
* @since 3.4
165+
* @see TaggableReadPreference#getMaxStaleness(TimeUnit)
162166
*/
163167
public static ReadPreference secondary(final long maxStaleness, final TimeUnit timeUnit) {
164168
return new SecondaryReadPreference(Collections.<TagSet>emptyList(), maxStaleness, timeUnit);
@@ -172,10 +176,12 @@ public static ReadPreference secondary(final long maxStaleness, final TimeUnit t
172176
* The driver estimates the staleness of each secondary, based on lastWriteDate values provided in server isMaster responses,
173177
* and selects only those secondaries whose staleness is less than or equal to maxStaleness.
174178
* </p> *
175-
* @param maxStaleness the max allowable staleness of secondaries.
179+
* @param maxStaleness the max allowable staleness of secondaries. The minimum value is either 90 seconds, or the heartbeat frequency
180+
* plus 10 seconds, whichever is greatest.
176181
* @param timeUnit the time unit of maxStaleness
177182
* @return ReadPreference which reads secondary if available, otherwise from primary.
178183
* @since 3.4
184+
* @see TaggableReadPreference#getMaxStaleness(TimeUnit)
179185
*/
180186
public static ReadPreference secondaryPreferred(final long maxStaleness, final TimeUnit timeUnit) {
181187
return new SecondaryPreferredReadPreference(Collections.<TagSet>emptyList(), maxStaleness, timeUnit);
@@ -189,10 +195,12 @@ public static ReadPreference secondaryPreferred(final long maxStaleness, final T
189195
* and selects only those secondaries whose staleness is less than or equal to maxStaleness.
190196
* </p>
191197
*
192-
* @param maxStaleness the max allowable staleness of secondaries.
198+
* @param maxStaleness the max allowable staleness of secondaries. The minimum value is either 90 seconds, or the heartbeat frequency
199+
* plus 10 seconds, whichever is greatest.
193200
* @param timeUnit the time unit of maxStaleness
194201
* @return ReadPreference which reads nearest
195202
* @since 3.4
203+
* @see TaggableReadPreference#getMaxStaleness(TimeUnit)
196204
*/
197205
public static ReadPreference nearest(final long maxStaleness, final TimeUnit timeUnit) {
198206
return new NearestReadPreference(Collections.<TagSet>emptyList(), maxStaleness, timeUnit);
@@ -252,10 +260,12 @@ public static TaggableReadPreference nearest(final TagSet tagSet) {
252260
* </p>
253261
*
254262
* @param tagSet the set of tags to limit the list of secondaries to.
255-
* @param maxStaleness the max allowable staleness of secondaries.
263+
* @param maxStaleness the max allowable staleness of secondaries. The minimum value is either 90 seconds, or the heartbeat frequency
264+
* plus 10 seconds, whichever is greatest.
256265
* @param timeUnit the time unit of maxStaleness
257266
* @return ReadPreference which reads primary if available, otherwise a secondary respective of tags.\
258267
* @since 3.4
268+
* @see TaggableReadPreference#getMaxStaleness(TimeUnit)
259269
*/
260270
public static TaggableReadPreference primaryPreferred(final TagSet tagSet,
261271
final long maxStaleness, final TimeUnit timeUnit) {
@@ -271,10 +281,12 @@ public static TaggableReadPreference primaryPreferred(final TagSet tagSet,
271281
* </p>
272282
*
273283
* @param tagSet the set of tags to limit the list of secondaries to
274-
* @param maxStaleness the max allowable staleness of secondaries.
284+
* @param maxStaleness the max allowable staleness of secondaries. The minimum value is either 90 seconds, or the heartbeat frequency
285+
* plus 10 seconds, whichever is greatest.
275286
* @param timeUnit the time unit of maxStaleness
276287
* @return ReadPreference which reads secondary respective of tags.
277288
* @since 3.4
289+
* @see TaggableReadPreference#getMaxStaleness(TimeUnit)
278290
*/
279291
public static TaggableReadPreference secondary(final TagSet tagSet,
280292
final long maxStaleness, final TimeUnit timeUnit) {
@@ -290,10 +302,12 @@ public static TaggableReadPreference secondary(final TagSet tagSet,
290302
* and selects only those secondaries whose staleness is less than or equal to maxStaleness.
291303
* </p> *
292304
* @param tagSet the set of tags to limit the list of secondaries to
293-
* @param maxStaleness the max allowable staleness of secondaries.
305+
* @param maxStaleness the max allowable staleness of secondaries. The minimum value is either 90 seconds, or the heartbeat frequency
306+
* plus 10 seconds, whichever is greatest.
294307
* @param timeUnit the time unit of maxStaleness
295308
* @return ReadPreference which reads secondary if available respective of tags, otherwise from primary irrespective of tags.
296309
* @since 3.4
310+
* @see TaggableReadPreference#getMaxStaleness(TimeUnit)
297311
*/
298312
public static TaggableReadPreference secondaryPreferred(final TagSet tagSet,
299313
final long maxStaleness, final TimeUnit timeUnit) {
@@ -310,10 +324,12 @@ public static TaggableReadPreference secondaryPreferred(final TagSet tagSet,
310324
* </p>
311325
*
312326
* @param tagSet the set of tags to limit the list of secondaries to
313-
* @param maxStaleness the max allowable staleness of secondaries.
327+
* @param maxStaleness the max allowable staleness of secondaries. The minimum value is either 90 seconds, or the heartbeat frequency
328+
* plus 10 seconds, whichever is greatest.
314329
* @param timeUnit the time unit of maxStaleness
315330
* @return ReadPreference which reads nearest node respective of tags.
316331
* @since 3.4
332+
* @see TaggableReadPreference#getMaxStaleness(TimeUnit)
317333
*/
318334
public static TaggableReadPreference nearest(final TagSet tagSet,
319335
final long maxStaleness, final TimeUnit timeUnit) {
@@ -388,10 +404,12 @@ public static TaggableReadPreference nearest(final List<TagSet> tagSetList) {
388404
* </p>
389405
*
390406
* @param tagSetList the list of tag sets to limit the list of secondaries to
391-
* @param maxStaleness the max allowable staleness of secondaries.
407+
* @param maxStaleness the max allowable staleness of secondaries. The minimum value is either 90 seconds, or the heartbeat frequency
408+
* plus 10 seconds, whichever is greatest.
392409
* @param timeUnit the time unit of maxStaleness
393410
* @return ReadPreference which reads primary if available, otherwise a secondary respective of tags.
394411
* @since 3.4
412+
* @see TaggableReadPreference#getMaxStaleness(TimeUnit)
395413
*/
396414
public static TaggableReadPreference primaryPreferred(final List<TagSet> tagSetList,
397415
final long maxStaleness, final TimeUnit timeUnit) {
@@ -413,10 +431,12 @@ public static TaggableReadPreference primaryPreferred(final List<TagSet> tagSetL
413431
* </p>
414432
*
415433
* @param tagSetList the list of tag sets to limit the list of secondaries to
416-
* @param maxStaleness the max allowable staleness of secondaries.
434+
* @param maxStaleness the max allowable staleness of secondaries. The minimum value is either 90 seconds, or the heartbeat frequency
435+
* plus 10 seconds, whichever is greatest.
417436
* @param timeUnit the time unit of maxStaleness
418437
* @return ReadPreference which reads secondary respective of tags.
419438
* @since 3.4
439+
* @see TaggableReadPreference#getMaxStaleness(TimeUnit)
420440
*/
421441
public static TaggableReadPreference secondary(final List<TagSet> tagSetList,
422442
final long maxStaleness, final TimeUnit timeUnit) {
@@ -438,10 +458,12 @@ public static TaggableReadPreference secondary(final List<TagSet> tagSetList,
438458
* </p>
439459
*
440460
* @param tagSetList the list of tag sets to limit the list of secondaries to
441-
* @param maxStaleness the max allowable staleness of secondaries.
461+
* @param maxStaleness the max allowable staleness of secondaries. The minimum value is either 90 seconds, or the heartbeat frequency
462+
* plus 10 seconds, whichever is greatest.
442463
* @param timeUnit the time unit of maxStaleness
443464
* @return ReadPreference which reads secondary if available respective of tags, otherwise from primary irrespective of tags.
444465
* @since 3.4
466+
* @see TaggableReadPreference#getMaxStaleness(TimeUnit)
445467
*/
446468
public static TaggableReadPreference secondaryPreferred(final List<TagSet> tagSetList,
447469
final long maxStaleness, final TimeUnit timeUnit) {
@@ -463,10 +485,12 @@ public static TaggableReadPreference secondaryPreferred(final List<TagSet> tagSe
463485
* </p>
464486
*
465487
* @param tagSetList the list of tag sets to limit the list of secondaries to
466-
* @param maxStaleness the max allowable staleness of secondaries.
488+
* @param maxStaleness the max allowable staleness of secondaries. The minimum value is either 90 seconds, or the heartbeat frequency
489+
* plus 10 seconds, whichever is greatest.
467490
* @param timeUnit the time unit of maxStaleness
468491
* @return ReadPreference which reads nearest node respective of tags.
469492
* @since 3.4
493+
* @see TaggableReadPreference#getMaxStaleness(TimeUnit)
470494
*/
471495
public static TaggableReadPreference nearest(final List<TagSet> tagSetList,
472496
final long maxStaleness, final TimeUnit timeUnit) {
@@ -527,10 +551,12 @@ public static TaggableReadPreference valueOf(final String name, final List<TagSe
527551
*
528552
* @param name the name of the read preference
529553
* @param tagSetList the list of tag sets
530-
* @param maxStaleness the max allowable staleness of secondaries.
554+
* @param maxStaleness the max allowable staleness of secondaries. The minimum value is either 90 seconds, or the heartbeat frequency
555+
* plus 10 seconds, whichever is greatest.
531556
* @param timeUnit the time unit of maxStaleness
532557
* @return the taggable read preference
533558
* @since 3.4
559+
* @see TaggableReadPreference#getMaxStaleness(TimeUnit)
534560
*/
535561
public static TaggableReadPreference valueOf(final String name, final List<TagSet> tagSetList, final long maxStaleness,
536562
final TimeUnit timeUnit) {

driver-core/src/main/com/mongodb/TaggableReadPreference.java

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
import com.mongodb.connection.ServerType;
2424
import org.bson.BsonArray;
2525
import org.bson.BsonDocument;
26-
import org.bson.BsonDouble;
26+
import org.bson.BsonInt64;
2727
import org.bson.BsonString;
2828

2929
import java.util.ArrayList;
@@ -36,12 +36,16 @@
3636
import static java.lang.String.format;
3737
import static java.util.concurrent.TimeUnit.MILLISECONDS;
3838
import static java.util.concurrent.TimeUnit.NANOSECONDS;
39+
import static java.util.concurrent.TimeUnit.SECONDS;
3940

4041
/**
4142
* Abstract class for all preference which can be combined with tags
4243
*/
4344
@Immutable
4445
public abstract class TaggableReadPreference extends ReadPreference {
46+
private static final int SMALLEST_MAX_STALENESS_MS = 90000;
47+
private static final int IDLE_WRITE_PERIOD_MS = 10000;
48+
4549
private final List<TagSet> tagSetList = new ArrayList<TagSet>();
4650
private final Long maxStalenessMS;
4751

@@ -73,7 +77,7 @@ public BsonDocument toDocument() {
7377
}
7478

7579
if (maxStalenessMS != null) {
76-
readPrefObject.put("maxStalenessSeconds", new BsonDouble(maxStalenessMS / 1000.0));
80+
readPrefObject.put("maxStalenessSeconds", new BsonInt64(MILLISECONDS.toSeconds(maxStalenessMS)));
7781
}
7882
return readPrefObject;
7983
}
@@ -90,12 +94,18 @@ public List<TagSet> getTagSetList() {
9094

9195
/**
9296
* Gets the maximum acceptable staleness of a secondary in order to be considered for read operations.
93-
*
97+
* <p>
98+
* The maximum staleness feature is designed to prevent badly-lagging servers from being selected. The staleness estimate is imprecise
99+
* and shouldn't be used to try to select "up-to-date" secondaries.
100+
* </p>
101+
* <p>
102+
* The driver estimates the staleness of each secondary, based on lastWriteDate values provided in server isMaster responses,
103+
* and selects only those secondaries whose staleness is less than or equal to maxStaleness.
104+
* </p>
94105
* @param timeUnit the time unit in which to return the value
95106
* @return the maximum acceptable staleness in the given time unit, or null if the value is not set
96-
*
97-
* @since 3.4
98107
* @mongodb.server.release 3.4
108+
* @since 3.4
99109
*/
100110
public Long getMaxStaleness(final TimeUnit timeUnit) {
101111
notNull("timeUnit", timeUnit);
@@ -176,16 +186,22 @@ protected List<ServerDescription> selectFreshServers(final ClusterDescription cl
176186
return servers;
177187
}
178188

189+
if (servers.isEmpty()) {
190+
return servers;
191+
}
192+
179193
long heartbeatFrequencyMS = clusterDescription.getServerSettings().getHeartbeatFrequency(MILLISECONDS);
180194

181-
ServerDescription mostUpToDateServerDescription = getMostUpToDateServerDescription(clusterDescription);
182-
if (mostUpToDateServerDescription != null
183-
&& getMaxStaleness(MILLISECONDS) < heartbeatFrequencyMS + mostUpToDateServerDescription.getIdleWritePeriodMillis()) {
184-
throw new MongoConfigurationException(format("Max staleness (%d ms) must be at least the heartbeat period (%d ms) "
185-
+ "plus the idle write period (%d ms)",
186-
getMaxStaleness(MILLISECONDS), heartbeatFrequencyMS, mostUpToDateServerDescription.getIdleWritePeriodMillis()));
195+
if (getMaxStaleness(MILLISECONDS) < Math.max(SMALLEST_MAX_STALENESS_MS, heartbeatFrequencyMS + IDLE_WRITE_PERIOD_MS)) {
196+
if (SMALLEST_MAX_STALENESS_MS > heartbeatFrequencyMS + IDLE_WRITE_PERIOD_MS){
197+
throw new MongoConfigurationException(format("Max staleness (%d sec) must be at least 90 seconds",
198+
getMaxStaleness(SECONDS)));
199+
} else {
200+
throw new MongoConfigurationException(format("Max staleness (%d ms) must be at least the heartbeat period (%d ms) "
201+
+ "plus the idle write period (%d ms)",
202+
getMaxStaleness(MILLISECONDS), heartbeatFrequencyMS, IDLE_WRITE_PERIOD_MS));
203+
}
187204
}
188-
189205
List<ServerDescription> freshServers = new ArrayList<ServerDescription>(servers.size());
190206

191207
ServerDescription primary = findPrimary(clusterDescription);
@@ -200,7 +216,7 @@ && getMaxStaleness(MILLISECONDS) < heartbeatFrequencyMS + mostUpToDateServerDesc
200216
}
201217
}
202218
}
203-
} else {
219+
} else {
204220
ServerDescription mostUpdateToDateSecondary = findMostUpToDateSecondary(clusterDescription);
205221
for (ServerDescription cur : servers) {
206222
if (mostUpdateToDateSecondary.getLastWriteDate().getTime() - cur.getLastWriteDate().getTime() + heartbeatFrequencyMS
@@ -363,7 +379,7 @@ public List<ServerDescription> chooseForReplicaSet(final ClusterDescription clus
363379
}
364380
}
365381
return selectedServers;
366-
}
382+
}
367383
}
368384

369385
/**

0 commit comments

Comments
 (0)