/*
 * 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.lucene.index;


import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.IOException;

import org.apache.lucene.store.Directory;

An IndexDeletionPolicy that wraps any other IndexDeletionPolicy and adds the ability to hold and later release snapshots of an index. While a snapshot is held, the IndexWriter will not remove any files associated with it even if the index is otherwise being actively, arbitrarily changed. Because we wrap another arbitrary IndexDeletionPolicy, this gives you the freedom to continue using whatever IndexDeletionPolicy you would normally want to use with your index.

This class maintains all snapshots in-memory, and so the information is not persisted and not protected against system failures. If persistence is important, you can use PersistentSnapshotDeletionPolicy.

@lucene.experimental
/** * An {@link IndexDeletionPolicy} that wraps any other * {@link IndexDeletionPolicy} and adds the ability to hold and later release * snapshots of an index. While a snapshot is held, the {@link IndexWriter} will * not remove any files associated with it even if the index is otherwise being * actively, arbitrarily changed. Because we wrap another arbitrary * {@link IndexDeletionPolicy}, this gives you the freedom to continue using * whatever {@link IndexDeletionPolicy} you would normally want to use with your * index. * * <p> * This class maintains all snapshots in-memory, and so the information is not * persisted and not protected against system failures. If persistence is * important, you can use {@link PersistentSnapshotDeletionPolicy}. * * @lucene.experimental */
public class SnapshotDeletionPolicy extends IndexDeletionPolicy {
Records how many snapshots are held against each commit generation
/** Records how many snapshots are held against each * commit generation */
protected final Map<Long,Integer> refCounts = new HashMap<>();
Used to map gen to IndexCommit.
/** Used to map gen to IndexCommit. */
protected final Map<Long,IndexCommit> indexCommits = new HashMap<>(); /** Wrapped {@link IndexDeletionPolicy} */ private final IndexDeletionPolicy primary;
Most recently committed IndexCommit.
/** Most recently committed {@link IndexCommit}. */
protected IndexCommit lastCommit;
Used to detect misuse
/** Used to detect misuse */
private boolean initCalled;
Sole constructor, taking the incoming IndexDeletionPolicy to wrap.
/** Sole constructor, taking the incoming {@link * IndexDeletionPolicy} to wrap. */
public SnapshotDeletionPolicy(IndexDeletionPolicy primary) { this.primary = primary; } @Override public synchronized void onCommit(List<? extends IndexCommit> commits) throws IOException { primary.onCommit(wrapCommits(commits)); lastCommit = commits.get(commits.size() - 1); } @Override public synchronized void onInit(List<? extends IndexCommit> commits) throws IOException { initCalled = true; primary.onInit(wrapCommits(commits)); for(IndexCommit commit : commits) { if (refCounts.containsKey(commit.getGeneration())) { indexCommits.put(commit.getGeneration(), commit); } } if (!commits.isEmpty()) { lastCommit = commits.get(commits.size() - 1); } }
Release a snapshotted commit.
Params:
  • commit – the commit previously returned by snapshot
/** * Release a snapshotted commit. * * @param commit * the commit previously returned by {@link #snapshot} */
public synchronized void release(IndexCommit commit) throws IOException { long gen = commit.getGeneration(); releaseGen(gen); }
Release a snapshot by generation.
/** Release a snapshot by generation. */
protected void releaseGen(long gen) throws IOException { if (!initCalled) { throw new IllegalStateException("this instance is not being used by IndexWriter; be sure to use the instance returned from writer.getConfig().getIndexDeletionPolicy()"); } Integer refCount = refCounts.get(gen); if (refCount == null) { throw new IllegalArgumentException("commit gen=" + gen + " is not currently snapshotted"); } int refCountInt = refCount.intValue(); assert refCountInt > 0; refCountInt--; if (refCountInt == 0) { refCounts.remove(gen); indexCommits.remove(gen); } else { refCounts.put(gen, refCountInt); } }
Increments the refCount for this IndexCommit.
/** Increments the refCount for this {@link IndexCommit}. */
protected synchronized void incRef(IndexCommit ic) { long gen = ic.getGeneration(); Integer refCount = refCounts.get(gen); int refCountInt; if (refCount == null) { indexCommits.put(gen, lastCommit); refCountInt = 0; } else { refCountInt = refCount.intValue(); } refCounts.put(gen, refCountInt+1); }
Snapshots the last commit and returns it. Once a commit is 'snapshotted,' it is protected from deletion (as long as this IndexDeletionPolicy is used). The snapshot can be removed by calling release(IndexCommit) followed by a call to IndexWriter.deleteUnusedFiles().

NOTE: while the snapshot is held, the files it references will not be deleted, which will consume additional disk space in your index. If you take a snapshot at a particularly bad time (say just before you call forceMerge) then in the worst case this could consume an extra 1X of your total index size, until you release the snapshot.

Throws:
Returns:the IndexCommit that was snapshotted.
/** * Snapshots the last commit and returns it. Once a commit is 'snapshotted,' it is protected * from deletion (as long as this {@link IndexDeletionPolicy} is used). The * snapshot can be removed by calling {@link #release(IndexCommit)} followed * by a call to {@link IndexWriter#deleteUnusedFiles()}. * * <p> * <b>NOTE:</b> while the snapshot is held, the files it references will not * be deleted, which will consume additional disk space in your index. If you * take a snapshot at a particularly bad time (say just before you call * forceMerge) then in the worst case this could consume an extra 1X of your * total index size, until you release the snapshot. * * @throws IllegalStateException * if this index does not have any commits yet * @return the {@link IndexCommit} that was snapshotted. */
public synchronized IndexCommit snapshot() throws IOException { if (!initCalled) { throw new IllegalStateException("this instance is not being used by IndexWriter; be sure to use the instance returned from writer.getConfig().getIndexDeletionPolicy()"); } if (lastCommit == null) { // No commit yet, eg this is a new IndexWriter: throw new IllegalStateException("No index commit to snapshot"); } incRef(lastCommit); return lastCommit; }
Returns all IndexCommits held by at least one snapshot.
/** Returns all IndexCommits held by at least one snapshot. */
public synchronized List<IndexCommit> getSnapshots() { return new ArrayList<>(indexCommits.values()); }
Returns the total number of snapshots currently held.
/** Returns the total number of snapshots currently held. */
public synchronized int getSnapshotCount() { int total = 0; for(Integer refCount : refCounts.values()) { total += refCount.intValue(); } return total; }
Retrieve an IndexCommit from its generation; returns null if this IndexCommit is not currently snapshotted
/** Retrieve an {@link IndexCommit} from its generation; * returns null if this IndexCommit is not currently * snapshotted */
public synchronized IndexCommit getIndexCommit(long gen) { return indexCommits.get(gen); } /** Wraps each {@link IndexCommit} as a {@link * SnapshotCommitPoint}. */ private List<IndexCommit> wrapCommits(List<? extends IndexCommit> commits) { List<IndexCommit> wrappedCommits = new ArrayList<>(commits.size()); for (IndexCommit ic : commits) { wrappedCommits.add(new SnapshotCommitPoint(ic)); } return wrappedCommits; }
Wraps a provided IndexCommit and prevents it from being deleted.
/** Wraps a provided {@link IndexCommit} and prevents it * from being deleted. */
private class SnapshotCommitPoint extends IndexCommit {
The IndexCommit we are preventing from deletion.
/** The {@link IndexCommit} we are preventing from deletion. */
protected IndexCommit cp;
Creates a SnapshotCommitPoint wrapping the provided IndexCommit.
/** Creates a {@code SnapshotCommitPoint} wrapping the provided * {@link IndexCommit}. */
protected SnapshotCommitPoint(IndexCommit cp) { this.cp = cp; } @Override public String toString() { return "SnapshotDeletionPolicy.SnapshotCommitPoint(" + cp + ")"; } @Override public void delete() { synchronized (SnapshotDeletionPolicy.this) { // Suppress the delete request if this commit point is // currently snapshotted. if (!refCounts.containsKey(cp.getGeneration())) { cp.delete(); } } } @Override public Directory getDirectory() { return cp.getDirectory(); } @Override public Collection<String> getFileNames() throws IOException { return cp.getFileNames(); } @Override public long getGeneration() { return cp.getGeneration(); } @Override public String getSegmentsFileName() { return cp.getSegmentsFileName(); } @Override public Map<String, String> getUserData() throws IOException { return cp.getUserData(); } @Override public boolean isDeleted() { return cp.isDeleted(); } @Override public int getSegmentCount() { return cp.getSegmentCount(); } } }