You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by ab...@apache.org on 2018/03/06 17:26:57 UTC

[2/5] lucene-solr:jira/solr-11670-2: Refactoring.

Refactoring.


Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/023d870c
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/023d870c
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/023d870c

Branch: refs/heads/jira/solr-11670-2
Commit: 023d870c6a76739bed28b00857de3aead9c8fc2a
Parents: 1ab5f1c
Author: Andrzej Bialecki <ab...@apache.org>
Authored: Mon Mar 5 13:44:23 2018 +0100
Committer: Andrzej Bialecki <ab...@apache.org>
Committed: Mon Mar 5 13:44:23 2018 +0100

----------------------------------------------------------------------
 .../org/apache/solr/cloud/MaintenanceTask.java  |  35 ------
 .../apache/solr/cloud/MaintenanceThread.java    | 104 ------------------
 .../java/org/apache/solr/cloud/Overseer.java    |  28 -----
 .../cloud/api/collections/SplitShardCmd.java    |  72 -------------
 .../solr/cloud/autoscaling/AutoScaling.java     |  15 +++
 .../cloud/autoscaling/ExecutePlanAction.java    |   3 +-
 .../autoscaling/InactiveSliceCleanupAction.java | 106 +++++++++++++++++++
 .../autoscaling/OverseerTriggerThread.java      |  19 +++-
 .../ScheduledMaintenanceTriggerTest.java        |   7 ++
 9 files changed, 144 insertions(+), 245 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/023d870c/solr/core/src/java/org/apache/solr/cloud/MaintenanceTask.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/cloud/MaintenanceTask.java b/solr/core/src/java/org/apache/solr/cloud/MaintenanceTask.java
deleted file mode 100644
index 5525c42..0000000
--- a/solr/core/src/java/org/apache/solr/cloud/MaintenanceTask.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.solr.cloud;
-
-import java.io.Closeable;
-import java.io.IOException;
-
-/**
- *
- */
-public interface MaintenanceTask extends Runnable, Closeable {
-
-  default String getName() {
-    return getClass().getSimpleName();
-  }
-
-  default void close() throws IOException {
-
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/023d870c/solr/core/src/java/org/apache/solr/cloud/MaintenanceThread.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/cloud/MaintenanceThread.java b/solr/core/src/java/org/apache/solr/cloud/MaintenanceThread.java
deleted file mode 100644
index 216beca..0000000
--- a/solr/core/src/java/org/apache/solr/cloud/MaintenanceThread.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.solr.cloud;
-
-import java.io.IOException;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.ScheduledThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-
-import org.apache.solr.client.solrj.cloud.autoscaling.AlreadyExistsException;
-import org.apache.solr.client.solrj.cloud.autoscaling.SolrCloudManager;
-import org.apache.solr.common.SolrCloseable;
-import org.apache.solr.common.util.IOUtils;
-import org.apache.solr.util.DefaultSolrThreadFactory;
-
-/**
- * Responsible for running periodically some maintenance tasks.
- */
-public class MaintenanceThread implements SolrCloseable {
-
-  private class Task {
-    MaintenanceTask maintenanceTask;
-    ScheduledFuture<?> scheduledFuture;
-  }
-
-  private final SolrCloudManager cloudManager;
-  private final ScheduledThreadPoolExecutor taskExecutor;
-  private final Map<String, Task> tasks = new ConcurrentHashMap<>();
-
-  private transient boolean isClosed = false;
-
-  public MaintenanceThread(SolrCloudManager cloudManager) {
-    this.cloudManager = cloudManager;
-    this.taskExecutor = (ScheduledThreadPoolExecutor) Executors.newScheduledThreadPool(3, new DefaultSolrThreadFactory("MaintenanceTask"));
-  }
-
-  public void addTask(MaintenanceTask task, int period, TimeUnit timeUnit, boolean replace) throws AlreadyExistsException {
-    if (tasks.containsKey(task.getName())) {
-      if (replace) {
-        Task oldTask = tasks.get(task.getName());
-        IOUtils.closeQuietly(oldTask.maintenanceTask);
-        if (oldTask.scheduledFuture != null) {
-          oldTask.scheduledFuture.cancel(false);
-        }
-      } else {
-        throw new AlreadyExistsException(task.getName());
-      }
-    }
-    Task t = new Task();
-    t.maintenanceTask = task;
-    tasks.put(task.getName(), t);
-    t.scheduledFuture = taskExecutor.scheduleWithFixedDelay(task, 0,
-        cloudManager.getTimeSource().convertDelay(timeUnit, period, TimeUnit.MILLISECONDS),
-        TimeUnit.MILLISECONDS);
-  }
-
-  public void removeTask(String name) {
-    Task t = tasks.get(name);
-    if (t == null) {
-      // ignore
-      return;
-    }
-    IOUtils.closeQuietly(t.maintenanceTask);
-    if (t.scheduledFuture != null) {
-      t.scheduledFuture.cancel(false);
-    }
-  }
-
-  @Override
-  public void close() throws IOException {
-    isClosed = true;
-    for (Task task : tasks.values()) {
-      IOUtils.closeQuietly(task.maintenanceTask);
-      if (task.scheduledFuture != null) {
-        task.scheduledFuture.cancel(false);
-      }
-    }
-    tasks.clear();
-    taskExecutor.shutdown();
-  }
-
-  @Override
-  public boolean isClosed() {
-    return isClosed;
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/023d870c/solr/core/src/java/org/apache/solr/cloud/Overseer.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/cloud/Overseer.java b/solr/core/src/java/org/apache/solr/cloud/Overseer.java
index dd03a99..608fb20 100644
--- a/solr/core/src/java/org/apache/solr/cloud/Overseer.java
+++ b/solr/core/src/java/org/apache/solr/cloud/Overseer.java
@@ -484,8 +484,6 @@ public class Overseer implements SolrCloseable {
 
   private OverseerThread triggerThread;
 
-  private OverseerThread maintenanceThread;
-
   private final ZkStateReader reader;
 
   private final ShardHandler shardHandler;
@@ -529,11 +527,6 @@ public class Overseer implements SolrCloseable {
     updaterThread = new OverseerThread(tg, new ClusterStateUpdater(reader, id, stats), "OverseerStateUpdate-" + id);
     updaterThread.setDaemon(true);
 
-    // create this early so that collection commands may register their tasks
-    ThreadGroup maintenanceThreadGroup = new ThreadGroup("Overseer maintenance process");
-    MaintenanceThread maintenance = new MaintenanceThread(zkController.getSolrCloudManager());
-    maintenanceThread = new OverseerThread(maintenanceThreadGroup, maintenance, "OverseerMaintenanceThread-" + id);
-
     ThreadGroup ccTg = new ThreadGroup("Overseer collection creation process.");
 
     OverseerNodePrioritizer overseerPrioritizer = new OverseerNodePrioritizer(reader, adminPath, shardHandler.getShardHandlerFactory());
@@ -549,7 +542,6 @@ public class Overseer implements SolrCloseable {
     updaterThread.start();
     ccThread.start();
     triggerThread.start();
-    maintenanceThread.start();
     assert ObjectReleaseTracker.track(this);
   }
 
@@ -587,16 +579,6 @@ public class Overseer implements SolrCloseable {
   public synchronized OverseerThread getTriggerThread() {
     return triggerThread;
   }
-
-  /**
-   * For tests.
-   * @lucene.internal
-   * @return trigger thread
-   */
-  public synchronized OverseerThread getMaintenanceThread() {
-    return maintenanceThread;
-  }
-
   
   public synchronized void close() {
     if (closed) return;
@@ -626,10 +608,6 @@ public class Overseer implements SolrCloseable {
       IOUtils.closeQuietly(triggerThread);
       triggerThread.interrupt();
     }
-    if (maintenanceThread != null) {
-      IOUtils.closeQuietly(maintenanceThread);
-      maintenanceThread.interrupt();
-    }
     if (updaterThread != null) {
       try {
         updaterThread.join();
@@ -645,15 +623,9 @@ public class Overseer implements SolrCloseable {
         triggerThread.join();
       } catch (InterruptedException e)  {}
     }
-    if (maintenanceThread != null)  {
-      try {
-        maintenanceThread.join();
-      } catch (InterruptedException e)  {}
-    }
     updaterThread = null;
     ccThread = null;
     triggerThread = null;
-    maintenanceThread = null;
   }
 
   /**

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/023d870c/solr/core/src/java/org/apache/solr/cloud/api/collections/SplitShardCmd.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/SplitShardCmd.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/SplitShardCmd.java
index 3c3ae4f..03e7430 100644
--- a/solr/core/src/java/org/apache/solr/cloud/api/collections/SplitShardCmd.java
+++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/SplitShardCmd.java
@@ -18,7 +18,6 @@
 package org.apache.solr.cloud.api.collections;
 
 
-import java.io.IOException;
 import java.lang.invoke.MethodHandles;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -27,16 +26,12 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 
 import org.apache.solr.client.solrj.cloud.DistributedQueue;
-import org.apache.solr.client.solrj.cloud.autoscaling.AlreadyExistsException;
 import org.apache.solr.client.solrj.cloud.autoscaling.PolicyHelper;
 import org.apache.solr.client.solrj.cloud.autoscaling.SolrCloudManager;
 import org.apache.solr.client.solrj.request.CoreAdminRequest;
-import org.apache.solr.cloud.MaintenanceTask;
-import org.apache.solr.cloud.MaintenanceThread;
 import org.apache.solr.cloud.Overseer;
 import org.apache.solr.cloud.overseer.OverseerAction;
 import org.apache.solr.common.SolrException;
@@ -74,24 +69,8 @@ public class SplitShardCmd implements OverseerCollectionMessageHandler.Cmd {
 
   private final OverseerCollectionMessageHandler ocmh;
 
-  public static final String CLEANUP_PERIOD_PROP = "split.shard.cleanup.period";
-  public static final String CLEANUP_TTL_PROP = "split.shard.cleanup.ttl";
-
-  public static final int DEFAULT_CLEANUP_PERIOD_SECONDS = 3600;
-  public static final int DEFAULT_CLEANUP_TTL_SECONDS = 3600 * 24 * 2;
-
   public SplitShardCmd(OverseerCollectionMessageHandler ocmh) {
     this.ocmh = ocmh;
-    int cleanupPeriod = ocmh.cloudManager.getClusterStateProvider().getClusterProperty(CLEANUP_PERIOD_PROP, DEFAULT_CLEANUP_PERIOD_SECONDS);
-    int cleanupTTL = ocmh.cloudManager.getClusterStateProvider().getClusterProperty(CLEANUP_TTL_PROP, DEFAULT_CLEANUP_TTL_SECONDS);
-    InactiveSliceCleanupTask task = new InactiveSliceCleanupTask(ocmh, cleanupTTL);
-    try {
-      ((MaintenanceThread) ocmh.overseer.getMaintenanceThread().getThread())
-          .addTask(task, cleanupPeriod, TimeUnit.SECONDS, false);
-    } catch (AlreadyExistsException e) { // should not happen
-      log.error("Already registered cleanup task? ", e);
-      throw new RuntimeException(e);
-    }
   }
 
   @Override
@@ -558,55 +537,4 @@ public class SplitShardCmd implements OverseerCollectionMessageHandler.Cmd {
     }
     return rangesStr;
   }
-
-  /**
-   * This maintenance task checks whether there are shards that have been inactive for a long
-   * time (which usually means they are left-overs from shard splitting) and removes them after
-   * their cleanupTTL period elapsed.
-   */
-  public static class InactiveSliceCleanupTask implements MaintenanceTask {
-    private OverseerCollectionMessageHandler ocmh;
-    private int cleanupTTL;
-    private DeleteShardCmd cmd;
-
-    public InactiveSliceCleanupTask(OverseerCollectionMessageHandler ocmh, int cleanupTTL) {
-      this.ocmh = ocmh;
-      this.cleanupTTL = cleanupTTL;
-      cmd = (DeleteShardCmd)ocmh.commandMap.get(DELETESHARD);
-    }
-
-    @Override
-    public synchronized void run() {
-      try {
-        ClusterState state = ocmh.cloudManager.getClusterStateProvider().getClusterState();
-        state.forEachCollection(coll -> {
-          coll.getSlices().forEach(s -> {
-            if (Slice.State.INACTIVE.equals(s.getState())) {
-              String tstampStr = s.getStr(ZkStateReader.STATE_TIMESTAMP_PROP);
-              if (tstampStr == null || tstampStr.isEmpty()) {
-                return;
-              }
-              long timestamp = Long.parseLong(tstampStr);
-              long delta = TimeUnit.NANOSECONDS.toSeconds(ocmh.cloudManager.getTimeSource().getTime() - timestamp);
-              if (delta > cleanupTTL) {
-                ZkNodeProps props = new ZkNodeProps(ZkStateReader.COLLECTION_PROP, coll.getName(),
-                    ZkStateReader.SHARD_ID_PROP, s.getName());
-                NamedList results = new NamedList();
-                try {
-                  cmd.call(state, props, results);
-                  if (results.get("failure") != null) {
-                    throw new Exception("Failed to delete inactive shard: " + results);
-                  }
-                } catch (Exception e) {
-                  log.warn("Exception deleting inactive shard " + coll.getName() + "/" + s.getName(), e);
-                }
-              }
-            }
-          });
-        });
-      } catch (IOException e) {
-        log.warn("Error running inactive shard cleanup task", e);
-      }
-    }
-  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/023d870c/solr/core/src/java/org/apache/solr/cloud/autoscaling/AutoScaling.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/cloud/autoscaling/AutoScaling.java b/solr/core/src/java/org/apache/solr/cloud/autoscaling/AutoScaling.java
index 3ebfbd0..355fb7d 100644
--- a/solr/core/src/java/org/apache/solr/cloud/autoscaling/AutoScaling.java
+++ b/solr/core/src/java/org/apache/solr/cloud/autoscaling/AutoScaling.java
@@ -183,4 +183,19 @@ public class AutoScaling {
       "    }";
 
   public static final Map<String, Object> AUTO_ADD_REPLICAS_TRIGGER_PROPS = (Map) Utils.fromJSONString(AUTO_ADD_REPLICAS_TRIGGER_DSL);
+
+  public static final String SCHEDULED_MAINTENANCE_TRIGGER_DSL =
+          "    {" +
+          "        'name' : '.scheduled_maintenance'," +
+          "        'event' : 'scheduled'," +
+          "        'enabled' : true," +
+          "        'actions' : [" +
+          "            {" +
+          "                'name':'inactive_slice_cleanup'," +
+          "                'class':'solr.InactiveSliceCleanupAction'" +
+          "            }" +
+          "        ]" +
+          "    }";
+
+  public static final Map<String, Object> SCHEDULED_MAINTENANCE_TRIGGER_PROPS = (Map) Utils.fromJSONString(SCHEDULED_MAINTENANCE_TRIGGER_DSL);
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/023d870c/solr/core/src/java/org/apache/solr/cloud/autoscaling/ExecutePlanAction.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/cloud/autoscaling/ExecutePlanAction.java b/solr/core/src/java/org/apache/solr/cloud/autoscaling/ExecutePlanAction.java
index bce0806..00fe50a 100644
--- a/solr/core/src/java/org/apache/solr/cloud/autoscaling/ExecutePlanAction.java
+++ b/solr/core/src/java/org/apache/solr/cloud/autoscaling/ExecutePlanAction.java
@@ -67,12 +67,11 @@ public class ExecutePlanAction extends TriggerActionBase {
         log.debug("Executing operation: {}", operation.getParams());
         try {
           SolrResponse response = null;
-          int counter = 0;
           if (operation instanceof CollectionAdminRequest.AsyncCollectionAdminRequest) {
             CollectionAdminRequest.AsyncCollectionAdminRequest req = (CollectionAdminRequest.AsyncCollectionAdminRequest) operation;
             // waitForFinalState so that the end effects of operations are visible
             req.setWaitForFinalState(true);
-            String asyncId = event.getSource() + '/' + event.getId() + '/' + counter;
+            String asyncId = event.getSource() + '/' + event.getId();
             String znode = saveAsyncId(cloudManager.getDistribStateManager(), event, asyncId);
             log.trace("Saved requestId: {} in znode: {}", asyncId, znode);
             // TODO: find a better way of using async calls using dataProvider API !!!

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/023d870c/solr/core/src/java/org/apache/solr/cloud/autoscaling/InactiveSliceCleanupAction.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/cloud/autoscaling/InactiveSliceCleanupAction.java b/solr/core/src/java/org/apache/solr/cloud/autoscaling/InactiveSliceCleanupAction.java
new file mode 100644
index 0000000..ed9db68
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/cloud/autoscaling/InactiveSliceCleanupAction.java
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.cloud.autoscaling;
+
+import java.lang.invoke.MethodHandles;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.solr.client.solrj.SolrRequest;
+import org.apache.solr.client.solrj.SolrResponse;
+import org.apache.solr.client.solrj.cloud.autoscaling.SolrCloudManager;
+import org.apache.solr.client.solrj.request.CollectionAdminRequest;
+import org.apache.solr.common.cloud.ClusterState;
+import org.apache.solr.common.cloud.Slice;
+import org.apache.solr.common.cloud.ZkStateReader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This maintenance action checks whether there are shards that have been inactive for a long
+ * time (which usually means they are left-overs from shard splitting) and removes them after
+ * their cleanupTTL period elapsed.
+ */
+public class InactiveSliceCleanupAction extends TriggerActionBase {
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+  public static final String TTL_PROP = "ttl";
+
+  public static final int DEFAULT_TTL_SECONDS = 3600 * 24 * 2;
+
+  private int cleanupTTL;
+
+  @Override
+  public void init(Map<String, String> args) {
+    String cleanupStr = args.getOrDefault(TTL_PROP, String.valueOf(DEFAULT_TTL_SECONDS));
+    try {
+      cleanupTTL = Integer.parseInt(cleanupStr);
+    } catch (Exception e) {
+      log.warn("Invalid " + TTL_PROP + " value: '" + cleanupStr + "', using default " + DEFAULT_TTL_SECONDS);
+      cleanupTTL = DEFAULT_TTL_SECONDS;
+    }
+    if (cleanupTTL < 0) {
+      log.warn("Invalid " + TTL_PROP + " value: '" + cleanupStr + "', using default " + DEFAULT_TTL_SECONDS);
+      cleanupTTL = DEFAULT_TTL_SECONDS;
+    }
+  }
+
+  @Override
+  public void process(TriggerEvent event, ActionContext context) throws Exception {
+    SolrCloudManager cloudManager = context.getCloudManager();
+    ClusterState state = cloudManager.getClusterStateProvider().getClusterState();
+    Map<String, List<String>> cleaned = new LinkedHashMap<>();
+    AtomicInteger totalInactive = new AtomicInteger(0);
+    state.forEachCollection(coll ->
+      coll.getSlices().forEach(s -> {
+        if (Slice.State.INACTIVE.equals(s.getState())) {
+          totalInactive.incrementAndGet();
+          String tstampStr = s.getStr(ZkStateReader.STATE_TIMESTAMP_PROP);
+          if (tstampStr == null || tstampStr.isEmpty()) {
+            return;
+          }
+          long timestamp = Long.parseLong(tstampStr);
+          long delta = TimeUnit.NANOSECONDS.toSeconds(cloudManager.getTimeSource().getTime() - timestamp);
+          if (delta > cleanupTTL) {
+            SolrRequest req = CollectionAdminRequest.deleteShard(coll.getName(), s.getName());
+            try {
+              SolrResponse rsp = cloudManager.request(req);
+              if (rsp.getResponse().get("failure") != null) {
+                throw new Exception("Failed to delete inactive shard: " + rsp);
+              }
+              cleaned.computeIfAbsent(coll.getName(), c -> new ArrayList<>()).add(s.getName());
+            } catch (Exception e) {
+              log.warn("Exception deleting inactive shard " + coll.getName() + "/" + s.getName(), e);
+            }
+          }
+        }
+      })
+    );
+    if (totalInactive.get() > 0) {
+      Map<String, Object> results = new LinkedHashMap<>();
+      results.put("totalInactive", totalInactive.get());
+      if (!cleaned.isEmpty()) {
+        results.put("cleaned", cleaned);
+      }
+      context.getProperties().put(getName(), results);
+    }
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/023d870c/solr/core/src/java/org/apache/solr/cloud/autoscaling/OverseerTriggerThread.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/cloud/autoscaling/OverseerTriggerThread.java b/solr/core/src/java/org/apache/solr/cloud/autoscaling/OverseerTriggerThread.java
index 717e666..d75453a 100644
--- a/solr/core/src/java/org/apache/solr/cloud/autoscaling/OverseerTriggerThread.java
+++ b/solr/core/src/java/org/apache/solr/cloud/autoscaling/OverseerTriggerThread.java
@@ -121,13 +121,15 @@ public class OverseerTriggerThread implements Runnable, SolrCloseable {
     int lastZnodeVersion = znodeVersion;
 
     // we automatically add a trigger for auto add replicas if it does not exists already
+    // we also automatically add a scheduled maintenance trigger
     while (!isClosed)  {
       try {
         AutoScalingConfig autoScalingConfig = cloudManager.getDistribStateManager().getAutoScalingConfig();
-        AutoScalingConfig withAutoAddReplicasTrigger = withAutoAddReplicasTrigger(autoScalingConfig);
-        if (withAutoAddReplicasTrigger.equals(autoScalingConfig)) break;
-        log.debug("Adding .autoAddReplicas trigger");
-        cloudManager.getDistribStateManager().setData(SOLR_AUTOSCALING_CONF_PATH, Utils.toJSON(withAutoAddReplicasTrigger), withAutoAddReplicasTrigger.getZkVersion());
+        AutoScalingConfig updatedConfig = withAutoAddReplicasTrigger(autoScalingConfig);
+        updatedConfig = withScheduledMaintenanceTrigger(updatedConfig);
+        if (updatedConfig.equals(autoScalingConfig)) break;
+        log.debug("Adding .auto_add_replicas and .scheduled_maintenance triggers");
+        cloudManager.getDistribStateManager().setData(SOLR_AUTOSCALING_CONF_PATH, Utils.toJSON(updatedConfig), updatedConfig.getZkVersion());
         break;
       } catch (BadVersionException bve) {
         // somebody else has changed the configuration so we must retry
@@ -338,6 +340,15 @@ public class OverseerTriggerThread implements Runnable, SolrCloseable {
 
   private AutoScalingConfig withAutoAddReplicasTrigger(AutoScalingConfig autoScalingConfig) {
     Map<String, Object> triggerProps = AutoScaling.AUTO_ADD_REPLICAS_TRIGGER_PROPS;
+    return withDefaultTrigger(triggerProps, autoScalingConfig);
+  }
+
+  private AutoScalingConfig withScheduledMaintenanceTrigger(AutoScalingConfig autoScalingConfig) {
+    Map<String, Object> triggerProps = AutoScaling.SCHEDULED_MAINTENANCE_TRIGGER_PROPS;
+    return withDefaultTrigger(triggerProps, autoScalingConfig);
+  }
+
+  private AutoScalingConfig withDefaultTrigger(Map<String, Object> triggerProps, AutoScalingConfig autoScalingConfig) {
     String triggerName = (String) triggerProps.get("name");
     Map<String, AutoScalingConfig.TriggerConfig> configs = autoScalingConfig.getTriggerConfigs();
     for (AutoScalingConfig.TriggerConfig cfg : configs.values()) {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/023d870c/solr/core/src/test/org/apache/solr/cloud/autoscaling/ScheduledMaintenanceTriggerTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/autoscaling/ScheduledMaintenanceTriggerTest.java b/solr/core/src/test/org/apache/solr/cloud/autoscaling/ScheduledMaintenanceTriggerTest.java
new file mode 100644
index 0000000..9b4cece
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/cloud/autoscaling/ScheduledMaintenanceTriggerTest.java
@@ -0,0 +1,7 @@
+package org.apache.solr.cloud.autoscaling;
+
+/**
+ *
+ */
+public class ScheduledMaintenanceTriggerTest {
+}