Skip to content

Commit

Permalink
Merge branch 'main' into solr-12089
Browse files Browse the repository at this point in the history
  • Loading branch information
Andrey Bozhko committed Mar 19, 2024
2 parents 1cedb2f + 768c5af commit 167a16d
Show file tree
Hide file tree
Showing 118 changed files with 4,724 additions and 1,769 deletions.
13 changes: 13 additions & 0 deletions gradle/testing/randomization/policies/solr-tests.policy
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,19 @@ grant {
// for Apache HttpClient useSystemProperties
permission java.net.NetPermission "getProxySelector";
permission java.net.NetPermission "requestPasswordAuthentication";

// for java.net.http.HttpClient. See HttpJdkSolrClientTest
permission "java.net.URLPermission" "http://127.0.0.1:*/solr/-", "HEAD,GET,PUT,POST:*";
permission "java.net.URLPermission" "https://127.0.0.1:*/solr/-", "HEAD,GET,PUT,POST:*";
permission "java.net.URLPermission" "socket://127.0.0.1:*", "CONNECT:*";

permission "java.net.URLPermission" "http://localhost:*/solr/-", "HEAD,GET,PUT,POST:*";
permission "java.net.URLPermission" "https://localhost:*/solr/-", "HEAD,GET,PUT,POST:*";
permission "java.net.URLPermission" "socket://localhost:*", "CONNECT:*";

permission "java.net.URLPermission" "http://[::1]:*/solr/-", "HEAD,GET,PUT,POST:*";
permission "java.net.URLPermission" "https://[::1]:*/solr/-", "HEAD,GET,PUT,POST:*";
permission "java.net.URLPermission" "socket://[::1]:*", "CONNECT:*";
};

// additional permissions based on system properties set by /bin/solr
Expand Down
18 changes: 17 additions & 1 deletion solr/CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ New Features
---------------------
* SOLR-17141: Implement 'cpuAllowed' query parameter to limit the maximum CPU usage by a running query. (Andrzej Bialecki, Gus Heck, David Smiley)

* SOLR-599: Add a new SolrJ client using the JDK’s built-in Http Client. (James Dyer)

* SOLR-16403: A new cluster singleton plugin to automatically remove inactive shards. (Paul McArthur, David Smiley)

Improvements
---------------------
* SOLR-17119: When registering or updating a ConfigurablePlugin through the `/cluster/plugin` API,
Expand All @@ -106,7 +110,7 @@ Improvements

* SOLR-17145: The INSTALLSHARDDATA API now includes a 'requestid' field when run asynchronously (Jason Gerlowski)

* SOLR-17159: bin/solr post now has proper unit testing. Users can specify a --dry-run option to
* SOLR-17159: bin/solr post now has proper unit testing. Users can specify a --dry-run option to
simulate posting documents without sending them to Solr. (Eric Pugh)

* SOLR-17058: Add 'distrib.statsCache' parameter to disable distributed stats requests at query time. (Wei Wang, Mikhail Khludnev)
Expand All @@ -116,6 +120,8 @@ Improvements
* SOLR-17172: Add QueryLimits termination to the existing heavy SearchComponent-s. This allows query limits (e.g. timeAllowed,
cpuAllowed) to terminate expensive operations within components if limits are exceeded. (Andrzej Bialecki)

* SOLR-17164: Add 2 arg variant of vectorSimilarity() function (Sanjay Dutt, hossman)

Optimizations
---------------------
* SOLR-17144: Close searcherExecutor thread per core after 1 minute (Pierre Salagnac, Christine Poerschke)
Expand All @@ -129,8 +135,16 @@ Bug Fixes

* SOLR-17148: Fixing Config API overlay property enabling or disabling the cache (Sanjay Dutt, hossman, Eric Pugh)

* PR#2320: Avoid NullPointerException in SolrJ client due to missing
EnvToSyspropMappings.properties file (janhoy)

* SOLR-17186: Streaming query breaks if token contains backtick (Rahul Goswami via Eric Pugh)

* SOLR-17197: Fix getting fieldType by its name in FileBasedSpellChecker (Andrey Bozhko via Eric Pugh)

* SOLR-17198: AffinityPlacementFactory can fail if Shard leadership changes occur while it is collecting metrics.
(Paul McArthur)

Dependency Upgrades
---------------------
(No changes)
Expand All @@ -142,6 +156,8 @@ Other Changes
* SOLR-17066: GenericSolrRequest now has a `setRequiresCollection` setter that allows it to specify whether
it should make use of the client-level default collection/core. (Jason Gerlowski)

* SOLR-17190: Replace org.apache.solr.util.LongSet with hppc LongHashSet (Michael Gibney)

* SOLR-12089: Update FileBasedSpellChecker and IndexBasedSpellChecker to accept accuracy parameter as float;
rename `breakSugestionTieBreaker` parameter to `breakSuggestionTieBreaker` in WordBreakSolrSpellChecker (Andrey Bozhko)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
/*
* 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.cluster.maintenance;

import com.google.common.annotations.VisibleForTesting;
import java.lang.invoke.MethodHandles;
import java.util.Collection;
import java.util.HashSet;
import java.util.Objects;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.apache.solr.api.ConfigurablePlugin;
import org.apache.solr.client.solrj.SolrResponse;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
import org.apache.solr.cloud.ClusterSingleton;
import org.apache.solr.common.cloud.ClusterState;
import org.apache.solr.common.cloud.DocCollection;
import org.apache.solr.common.cloud.Slice;
import org.apache.solr.common.cloud.ZkStateReader;
import org.apache.solr.common.util.ExecutorUtil;
import org.apache.solr.common.util.SolrNamedThreadFactory;
import org.apache.solr.core.CoreContainer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* This Cluster Singleton can be configured to periodically find and remove {@link
* org.apache.solr.common.cloud.Slice.State#INACTIVE} Shards that are left behind after a Shard is
* split
*/
public class InactiveShardRemover
implements ClusterSingleton, ConfigurablePlugin<InactiveShardRemoverConfig> {

private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

public static final String PLUGIN_NAME = ".inactive-shard-remover";

static class DeleteActor {

private final CoreContainer coreContainer;

DeleteActor(final CoreContainer coreContainer) {
this.coreContainer = coreContainer;
}

void delete(final Slice slice) {
CollectionAdminRequest.DeleteShard deleteRequest =
CollectionAdminRequest.deleteShard(slice.getCollection(), slice.getName());
try {
SolrResponse response =
coreContainer.getZkController().getSolrCloudManager().request(deleteRequest);
if (response.getException() != null) {
throw response.getException();
}
} catch (Exception e) {
log.warn("An exception occurred when deleting an inactive shard", e);
}
}
}

private State state = State.STOPPED;

private final CoreContainer coreContainer;

private final DeleteActor deleteActor;

private ScheduledExecutorService executor;

private long scheduleIntervalSeconds;

private long ttlSeconds;

private int maxDeletesPerCycle;

/** Constructor invoked via Reflection */
public InactiveShardRemover(final CoreContainer cc) {
this(cc, new DeleteActor(cc));
}

public InactiveShardRemover(final CoreContainer cc, final DeleteActor deleteActor) {
this.coreContainer = cc;
this.deleteActor = deleteActor;
}

@Override
public void configure(final InactiveShardRemoverConfig cfg) {
Objects.requireNonNull(cfg, "config must be specified");
cfg.validate();
this.scheduleIntervalSeconds = cfg.scheduleIntervalSeconds;
this.maxDeletesPerCycle = cfg.maxDeletesPerCycle;
this.ttlSeconds = cfg.ttlSeconds;
}

@Override
public String getName() {
return PLUGIN_NAME;
}

@Override
public State getState() {
return state;
}

@Override
public void start() throws Exception {
state = State.STARTING;
executor = Executors.newScheduledThreadPool(1, new SolrNamedThreadFactory(PLUGIN_NAME));
executor.scheduleAtFixedRate(
this::deleteInactiveSlices,
scheduleIntervalSeconds,
scheduleIntervalSeconds,
TimeUnit.SECONDS);
state = State.RUNNING;
}

@Override
public void stop() {
if (state == State.RUNNING) {
state = State.STOPPING;
ExecutorUtil.shutdownNowAndAwaitTermination(executor);
}
state = State.STOPPED;
}

@VisibleForTesting
void deleteInactiveSlices() {
final ClusterState clusterState = coreContainer.getZkController().getClusterState();
Collection<Slice> inactiveSlices =
clusterState.getCollectionsMap().values().stream()
.flatMap(v -> collectInactiveSlices(v).stream())
.collect(Collectors.toSet());

if (log.isInfoEnabled()) {
log.info(
"Found {} inactive Shards to delete, {} will be deleted",
inactiveSlices.size(),
Math.min(inactiveSlices.size(), maxDeletesPerCycle));
}

inactiveSlices.stream().limit(maxDeletesPerCycle).forEach(this::deleteShard);
}

private Collection<Slice> collectInactiveSlices(final DocCollection docCollection) {
final Collection<Slice> slices = new HashSet<>(docCollection.getSlices());
slices.removeAll(docCollection.getActiveSlices());
return slices.stream().filter(this::isExpired).collect(Collectors.toSet());
}

private void deleteShard(final Slice s) {
deleteActor.delete(s);
}

/**
* An Inactive Shard is expired if it has not undergone a state change in the period of time
* defined by {@link InactiveShardRemover#ttlSeconds}. If it is expired, it is eligible for
* removal.
*/
private boolean isExpired(final Slice slice) {

final String collectionName = slice.getCollection();
final String sliceName = slice.getName();

if (slice.getState() != Slice.State.INACTIVE) {
return false;
}

final String lastChangeTimestamp = slice.getStr(ZkStateReader.STATE_TIMESTAMP_PROP);
if (lastChangeTimestamp == null || lastChangeTimestamp.isEmpty()) {
log.warn(
"Collection {} Shard {} has no last change timestamp and will not be deleted",
collectionName,
sliceName);
return false;
}

final long epochTimestampNs;
try {
epochTimestampNs = Long.parseLong(lastChangeTimestamp);
} catch (NumberFormatException e) {
log.warn(
"Collection {} Shard {} has an invalid last change timestamp and will not be deleted",
collectionName,
sliceName);
return false;
}

long currentEpochTimeNs =
coreContainer.getZkController().getSolrCloudManager().getTimeSource().getEpochTimeNs();
long delta = TimeUnit.NANOSECONDS.toSeconds(currentEpochTimeNs - epochTimestampNs);

boolean expired = delta >= ttlSeconds;
if (log.isDebugEnabled()) {
log.debug(
"collection {} shard {} last state change {} seconds ago. Expired={}",
slice.getCollection(),
slice.getName(),
delta,
expired);
}
return expired;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* 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.cluster.maintenance;

import org.apache.solr.common.SolrException;
import org.apache.solr.common.annotation.JsonProperty;
import org.apache.solr.common.util.ReflectMapWriter;

public class InactiveShardRemoverConfig implements ReflectMapWriter {

public static final long DEFAULT_SCHEDULE_INTERVAL_SECONDS = 900L; // 15 minutes

public static final long DEFAULT_TTL_SECONDS = 900L; // 15 minutes

public static final int DEFAULT_MAX_DELETES_PER_CYCLE = 20;

@JsonProperty public long scheduleIntervalSeconds;

@JsonProperty public long ttlSeconds;

@JsonProperty public int maxDeletesPerCycle;

/** Default constructor required for deserialization */
public InactiveShardRemoverConfig() {
this(DEFAULT_SCHEDULE_INTERVAL_SECONDS, DEFAULT_TTL_SECONDS, DEFAULT_MAX_DELETES_PER_CYCLE);
}

public InactiveShardRemoverConfig(
final long scheduleIntervalSeconds, final long ttlSeconds, final int maxDeletesPerCycle) {
this.scheduleIntervalSeconds = scheduleIntervalSeconds;
this.ttlSeconds = ttlSeconds;
this.maxDeletesPerCycle = maxDeletesPerCycle;
}

public void validate() {
if (scheduleIntervalSeconds <= 0) {
throw new SolrException(
SolrException.ErrorCode.BAD_REQUEST, "scheduleIntervalSeconds must be greater than 0");
}
if (maxDeletesPerCycle <= 0) {
throw new SolrException(
SolrException.ErrorCode.BAD_REQUEST, "maxDeletesPerCycle must be greater than 0");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* 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.
*/

/** Cluster Singleton plugins that are used to perform maintenance tasks within the cluster. */
package org.apache.solr.cluster.maintenance;
Loading

0 comments on commit 167a16d

Please sign in to comment.