diff --git a/docs/reference/migration/index.asciidoc b/docs/reference/migration/index.asciidoc index aa8455a6729c1..5782ddf86a8f8 100644 --- a/docs/reference/migration/index.asciidoc +++ b/docs/reference/migration/index.asciidoc @@ -9,6 +9,8 @@ release, see the <> and <>. For information about how to upgrade your cluster, see <>. +* <> +* <> * <> * <> * <> @@ -23,6 +25,8 @@ For information about how to upgrade your cluster, see <>. -- +include::migrate_7_12.asciidoc[] +include::migrate_7_11.asciidoc[] include::migrate_7_10.asciidoc[] include::migrate_7_9.asciidoc[] include::migrate_7_8.asciidoc[] diff --git a/docs/reference/migration/migrate_7_12.asciidoc b/docs/reference/migration/migrate_7_12.asciidoc new file mode 100644 index 0000000000000..03401c9b73c72 --- /dev/null +++ b/docs/reference/migration/migrate_7_12.asciidoc @@ -0,0 +1,34 @@ +[[breaking-changes-7.12]] +== Breaking changes in 7.12 +++++ +7.12 +++++ + +This section discusses the changes that you need to be aware of when migrating +your application to {es} 7.12. + +See also <> and <>. + +// * <> +// * <> + +//NOTE: The notable-breaking-changes tagged regions are re-used in the +//Installation and Upgrade Guide + +//tag::notable-breaking-changes[] + +[discrete] +[[breaking_712_engine_changes]] +=== Engine changes + +[[breaking_712_engine_forcemerge_change]] +.Force-merges on frozen and searchable snapshot indices will fail if merging is required +[%collapsible] +==== +*Details* + +In earlier versions a force-merge on a frozen index or a searchable snapshot +index would incorrectly yield a successful response without performing the +requested merge. This bug is fixed in version 7.12: from this version onwards a +force-merge on these immutable indices will fail if the requested merge is not +a no-op. +==== diff --git a/server/src/main/java/org/elasticsearch/index/engine/ReadOnlyEngine.java b/server/src/main/java/org/elasticsearch/index/engine/ReadOnlyEngine.java index c625d6e50d820..33bb53184f890 100644 --- a/server/src/main/java/org/elasticsearch/index/engine/ReadOnlyEngine.java +++ b/server/src/main/java/org/elasticsearch/index/engine/ReadOnlyEngine.java @@ -29,6 +29,7 @@ import org.apache.lucene.store.Directory; import org.apache.lucene.store.Lock; import org.elasticsearch.Version; +import org.elasticsearch.action.admin.indices.forcemerge.ForceMergeRequest; import org.elasticsearch.common.lucene.Lucene; import org.elasticsearch.common.lucene.index.ElasticsearchDirectoryReader; import org.elasticsearch.common.util.concurrent.ReleasableLock; @@ -404,6 +405,16 @@ public CommitId flush(boolean force, boolean waitIfOngoing) throws EngineExcepti @Override public void forceMerge(boolean flush, int maxNumSegments, boolean onlyExpungeDeletes, boolean upgrade, boolean upgradeOnlyAncientSegments, String forceMergeUUID) { + if (maxNumSegments == ForceMergeRequest.Defaults.MAX_NUM_SEGMENTS) { + // noop + } else if (maxNumSegments < lastCommittedSegmentInfos.size()) { + throw new UnsupportedOperationException("force merge is not supported on a read-only engine, " + + "target max number of segments[" + maxNumSegments + "], " + + "current number of segments[" + lastCommittedSegmentInfos.size() + "]."); + } else { + logger.debug("current number of segments[{}] is not greater than target max number of segments[{}].", + lastCommittedSegmentInfos.size(), maxNumSegments); + } } @Override diff --git a/server/src/test/java/org/elasticsearch/index/engine/ReadOnlyEngineTests.java b/server/src/test/java/org/elasticsearch/index/engine/ReadOnlyEngineTests.java index 0bb8159f4a887..d5d1b9f73027b 100644 --- a/server/src/test/java/org/elasticsearch/index/engine/ReadOnlyEngineTests.java +++ b/server/src/test/java/org/elasticsearch/index/engine/ReadOnlyEngineTests.java @@ -21,6 +21,8 @@ import org.apache.lucene.index.DirectoryReader; import org.apache.lucene.index.IndexReader; import org.apache.lucene.util.LuceneTestCase; +import org.elasticsearch.action.admin.indices.forcemerge.ForceMergeRequest; +import org.elasticsearch.common.UUIDs; import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.lucene.index.ElasticsearchDirectoryReader; import org.elasticsearch.core.internal.io.IOUtils; @@ -216,6 +218,43 @@ public void testVerifyShardBeforeIndexClosingIsNoOp() throws IOException { } } + public void testForceMergeOnReadOnlyEngine() throws IOException { + IOUtils.close(engine, store); + final AtomicLong globalCheckpoint = new AtomicLong(SequenceNumbers.NO_OPS_PERFORMED); + try (Store store = createStore()) { + EngineConfig config = config(defaultSettings, store, createTempDir(), newMergePolicy(), null, null, globalCheckpoint::get); + int numDocs = scaledRandomIntBetween(10, 100); + int numSegments; + try (InternalEngine engine = createEngine(config)) { + for (int i = 0; i < numDocs; i++) { + ParsedDocument doc = testParsedDocument(Integer.toString(i), null, testDocument(), new BytesArray("{}"), null); + engine.index(new Engine.Index(newUid(doc), doc, i, primaryTerm.get(), 1, null, Engine.Operation.Origin.REPLICA, + System.nanoTime(), -1, false, SequenceNumbers.UNASSIGNED_SEQ_NO, 0)); + engine.flush(); + globalCheckpoint.set(i); + } + engine.syncTranslog(); + engine.flushAndClose(); + numSegments = engine.getLastCommittedSegmentInfos().size(); + assertTrue( numSegments > 1); + } + + try (ReadOnlyEngine readOnlyEngine = new ReadOnlyEngine(config, null , null, true, Function.identity(), true)) { + UnsupportedOperationException exception = expectThrows(UnsupportedOperationException.class, + () -> readOnlyEngine.forceMerge( + true, numSegments-1, false, false, false, UUIDs.randomBase64UUID())); + assertThat(exception.getMessage(), equalTo("force merge is not supported on a read-only engine, " + + "target max number of segments[" + (numSegments-1) + "], current number of segments[" + numSegments + "].")); + + readOnlyEngine.forceMerge(true, ForceMergeRequest.Defaults.MAX_NUM_SEGMENTS, + false, false, false, UUIDs.randomBase64UUID()); + readOnlyEngine.forceMerge(true, numSegments, false, false, false, UUIDs.randomBase64UUID()); + readOnlyEngine.forceMerge(true, numSegments+1, false, false, false, UUIDs.randomBase64UUID()); + assertEquals(readOnlyEngine.getLastCommittedSegmentInfos().size(), numSegments); + } + } + } + public void testRecoverFromTranslogAppliesNoOperations() throws IOException { IOUtils.close(engine, store); final AtomicLong globalCheckpoint = new AtomicLong(SequenceNumbers.NO_OPS_PERFORMED);