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

org.elasticsearch.index.engine.CombinedDeletionPolicy Maven / Gradle / Ivy

There is a newer version: 8.13.2
Show newest version
/*
 * Licensed to Elasticsearch under one or more contributor
 * license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright
 * ownership. Elasticsearch 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.elasticsearch.index.engine;

import com.carrotsearch.hppc.ObjectIntHashMap;
import org.apache.logging.log4j.Logger;
import org.apache.lucene.index.IndexCommit;
import org.apache.lucene.index.IndexDeletionPolicy;
import org.apache.lucene.index.SegmentInfos;
import org.apache.lucene.store.Directory;
import org.elasticsearch.index.seqno.SequenceNumbers;
import org.elasticsearch.index.translog.Translog;
import org.elasticsearch.index.translog.TranslogDeletionPolicy;

import java.io.IOException;
import java.nio.file.Path;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.function.LongSupplier;

/**
 * An {@link IndexDeletionPolicy} that coordinates between Lucene's commits and the retention of translog generation files,
 * making sure that all translog files that are needed to recover from the Lucene commit are not deleted.
 * 

* In particular, this policy will delete index commits whose max sequence number is at most * the current global checkpoint except the index commit which has the highest max sequence number among those. */ public class CombinedDeletionPolicy extends IndexDeletionPolicy { private final Logger logger; private final TranslogDeletionPolicy translogDeletionPolicy; private final SoftDeletesPolicy softDeletesPolicy; private final LongSupplier globalCheckpointSupplier; private final ObjectIntHashMap snapshottedCommits; // Number of snapshots held against each commit point. private volatile IndexCommit safeCommit; // the most recent safe commit point - its max_seqno at most the persisted global checkpoint. private volatile long maxSeqNoOfNextSafeCommit; private volatile IndexCommit lastCommit; // the most recent commit point private volatile SafeCommitInfo safeCommitInfo = SafeCommitInfo.EMPTY; CombinedDeletionPolicy(Logger logger, TranslogDeletionPolicy translogDeletionPolicy, SoftDeletesPolicy softDeletesPolicy, LongSupplier globalCheckpointSupplier) { this.logger = logger; this.translogDeletionPolicy = translogDeletionPolicy; this.softDeletesPolicy = softDeletesPolicy; this.globalCheckpointSupplier = globalCheckpointSupplier; this.snapshottedCommits = new ObjectIntHashMap<>(); } @Override public void onInit(List commits) throws IOException { assert commits.isEmpty() == false : "index is opened, but we have no commits"; onCommit(commits); if (safeCommit != commits.get(commits.size() - 1)) { throw new IllegalStateException("Engine is opened, but the last commit isn't safe. Global checkpoint [" + globalCheckpointSupplier.getAsLong() + "], seqNo is last commit [" + SequenceNumbers.loadSeqNoInfoFromLuceneCommit(lastCommit.getUserData().entrySet()) + "], " + "seqNos in safe commit [" + SequenceNumbers.loadSeqNoInfoFromLuceneCommit(safeCommit.getUserData().entrySet()) + "]"); } } @Override public void onCommit(List commits) throws IOException { final IndexCommit safeCommit; synchronized (this) { final int keptPosition = indexOfKeptCommits(commits, globalCheckpointSupplier.getAsLong()); this.safeCommitInfo = SafeCommitInfo.EMPTY; this.lastCommit = commits.get(commits.size() - 1); this.safeCommit = commits.get(keptPosition); if (keptPosition == commits.size() - 1) { this.maxSeqNoOfNextSafeCommit = Long.MAX_VALUE; } else { this.maxSeqNoOfNextSafeCommit = Long.parseLong(commits.get(keptPosition + 1).getUserData().get(SequenceNumbers.MAX_SEQ_NO)); } for (int i = 0; i < keptPosition; i++) { if (snapshottedCommits.containsKey(commits.get(i)) == false) { deleteCommit(commits.get(i)); } } updateRetentionPolicy(); safeCommit = this.safeCommit; } assert Thread.holdsLock(this) == false : "should not block concurrent acquire or relesase"; safeCommitInfo = new SafeCommitInfo(Long.parseLong( safeCommit.getUserData().get(SequenceNumbers.LOCAL_CHECKPOINT_KEY)), getDocCountOfCommit(safeCommit)); // This is protected from concurrent calls by a lock on the IndexWriter, but this assertion makes sure that we notice if that ceases // to be true in future. It is not disastrous if safeCommitInfo refers to an older safeCommit, it just means that we might retain a // bit more history and do a few more ops-based recoveries than we would otherwise. final IndexCommit newSafeCommit = this.safeCommit; assert safeCommit == newSafeCommit : "onCommit called concurrently? " + safeCommit.getGeneration() + " vs " + newSafeCommit.getGeneration(); } private void deleteCommit(IndexCommit commit) throws IOException { assert commit.isDeleted() == false : "Index commit [" + commitDescription(commit) + "] is deleted twice"; logger.debug("Delete index commit [{}]", commitDescription(commit)); commit.delete(); assert commit.isDeleted() : "Deletion commit [" + commitDescription(commit) + "] was suppressed"; } private void updateRetentionPolicy() throws IOException { assert Thread.holdsLock(this); logger.debug("Safe commit [{}], last commit [{}]", commitDescription(safeCommit), commitDescription(lastCommit)); assert safeCommit.isDeleted() == false : "The safe commit must not be deleted"; final long minRequiredGen = Long.parseLong(safeCommit.getUserData().get(Translog.TRANSLOG_GENERATION_KEY)); assert lastCommit.isDeleted() == false : "The last commit must not be deleted"; final long lastGen = Long.parseLong(lastCommit.getUserData().get(Translog.TRANSLOG_GENERATION_KEY)); assert minRequiredGen <= lastGen : "minRequiredGen must not be greater than lastGen"; translogDeletionPolicy.setTranslogGenerationOfLastCommit(lastGen); translogDeletionPolicy.setMinTranslogGenerationForRecovery(minRequiredGen); softDeletesPolicy.setLocalCheckpointOfSafeCommit( Long.parseLong(safeCommit.getUserData().get(SequenceNumbers.LOCAL_CHECKPOINT_KEY))); } protected int getDocCountOfCommit(IndexCommit indexCommit) throws IOException { return SegmentInfos.readCommit(indexCommit.getDirectory(), indexCommit.getSegmentsFileName()).totalMaxDoc(); } SafeCommitInfo getSafeCommitInfo() { return safeCommitInfo; } /** * Captures the most recent commit point {@link #lastCommit} or the most recent safe commit point {@link #safeCommit}. * Index files of the capturing commit point won't be released until the commit reference is closed. * * @param acquiringSafeCommit captures the most recent safe commit point if true; otherwise captures the most recent commit point. */ synchronized IndexCommit acquireIndexCommit(boolean acquiringSafeCommit) { assert safeCommit != null : "Safe commit is not initialized yet"; assert lastCommit != null : "Last commit is not initialized yet"; final IndexCommit snapshotting = acquiringSafeCommit ? safeCommit : lastCommit; snapshottedCommits.addTo(snapshotting, 1); // increase refCount return new SnapshotIndexCommit(snapshotting); } /** * Releases an index commit that acquired by {@link #acquireIndexCommit(boolean)}. * * @return true if the snapshotting commit can be clean up. */ synchronized boolean releaseCommit(final IndexCommit snapshotCommit) { final IndexCommit releasingCommit = ((SnapshotIndexCommit) snapshotCommit).delegate; assert snapshottedCommits.containsKey(releasingCommit) : "Release non-snapshotted commit;" + "snapshotted commits [" + snapshottedCommits + "], releasing commit [" + releasingCommit + "]"; final int refCount = snapshottedCommits.addTo(releasingCommit, -1); // release refCount assert refCount >= 0 : "Number of snapshots can not be negative [" + refCount + "]"; if (refCount == 0) { snapshottedCommits.remove(releasingCommit); } // The commit can be clean up only if no pending snapshot and it is neither the safe commit nor last commit. return refCount == 0 && releasingCommit.equals(safeCommit) == false && releasingCommit.equals(lastCommit) == false; } /** * Find a safe commit point from a list of existing commits based on the supplied global checkpoint. * The max sequence number of a safe commit point should be at most the global checkpoint. * If an index was created before 6.2 or recovered from remote, we might not have a safe commit. * In this case, this method will return the oldest index commit. * * @param commits a list of existing commit points * @param globalCheckpoint the persisted global checkpoint from the translog, see {@link Translog#readGlobalCheckpoint(Path, String)} * @return a safe commit or the oldest commit if a safe commit is not found */ public static IndexCommit findSafeCommitPoint(List commits, long globalCheckpoint) throws IOException { if (commits.isEmpty()) { throw new IllegalArgumentException("Commit list must not empty"); } final int keptPosition = indexOfKeptCommits(commits, globalCheckpoint); return commits.get(keptPosition); } /** * Find the highest index position of a safe index commit whose max sequence number is not greater than the global checkpoint. * Index commits with different translog UUID will be filtered out as they don't belong to this engine. */ private static int indexOfKeptCommits(List commits, long globalCheckpoint) throws IOException { final String expectedTranslogUUID = commits.get(commits.size() - 1).getUserData().get(Translog.TRANSLOG_UUID_KEY); // Commits are sorted by age (the 0th one is the oldest commit). for (int i = commits.size() - 1; i >= 0; i--) { final Map commitUserData = commits.get(i).getUserData(); // Ignore index commits with different translog uuid. if (expectedTranslogUUID.equals(commitUserData.get(Translog.TRANSLOG_UUID_KEY)) == false) { return i + 1; } final long maxSeqNoFromCommit = Long.parseLong(commitUserData.get(SequenceNumbers.MAX_SEQ_NO)); if (maxSeqNoFromCommit <= globalCheckpoint) { return i; } } // If an index was created before 6.2 or recovered from remote, we might not have a safe commit. // In this case, we return the oldest index commit instead. return 0; } /** * Checks whether the deletion policy is holding on to snapshotted commits */ synchronized boolean hasSnapshottedCommits() { return snapshottedCommits.isEmpty() == false; } /** * Checks if the deletion policy can delete some index commits with the latest global checkpoint. */ boolean hasUnreferencedCommits() { return maxSeqNoOfNextSafeCommit <= globalCheckpointSupplier.getAsLong(); } /** * Returns a description for a given {@link IndexCommit}. This should be only used for logging and debugging. */ public static String commitDescription(IndexCommit commit) throws IOException { return String.format(Locale.ROOT, "CommitPoint{segment[%s], userData[%s]}", commit.getSegmentsFileName(), commit.getUserData()); } /** * A wrapper of an index commit that prevents it from being deleted. */ private static class SnapshotIndexCommit extends IndexCommit { private final IndexCommit delegate; SnapshotIndexCommit(IndexCommit delegate) { this.delegate = delegate; } @Override public String getSegmentsFileName() { return delegate.getSegmentsFileName(); } @Override public Collection getFileNames() throws IOException { return delegate.getFileNames(); } @Override public Directory getDirectory() { return delegate.getDirectory(); } @Override public void delete() { throw new UnsupportedOperationException("A snapshot commit does not support deletion"); } @Override public boolean isDeleted() { return delegate.isDeleted(); } @Override public int getSegmentCount() { return delegate.getSegmentCount(); } @Override public long getGeneration() { return delegate.getGeneration(); } @Override public Map getUserData() throws IOException { return delegate.getUserData(); } @Override public String toString() { return "SnapshotIndexCommit{" + delegate + "}"; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy