Skip to content

Commit ab94a74

Browse files
committed
dynamic clusterprops control over unloading ttl
1 parent 406fa9b commit ab94a74

File tree

2 files changed

+72
-10
lines changed

2 files changed

+72
-10
lines changed

lucene

solr/core/src/java/org/apache/solr/core/UnloadHelper.java

Lines changed: 71 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import com.codahale.metrics.Snapshot;
77
import java.io.IOException;
88
import java.lang.invoke.MethodHandles;
9+
import java.util.Objects;
910
import java.util.concurrent.ScheduledExecutorService;
1011
import java.util.concurrent.TimeUnit;
1112
import java.util.concurrent.atomic.AtomicBoolean;
@@ -15,6 +16,7 @@
1516
import java.util.function.Supplier;
1617
import org.apache.lucene.index.Unloader;
1718
import org.apache.lucene.util.InfoStream;
19+
import org.apache.solr.cloud.ZkController;
1820
import org.apache.solr.common.MapWriter;
1921
import org.apache.solr.handler.admin.MetricsHandler;
2022
import org.apache.solr.metrics.MetricSuppliers;
@@ -41,7 +43,58 @@ final class UnloadHelper<T extends Unloader.UnloadHelper>
4143
private final Meter closed;
4244
private final InfoStream infoStream = new UnloaderLoggingInfoStream();
4345

46+
private volatile Object lastMinUnloadSpec;
47+
private volatile long minUnloadNanos = Unloader.KEEP_ALIVE_NANOS;
48+
4449
UnloadHelper(CoreContainer cc) {
50+
ZkController zkController = cc.getZkController();
51+
if (zkController != null) {
52+
// NOTE: this is scoped to the CoreContainer lifecycle, so we don't have to worry about
53+
// removing the clusterProps listener.
54+
log.info("adding clusterprops listener for minUnloadTime");
55+
zkController
56+
.getZkStateReader()
57+
.registerClusterPropertiesListener(
58+
(p) -> {
59+
Object o = p.get("minUnloadTime");
60+
if (Objects.equals(lastMinUnloadSpec, o)) {
61+
// we get notified every time clusterprops changes; shortcircuit processing
62+
// unless it's actually _our_ value that's changed.
63+
return false;
64+
}
65+
lastMinUnloadSpec = o;
66+
long newVal;
67+
if (o instanceof Number) {
68+
// millis
69+
newVal = TimeUnit.MILLISECONDS.toNanos(((Number) o).longValue());
70+
} else if (o instanceof String) {
71+
try {
72+
newVal = Unloader.getNanos((String) o);
73+
} catch (Exception ex) {
74+
log.warn("problem parsing clusterprops `minUnloadTime` spec: {}", o, ex);
75+
newVal = Unloader.KEEP_ALIVE_NANOS;
76+
}
77+
} else {
78+
// either null, or unrecognized type
79+
newVal = Unloader.KEEP_ALIVE_NANOS;
80+
if (o != null) {
81+
log.warn(
82+
"unrecognized type for clusterprops `minUnloadTime`: {} ({})",
83+
o,
84+
o.getClass());
85+
}
86+
}
87+
if (newVal < 0) {
88+
log.warn(
89+
"clusterprops minUnloadTime should be >= 0; found {}, from {}", newVal, o);
90+
minUnloadNanos = Unloader.KEEP_ALIVE_NANOS;
91+
} else {
92+
log.info("set `minUnloadNanos` from clusterprops {} ({} nanos)", o, newVal);
93+
minUnloadNanos = newVal;
94+
}
95+
return false;
96+
});
97+
}
4598
this.exec = cc.getUnloaderExecutor();
4699
MetricsHandler metricsHandler = cc.getMetricsHandler();
47100
if (metricsHandler == null) {
@@ -251,21 +304,30 @@ public void onClose() {
251304

252305
@Override
253306
public long deferUnload(long nanosSinceLastAccess) {
254-
if (nanosSinceLastAccess >= MAX_THRESHOLD) {
307+
long minUnloadNanos = UnloadHelper.this.minUnloadNanos;
308+
if (nanosSinceLastAccess >= Math.max(MAX_THRESHOLD, minUnloadNanos)) {
255309
// we've waited the max amount of time; don't defer any longer
256310
return -1;
257311
}
258312
Snapshot s = h.getSnapshot();
259-
if (s.size() == 0) {
260-
// no recent loads; establish a baseline, don't defer
261-
return -1;
262-
}
263313
// get a pessimistic estimate (based on recent history) of how long we anticipate
264314
// we might enjoy the benefits of unloading if we were to unload now.
265-
long pessimisticExpectRemaining = (long) s.getValue(0.25) - nanosSinceLastAccess;
266-
if (pessimisticExpectRemaining >= ACCEPTABLE_THRESHOLD) {
267-
// we expect it's worth unloading; don't defer.
268-
return -1;
315+
@SuppressWarnings("unused")
316+
long pessimisticExpectRemaining;
317+
if (s.size() == 0
318+
|| (pessimisticExpectRemaining = (long) s.getValue(0.25) - nanosSinceLastAccess)
319+
>= ACCEPTABLE_THRESHOLD) {
320+
// at this point we either expect it's worth unloading, or (size==0) we have no
321+
// context with which to make a decision, so allow to proceed (establish a baseline)
322+
if (nanosSinceLastAccess >= minUnloadNanos) {
323+
// we meet the `minUnloadNanos` criterion; don't defer
324+
return -1;
325+
} else {
326+
// we wait until `minUnloadNanos` criterion may have been satisfied; but don't
327+
// wait longer than `MAX_THRESHOLD`, because we still want to check periodically
328+
// in case `minUnloadNanos` is reduced after being set very long.
329+
return Math.min(minUnloadNanos - nanosSinceLastAccess, MAX_THRESHOLD);
330+
}
269331
} else {
270332
// wait a generous amount of time, up to `MAX_THRESHOLD`, to give a chance to
271333
// be kept alive without being closed.

0 commit comments

Comments
 (0)