/*
 * 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.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.lucene.codecs.CodecUtil;
import org.apache.lucene.index.IndexWriterConfig.OpenMode;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.IOContext;
import org.apache.lucene.store.IndexInput;
import org.apache.lucene.store.IndexOutput;
import org.apache.lucene.util.IOUtils;

A SnapshotDeletionPolicy which adds a persistence layer so that snapshots can be maintained across the life of an application. The snapshots are persisted in a Directory and are committed as soon as snapshot() or release(IndexCommit) is called.

NOTE: Sharing PersistentSnapshotDeletionPolicys that write to the same directory across IndexWriters will corrupt snapshots. You should make sure every IndexWriter has its own PersistentSnapshotDeletionPolicy and that they all write to a different Directory. It is OK to use the same Directory that holds the index.

This class adds a release(long) method to release commits from a previous snapshot's IndexCommit.getGeneration.

@lucene.experimental
/** * A {@link SnapshotDeletionPolicy} which adds a persistence layer so that * snapshots can be maintained across the life of an application. The snapshots * are persisted in a {@link Directory} and are committed as soon as * {@link #snapshot()} or {@link #release(IndexCommit)} is called. * <p> * <b>NOTE:</b> Sharing {@link PersistentSnapshotDeletionPolicy}s that write to * the same directory across {@link IndexWriter}s will corrupt snapshots. You * should make sure every {@link IndexWriter} has its own * {@link PersistentSnapshotDeletionPolicy} and that they all write to a * different {@link Directory}. It is OK to use the same * Directory that holds the index. * * <p> This class adds a {@link #release(long)} method to * release commits from a previous snapshot's {@link IndexCommit#getGeneration}. * * @lucene.experimental */
public class PersistentSnapshotDeletionPolicy extends SnapshotDeletionPolicy {
Prefix used for the save file.
/** Prefix used for the save file. */
public static final String SNAPSHOTS_PREFIX = "snapshots_"; private static final int VERSION_START = 0; private static final int VERSION_CURRENT = VERSION_START; private static final String CODEC_NAME = "snapshots"; // The index writer which maintains the snapshots metadata private long nextWriteGen; private final Directory dir;
PersistentSnapshotDeletionPolicy wraps another IndexDeletionPolicy to enable flexible snapshotting, passing OpenMode.CREATE_OR_APPEND by default.
Params:
  • primary – the IndexDeletionPolicy that is used on non-snapshotted commits. Snapshotted commits, by definition, are not deleted until explicitly released via release.
  • dir – the Directory which will be used to persist the snapshots information.
/** * {@link PersistentSnapshotDeletionPolicy} wraps another * {@link IndexDeletionPolicy} to enable flexible * snapshotting, passing {@link OpenMode#CREATE_OR_APPEND} * by default. * * @param primary * the {@link IndexDeletionPolicy} that is used on non-snapshotted * commits. Snapshotted commits, by definition, are not deleted until * explicitly released via {@link #release}. * @param dir * the {@link Directory} which will be used to persist the snapshots * information. */
public PersistentSnapshotDeletionPolicy(IndexDeletionPolicy primary, Directory dir) throws IOException { this(primary, dir, OpenMode.CREATE_OR_APPEND); }
PersistentSnapshotDeletionPolicy wraps another IndexDeletionPolicy to enable flexible snapshotting.
Params:
  • primary – the IndexDeletionPolicy that is used on non-snapshotted commits. Snapshotted commits, by definition, are not deleted until explicitly released via release.
  • dir – the Directory which will be used to persist the snapshots information.
  • mode – specifies whether a new index should be created, deleting all existing snapshots information (immediately), or open an existing index, initializing the class with the snapshots information.
/** * {@link PersistentSnapshotDeletionPolicy} wraps another * {@link IndexDeletionPolicy} to enable flexible snapshotting. * * @param primary * the {@link IndexDeletionPolicy} that is used on non-snapshotted * commits. Snapshotted commits, by definition, are not deleted until * explicitly released via {@link #release}. * @param dir * the {@link Directory} which will be used to persist the snapshots * information. * @param mode * specifies whether a new index should be created, deleting all * existing snapshots information (immediately), or open an existing * index, initializing the class with the snapshots information. */
public PersistentSnapshotDeletionPolicy(IndexDeletionPolicy primary, Directory dir, OpenMode mode) throws IOException { super(primary); this.dir = dir; if (mode == OpenMode.CREATE) { clearPriorSnapshots(); } loadPriorSnapshots(); if (mode == OpenMode.APPEND && nextWriteGen == 0) { throw new IllegalStateException("no snapshots stored in this directory"); } }
Snapshots the last commit. Once this method returns, the snapshot information is persisted in the directory.
See Also:
  • snapshot.snapshot
/** * Snapshots the last commit. Once this method returns, the * snapshot information is persisted in the directory. * * @see SnapshotDeletionPolicy#snapshot */
@Override public synchronized IndexCommit snapshot() throws IOException { IndexCommit ic = super.snapshot(); boolean success = false; try { persist(); success = true; } finally { if (!success) { try { super.release(ic); } catch (Exception e) { // Suppress so we keep throwing original exception } } } return ic; }
Deletes a snapshotted commit. Once this method returns, the snapshot information is persisted in the directory.
See Also:
  • release.release
/** * Deletes a snapshotted commit. Once this method returns, the snapshot * information is persisted in the directory. * * @see SnapshotDeletionPolicy#release */
@Override public synchronized void release(IndexCommit commit) throws IOException { super.release(commit); boolean success = false; try { persist(); success = true; } finally { if (!success) { try { incRef(commit); } catch (Exception e) { // Suppress so we keep throwing original exception } } } }
Deletes a snapshotted commit by generation. Once this method returns, the snapshot information is persisted in the directory.
See Also:
/** * Deletes a snapshotted commit by generation. Once this method returns, the snapshot * information is persisted in the directory. * * @see IndexCommit#getGeneration * @see SnapshotDeletionPolicy#release */
public synchronized void release(long gen) throws IOException { super.releaseGen(gen); persist(); } synchronized private void persist() throws IOException { String fileName = SNAPSHOTS_PREFIX + nextWriteGen; IndexOutput out = dir.createOutput(fileName, IOContext.DEFAULT); boolean success = false; try { CodecUtil.writeHeader(out, CODEC_NAME, VERSION_CURRENT); out.writeVInt(refCounts.size()); for(Entry<Long,Integer> ent : refCounts.entrySet()) { out.writeVLong(ent.getKey()); out.writeVInt(ent.getValue()); } success = true; } finally { if (!success) { IOUtils.closeWhileHandlingException(out); IOUtils.deleteFilesIgnoringExceptions(dir, fileName); } else { IOUtils.close(out); } } dir.sync(Collections.singletonList(fileName)); if (nextWriteGen > 0) { String lastSaveFile = SNAPSHOTS_PREFIX + (nextWriteGen-1); // exception OK: likely it didn't exist IOUtils.deleteFilesIgnoringExceptions(dir, lastSaveFile); } nextWriteGen++; } private synchronized void clearPriorSnapshots() throws IOException { for(String file : dir.listAll()) { if (file.startsWith(SNAPSHOTS_PREFIX)) { dir.deleteFile(file); } } }
Returns the file name the snapshots are currently saved to, or null if no snapshots have been saved.
/** Returns the file name the snapshots are currently * saved to, or null if no snapshots have been saved. */
public String getLastSaveFile() { if (nextWriteGen == 0) { return null; } else { return SNAPSHOTS_PREFIX + (nextWriteGen-1); } }
Reads the snapshots information from the given Directory. This method can be used if the snapshots information is needed, however you cannot instantiate the deletion policy (because e.g., some other process keeps a lock on the snapshots directory).
/** * Reads the snapshots information from the given {@link Directory}. This * method can be used if the snapshots information is needed, however you * cannot instantiate the deletion policy (because e.g., some other process * keeps a lock on the snapshots directory). */
private synchronized void loadPriorSnapshots() throws IOException { long genLoaded = -1; IOException ioe = null; List<String> snapshotFiles = new ArrayList<>(); for(String file : dir.listAll()) { if (file.startsWith(SNAPSHOTS_PREFIX)) { long gen = Long.parseLong(file.substring(SNAPSHOTS_PREFIX.length())); if (genLoaded == -1 || gen > genLoaded) { snapshotFiles.add(file); Map<Long,Integer> m = new HashMap<>(); IndexInput in = dir.openInput(file, IOContext.DEFAULT); try { CodecUtil.checkHeader(in, CODEC_NAME, VERSION_START, VERSION_START); int count = in.readVInt(); for(int i=0;i<count;i++) { long commitGen = in.readVLong(); int refCount = in.readVInt(); m.put(commitGen, refCount); } } catch (IOException ioe2) { // Save first exception & throw in the end if (ioe == null) { ioe = ioe2; } } finally { in.close(); } genLoaded = gen; refCounts.clear(); refCounts.putAll(m); } } } if (genLoaded == -1) { // Nothing was loaded... if (ioe != null) { // ... not for lack of trying: throw ioe; } } else { if (snapshotFiles.size() > 1) { // Remove any broken / old snapshot files: String curFileName = SNAPSHOTS_PREFIX + genLoaded; for(String file : snapshotFiles) { if (!curFileName.equals(file)) { IOUtils.deleteFilesIgnoringExceptions(dir, file); } } } nextWriteGen = 1+genLoaded; } } }