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.15.1
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.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 final 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 IndexCommit lastCommit; // the most recent commit point 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 synchronized 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 synchronized void onCommit(List commits) throws IOException { final int keptPosition = indexOfKeptCommits(commits, globalCheckpointSupplier.getAsLong()); lastCommit = commits.get(commits.size() - 1); safeCommit = commits.get(keptPosition); for (int i = 0; i < keptPosition; i++) { if (snapshottedCommits.containsKey(commits.get(i)) == false) { deleteCommit(commits.get(i)); } } updateRetentionPolicy(); } 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))); } /** * 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 v6.2, and we haven't retained a safe commit yet, this method will return the oldest 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; } // 5.x commits do not contain MAX_SEQ_NO, we should not keep it and the older commits. if (commitUserData.containsKey(SequenceNumbers.MAX_SEQ_NO) == false) { return Math.min(commits.size() - 1, i + 1); } final long maxSeqNoFromCommit = Long.parseLong(commitUserData.get(SequenceNumbers.MAX_SEQ_NO)); // If a 6.x node with a 5.x index is promoted to be a primary, it will flush a new index commit to // make sure translog operations without seqno will never be replayed (see IndexShard#updateShardState). // However the global checkpoint is still UNASSIGNED and the max_seqno of both commits are NO_OPS_PERFORMED. // If this policy considers the first commit as a safe commit, we will send the first commit without replaying // translog between these commits to the replica in a peer-recovery. This causes the replica missing those operations. // To prevent this, we should not keep more than one commit whose max_seqno is NO_OPS_PERFORMED. // Once we can retain a safe commit, a NO_OPS_PERFORMED commit will be deleted just as other commits. if (maxSeqNoFromCommit == SequenceNumbers.NO_OPS_PERFORMED) { return i; } if (maxSeqNoFromCommit <= globalCheckpoint) { return i; } } /* * We may reach to this point in these cases: * 1. In the previous 6.x, we keep only the last commit - which is likely not a safe commit if writes are in progress. * Thus, after upgrading, we may not find a safe commit until we can reserve one. * 2. In peer-recovery, if the file-based happens, a replica will be received the latest commit from a primary. * However, that commit may not be a safe commit if writes are in progress in the primary. */ 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 release some index commits with the latest global checkpoint. */ boolean hasUnreferencedCommits() throws IOException { final IndexCommit lastCommit = this.lastCommit; if (safeCommit != lastCommit) { // Race condition can happen but harmless if (lastCommit.getUserData().containsKey(SequenceNumbers.MAX_SEQ_NO)) { final long maxSeqNoFromLastCommit = Long.parseLong(lastCommit.getUserData().get(SequenceNumbers.MAX_SEQ_NO)); // We can clean up the current safe commit if the last commit is safe return globalCheckpointSupplier.getAsLong() >= maxSeqNoFromLastCommit; } } return false; } /** * 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