All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.solr.core.snapshots.SolrSnapshotManager Maven / Gradle / Ivy

There is a newer version: 9.7.0
Show newest version
/*
 * 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.core.snapshots;

import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import org.apache.lucene.index.IndexCommit;
import org.apache.lucene.index.IndexDeletionPolicy;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.IndexWriterConfig.OpenMode;
import org.apache.lucene.index.NoMergePolicy;
import org.apache.lucene.store.Directory;
import org.apache.solr.common.cloud.SolrZkClient;
import org.apache.solr.common.util.Utils;
import org.apache.solr.core.SolrCore;
import org.apache.solr.core.snapshots.SolrSnapshotMetaDataManager.SnapshotMetaData;
import org.apache.solr.update.SolrIndexWriter;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This class provides functionality required to handle the data files corresponding to Solr snapshots.
 */
public class SolrSnapshotManager {
  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

  public static final String INDEX_DIR_PATH = "indexDirPath";
  public static final String GENERATION_NUM = "generation";
  public static final String SNAPSHOT_STATUS = "status";
  public static final String CREATION_DATE = "creationDate";
  public static final String SNAPSHOT_REPLICAS = "replicas";
  public static final String SNAPSHOTS_INFO = "snapshots";
  public static final String LEADER = "leader";
  public static final String SHARD_ID = "shard_id";
  public static final String FILE_LIST = "files";

  /**
   * This method returns if a named snapshot exists for the specified collection.
   *
   * @param zkClient Zookeeper client
   * @param collectionName The name of the collection
   * @param commitName The name of the snapshot
   * @return true if the named snapshot exists
   *         false Otherwise
   * @throws KeeperException In case of Zookeeper error
   * @throws InterruptedException In case of thread interruption.
   */
  public static boolean snapshotExists(SolrZkClient zkClient, String collectionName, String commitName)
      throws KeeperException, InterruptedException {
    String zkPath = getSnapshotMetaDataZkPath(collectionName, Optional.ofNullable(commitName));
    return zkClient.exists(zkPath, true);
  }

  /**
   * This method creates an entry for the named snapshot for the specified collection in Zookeeper.
   *
   * @param zkClient Zookeeper client
   * @param collectionName The name of the collection
   * @param meta The {@linkplain CollectionSnapshotMetaData} corresponding to named snapshot
   * @throws KeeperException In case of Zookeeper error
   * @throws InterruptedException In case of thread interruption.
   */
  public static void createCollectionLevelSnapshot(SolrZkClient zkClient, String collectionName,
      CollectionSnapshotMetaData meta) throws KeeperException, InterruptedException {
    String zkPath = getSnapshotMetaDataZkPath(collectionName, Optional.of(meta.getName()));
    zkClient.makePath(zkPath, Utils.toJSON(meta), CreateMode.PERSISTENT, true);
  }

  /**
   * This method updates an entry for the named snapshot for the specified collection in Zookeeper.
   *
   * @param zkClient Zookeeper client
   * @param collectionName  The name of the collection
   * @param meta The {@linkplain CollectionSnapshotMetaData} corresponding to named snapshot
   * @throws KeeperException In case of Zookeeper error
   * @throws InterruptedException In case of thread interruption.
   */
  public static void updateCollectionLevelSnapshot(SolrZkClient zkClient, String collectionName,
      CollectionSnapshotMetaData meta) throws KeeperException, InterruptedException {
    String zkPath = getSnapshotMetaDataZkPath(collectionName, Optional.of(meta.getName()));
    zkClient.setData(zkPath, Utils.toJSON(meta), -1, true);
  }

  /**
   * This method deletes an entry for the named snapshot for the specified collection in Zookeeper.
   *
   * @param zkClient Zookeeper client
   * @param collectionName The name of the collection
   * @param commitName  The name of the snapshot
   * @throws InterruptedException In case of thread interruption.
   * @throws KeeperException  In case of Zookeeper error
   */
  public static void deleteCollectionLevelSnapshot(SolrZkClient zkClient, String collectionName, String commitName)
      throws InterruptedException, KeeperException {
    String zkPath = getSnapshotMetaDataZkPath(collectionName, Optional.of(commitName));
    zkClient.delete(zkPath, -1, true);
  }

  /**
   * This method deletes all snapshots for the specified collection in Zookeeper.
   *
   * @param zkClient  Zookeeper client
   * @param collectionName The name of the collection
   * @throws InterruptedException In case of thread interruption.
   * @throws KeeperException In case of Zookeeper error
   */
  public static void cleanupCollectionLevelSnapshots(SolrZkClient zkClient, String collectionName)
      throws InterruptedException, KeeperException {
    String zkPath = getSnapshotMetaDataZkPath(collectionName, Optional.empty());
    try {
      // Delete the meta-data for each snapshot.
      Collection snapshots = zkClient.getChildren(zkPath, null, true);
      for (String snapshot : snapshots) {
        String path = getSnapshotMetaDataZkPath(collectionName, Optional.of(snapshot));
        try {
          zkClient.delete(path, -1, true);
        } catch (KeeperException ex) {
          // Gracefully handle the case when the zk node doesn't exist
          if ( ex.code() != KeeperException.Code.NONODE ) {
            throw ex;
          }
        }
      }

      // Delete the parent node.
      zkClient.delete(zkPath, -1, true);
    } catch (KeeperException ex) {
      // Gracefully handle the case when the zk node doesn't exist (e.g. if no snapshots were created for this collection).
      if ( ex.code() != KeeperException.Code.NONODE ) {
        throw ex;
      }
    }
  }

