Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SAI-4837 : In-memory synthetic SolrCore for QA node #181

Merged
merged 20 commits into from
May 2, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions solr/core/src/java/org/apache/solr/cloud/ZkConfigSetService.java
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,20 @@ public ZkConfigSetService(SolrZkClient zkClient) {
this.zkClient = zkClient;
}

/**
* Do not perform Zk operations if configSetName is provided.
*
* <p>This is only used by {@link org.apache.solr.core.SyntheticSolrCore}, which is not registered
* with Zookeeper
*/
@Override
protected SolrResourceLoader createCoreResourceLoader(CoreDescriptor cd, String configSetName) {
return configSetName != null
? new ZkSolrResourceLoader(
cd.getInstanceDir(), configSetName, parentLoader.getClassLoader(), zkController)
: createCoreResourceLoader(cd);
}

@Override
public SolrResourceLoader createCoreResourceLoader(CoreDescriptor cd) {
final String colName = cd.getCollectionName();
Expand Down
27 changes: 26 additions & 1 deletion solr/core/src/java/org/apache/solr/core/ConfigSetService.java
Original file line number Diff line number Diff line change
Expand Up @@ -251,8 +251,19 @@ public boolean isConfigSetTrusted(SolrResourceLoader coreLoader) throws IOExcept
* @return a ConfigSet
*/
public final ConfigSet loadConfigSet(CoreDescriptor dcore) {
return loadConfigSet(dcore, null);
}

/**
* Load the ConfigSet for a core with an explicit config set name
*
* @param dcore the core's CoreDescriptor
* @param configSetName an optional and explicit config set name
* @return a ConfigSet
*/
final ConfigSet loadConfigSet(CoreDescriptor dcore, String configSetName) {

SolrResourceLoader coreLoader = createCoreResourceLoader(dcore);
SolrResourceLoader coreLoader = createCoreResourceLoader(dcore, configSetName);

try {
// ConfigSet properties are loaded from ConfigSetProperties.DEFAULT_FILENAME file.
Expand Down Expand Up @@ -391,6 +402,20 @@ protected NamedList<Object> loadConfigSetFlags(SolrResourceLoader loader) throws
*/
protected abstract SolrResourceLoader createCoreResourceLoader(CoreDescriptor cd);

/**
* Create a SolrResourceLoader for a core with the provided configSetName.
*
* <p>By default, this will just call {@link
* ConfigSetService#createConfigSetService(CoreContainer)}. Child implementation might override
* this to make use of the configSetName directly
*
* @param cd the core's CoreDescriptor
* @param configSetName an optional config set name
* @return a SolrResourceLoader
*/
protected SolrResourceLoader createCoreResourceLoader(CoreDescriptor cd, String configSetName) {
return createCoreResourceLoader(cd);
}
/**
* Return a name for the ConfigSet for a core to be used for printing/diagnostic purposes.
*
Expand Down
2 changes: 1 addition & 1 deletion solr/core/src/java/org/apache/solr/core/SolrCore.java
Original file line number Diff line number Diff line change
Expand Up @@ -1255,7 +1255,7 @@ public void seedVersionBuckets() {
}

/** Set UpdateLog to buffer updates if the slice is in construction. */
private void bufferUpdatesIfConstructing(CoreDescriptor coreDescriptor) {
protected void bufferUpdatesIfConstructing(CoreDescriptor coreDescriptor) {

if (coreContainer != null && coreContainer.isZooKeeperAware()) {
if (reqHandlers.get("/get") == null) {
Expand Down
61 changes: 61 additions & 0 deletions solr/core/src/java/org/apache/solr/core/SyntheticSolrCore.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package org.apache.solr.core;

import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.params.CoreAdminParams;
import org.apache.solr.rest.RestManager;

/**
* A synthetic core that is created only in memory and not registered against Zookeeper.
*
* <p>This is only used in Coordinator node to support a subset of SolrCore functionalities required
* by Coordinator operations such as aggregating and writing out response and providing configset
* info.
*
* <p>There should only be one instance of SyntheticSolrCore per configset
*/
public class SyntheticSolrCore extends SolrCore {
public SyntheticSolrCore(CoreContainer coreContainer, CoreDescriptor cd, ConfigSet configSet) {
super(coreContainer, cd, configSet);
}

public static SyntheticSolrCore createAndRegisterCore(
CoreContainer coreContainer, String syntheticCollectionName, String configSetName) {
Map<String, String> coreProps = new HashMap<>();
coreProps.put(CoreAdminParams.CORE_NODE_NAME, coreContainer.getHostName());
coreProps.put(CoreAdminParams.COLLECTION, syntheticCollectionName);

CoreDescriptor syntheticCoreDescriptor =
new CoreDescriptor(
syntheticCollectionName,
Paths.get(coreContainer.getSolrHome() + "/" + syntheticCollectionName),
coreProps,
coreContainer.getContainerProperties(),
coreContainer.getZkController());

ConfigSet coreConfig =
coreContainer.getConfigSetService().loadConfigSet(syntheticCoreDescriptor, configSetName);
syntheticCoreDescriptor.setConfigSetTrusted(coreConfig.isTrusted());
SyntheticSolrCore syntheticCore =
new SyntheticSolrCore(coreContainer, syntheticCoreDescriptor, coreConfig);
coreContainer.registerCore(syntheticCoreDescriptor, syntheticCore, false, false);

return syntheticCore;
}

@Override
protected void bufferUpdatesIfConstructing(CoreDescriptor coreDescriptor) {
// no updates to SyntheticSolrCore
}

@Override
protected RestManager initRestManager() throws SolrException {
// returns an initialized RestManager. As init routines requires reading configname of the
// core's collection from ZK
// which synthetic core is not registered in ZK.
// We do not expect RestManager ops on Coordinator Nodes
return new RestManager();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what restManager enables?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, there is only a tiny feature called "managed resources" being powered by RestManager. It's not even reuired

}
}
186 changes: 22 additions & 164 deletions solr/core/src/java/org/apache/solr/servlet/CoordinatorHttpSolrCall.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,32 +18,24 @@
package org.apache.solr.servlet;

import java.lang.invoke.MethodHandles;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.solr.api.CoordinatorV2HttpSolrCall;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
import org.apache.solr.cloud.CloudDescriptor;
import org.apache.solr.cloud.api.collections.Assign;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.cloud.ClusterState;
import org.apache.solr.common.cloud.DocCollection;
import org.apache.solr.common.cloud.Replica;
import org.apache.solr.common.cloud.ZkStateReader;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.Utils;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.core.CoreDescriptor;
import org.apache.solr.core.SolrCore;
import org.apache.solr.logging.MDCLoggingContext;
import org.apache.solr.core.SyntheticSolrCore;
import org.apache.solr.request.DelegatingSolrQueryRequest;
import org.apache.solr.request.LocalSolrQueryRequest;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.SolrQueryResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -78,103 +70,31 @@ public static SolrCore getCore(
Factory factory, HttpSolrCall solrCall, String collectionName, boolean isPreferLeader) {
String syntheticCoreName = factory.collectionVsCoreNameMapping.get(collectionName);
if (syntheticCoreName != null) {
SolrCore syntheticCore = solrCall.cores.getCore(syntheticCoreName);
setMDCLoggingContext(collectionName);
return syntheticCore;
return solrCall.cores.getCore(syntheticCoreName);
} else {
ZkStateReader zkStateReader = solrCall.cores.getZkController().getZkStateReader();
ClusterState clusterState = zkStateReader.getClusterState();
DocCollection coll = clusterState.getCollectionOrNull(collectionName, true);
SolrCore core = null;
if (coll != null) {

if (coll == null) { // querying on a non-existent collection, it could have been removed
log.info(
"Cannot find collection {} to proxy call to, it could have been deleted",
collectionName);
return null;
}
synchronized (CoordinatorHttpSolrCall.class) {
String confName = coll.getConfigName();
String syntheticCollectionName = getSyntheticCollectionName(confName);

DocCollection syntheticColl = clusterState.getCollectionOrNull(syntheticCollectionName);
synchronized (CoordinatorHttpSolrCall.class) {
if (syntheticColl == null) {
// no synthetic collection for this config, let's create one
if (log.isInfoEnabled()) {
log.info(
"synthetic collection: {} does not exist, creating.. ", syntheticCollectionName);
}
CoreContainer coreContainer = solrCall.cores;
SyntheticSolrCore syntheticCore =
SyntheticSolrCore.createAndRegisterCore(
coreContainer, syntheticCollectionName, coll.getConfigName());

SolrException createException = null;
try {
createColl(syntheticCollectionName, solrCall.cores, confName);
} catch (SolrException exception) {
// concurrent requests could have created the collection hence causing collection
// exists
// exception
createException = exception;
} finally {
syntheticColl =
zkStateReader.getClusterState().getCollectionOrNull(syntheticCollectionName);
}

// then indeed the collection was not created properly, either by this or other
// concurrent
// requests
if (syntheticColl == null) {
if (createException != null) {
throw createException; // rethrow the exception since such collection was not
// created
} else {
throw new SolrException(
SolrException.ErrorCode.SERVER_ERROR,
"Could not locate synthetic collection ["
+ syntheticCollectionName
+ "] after creation!");
}
}
}

// get docCollection again to ensure we get the fresh state
syntheticColl =
zkStateReader.getClusterState().getCollectionOrNull(syntheticCollectionName);
List<Replica> nodeNameSyntheticReplicas =
syntheticColl.getReplicas(solrCall.cores.getZkController().getNodeName());
if (nodeNameSyntheticReplicas == null || nodeNameSyntheticReplicas.isEmpty()) {
// this node does not have a replica. add one
if (log.isInfoEnabled()) {
log.info(
"this node does not have a replica of the synthetic collection: {} , adding replica ",
syntheticCollectionName);
}

addReplica(syntheticCollectionName, solrCall.cores);
}
// after this point the sync core should be available in the container. Double check
if (coreContainer.getCore(syntheticCore.getName()) != null) {
factory.collectionVsCoreNameMapping.put(collectionName, syntheticCore.getName());

// still have to ensure that it's active, otherwise super.getCoreByCollection
// will return null and then CoordinatorHttpSolrCall will call getCore again
// hence creating a calling loop
try {
zkStateReader.waitForState(
syntheticCollectionName,
10,
TimeUnit.SECONDS,
docCollection -> {
for (Replica nodeNameSyntheticReplica :
docCollection.getReplicas(solrCall.cores.getZkController().getNodeName())) {
if (nodeNameSyntheticReplica.getState() == Replica.State.ACTIVE) {
return true;
}
}
return false;
});
} catch (Exception e) {
throw new SolrException(
SolrException.ErrorCode.SERVER_ERROR,
"Failed to wait for active replica for synthetic collection ["
+ syntheticCollectionName
+ "]",
e);
}
}

core = solrCall.getCoreByCollection(syntheticCollectionName, isPreferLeader);
if (core != null) {
factory.collectionVsCoreNameMapping.put(collectionName, core.getName());
// for the watcher, only remove on collection deletion (ie collection == null), since
// watch from coordinator is collection specific
solrCall
Expand All @@ -192,11 +112,10 @@ public static SolrCore getCore(
}
});
if (log.isDebugEnabled()) {
log.debug("coordinator node, returns synthetic core: {}", core.getName());
log.debug("coordinator node, returns synthetic core: {}", syntheticCore.getName());
}
return syntheticCore;
}
setMDCLoggingContext(collectionName);
return core;
}
return null;
}
Expand All @@ -206,69 +125,6 @@ public static String getSyntheticCollectionName(String configName) {
return SYNTHETIC_COLL_PREFIX + configName;
}

/**
* Overrides the MDC context as the core set was synthetic core, which does not reflect the
* collection being operated on
*/
private static void setMDCLoggingContext(String collectionName) {
MDCLoggingContext.setCollection(collectionName);

// below is irrelevant for call to coordinator
MDCLoggingContext.setCoreName(null);
MDCLoggingContext.setShard(null);
MDCLoggingContext.setCoreName(null);
}

private static void addReplica(String syntheticCollectionName, CoreContainer cores) {
SolrQueryResponse rsp = new SolrQueryResponse();
try {
CollectionAdminRequest.AddReplica addReplicaRequest =
CollectionAdminRequest.addReplicaToShard(syntheticCollectionName, "shard1")
// we are fixing the name, so that no two replicas are created in the same node
.setNode(cores.getZkController().getNodeName());
addReplicaRequest.setWaitForFinalState(true);
cores
.getCollectionsHandler()
.handleRequestBody(new LocalSolrQueryRequest(null, addReplicaRequest.getParams()), rsp);
if (rsp.getValues().get("success") == null) {
throw new SolrException(
SolrException.ErrorCode.SERVER_ERROR,
"Could not auto-create collection: " + Utils.toJSONString(rsp.getValues()));
}
} catch (Exception e) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
}
}

private static void createColl(
String syntheticCollectionName, CoreContainer cores, String confName) {
SolrQueryResponse rsp = new SolrQueryResponse();
try {
CollectionAdminRequest.Create collCreationRequest =
CollectionAdminRequest.createCollection(syntheticCollectionName, confName, 1, 1)
.setCreateNodeSet(cores.getZkController().getNodeName());
collCreationRequest.setWaitForFinalState(true);
SolrParams params = collCreationRequest.getParams();
if (log.isInfoEnabled()) {
log.info("sending collection admin command : {}", Utils.toJSONString(params));
}
cores.getCollectionsHandler().handleRequestBody(new LocalSolrQueryRequest(null, params), rsp);
if (rsp.getValues().get("success") == null) {
throw new SolrException(
SolrException.ErrorCode.SERVER_ERROR,
"Could not create :"
+ syntheticCollectionName
+ " collection: "
+ Utils.toJSONString(rsp.getValues()));
}
} catch (SolrException e) {
throw e;

} catch (Exception e) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
}
}

@Override
protected void init() throws Exception {
super.init();
Expand All @@ -280,7 +136,9 @@ protected void init() throws Exception {
public static SolrQueryRequest wrappedReq(
SolrQueryRequest delegate, String collectionName, HttpSolrCall httpSolrCall) {
Properties p = new Properties();
p.put(CoreDescriptor.CORE_COLLECTION, collectionName);
if (collectionName != null) {
p.put(CoreDescriptor.CORE_COLLECTION, collectionName);
}
p.put(CloudDescriptor.REPLICA_TYPE, Replica.Type.PULL.toString());
p.put(CoreDescriptor.CORE_SHARD, "_");

Expand Down
Loading
Loading