Store Pending Deletions Fix (#40345)
FilterDirectory.getPendingDeletions does not delegate, fixed temporarily by overriding in StoreDirectory. This in turn caused duplicate file name use after a trimUnsafeCommits had been done, since a new IndexWriter would not consider the pending deletes in IndexFileDeleter. This should only happen on windows (AFAIK). Reenabled doing index updates for all tests using IndexShardTests.indexOnReplicaWithGaps (which could fail due to above when using mocked WindowsFS). Added getPendingDeletions delegation to all elasticsearch FilterDirectory subclasses that were not trivial test-only overrides to minimize the risk of hitting this issue in another case.
This commit is contained in:
parent
b5a8de9a7f
commit
bf444b9f02
|
@ -19,17 +19,19 @@
|
||||||
|
|
||||||
package org.elasticsearch.index.store;
|
package org.elasticsearch.index.store;
|
||||||
|
|
||||||
import java.io.FilterOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.channels.Channels;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.StandardOpenOption;
|
|
||||||
import org.apache.lucene.store.FSDirectory;
|
import org.apache.lucene.store.FSDirectory;
|
||||||
import org.apache.lucene.store.FilterDirectory;
|
import org.apache.lucene.store.FilterDirectory;
|
||||||
import org.apache.lucene.store.IOContext;
|
import org.apache.lucene.store.IOContext;
|
||||||
import org.apache.lucene.store.IndexOutput;
|
import org.apache.lucene.store.IndexOutput;
|
||||||
import org.apache.lucene.store.OutputStreamIndexOutput;
|
import org.apache.lucene.store.OutputStreamIndexOutput;
|
||||||
|
|
||||||
|
import java.io.FilterOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.channels.Channels;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.StandardOpenOption;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is used to wrap an existing {@link org.apache.lucene.store.FSDirectory} so that
|
* This class is used to wrap an existing {@link org.apache.lucene.store.FSDirectory} so that
|
||||||
* the new shard segment files will be opened for Read and Write access.
|
* the new shard segment files will be opened for Read and Write access.
|
||||||
|
@ -78,4 +80,10 @@ public final class SmbDirectoryWrapper extends FilterDirectory {
|
||||||
CHUNK_SIZE);
|
CHUNK_SIZE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// temporary override until LUCENE-8735 is integrated
|
||||||
|
@Override
|
||||||
|
public Set<String> getPendingDeletions() throws IOException {
|
||||||
|
return in.getPendingDeletions();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@ import org.elasticsearch.index.store.Store;
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
final class LocalShardSnapshot implements Closeable {
|
final class LocalShardSnapshot implements Closeable {
|
||||||
|
@ -116,6 +117,12 @@ final class LocalShardSnapshot implements Closeable {
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
throw new UnsupportedOperationException("nobody should close this directory wrapper");
|
throw new UnsupportedOperationException("nobody should close this directory wrapper");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// temporary override until LUCENE-8735 is integrated
|
||||||
|
@Override
|
||||||
|
public Set<String> getPendingDeletions() throws IOException {
|
||||||
|
return in.getPendingDeletions();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -259,6 +259,12 @@ final class StoreRecovery {
|
||||||
assert index.getFileDetails(dest).recovered() == l : index.getFileDetails(dest).toString();
|
assert index.getFileDetails(dest).recovered() == l : index.getFileDetails(dest).toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// temporary override until LUCENE-8735 is integrated
|
||||||
|
@Override
|
||||||
|
public Set<String> getPendingDeletions() throws IOException {
|
||||||
|
return in.getPendingDeletions();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -32,6 +32,7 @@ import java.io.IOException;
|
||||||
import java.io.UncheckedIOException;
|
import java.io.UncheckedIOException;
|
||||||
import java.nio.file.AccessDeniedException;
|
import java.nio.file.AccessDeniedException;
|
||||||
import java.nio.file.NoSuchFileException;
|
import java.nio.file.NoSuchFileException;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
final class ByteSizeCachingDirectory extends FilterDirectory {
|
final class ByteSizeCachingDirectory extends FilterDirectory {
|
||||||
|
|
||||||
|
@ -180,4 +181,9 @@ final class ByteSizeCachingDirectory extends FilterDirectory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// temporary override until LUCENE-8735 is integrated
|
||||||
|
@Override
|
||||||
|
public Set<String> getPendingDeletions() throws IOException {
|
||||||
|
return in.getPendingDeletions();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -760,6 +760,13 @@ public class Store extends AbstractIndexShardComponent implements Closeable, Ref
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "store(" + in.toString() + ")";
|
return "store(" + in.toString() + ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> getPendingDeletions() throws IOException {
|
||||||
|
// FilterDirectory.getPendingDeletions does not delegate, working around it here.
|
||||||
|
// to be removed once fixed in FilterDirectory.
|
||||||
|
return unwrap(this).getPendingDeletions();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -546,7 +546,7 @@ public class IndexShardTests extends IndexShardTestCase {
|
||||||
|
|
||||||
// most of the time this is large enough that most of the time there will be at least one gap
|
// most of the time this is large enough that most of the time there will be at least one gap
|
||||||
final int operations = 1024 - scaledRandomIntBetween(0, 1024);
|
final int operations = 1024 - scaledRandomIntBetween(0, 1024);
|
||||||
final Result result = indexOnReplicaWithGaps(indexShard, operations, Math.toIntExact(SequenceNumbers.NO_OPS_PERFORMED), false);
|
final Result result = indexOnReplicaWithGaps(indexShard, operations, Math.toIntExact(SequenceNumbers.NO_OPS_PERFORMED));
|
||||||
|
|
||||||
final int maxSeqNo = result.maxSeqNo;
|
final int maxSeqNo = result.maxSeqNo;
|
||||||
|
|
||||||
|
@ -1093,7 +1093,7 @@ public class IndexShardTests extends IndexShardTestCase {
|
||||||
public void testRestoreLocalHistoryFromTranslogOnPromotion() throws IOException, InterruptedException {
|
public void testRestoreLocalHistoryFromTranslogOnPromotion() throws IOException, InterruptedException {
|
||||||
final IndexShard indexShard = newStartedShard(false);
|
final IndexShard indexShard = newStartedShard(false);
|
||||||
final int operations = 1024 - scaledRandomIntBetween(0, 1024);
|
final int operations = 1024 - scaledRandomIntBetween(0, 1024);
|
||||||
indexOnReplicaWithGaps(indexShard, operations, Math.toIntExact(SequenceNumbers.NO_OPS_PERFORMED), true);
|
indexOnReplicaWithGaps(indexShard, operations, Math.toIntExact(SequenceNumbers.NO_OPS_PERFORMED));
|
||||||
|
|
||||||
final long maxSeqNo = indexShard.seqNoStats().getMaxSeqNo();
|
final long maxSeqNo = indexShard.seqNoStats().getMaxSeqNo();
|
||||||
final long globalCheckpointOnReplica = randomLongBetween(UNASSIGNED_SEQ_NO, indexShard.getLocalCheckpoint());
|
final long globalCheckpointOnReplica = randomLongBetween(UNASSIGNED_SEQ_NO, indexShard.getLocalCheckpoint());
|
||||||
|
@ -1159,9 +1159,7 @@ public class IndexShardTests extends IndexShardTestCase {
|
||||||
|
|
||||||
// most of the time this is large enough that most of the time there will be at least one gap
|
// most of the time this is large enough that most of the time there will be at least one gap
|
||||||
final int operations = 1024 - scaledRandomIntBetween(0, 1024);
|
final int operations = 1024 - scaledRandomIntBetween(0, 1024);
|
||||||
// todo: all tests should run with allowUpdates=true, but this specific test sometimes fails during lucene commit when updates are
|
indexOnReplicaWithGaps(indexShard, operations, Math.toIntExact(SequenceNumbers.NO_OPS_PERFORMED));
|
||||||
// added (seed = F37E9647ABE5928)
|
|
||||||
indexOnReplicaWithGaps(indexShard, operations, Math.toIntExact(SequenceNumbers.NO_OPS_PERFORMED), false);
|
|
||||||
|
|
||||||
final long globalCheckpointOnReplica = randomLongBetween(UNASSIGNED_SEQ_NO, indexShard.getLocalCheckpoint());
|
final long globalCheckpointOnReplica = randomLongBetween(UNASSIGNED_SEQ_NO, indexShard.getLocalCheckpoint());
|
||||||
indexShard.updateGlobalCheckpointOnReplica(globalCheckpointOnReplica, "test");
|
indexShard.updateGlobalCheckpointOnReplica(globalCheckpointOnReplica, "test");
|
||||||
|
@ -1204,7 +1202,7 @@ public class IndexShardTests extends IndexShardTestCase {
|
||||||
}
|
}
|
||||||
assertThat(indexShard.getMaxSeqNoOfUpdatesOrDeletes(), equalTo(newMaxSeqNoOfUpdates));
|
assertThat(indexShard.getMaxSeqNoOfUpdatesOrDeletes(), equalTo(newMaxSeqNoOfUpdates));
|
||||||
// ensure that after the local checkpoint throw back and indexing again, the local checkpoint advances
|
// ensure that after the local checkpoint throw back and indexing again, the local checkpoint advances
|
||||||
final Result result = indexOnReplicaWithGaps(indexShard, operations, Math.toIntExact(indexShard.getLocalCheckpoint()), false);
|
final Result result = indexOnReplicaWithGaps(indexShard, operations, Math.toIntExact(indexShard.getLocalCheckpoint()));
|
||||||
assertThat(indexShard.getLocalCheckpoint(), equalTo((long) result.localCheckpoint));
|
assertThat(indexShard.getLocalCheckpoint(), equalTo((long) result.localCheckpoint));
|
||||||
closeShard(indexShard, false);
|
closeShard(indexShard, false);
|
||||||
}
|
}
|
||||||
|
@ -1462,6 +1460,12 @@ public class IndexShardTests extends IndexShardTestCase {
|
||||||
return super.listAll();
|
return super.listAll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// temporary override until LUCENE-8735 is integrated
|
||||||
|
@Override
|
||||||
|
public Set<String> getPendingDeletions() throws IOException {
|
||||||
|
return in.getPendingDeletions();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
try (Store store = createStore(shardId, new IndexSettings(metaData, Settings.EMPTY), directory)) {
|
try (Store store = createStore(shardId, new IndexSettings(metaData, Settings.EMPTY), directory)) {
|
||||||
|
@ -3166,15 +3170,13 @@ public class IndexShardTests extends IndexShardTestCase {
|
||||||
* @param indexShard the shard
|
* @param indexShard the shard
|
||||||
* @param operations the number of operations
|
* @param operations the number of operations
|
||||||
* @param offset the starting sequence number
|
* @param offset the starting sequence number
|
||||||
* @param allowUpdates whether updates should be added.
|
|
||||||
* @return a pair of the maximum sequence number and whether or not a gap was introduced
|
* @return a pair of the maximum sequence number and whether or not a gap was introduced
|
||||||
* @throws IOException if an I/O exception occurs while indexing on the shard
|
* @throws IOException if an I/O exception occurs while indexing on the shard
|
||||||
*/
|
*/
|
||||||
private Result indexOnReplicaWithGaps(
|
private Result indexOnReplicaWithGaps(
|
||||||
final IndexShard indexShard,
|
final IndexShard indexShard,
|
||||||
final int operations,
|
final int operations,
|
||||||
final int offset,
|
final int offset) throws IOException {
|
||||||
boolean allowUpdates) throws IOException {
|
|
||||||
int localCheckpoint = offset;
|
int localCheckpoint = offset;
|
||||||
int max = offset;
|
int max = offset;
|
||||||
boolean gap = false;
|
boolean gap = false;
|
||||||
|
@ -3182,7 +3184,7 @@ public class IndexShardTests extends IndexShardTestCase {
|
||||||
for (int i = offset + 1; i < operations; i++) {
|
for (int i = offset + 1; i < operations; i++) {
|
||||||
if (!rarely() || i == operations - 1) { // last operation can't be a gap as it's not a gap anymore
|
if (!rarely() || i == operations - 1) { // last operation can't be a gap as it's not a gap anymore
|
||||||
final String id = ids.isEmpty() || randomBoolean() ? Integer.toString(i) : randomFrom(ids);
|
final String id = ids.isEmpty() || randomBoolean() ? Integer.toString(i) : randomFrom(ids);
|
||||||
if (allowUpdates && ids.add(id) == false) { // this is an update
|
if (ids.add(id) == false) { // this is an update
|
||||||
indexShard.advanceMaxSeqNoOfUpdatesOrDeletes(i);
|
indexShard.advanceMaxSeqNoOfUpdatesOrDeletes(i);
|
||||||
}
|
}
|
||||||
SourceToParse sourceToParse = new SourceToParse(indexShard.shardId().getIndexName(), "_doc", id,
|
SourceToParse sourceToParse = new SourceToParse(indexShard.shardId().getIndexName(), "_doc", id,
|
||||||
|
@ -3639,7 +3641,7 @@ public class IndexShardTests extends IndexShardTestCase {
|
||||||
|
|
||||||
public void testResetEngine() throws Exception {
|
public void testResetEngine() throws Exception {
|
||||||
IndexShard shard = newStartedShard(false);
|
IndexShard shard = newStartedShard(false);
|
||||||
indexOnReplicaWithGaps(shard, between(0, 1000), Math.toIntExact(shard.getLocalCheckpoint()), false);
|
indexOnReplicaWithGaps(shard, between(0, 1000), Math.toIntExact(shard.getLocalCheckpoint()));
|
||||||
final long globalCheckpoint = randomLongBetween(shard.getGlobalCheckpoint(), shard.getLocalCheckpoint());
|
final long globalCheckpoint = randomLongBetween(shard.getGlobalCheckpoint(), shard.getLocalCheckpoint());
|
||||||
shard.updateGlobalCheckpointOnReplica(globalCheckpoint, "test");
|
shard.updateGlobalCheckpointOnReplica(globalCheckpoint, "test");
|
||||||
Set<String> docBelowGlobalCheckpoint = getShardDocUIDs(shard).stream()
|
Set<String> docBelowGlobalCheckpoint = getShardDocUIDs(shard).stream()
|
||||||
|
@ -3679,7 +3681,7 @@ public class IndexShardTests extends IndexShardTestCase {
|
||||||
|
|
||||||
public void testConcurrentAcquireAllReplicaOperationsPermitsWithPrimaryTermUpdate() throws Exception {
|
public void testConcurrentAcquireAllReplicaOperationsPermitsWithPrimaryTermUpdate() throws Exception {
|
||||||
final IndexShard replica = newStartedShard(false);
|
final IndexShard replica = newStartedShard(false);
|
||||||
indexOnReplicaWithGaps(replica, between(0, 1000), Math.toIntExact(replica.getLocalCheckpoint()), false);
|
indexOnReplicaWithGaps(replica, between(0, 1000), Math.toIntExact(replica.getLocalCheckpoint()));
|
||||||
|
|
||||||
final int nbTermUpdates = randomIntBetween(1, 5);
|
final int nbTermUpdates = randomIntBetween(1, 5);
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@ import org.elasticsearch.common.unit.TimeValue;
|
||||||
import org.elasticsearch.test.ESTestCase;
|
import org.elasticsearch.test.ESTestCase;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
@LuceneTestCase.SuppressFileSystems("ExtrasFS")
|
@LuceneTestCase.SuppressFileSystems("ExtrasFS")
|
||||||
public class ByteSizeCachingDirectoryTests extends ESTestCase {
|
public class ByteSizeCachingDirectoryTests extends ESTestCase {
|
||||||
|
@ -45,6 +46,12 @@ public class ByteSizeCachingDirectoryTests extends ESTestCase {
|
||||||
numFileLengthCalls++;
|
numFileLengthCalls++;
|
||||||
return super.fileLength(name);
|
return super.fileLength(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// temporary override until LUCENE-8735 is integrated
|
||||||
|
@Override
|
||||||
|
public Set<String> getPendingDeletions() throws IOException {
|
||||||
|
return in.getPendingDeletions();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testBasics() throws IOException {
|
public void testBasics() throws IOException {
|
||||||
|
|
|
@ -43,10 +43,12 @@ import org.apache.lucene.store.BaseDirectoryWrapper;
|
||||||
import org.apache.lucene.store.ByteBufferIndexInput;
|
import org.apache.lucene.store.ByteBufferIndexInput;
|
||||||
import org.apache.lucene.store.ChecksumIndexInput;
|
import org.apache.lucene.store.ChecksumIndexInput;
|
||||||
import org.apache.lucene.store.Directory;
|
import org.apache.lucene.store.Directory;
|
||||||
|
import org.apache.lucene.store.FilterDirectory;
|
||||||
import org.apache.lucene.store.IOContext;
|
import org.apache.lucene.store.IOContext;
|
||||||
import org.apache.lucene.store.IndexInput;
|
import org.apache.lucene.store.IndexInput;
|
||||||
import org.apache.lucene.store.IndexOutput;
|
import org.apache.lucene.store.IndexOutput;
|
||||||
import org.apache.lucene.store.MMapDirectory;
|
import org.apache.lucene.store.MMapDirectory;
|
||||||
|
import org.apache.lucene.store.NIOFSDirectory;
|
||||||
import org.apache.lucene.store.RAMDirectory;
|
import org.apache.lucene.store.RAMDirectory;
|
||||||
import org.apache.lucene.util.BytesRef;
|
import org.apache.lucene.util.BytesRef;
|
||||||
import org.apache.lucene.util.TestUtil;
|
import org.apache.lucene.util.TestUtil;
|
||||||
|
@ -1123,4 +1125,16 @@ public class StoreTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testGetPendingFiles() throws IOException {
|
||||||
|
final ShardId shardId = new ShardId("index", "_na_", 1);
|
||||||
|
final String testfile = "testfile";
|
||||||
|
try (Store store = new Store(shardId, INDEX_SETTINGS, new NIOFSDirectory(createTempDir()), new DummyShardLock(shardId))) {
|
||||||
|
store.directory().createOutput(testfile, IOContext.DEFAULT).close();
|
||||||
|
try (IndexInput input = store.directory().openInput(testfile, IOContext.DEFAULT)) {
|
||||||
|
store.directory().deleteFile(testfile);
|
||||||
|
assertEquals(FilterDirectory.unwrap(store.directory()).getPendingDeletions(), store.directory().getPendingDeletions());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,6 @@ package org.elasticsearch.test.store;
|
||||||
|
|
||||||
import com.carrotsearch.randomizedtesting.SeedUtils;
|
import com.carrotsearch.randomizedtesting.SeedUtils;
|
||||||
import com.carrotsearch.randomizedtesting.generators.RandomPicks;
|
import com.carrotsearch.randomizedtesting.generators.RandomPicks;
|
||||||
|
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import org.apache.lucene.index.CheckIndex;
|
import org.apache.lucene.index.CheckIndex;
|
||||||
import org.apache.lucene.store.BaseDirectoryWrapper;
|
import org.apache.lucene.store.BaseDirectoryWrapper;
|
||||||
|
@ -55,6 +54,7 @@ import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
public class MockFSDirectoryService extends FsDirectoryService {
|
public class MockFSDirectoryService extends FsDirectoryService {
|
||||||
|
|
||||||
|
@ -179,6 +179,12 @@ public class MockFSDirectoryService extends FsDirectoryService {
|
||||||
super.crash();
|
super.crash();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// temporary override until LUCENE-8735 is integrated
|
||||||
|
@Override
|
||||||
|
public Set<String> getPendingDeletions() throws IOException {
|
||||||
|
return in.getPendingDeletions();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static final class CloseableDirectory implements Closeable {
|
static final class CloseableDirectory implements Closeable {
|
||||||
|
|
Loading…
Reference in New Issue