  /**
   * This method returns the {@linkplain CollectionSnapshotMetaData} for the named snapshot for the specified collection in Zookeeper.
   *
   * @param zkClient  Zookeeper client
   * @param collectionName  The name of the collection
   * @param commitName The name of the snapshot
   * @return (Optional) the {@linkplain CollectionSnapshotMetaData}
   * @throws InterruptedException In case of thread interruption.
   * @throws KeeperException In case of Zookeeper error
   */
  public static Optional getCollectionLevelSnapshot(SolrZkClient zkClient, String collectionName, String commitName)
      throws InterruptedException, KeeperException {
    String zkPath = getSnapshotMetaDataZkPath(collectionName, Optional.of(commitName));
    try {
      Map data = (Map)Utils.fromJSON(zkClient.getData(zkPath, null, null, true));
      return Optional.of(new CollectionSnapshotMetaData(data));
    } catch (KeeperException ex) {
      // Gracefully handle the case when the zk node for a specific
      // snapshot doesn't exist (e.g. due to a concurrent delete operation).
      if ( ex.code() == KeeperException.Code.NONODE ) {
        return Optional.empty();
      }
      throw ex;
    }
  }

  /**
   * This method returns the {@linkplain CollectionSnapshotMetaData} for each named snapshot for the specified collection in Zookeeper.
   *
   * @param zkClient Zookeeper client
   * @param collectionName The name of the collection
   * @return the {@linkplain CollectionSnapshotMetaData} for each named snapshot
   * @throws InterruptedException In case of thread interruption.
   * @throws KeeperException In case of Zookeeper error
   */
  public static Collection listSnapshots(SolrZkClient zkClient, String collectionName)
      throws InterruptedException, KeeperException {
    Collection result = new ArrayList<>();
    String zkPath = getSnapshotMetaDataZkPath(collectionName, Optional.empty());

    try {
      Collection snapshots = zkClient.getChildren(zkPath, null, true);
      for (String snapshot : snapshots) {
        Optional s = getCollectionLevelSnapshot(zkClient, collectionName, snapshot);
        if (s.isPresent()) {
          result.add(s.get());
        }
      }
    } catch (KeeperException ex) {
      // Gracefully handle the case when the zk node doesn't exist (e.g. due to a concurrent delete collection operation).
      if ( ex.code() != KeeperException.Code.NONODE ) {
        throw ex;
      }
    }
    return result;
  }


  /**
   * This method deletes index files of the {@linkplain IndexCommit} for the specified generation number.
   *
   * @param core The Solr core
   * @param dir The index directory storing the snapshot.
   * @param gen The generation number of the {@linkplain IndexCommit} to be deleted.
   * @throws IOException in case of I/O errors.
   */
  public static void deleteSnapshotIndexFiles(SolrCore core, Directory dir, final long gen) throws IOException {
    deleteSnapshotIndexFiles(core, dir, new IndexDeletionPolicy() {
      @Override
      public void onInit(List commits) throws IOException {
        for (IndexCommit ic : commits) {
          if (gen == ic.getGeneration()) {
            log.info("Deleting non-snapshotted index commit with generation {}", ic.getGeneration());
            ic.delete();
          }
        }
      }

      @Override
      public void onCommit(List commits)
          throws IOException {}
    });
  }

  /**
   * This method deletes index files not associated with the specified snapshots.
   *
   * @param core The Solr core
   * @param dir The index directory storing the snapshot.
   * @param snapshots The snapshots to be preserved.
   * @throws IOException in case of I/O errors.
   */
  public static void deleteNonSnapshotIndexFiles(SolrCore core, Directory dir, Collection snapshots) throws IOException {
    final Set genNumbers = new HashSet<>();
    for (SnapshotMetaData m : snapshots) {
      genNumbers.add(m.getGenerationNumber());
    }

    deleteSnapshotIndexFiles(core, dir, new IndexDeletionPolicy() {
      @Override
      public void onInit(List commits) throws IOException {
        for (IndexCommit ic : commits) {
          if (!genNumbers.contains(ic.getGeneration())) {
            log.info("Deleting non-snapshotted index commit with generation {}", ic.getGeneration());
            ic.delete();
          }
        }
      }

      @Override
      public void onCommit(List commits)
          throws IOException {}
    });
  }

  /**
   * This method deletes index files of the {@linkplain IndexCommit} for the specified generation number.
   *
   * @param core The Solr core
   * @param dir The index directory storing the snapshot.
   * @throws IOException in case of I/O errors.
   */
  private static void deleteSnapshotIndexFiles(SolrCore core, Directory dir, IndexDeletionPolicy delPolicy) throws IOException {
    IndexWriterConfig conf = core.getSolrConfig().indexConfig.toIndexWriterConfig(core);
    conf.setOpenMode(OpenMode.APPEND);
    conf.setMergePolicy(NoMergePolicy.INSTANCE);//Don't want to merge any commits here!
    conf.setIndexDeletionPolicy(delPolicy);
    conf.setCodec(core.getCodec());

    try (SolrIndexWriter iw = new SolrIndexWriter("SolrSnapshotCleaner", dir, conf)) {
      // Do nothing. The only purpose of opening index writer is to invoke the Lucene IndexDeletionPolicy#onInit
      // method so that we can cleanup the files associated with specified index commit.
      // Note the index writer creates a new commit during the close() operation (which is harmless).
    }
  }

  private static String getSnapshotMetaDataZkPath(String collectionName, Optional commitName) {
    if (commitName.isPresent()) {
      return "/snapshots/"+collectionName+"/"+commitName.get();
    }
    return "/snapshots/"+collectionName;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy