This is an automated email from the ASF dual-hosted git repository.

ibessonov pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


The following commit(s) were added to refs/heads/main by this push:
     new 9ad42b7  IGNITE-14748 Implemented ability to persist ordering of named 
list configuration elements. (#208)
9ad42b7 is described below

commit 9ad42b75e54323d927d0a1a665d6a8a8be274c7c
Author: ibessonov <[email protected]>
AuthorDate: Tue Jul 20 10:25:36 2021 +0300

    IGNITE-14748 Implemented ability to persist ordering of named list 
configuration elements. (#208)
---
 .../configuration/ConfigurationChangerTest.java    |  34 +--
 .../configuration/TestConfigurationChanger.java    |  51 +++++
 .../notifications/ConfigurationListenerTest.java   |   2 +-
 .../storage/TestConfigurationStorage.java          |   7 +-
 .../{sample => tree}/ConfigurationArrayTest.java   |   3 +-
 .../ConstructableTreeNodeTest.java                 |  13 +-
 .../configuration/tree/NamedListOrderTest.java     | 239 +++++++++++++++++++++
 .../{sample => tree}/TraversableTreeNodeTest.java  |  33 ++-
 .../configuration/util/ConfigurationUtilTest.java  | 208 ++++++++++--------
 .../validation/ValidationUtilTest.java             |   5 +-
 .../ignite/configuration/NamedListChange.java      |  58 +++--
 .../apache/ignite/configuration/NamedListView.java |   4 +-
 modules/configuration/pom.xml                      |   7 +
 .../configuration/ConfigurationChanger.java        |  12 +-
 .../configuration/ConfigurationManager.java        |   3 +-
 .../configuration/tree/ConfigurationVisitor.java   |   2 +-
 .../internal/configuration/tree/NamedListNode.java | 131 ++++++++---
 .../internal/configuration/tree/OrderedMap.java    | 199 +++++++++++++++++
 .../configuration/util/ConfigurationFlattener.java | 212 ++++++++++++++++++
 .../util/ConfigurationNotificationsUtil.java       |   4 +-
 .../configuration/util/ConfigurationUtil.java      | 225 +++++++------------
 .../configuration/tree/OrderedMapTest.java         | 150 +++++++++++++
 .../runner/app/AbstractSchemaChangeTest.java       |  10 +-
 .../SchemaConfigurationConverterTest.java          |   2 +-
 .../internal/table/distributed/TableManager.java   |   2 +-
 25 files changed, 1242 insertions(+), 374 deletions(-)

diff --git 
a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/ConfigurationChangerTest.java
 
b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/ConfigurationChangerTest.java
index b69a14b..ef7503f 100644
--- 
a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/ConfigurationChangerTest.java
+++ 
b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/ConfigurationChangerTest.java
@@ -24,7 +24,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ExecutionException;
 import org.apache.ignite.configuration.ConfigurationChangeException;
-import org.apache.ignite.configuration.RootKey;
 import org.apache.ignite.configuration.annotation.Config;
 import org.apache.ignite.configuration.annotation.ConfigValue;
 import org.apache.ignite.configuration.annotation.ConfigurationRoot;
@@ -38,14 +37,12 @@ import org.apache.ignite.configuration.validation.Validator;
 import org.apache.ignite.internal.configuration.asm.ConfigurationAsmGenerator;
 import org.apache.ignite.internal.configuration.storage.Data;
 import 
org.apache.ignite.internal.configuration.storage.TestConfigurationStorage;
-import org.apache.ignite.internal.configuration.tree.InnerNode;
 import org.apache.ignite.internal.configuration.tree.TraversableTreeNode;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.Test;
 
 import static java.lang.annotation.ElementType.FIELD;
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import static java.util.concurrent.CompletableFuture.completedFuture;
 import static java.util.concurrent.TimeUnit.SECONDS;
 import static org.apache.ignite.internal.configuration.AConfiguration.KEY;
 import static 
org.apache.ignite.internal.configuration.util.ConfigurationUtil.superRootPatcher;
@@ -111,7 +108,7 @@ public class ConfigurationChangerTest {
     public void testSimpleConfigurationChange() throws Exception {
         final TestConfigurationStorage storage = new 
TestConfigurationStorage();
 
-        ConfigurationChanger changer = new TestConfigurationChanger();
+        ConfigurationChanger changer = new TestConfigurationChanger(cgen);
         changer.addRootKey(KEY);
         changer.register(storage);
 
@@ -135,11 +132,11 @@ public class ConfigurationChangerTest {
     public void testModifiedFromAnotherStorage() throws Exception {
         final TestConfigurationStorage storage = new 
TestConfigurationStorage();
 
-        ConfigurationChanger changer1 = new TestConfigurationChanger();
+        ConfigurationChanger changer1 = new TestConfigurationChanger(cgen);
         changer1.addRootKey(KEY);
         changer1.register(storage);
 
-        ConfigurationChanger changer2 = new TestConfigurationChanger();
+        ConfigurationChanger changer2 = new TestConfigurationChanger(cgen);
         changer2.addRootKey(KEY);
         changer2.register(storage);
 
@@ -180,11 +177,11 @@ public class ConfigurationChangerTest {
     public void testModifiedFromAnotherStorageWithIncompatibleChanges() throws 
Exception {
         final TestConfigurationStorage storage = new 
TestConfigurationStorage();
 
-        ConfigurationChanger changer1 = new TestConfigurationChanger();
+        ConfigurationChanger changer1 = new TestConfigurationChanger(cgen);
         changer1.addRootKey(KEY);
         changer1.register(storage);
 
-        ConfigurationChanger changer2 = new TestConfigurationChanger();
+        ConfigurationChanger changer2 = new TestConfigurationChanger(cgen);
         changer2.addRootKey(KEY);
         changer2.register(storage);
 
@@ -223,7 +220,7 @@ public class ConfigurationChangerTest {
     public void testFailedToWrite() {
         final TestConfigurationStorage storage = new 
TestConfigurationStorage();
 
-        ConfigurationChanger changer = new TestConfigurationChanger();
+        ConfigurationChanger changer = new TestConfigurationChanger(cgen);
         changer.addRootKey(KEY);
 
         storage.fail(true);
@@ -281,7 +278,7 @@ public class ConfigurationChangerTest {
 
     @Test
     public void defaultsOnInit() throws Exception {
-        var changer = new TestConfigurationChanger();
+        var changer = new TestConfigurationChanger(cgen);
 
         changer.addRootKey(DefaultsConfiguration.KEY);
 
@@ -308,21 +305,4 @@ public class ConfigurationChangerTest {
         assertEquals("bar", root.childsList().get("name").defStr());
     }
 
-    private static class TestConfigurationChanger extends ConfigurationChanger 
{
-        TestConfigurationChanger() {
-            super((oldRoot, newRoot, revision) -> completedFuture(null));
-        }
-
-        /** {@inheritDoc} */
-        @Override public void addRootKey(RootKey<?, ?> rootKey) {
-            super.addRootKey(rootKey);
-
-            cgen.compileRootSchema(rootKey.schemaClass());
-        }
-
-        /** {@inheritDoc} */
-        @Override public InnerNode createRootNode(RootKey<?, ?> rootKey) {
-            return cgen.instantiateNode(rootKey.schemaClass());
-        }
-    }
 }
diff --git 
a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/TestConfigurationChanger.java
 
b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/TestConfigurationChanger.java
new file mode 100644
index 0000000..23f27d3
--- /dev/null
+++ 
b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/TestConfigurationChanger.java
@@ -0,0 +1,51 @@
+/*
+ * 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.ignite.internal.configuration;
+
+import org.apache.ignite.configuration.RootKey;
+import org.apache.ignite.internal.configuration.asm.ConfigurationAsmGenerator;
+import org.apache.ignite.internal.configuration.tree.InnerNode;
+
+import static java.util.concurrent.CompletableFuture.completedFuture;
+
+/** Implementation of {@link ConfigurationChanger} to be used in tests. Has no 
support of listeners. */
+public class TestConfigurationChanger extends ConfigurationChanger {
+    /** Runtime implementations generator for node classes. */
+    private final ConfigurationAsmGenerator cgen;
+
+    /**
+     * @param cgen Runtime implementations generator for node classes. Will be 
used to instantiate nodes objects.
+     */
+    public TestConfigurationChanger(ConfigurationAsmGenerator cgen) {
+        super((oldRoot, newRoot, revision) -> completedFuture(null));
+
+        this.cgen = cgen;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void addRootKey(RootKey<?, ?> rootKey) {
+        super.addRootKey(rootKey);
+
+        cgen.compileRootSchema(rootKey.schemaClass());
+    }
+
+    /** {@inheritDoc} */
+    @Override public InnerNode createRootNode(RootKey<?, ?> rootKey) {
+        return cgen.instantiateNode(rootKey.schemaClass());
+    }
+}
diff --git 
a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/notifications/ConfigurationListenerTest.java
 
b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/notifications/ConfigurationListenerTest.java
index d95790f..ec1bbcb 100644
--- 
a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/notifications/ConfigurationListenerTest.java
+++ 
b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/notifications/ConfigurationListenerTest.java
@@ -238,7 +238,7 @@ public class ConfigurationListenerTest {
         log.clear();
 
         configuration.change(parent ->
-            parent.changeElements(elements -> elements.update("name", element 
-> element.changeStr("foo")))
+            parent.changeElements(elements -> elements.createOrUpdate("name", 
element -> element.changeStr("foo")))
         ).get(1, SECONDS);
 
         assertEquals(List.of("parent", "elements", "update"), log);
diff --git 
a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/storage/TestConfigurationStorage.java
 
b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/storage/TestConfigurationStorage.java
index ce5965a..c8530ed 100644
--- 
a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/storage/TestConfigurationStorage.java
+++ 
b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/storage/TestConfigurationStorage.java
@@ -17,12 +17,11 @@
 package org.apache.ignite.internal.configuration.storage;
 
 import java.io.Serializable;
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.atomic.AtomicLong;
 import org.apache.ignite.configuration.annotation.ConfigurationType;
 
@@ -31,10 +30,10 @@ import 
org.apache.ignite.configuration.annotation.ConfigurationType;
  */
 public class TestConfigurationStorage implements ConfigurationStorage {
     /** Map to store values. */
-    private Map<String, Serializable> map = new ConcurrentHashMap<>();
+    private Map<String, Serializable> map = new HashMap<>();
 
     /** Change listeners. */
-    private List<ConfigurationStorageListener> listeners = new ArrayList<>();
+    private List<ConfigurationStorageListener> listeners = new 
CopyOnWriteArrayList<>();
 
     /** Storage version. */
     private AtomicLong version = new AtomicLong(0);
diff --git 
a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/sample/ConfigurationArrayTest.java
 
b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/tree/ConfigurationArrayTest.java
similarity index 98%
rename from 
modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/sample/ConfigurationArrayTest.java
rename to 
modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/tree/ConfigurationArrayTest.java
index 28ed24b..a0422ff 100644
--- 
a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/sample/ConfigurationArrayTest.java
+++ 
b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/tree/ConfigurationArrayTest.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.configuration.sample;
+package org.apache.ignite.internal.configuration.tree;
 
 import java.util.Random;
 import java.util.UUID;
@@ -23,7 +23,6 @@ import java.util.stream.Stream;
 import org.apache.ignite.configuration.annotation.Config;
 import org.apache.ignite.configuration.annotation.Value;
 import org.apache.ignite.internal.configuration.asm.ConfigurationAsmGenerator;
-import org.apache.ignite.internal.configuration.tree.InnerNode;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.params.ParameterizedTest;
diff --git 
a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/sample/ConstructableTreeNodeTest.java
 
b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/tree/ConstructableTreeNodeTest.java
similarity index 91%
rename from 
modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/sample/ConstructableTreeNodeTest.java
rename to 
modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/tree/ConstructableTreeNodeTest.java
index 7abed25..6d1503c 100644
--- 
a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/sample/ConstructableTreeNodeTest.java
+++ 
b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/tree/ConstructableTreeNodeTest.java
@@ -15,14 +15,11 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.configuration.sample;
+package org.apache.ignite.internal.configuration.tree;
 
-import java.util.Collections;
+import java.util.List;
 import java.util.NoSuchElementException;
 import org.apache.ignite.internal.configuration.asm.ConfigurationAsmGenerator;
-import org.apache.ignite.internal.configuration.tree.ConfigurationSource;
-import org.apache.ignite.internal.configuration.tree.ConstructableTreeNode;
-import org.apache.ignite.internal.configuration.tree.InnerNode;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
@@ -49,11 +46,11 @@ public class ConstructableTreeNodeTest {
         cgen = null;
     }
 
-    public static <P extends InnerNode & ParentView & ParentChange> P 
newParentInstance() {
+    public static <P extends InnerNode & ParentChange> P newParentInstance() {
         return 
(P)cgen.instantiateNode(TraversableTreeNodeTest.ParentConfigurationSchema.class);
     }
 
-    public static <C extends InnerNode & ChildView & ChildChange> C 
newChildInstance() {
+    public static <C extends InnerNode & ChildChange> C newChildInstance() {
         return 
(C)cgen.instantiateNode(TraversableTreeNodeTest.ChildConfigurationSchema.class);
     }
 
@@ -84,7 +81,7 @@ public class ConstructableTreeNodeTest {
 
         assertNotNull(parentNode.elements());
         assertNotSame(elements, parentNode.elements());
-        assertEquals(Collections.emptySet(), 
parentNode.elements().namedListKeys());
+        assertEquals(List.of(), parentNode.elements().namedListKeys());
 
         // Inner node.
         NamedElementView element = elements.get("name");
diff --git 
a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/tree/NamedListOrderTest.java
 
b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/tree/NamedListOrderTest.java
new file mode 100644
index 0000000..16637c6
--- /dev/null
+++ 
b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/tree/NamedListOrderTest.java
@@ -0,0 +1,239 @@
+/*
+ * 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.ignite.internal.configuration.tree;
+
+import java.util.Map;
+import org.apache.ignite.configuration.NamedListChange;
+import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.ConfigurationRoot;
+import org.apache.ignite.configuration.annotation.NamedConfigValue;
+import org.apache.ignite.configuration.annotation.Value;
+import org.apache.ignite.internal.configuration.TestConfigurationChanger;
+import org.apache.ignite.internal.configuration.asm.ConfigurationAsmGenerator;
+import 
org.apache.ignite.internal.configuration.storage.TestConfigurationStorage;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/** Test for keys ordering in named list nodes. */
+public class NamedListOrderTest {
+    /** Root that has a single named list. */
+    @ConfigurationRoot(rootName = "a")
+    public static class AConfigurationSchema {
+        /** */
+        @NamedConfigValue
+        public BConfigurationSchema b;
+    }
+
+    /** Named list element node that in inself contains another named list. */
+    @Config
+    public static class BConfigurationSchema {
+        /** Every named list element node must have at least one configuration 
field that is not named list. */
+        @Value(hasDefault = true)
+        public String c = "foo";
+
+        @NamedConfigValue
+        public BConfigurationSchema b;
+    }
+
+    /** Runtime implementations generator. */
+    private static ConfigurationAsmGenerator cgen;
+
+    /** Test configuration storage. */
+    private TestConfigurationStorage storage;
+
+    /** Test configuration changer. */
+    private TestConfigurationChanger changer;
+
+    /** Instantiates {@link #cgen}. */
+    @BeforeAll
+    public static void beforeAll() {
+        cgen = new ConfigurationAsmGenerator();
+    }
+
+    /** Nullifies {@link #cgen} to prevent memory leak from having runtime 
ClassLoader accessible from GC root. */
+    @AfterAll
+    public static void afterAll() {
+        cgen = null;
+    }
+
+    /** */
+    @BeforeEach
+    public void before() {
+        storage = new TestConfigurationStorage();
+
+        changer = new TestConfigurationChanger(cgen);
+        changer.addRootKey(AConfiguration.KEY);
+        changer.register(storage);
+    }
+
+    /** */
+    @AfterEach
+    public void after() {
+        changer.stop();
+    }
+
+    /**
+     * Tests that there are no unnecessary {@code <idx>} values in the storage 
after all basic named list operations.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void storageData() throws Exception {
+        // Manually instantiate configuration instance.
+        var a = (AConfiguration)cgen.instantiateCfg(AConfiguration.KEY, 
changer);
+
+        // Create values on several layers at the same time. They all should 
have <idx> = 0.
+        a.b().change(b -> b.create("X", x -> x.changeB(xb -> xb.create("Z0", 
z0 -> {})))).get();
+
+        assertEquals(
+            Map.of(
+                "a.b.X.c", "foo",
+                "a.b.X.<idx>", 0,
+                "a.b.X.b.Z0.c", "foo",
+                "a.b.X.b.Z0.<idx>", 0
+            ),
+            storage.readAll().values()
+        );
+
+        BConfiguration x = a.b().get("X");
+
+        // Append new key. It should have <idx> = 1.
+        x.b().change(xb -> xb.create("Z5", z5 -> {})).get();
+
+        assertEquals(
+            Map.of(
+                "a.b.X.c", "foo",
+                "a.b.X.<idx>", 0,
+                "a.b.X.b.Z0.c", "foo",
+                "a.b.X.b.Z0.<idx>", 0,
+                "a.b.X.b.Z5.c", "foo",
+                "a.b.X.b.Z5.<idx>", 1
+            ),
+            storage.readAll().values()
+        );
+
+        // Insert new key somewhere in the middle. Index of Z5 should be 
updated to 2.
+        x.b().change(xb -> xb.create(1, "Z2", z2 -> {})).get();
+
+        assertEquals(
+            Map.of(
+                "a.b.X.c", "foo",
+                "a.b.X.<idx>", 0,
+                "a.b.X.b.Z0.c", "foo",
+                "a.b.X.b.Z0.<idx>", 0,
+                "a.b.X.b.Z2.c", "foo",
+                "a.b.X.b.Z2.<idx>", 1,
+                "a.b.X.b.Z5.c", "foo",
+                "a.b.X.b.Z5.<idx>", 2
+            ),
+            storage.readAll().values()
+        );
+
+        // Insert new key somewhere in the middle. Indexes of Z3 and Z5 should 
be updated to 2 and 3.
+        x.b().change(xb -> xb.createAfter("Z2", "Z3", z3 -> {})).get();
+
+        assertEquals(
+            Map.of(
+                "a.b.X.c", "foo",
+                "a.b.X.<idx>", 0,
+                "a.b.X.b.Z0.c", "foo",
+                "a.b.X.b.Z0.<idx>", 0,
+                "a.b.X.b.Z2.c", "foo",
+                "a.b.X.b.Z2.<idx>", 1,
+                "a.b.X.b.Z3.c", "foo",
+                "a.b.X.b.Z3.<idx>", 2,
+                "a.b.X.b.Z5.c", "foo",
+                "a.b.X.b.Z5.<idx>", 3
+            ),
+            storage.readAll().values()
+        );
+
+        // Delete key from the middle. Indexes of Z3 and Z5 should be updated 
to 1 and 2.
+        x.b().change(xb -> xb.delete("Z2")).get();
+
+        assertEquals(
+            Map.of(
+                "a.b.X.c", "foo",
+                "a.b.X.<idx>", 0,
+                "a.b.X.b.Z0.c", "foo",
+                "a.b.X.b.Z0.<idx>", 0,
+                "a.b.X.b.Z3.c", "foo",
+                "a.b.X.b.Z3.<idx>", 1,
+                "a.b.X.b.Z5.c", "foo",
+                "a.b.X.b.Z5.<idx>", 2
+            ),
+            storage.readAll().values()
+        );
+
+        // Delete values on several layers simultaneously. Storage must be 
empty after that.
+        a.b().change(b -> b.delete("X")).get();
+
+        assertEquals(
+            Map.of(),
+            storage.readAll().values()
+        );
+    }
+
+    /** Tests exceptions described in methods signatures. */
+    @Test
+    public void creationErrors() throws Exception {
+        // Manually instantiate configuration instance.
+        var a = (AConfiguration)cgen.instantiateCfg(AConfiguration.KEY, 
changer);
+
+        a.b().change(b -> b.create("X", x -> {}).create("Y", y -> {})).get();
+
+        // Dirty cast, but appropriate for this particular test.
+        var b = (NamedListChange<BView>)a.b().value();
+
+        // NPE in keys.
+        assertThrows(NullPointerException.class, () -> b.create(null, z -> 
{}));
+        assertThrows(NullPointerException.class, () -> b.createOrUpdate(null, 
z -> {}));
+        assertThrows(NullPointerException.class, () -> b.create(0, null, z -> 
{}));
+        assertThrows(NullPointerException.class, () -> b.createAfter(null, 
"Z", z -> {}));
+        assertThrows(NullPointerException.class, () -> b.createAfter("X", 
null, z -> {}));
+
+        // NPE in closures.
+        assertThrows(NullPointerException.class, () -> b.create("Z", null));
+        assertThrows(NullPointerException.class, () -> b.createOrUpdate("Z", 
null));
+        assertThrows(NullPointerException.class, () -> b.create(0, "Z", null));
+        assertThrows(NullPointerException.class, () -> b.createAfter("X", "Z", 
null));
+
+        // Already existing keys.
+        assertThrows(IllegalArgumentException.class, () -> b.create("X", x -> 
{}));
+        assertThrows(IllegalArgumentException.class, () -> b.create(0, "X", x 
-> {}));
+        assertThrows(IllegalArgumentException.class, () -> b.createAfter("X", 
"Y", y -> {}));
+
+        // Nonexistent preceding key.
+        assertThrows(IllegalArgumentException.class, () -> b.createAfter("A", 
"Z", z -> {}));
+
+        // Wrong indexes.
+        assertThrows(IndexOutOfBoundsException.class, () -> b.create(-1, "Z", 
z -> {}));
+        assertThrows(IndexOutOfBoundsException.class, () -> b.create(3, "Z", z 
-> {}));
+
+        // Create after delete.
+        b.delete("X");
+        assertThrows(IllegalArgumentException.class, () -> b.create("X", x -> 
{}));
+        assertThrows(IllegalArgumentException.class, () -> b.create(0, "X", x 
-> {}));
+    }
+}
diff --git 
a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/sample/TraversableTreeNodeTest.java
 
b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/tree/TraversableTreeNodeTest.java
similarity index 88%
rename from 
modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/sample/TraversableTreeNodeTest.java
rename to 
modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/tree/TraversableTreeNodeTest.java
index 15eb5ca..1894f6a 100644
--- 
a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/sample/TraversableTreeNodeTest.java
+++ 
b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/tree/TraversableTreeNodeTest.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.configuration.sample;
+package org.apache.ignite.internal.configuration.tree;
 
 import java.io.Serializable;
 import java.util.ArrayList;
@@ -29,16 +29,11 @@ import 
org.apache.ignite.configuration.annotation.NamedConfigValue;
 import org.apache.ignite.configuration.annotation.Value;
 import org.apache.ignite.configuration.validation.Immutable;
 import org.apache.ignite.internal.configuration.asm.ConfigurationAsmGenerator;
-import org.apache.ignite.internal.configuration.tree.ConfigurationVisitor;
-import org.apache.ignite.internal.configuration.tree.InnerNode;
-import org.apache.ignite.internal.configuration.tree.NamedListNode;
-import org.apache.ignite.internal.configuration.tree.TraversableTreeNode;
 import org.hamcrest.CoreMatchers;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 
-import static java.util.Collections.emptySet;
 import static org.hamcrest.CoreMatchers.hasItem;
 import static org.hamcrest.CoreMatchers.instanceOf;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -64,11 +59,11 @@ public class TraversableTreeNodeTest {
         cgen = null;
     }
 
-    public static <P extends InnerNode & ParentView & ParentChange> P 
newParentInstance() {
+    public static <P extends InnerNode & ParentChange> P newParentInstance() {
         return (P)cgen.instantiateNode(ParentConfigurationSchema.class);
     }
 
-    public static <C extends InnerNode & ChildView & ChildChange> C 
newChildInstance() {
+    public static <C extends InnerNode & ChildChange> C newChildInstance() {
         return (C)cgen.instantiateNode(ChildConfigurationSchema.class);
     }
 
@@ -178,7 +173,7 @@ public class TraversableTreeNodeTest {
         // Named list node must always be instantiated.
         assertNotNull(elementsNode);
 
-        parentNode.changeElements(elements -> elements.update("key", element 
-> {}));
+        parentNode.changeElements(elements -> elements.createOrUpdate("key", 
element -> {}));
 
         assertNotSame(elementsNode, parentNode.elements());
     }
@@ -188,11 +183,13 @@ public class TraversableTreeNodeTest {
      */
     @Test
     public void putRemoveNamedConfiguration() {
-        var elementsNode = newParentInstance().elements();
+        var elementsNode = 
(NamedListChange<NamedElementChange>)newParentInstance().elements();
 
-        assertEquals(emptySet(), elementsNode.namedListKeys());
+        assertEquals(List.of(), elementsNode.namedListKeys());
 
-        ((NamedListChange<?>)elementsNode).update("keyPut", element -> {});
+        elementsNode.createOrUpdate("keyPut", element -> {});
+
+        assertThrows(IllegalArgumentException.class, () -> 
elementsNode.create("keyPut", element -> {}));
 
         assertThat(elementsNode.namedListKeys(), hasItem("keyPut"));
 
@@ -202,7 +199,7 @@ public class TraversableTreeNodeTest {
 
         assertNull(elementNode.strCfg());
 
-        ((NamedListChange<NamedElementChange>)elementsNode).update("keyPut", 
element -> element.changeStrCfg("val"));
+        elementsNode.createOrUpdate("keyPut", element -> 
element.changeStrCfg("val"));
 
         // Assert that consecutive put methods create new object every time.
         assertNotSame(elementNode, elementsNode.get("keyPut"));
@@ -211,21 +208,21 @@ public class TraversableTreeNodeTest {
 
         assertEquals("val", elementNode.strCfg());
 
-        ((NamedListChange<?>)elementsNode).delete("keyPut");
+        elementsNode.delete("keyPut");
 
         assertThat(elementsNode.namedListKeys(), 
CoreMatchers.hasItem("keyPut"));
 
         assertNull(elementsNode.get("keyPut"));
 
-        ((NamedListChange<?>)elementsNode).delete("keyRemove");
+        elementsNode.delete("keyPut");
 
         // Assert that "remove" method creates null element inside of the node.
-        assertThat(elementsNode.namedListKeys(), hasItem("keyRemove"));
+        assertThat(elementsNode.namedListKeys(), hasItem("keyPut"));
 
-        assertNull(elementsNode.get("keyRemove"));
+        assertNull(elementsNode.get("keyPut"));
 
         // Assert that once you remove something from list, you can't put it 
back again with different set of fields.
-        assertThrows(IllegalStateException.class, () -> 
((NamedListChange<?>)elementsNode).update("keyRemove", element -> {}));
+        assertThrows(IllegalArgumentException.class, () -> 
elementsNode.createOrUpdate("keyPut", element -> {}));
     }
 
     /**
diff --git 
a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/util/ConfigurationUtilTest.java
 
b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/util/ConfigurationUtilTest.java
index 7e1d69f..0b7fe20 100644
--- 
a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/util/ConfigurationUtilTest.java
+++ 
b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/util/ConfigurationUtilTest.java
@@ -17,8 +17,10 @@
 
 package org.apache.ignite.internal.configuration.util;
 
+import java.io.Serializable;
 import java.util.List;
 import java.util.Map;
+import java.util.function.Consumer;
 import org.apache.ignite.configuration.annotation.Config;
 import org.apache.ignite.configuration.annotation.ConfigValue;
 import org.apache.ignite.configuration.annotation.ConfigurationRoot;
@@ -27,14 +29,24 @@ import 
org.apache.ignite.configuration.annotation.NamedConfigValue;
 import org.apache.ignite.configuration.annotation.Value;
 import org.apache.ignite.internal.configuration.SuperRoot;
 import org.apache.ignite.internal.configuration.asm.ConfigurationAsmGenerator;
+import org.apache.ignite.internal.configuration.tree.ConfigurationSource;
+import org.apache.ignite.internal.configuration.tree.ConstructableTreeNode;
 import org.apache.ignite.internal.configuration.tree.InnerNode;
 import org.apache.ignite.internal.configuration.tree.TraversableTreeNode;
+import org.jetbrains.annotations.NotNull;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 
-import static java.util.Collections.emptyMap;
 import static java.util.Collections.singletonMap;
+import static 
org.apache.ignite.internal.configuration.tree.NamedListNode.ORDER_IDX;
+import static 
org.apache.ignite.internal.configuration.util.ConfigurationFlattener.createFlattenedUpdatesMap;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.aMapWithSize;
+import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.anEmptyMap;
+import static org.hamcrest.Matchers.hasEntry;
+import static org.hamcrest.Matchers.is;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNotSame;
@@ -58,7 +70,7 @@ public class ConfigurationUtilTest {
         cgen = null;
     }
 
-    public static <P extends InnerNode & ParentView & ParentChange> P 
newParentInstance() {
+    public static <P extends InnerNode & ParentChange> P newParentInstance() {
         return (P)cgen.instantiateNode(ParentConfigurationSchema.class);
     }
 
@@ -122,13 +134,16 @@ public class ConfigurationUtilTest {
         public String str;
     }
 
-    /** */
+    /**
+     * Tests that {@link ConfigurationUtil#find(List, TraversableTreeNode)} 
finds proper node when provided with correct
+     * path.
+     */
     @Test
     public void findSuccessfully() {
         var parent = newParentInstance();
 
         parent.changeElements(elements ->
-            elements.update("name", element ->
+            elements.createOrUpdate("name", element ->
                 element.changeChild(child ->
                     child.changeStr("value")
                 )
@@ -161,14 +176,17 @@ public class ConfigurationUtilTest {
         );
     }
 
-    /** */
+    /**
+     * Tests that {@link ConfigurationUtil#find(List, TraversableTreeNode)} 
returns null when path points to nonexistent
+     * named list element.
+     */
     @Test
     public void findNulls() {
         var parent = newParentInstance();
 
         assertNull(ConfigurationUtil.find(List.of("elements", "name"), 
parent));
 
-        parent.changeElements(elements -> elements.update("name", element -> 
{}));
+        parent.changeElements(elements -> elements.createOrUpdate("name", 
element -> {}));
 
         assertNull(ConfigurationUtil.find(List.of("elements", "name", 
"child"), parent));
 
@@ -177,7 +195,10 @@ public class ConfigurationUtilTest {
         assertNull(ConfigurationUtil.find(List.of("elements", "name", "child", 
"str"), parent));
     }
 
-    /** */
+    /**
+     * Tests that {@link ConfigurationUtil#find(List, TraversableTreeNode)} 
throws {@link KeyNotFoundException} when
+     * provided with a wrong path.
+     */
     @Test
     public void findUnsuccessfully() {
         var parent = newParentInstance();
@@ -187,7 +208,7 @@ public class ConfigurationUtilTest {
             () -> ConfigurationUtil.find(List.of("elements", "name", "child"), 
parent)
         );
 
-        parent.changeElements(elements -> elements.update("name", element -> 
{}));
+        parent.changeElements(elements -> elements.createOrUpdate("name", 
element -> {}));
 
         assertThrows(
             KeyNotFoundException.class,
@@ -202,7 +223,9 @@ public class ConfigurationUtilTest {
         );
     }
 
-    /** */
+    /**
+     * Tests convertion of flat map to a prefix map.
+     */
     @Test
     public void toPrefixMap() {
         assertEquals(
@@ -226,18 +249,22 @@ public class ConfigurationUtilTest {
         );
     }
 
-    /** */
+    /**
+     * Tests that patching of configuration node with a prefix map works fine 
when prefix map is valid.
+     */
     @Test
     public void fillFromPrefixMapSuccessfully() {
         var parentNode = newParentInstance();
 
         ConfigurationUtil.fillFromPrefixMap(parentNode, Map.of(
             "elements", Map.of(
-                "name1", Map.of(
-                    "child", Map.of("str", "value1")
-                ),
                 "name2", Map.of(
-                    "child", Map.of("str", "value2")
+                    "child", Map.of("str", "value2"),
+                    ORDER_IDX, 1
+                ),
+                "name1", Map.of(
+                    "child", Map.of("str", "value1"),
+                    ORDER_IDX, 0
                 )
             )
         ));
@@ -246,13 +273,15 @@ public class ConfigurationUtilTest {
         assertEquals("value2", 
parentNode.elements().get("name2").child().str());
     }
 
-    /** */
+    /**
+     * Tests that patching of configuration node with a prefix map works fine 
when prefix map is valid.
+     */
     @Test
     public void fillFromPrefixMapSuccessfullyWithRemove() {
         var parentNode = newParentInstance();
 
         parentNode.changeElements(elements ->
-            elements.update("name", element ->
+            elements.createOrUpdate("name", element ->
                 element.changeChild(child -> {})
             )
         );
@@ -264,64 +293,74 @@ public class ConfigurationUtilTest {
         assertNull(parentNode.elements().get("node"));
     }
 
-    /** */
+    /**
+     * Tests that conversion from "changer" lambda to a flat map of updates 
for the storage works properly.
+     */
     @Test
-    public void nodeToFlatMap() {
-        var parentNode = newParentInstance();
+    public void flattenedUpdatesMap() {
+        var superRoot = new SuperRoot(key -> null, 
Map.of(ParentConfiguration.KEY, newParentInstance()));
 
-        assertEquals(
-            emptyMap(),
-            ConfigurationUtil.nodeToFlatMap(null, new SuperRoot(key -> null, 
Map.of(
-                ParentConfiguration.KEY,
-                parentNode
-            )))
-        );
+        assertThat(flattenedMap(superRoot, parent -> {}), is(anEmptyMap()));
 
-        // No defaults in this test so everything must be initialized 
explicitly.
-        parentNode.changeElements(elements ->
-            elements.create("name", element ->
-                element.changeChild(child ->
-                    child.changeStr("foo")
+        assertThat(
+            flattenedMap(superRoot, parent -> parent
+                .changeElements(elements -> elements
+                    .create("name", element -> element
+                        .changeChild(child -> child.changeStr("foo"))
+                    )
                 )
-            )
+            ),
+            is(allOf(
+                aMapWithSize(2),
+                hasEntry("root.elements.name.child.str", (Serializable)"foo"),
+                hasEntry("root.elements.name.<idx>", 0)
+            ))
         );
 
-        assertEquals(
-            singletonMap("root.elements.name.child.str", "foo"),
-            ConfigurationUtil.nodeToFlatMap(null, new SuperRoot(key -> null, 
Map.of(
-                ParentConfiguration.KEY,
-                parentNode
-            )))
+        assertThat(
+            flattenedMap(superRoot, parent -> parent
+                .changeElements(elements1 -> elements1.delete("void"))
+            ),
+            is(anEmptyMap())
         );
 
-        assertEquals(
-            emptyMap(),
-            ConfigurationUtil.nodeToFlatMap(new SuperRoot(key -> null, Map.of(
-                ParentConfiguration.KEY,
-                parentNode
-            )), new SuperRoot(key -> null, singletonMap(
-                ParentConfiguration.KEY,
-                (InnerNode)newParentInstance().changeElements(elements ->
-                    elements.delete("void")
-                )
-            )))
+        assertThat(
+            flattenedMap(superRoot, parent -> parent
+                .changeElements(elements -> elements.delete("name"))
+            ),
+            is(allOf(
+                aMapWithSize(2),
+                hasEntry("root.elements.name.child.str", null),
+                hasEntry("root.elements.name.<idx>", null)
+            ))
         );
+    }
 
-        assertEquals(
-            singletonMap("root.elements.name.child.str", null),
-            ConfigurationUtil.nodeToFlatMap(new SuperRoot(key -> null, Map.of(
-                ParentConfiguration.KEY,
-                parentNode
-            )), new SuperRoot(key -> null, singletonMap(
-                ParentConfiguration.KEY,
-                (InnerNode)newParentInstance().changeElements(elements ->
-                    elements.delete("name")
-                )
-            )))
-        );
+    /**
+     * Patches super root and returns flat representation of the changes. 
Passed {@code superRoot} object will contain
+     * patched tree when method execution is completed.
+     *
+     * @param superRoot Super root to patch.
+     * @param patch Closure to cnahge parent node.
+     * @return Flat map with all changes from the patch.
+     */
+    @NotNull private Map<String, Serializable> flattenedMap(SuperRoot 
superRoot, Consumer<ParentChange> patch) {
+        // Preserve a copy of the super root to use it as a golden source of 
data.
+        SuperRoot originalSuperRoot = superRoot.copy();
+
+        // Make a copy of the root insode of the superRoot. This copy will be 
used for further patching.
+        superRoot.construct(ParentConfiguration.KEY.key(), new 
ConfigurationSource() {});
+
+        // Patch root node.
+        patch.accept((ParentChange)superRoot.getRoot(ParentConfiguration.KEY));
+
+        // Create flat diff between two super trees.
+        return createFlattenedUpdatesMap(originalSuperRoot, superRoot);
     }
 
-    /** */
+    /**
+     * Tests basic invariants of {@link 
ConfigurationUtil#patch(ConstructableTreeNode, TraversableTreeNode)} method.
+     */
     @Test
     public void patch() {
         var originalRoot = newParentInstance();
@@ -333,8 +372,8 @@ public class ConfigurationUtilTest {
         );
 
         // Updating config.
-        ParentView updatedRoot = ConfigurationUtil.patch(originalRoot, 
(TraversableTreeNode)newParentInstance().changeElements(elements ->
-            elements.update("name1", element ->
+        ParentView updatedRoot = ConfigurationUtil.patch(originalRoot, 
(TraversableTreeNode)copy(originalRoot).changeElements(elements ->
+            elements.createOrUpdate("name1", element ->
                 element.changeChild(child -> child.changeStr("value2"))
             )
         ));
@@ -348,8 +387,8 @@ public class ConfigurationUtilTest {
         assertEquals("value2", 
updatedRoot.elements().get("name1").child().str());
 
         // Expanding config.
-        ParentView expandedRoot = ConfigurationUtil.patch(originalRoot, 
(TraversableTreeNode)newParentInstance().changeElements(elements ->
-            elements.update("name2", element ->
+        ParentView expandedRoot = ConfigurationUtil.patch(originalRoot, 
(TraversableTreeNode)copy(originalRoot).changeElements(elements ->
+            elements.createOrUpdate("name2", element ->
                 element.changeChild(child -> child.changeStr("value2"))
             )
         ));
@@ -364,7 +403,7 @@ public class ConfigurationUtilTest {
         assertEquals("value2", 
expandedRoot.elements().get("name2").child().str());
 
         // Shrinking config.
-        ParentView shrinkedRoot = 
(ParentView)ConfigurationUtil.patch((InnerNode)expandedRoot, 
(TraversableTreeNode)newParentInstance().changeElements(elements ->
+        ParentView shrinkedRoot = 
(ParentView)ConfigurationUtil.patch((InnerNode)expandedRoot, 
(TraversableTreeNode)copy(expandedRoot).changeElements(elements ->
             elements.delete("name1")
         ));
 
@@ -376,33 +415,12 @@ public class ConfigurationUtilTest {
         assertNotNull(shrinkedRoot.elements().get("name2"));
     }
 
-    /** */
-    @Test
-    public void cleanupMatchingValues() {
-        var curParent = newParentInstance();
-
-        curParent.changeElements(elements -> elements
-            .create("missing", element -> {})
-            .create("match", element -> element.changeChild(child -> 
child.changeStr("match")))
-            .create("mismatch", element -> element.changeChild(child -> 
child.changeStr("foo")))
-        );
-
-        var newParent = newParentInstance();
-
-        newParent.changeElements(elements -> elements
-            .create("extra", element -> {})
-            .create("match", element -> element.changeChild(child -> 
child.changeStr("match")))
-            .create("mismatch", element -> element.changeChild(child -> 
child.changeStr("bar")))
-        );
-
-        ConfigurationUtil.cleanupMatchingValues(curParent, newParent);
-
-        // Old node stayed intact.
-        assertEquals("match", curParent.elements().get("match").child().str());
-        assertEquals("foo", 
curParent.elements().get("mismatch").child().str());
-
-        // New node was modified.
-        assertNull(newParent.elements().get("match").child().str());
-        assertEquals("bar", 
newParent.elements().get("mismatch").child().str());
+    /**
+     * @param parent {@link ParentView} object.
+     * @return Copy of the {@code parent} objects cast to {@link ParentChange}.
+     * @see ConstructableTreeNode#copy()
+     */
+    private ParentChange copy(ParentView parent) {
+        return (ParentChange)((ConstructableTreeNode)parent).copy();
     }
 }
diff --git 
a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/validation/ValidationUtilTest.java
 
b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/validation/ValidationUtilTest.java
index a090d33..1a95c85 100644
--- 
a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/validation/ValidationUtilTest.java
+++ 
b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/validation/ValidationUtilTest.java
@@ -45,7 +45,6 @@ import org.junit.jupiter.api.Test;
 
 import static java.lang.annotation.ElementType.FIELD;
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import static java.util.Collections.emptySet;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
 /** */
@@ -175,8 +174,8 @@ public class ValidationUtilTest {
             @Override public void validate(NamedListValidation annotation, 
ValidationContext<NamedListView<?>> ctx) {
                 assertEquals("root.elements", ctx.currentKey());
 
-                assertEquals(emptySet(), ctx.getOldValue().namedListKeys());
-                assertEquals(emptySet(), ctx.getNewValue().namedListKeys());
+                assertEquals(List.of(), ctx.getOldValue().namedListKeys());
+                assertEquals(List.of(), ctx.getNewValue().namedListKeys());
 
                 ctx.addIssue(new ValidationIssue("bar"));
             }
diff --git 
a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/NamedListChange.java
 
b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/NamedListChange.java
index 3a9af1b..a6f8405 100644
--- 
a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/NamedListChange.java
+++ 
b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/NamedListChange.java
@@ -20,29 +20,62 @@ package org.apache.ignite.configuration;
 import java.util.function.Consumer;
 
 /** */
-public interface NamedListChange<Change> {
+public interface NamedListChange<Change> extends NamedListView<Change> {
     /**
-     * Update the value in named list configuration.
+     * Creates a new value in the named list configuration.
      *
      * @param key Key for the value to be created.
-     * @param valConsumer Closure to modify value associated with the key. 
Object of type {@code T},
-     *      passed to the closure, must not be reused anywhere else.
+     * @param valConsumer Closure to modify the value associated with the key. 
Closure parameter must not be leaked
+     *      outside the scope of the closure.
      * @return {@code this} for chaining.
+     *
+     * @throws NullPointerException If one of the parameters is null.
+     * @throws IllegalArgumentException If an element with the given name 
already exists.
      */
-    //TODO Replace with "createOrUpdate"
     NamedListChange<Change> create(String key, Consumer<Change> valConsumer);
 
     /**
-     * Update the value in named list configuration.
+     * Creates a new value at the given position in the named list 
configuration.
+     *
+     * @param index Index of the inserted element.
+     * @param key Key for the value to be created.
+     * @param valConsumer Closure to modify the value associated with the key. 
Closure parameter must not be leaked
+     *      outside the scope of the closure.
+     * @return {@code this} for chaining.
+     *
+     * @throws NullPointerException If one of the parameters is null.
+     * @throws IndexOutOfBoundsException If index is negative of exceeds the 
size of the list.
+     * @throws IllegalArgumentException If an element with the given name 
already exists.
+     */
+    NamedListChange<Change> create(int index, String key, Consumer<Change> 
valConsumer);
+
+    /**
+     * Create a new value after a given precedingKey key in the named list 
configuration.
+     *
+     * @param precedingKey Name of the preceding element.
+     * @param key Key for the value to be created.
+     * @param valConsumer Closure to modify the value associated with the key. 
Closure parameter must not be leaked
+     *      outside the scope of the closure.
+     * @return {@code this} for chaining.
+     *
+     * @throws NullPointerException If one of parameters is null.
+     * @throws IllegalArgumentException If element with given name already 
exists
+     *      or if {@code precedingKey} element doesn't exist.
+     */
+    NamedListChange<Change> createAfter(String precedingKey, String key, 
Consumer<Change> valConsumer);
+
+    /**
+     * Updates a value in the named list configuration. If the value cannot be 
found, creates a new one instead.
      *
      * @param key Key for the value to be updated.
-     * @param valConsumer Closure to modify value associated with the key. 
Object of type {@code T},
-     *      passed to the closure, must not be reused anywhere else.
+     * @param valConsumer Closure to modify the value associated with the key. 
Closure parameter must not be leaked
+     *      outside the scope of the closure.
      * @return {@code this} for chaining.
      *
-     * @throws IllegalStateException If {@link #delete(String)} has been 
invoked with the same key previously.
+     * @throws NullPointerException If one of parameters is null.
+     * @throws IllegalArgumentException If {@link #delete(String)} has been 
invoked with the same key previously.
      */
-    NamedListChange<Change> update(String key, Consumer<Change> valConsumer) 
throws IllegalStateException;
+    NamedListChange<Change> createOrUpdate(String key, Consumer<Change> 
valConsumer);
 
     /**
      * Remove the value from named list configuration.
@@ -50,7 +83,8 @@ public interface NamedListChange<Change> {
      * @param key Key for the value to be removed.
      * @return {@code this} for chaining.
      *
-     * @throws IllegalStateException If {@link #update(String, Consumer)} has 
been invoked with the same key previously.
+     * @throws NullPointerException If key is null.
+     * @throws IllegalArgumentException If {@link #createOrUpdate(String, 
Consumer)} has been invoked with the same key previously.
      */
-    NamedListChange<Change> delete(String key) throws IllegalStateException;
+    NamedListChange<Change> delete(String key);
 }
diff --git 
a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/NamedListView.java
 
b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/NamedListView.java
index 5016bfb..1e1c0ea 100644
--- 
a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/NamedListView.java
+++ 
b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/NamedListView.java
@@ -17,14 +17,14 @@
 
 package org.apache.ignite.configuration;
 
-import java.util.Set;
+import java.util.List;
 
 /** */
 public interface NamedListView<T> {
     /**
      * @return Immutable collection of keys contained within this list.
      */
-    Set<String> namedListKeys();
+    List<String> namedListKeys();
 
     /**
      * Returns value associated with the passed key.
diff --git a/modules/configuration/pom.xml b/modules/configuration/pom.xml
index d10e833..9af6f5e 100644
--- a/modules/configuration/pom.xml
+++ b/modules/configuration/pom.xml
@@ -53,5 +53,12 @@
             <groupId>com.typesafe</groupId>
             <artifactId>config</artifactId>
         </dependency>
+
+        <!-- Test dependencies. -->
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-api</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 </project>
diff --git 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationChanger.java
 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationChanger.java
index ec84b8b..e54121a 100644
--- 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationChanger.java
+++ 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationChanger.java
@@ -50,11 +50,10 @@ import org.jetbrains.annotations.Nullable;
 
 import static java.util.concurrent.CompletableFuture.completedFuture;
 import static java.util.stream.Collectors.toList;
+import static 
org.apache.ignite.internal.configuration.util.ConfigurationFlattener.createFlattenedUpdatesMap;
 import static 
org.apache.ignite.internal.configuration.util.ConfigurationUtil.addDefaults;
-import static 
org.apache.ignite.internal.configuration.util.ConfigurationUtil.cleanupMatchingValues;
 import static 
org.apache.ignite.internal.configuration.util.ConfigurationUtil.fillFromPrefixMap;
 import static 
org.apache.ignite.internal.configuration.util.ConfigurationUtil.nodePatcher;
-import static 
org.apache.ignite.internal.configuration.util.ConfigurationUtil.nodeToFlatMap;
 import static 
org.apache.ignite.internal.configuration.util.ConfigurationUtil.patch;
 import static 
org.apache.ignite.internal.configuration.util.ConfigurationUtil.toPrefixMap;
 
@@ -290,6 +289,8 @@ public abstract class ConfigurationChanger {
             }
         };
 
+        source.reset();
+
         source.descend(collector);
 
         assert !storagesTypes.isEmpty();
@@ -357,9 +358,6 @@ public abstract class ConfigurationChanger {
 
                 src.descend(changes);
 
-                // It is necessary to reinitialize default values every time.
-                // Possible use case that explicitly requires it: creation of 
the same named list entry with slightly
-                // different set of values and different dynamic defaults at 
the same time.
                 SuperRoot patchedSuperRoot = patch(curRoots, changes);
 
                 SuperRoot defaultsNode = new SuperRoot(rootCreator());
@@ -368,9 +366,7 @@ public abstract class ConfigurationChanger {
 
                 SuperRoot patchedChanges = patch(changes, defaultsNode);
 
-                cleanupMatchingValues(curRoots, patchedChanges);
-
-                Map<String, Serializable> allChanges = nodeToFlatMap(curRoots, 
patchedChanges);
+                Map<String, Serializable> allChanges = 
createFlattenedUpdatesMap(curRoots, patchedChanges);
 
                 // Unlikely but still possible.
                 if (allChanges.isEmpty())
diff --git 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationManager.java
 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationManager.java
index 7aebf4e..1cb81e4 100644
--- 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationManager.java
+++ 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationManager.java
@@ -37,8 +37,7 @@ import 
org.apache.ignite.internal.configuration.storage.ConfigurationStorage;
 /**
  * Configuration manager is responsible for handling configuration lifecycle 
and provides configuration API.
  */
-// TODO: IGNITE-14586 Remove @SuppressWarnings when implementation provided.
-@SuppressWarnings("WeakerAccess") public class ConfigurationManager {
+public class ConfigurationManager {
     /** Configuration registry. */
     private final ConfigurationRegistry confRegistry;
 
diff --git 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConfigurationVisitor.java
 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConfigurationVisitor.java
index 825bbbd..58238d6 100644
--- 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConfigurationVisitor.java
+++ 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConfigurationVisitor.java
@@ -41,7 +41,7 @@ public interface ConfigurationVisitor<T> {
      * @param node Inner configuration node.
      * @return Anything that implementation decides to return.
      */
-    default T visitInnerNode(String key, InnerNode node) { //TODO IGNITE-14372 
Pass interface, not implementation.
+    default T visitInnerNode(String key, InnerNode node) {
         return null;
     }
 
diff --git 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/NamedListNode.java
 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/NamedListNode.java
index 55c8f20..0cd3874 100644
--- 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/NamedListNode.java
+++ 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/NamedListNode.java
@@ -18,22 +18,27 @@
 package org.apache.ignite.internal.configuration.tree;
 
 import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.List;
 import java.util.Objects;
-import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
 import org.apache.ignite.configuration.NamedListChange;
-import org.apache.ignite.configuration.NamedListView;
 
-/** */
-public final class NamedListNode<N extends InnerNode> implements 
NamedListView<N>, NamedListChange<N>, TraversableTreeNode, 
ConstructableTreeNode {
-    /** */
-    public final Supplier<N> valSupplier;
+/**
+ * Configuration node implementation for the collection of named {@link 
InnerNode}s. Unlike implementations of
+ * {@link InnerNode}, this class is used for every named list in configuration.
+ *
+ * @param <N> Type of the {@link InnerNode} that is stored in named list node 
object.
+ */
+public final class NamedListNode<N extends InnerNode> implements 
NamedListChange<N>, TraversableTreeNode, ConstructableTreeNode {
+    /** Name of a synthetic configuration property that describes the order of 
elements in a named list. */
+    public static final String ORDER_IDX = "<idx>";
 
-    /** */
-    private final Map<String, N> map;
+    /** Supplier of new node objects when new list element node has to be 
created. */
+    private final Supplier<N> valSupplier;
+
+    /** Internal container for named list element. Maps keys to named list 
elements nodes. */
+    private final OrderedMap<N> map;
 
     /**
      * Default constructor.
@@ -42,7 +47,7 @@ public final class NamedListNode<N extends InnerNode> 
implements NamedListView<N
      */
     public NamedListNode(Supplier<N> valSupplier) {
         this.valSupplier = valSupplier;
-        map = new HashMap<>();
+        map = new OrderedMap<>();
     }
 
     /**
@@ -52,7 +57,7 @@ public final class NamedListNode<N extends InnerNode> 
implements NamedListView<N
      */
     private NamedListNode(NamedListNode<N> node) {
         valSupplier = node.valSupplier;
-        map = new HashMap<>(node.map);
+        map = new OrderedMap<>(node.map);
     }
 
     /** {@inheritDoc} */
@@ -61,8 +66,8 @@ public final class NamedListNode<N extends InnerNode> 
implements NamedListView<N
     }
 
     /** {@inheritDoc} */
-    @Override public final Set<String> namedListKeys() {
-        return Collections.unmodifiableSet(map.keySet());
+    @Override public final List<String> namedListKeys() {
+        return Collections.unmodifiableList(map.keys());
     }
 
     /** {@inheritDoc} */
@@ -76,11 +81,67 @@ public final class NamedListNode<N extends InnerNode> 
implements NamedListView<N
     }
 
     /** {@inheritDoc} */
-    @Override public final NamedListChange<N> update(String key, Consumer<N> 
valConsumer) {
+    @Override public NamedListChange<N> create(String key, Consumer<N> 
valConsumer) {
+        Objects.requireNonNull(key, "key");
+        Objects.requireNonNull(valConsumer, "valConsumer");
+
+        checkNewKey(key);
+
+        N val = valSupplier.get();
+
+        map.put(key, val);
+
+        valConsumer.accept(val);
+
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override public NamedListChange<N> create(int index, String key, 
Consumer<N> valConsumer) {
+        Objects.requireNonNull(key, "key");
+        Objects.requireNonNull(valConsumer, "valConsumer");
+
+        if (index < 0 || index > map.size())
+            throw new IndexOutOfBoundsException(index);
+
+        checkNewKey(key);
+
+        N val = valSupplier.get();
+
+        map.putByIndex(index, key, val);
+
+        valConsumer.accept(val);
+
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override public NamedListChange<N> createAfter(String precedingKey, 
String key, Consumer<N> valConsumer) {
+        Objects.requireNonNull(precedingKey, "precedingKey");
+        Objects.requireNonNull(key, "key");
+        Objects.requireNonNull(valConsumer, "valConsumer");
+
+        if (!map.containsKey(precedingKey))
+            throw new IllegalArgumentException("Element with name " + 
precedingKey + " doesn't exist.");
+
+        checkNewKey(key);
+
+        N val = valSupplier.get();
+
+        map.putAfter(precedingKey, key, val);
+
+        valConsumer.accept(val);
+
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override public final NamedListChange<N> createOrUpdate(String key, 
Consumer<N> valConsumer) {
+        Objects.requireNonNull(key, "key");
         Objects.requireNonNull(valConsumer, "valConsumer");
 
         if (map.containsKey(key) && map.get(key) == null)
-            throw new IllegalStateException("You can't create entity that has 
just been deleted [key=" + key + ']');
+            throw new IllegalArgumentException("You can't create entity that 
has just been deleted [key=" + key + ']');
 
         N val = map.get(key);
 
@@ -94,9 +155,26 @@ public final class NamedListNode<N extends InnerNode> 
implements NamedListView<N
         return this;
     }
 
+    /**
+     * Checks that this new key can be inserted into the map.
+     * @param key New key.
+     * @throws IllegalArgumentException If key already exists.
+     */
+    private void checkNewKey(String key) {
+        if (map.containsKey(key)) {
+            if (map.get(key) == null)
+                throw new IllegalArgumentException("You can't create entity 
that has just been deleted [key=" + key + ']');
+
+            throw new IllegalArgumentException("Element with name " + key + " 
already exists.");
+        }
+    }
+
     /** {@inheritDoc} */
     @Override public NamedListChange<N> delete(String key) {
-        map.put(key, null);
+        Objects.requireNonNull(key, "key");
+
+        if (map.containsKey(key))
+            map.put(key, null);
 
         return this;
     }
@@ -110,18 +188,13 @@ public final class NamedListNode<N extends InnerNode> 
implements NamedListView<N
         map.remove(key);
     }
 
-    /** {@inheritDoc} */
-    @Override public NamedListChange<N> create(String key, Consumer<N> 
valConsumer) {
-        Objects.requireNonNull(valConsumer, "valConsumer");
-
-        N val = map.get(key);
-
-        if (val == null)
-            map.put(key, val = valSupplier.get());
-
-        valConsumer.accept(val);
-
-        return this;
+    /**
+     * Reorders keys in the map.
+     *
+     * @param orderedKeys List of keys in new order. Must have the same set of 
keys in it.
+     */
+    public void reorderKeys(List<String> orderedKeys) {
+        map.reorderKeys(orderedKeys);
     }
 
     /** {@inheritDoc} */
diff --git 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/OrderedMap.java
 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/OrderedMap.java
new file mode 100644
index 0000000..d67d3f3
--- /dev/null
+++ 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/OrderedMap.java
@@ -0,0 +1,199 @@
+/*
+ * 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.ignite.internal.configuration.tree;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Simplified map class that preserves keys order.
+ *
+ * @param <V> Type of the value.
+ */
+class OrderedMap<V> {
+    /** Underlying hash map. */
+    private final Map<String, V> map;
+
+    /** Ordered keys. */
+    private final List<String> orderedKeys;
+
+    /** Default constructor. */
+    OrderedMap() {
+        map = new HashMap<>();
+        orderedKeys = new ArrayList<>();
+    }
+
+    /**
+     * Copy constructor.
+     *
+     * @param other Source of keys/values to copy from.
+     */
+    OrderedMap(OrderedMap<V> other) {
+        map = new HashMap<>(other.map);
+        orderedKeys = new ArrayList<>(other.orderedKeys);
+    }
+
+    /**
+     * Same as {@link Map#containsKey(Object)}.
+     *
+     * @param key Key to check.
+     * @return {@code true} if map contains the key.
+     */
+    public boolean containsKey(String key) {
+        return map.containsKey(key);
+    }
+
+    /**
+     * Same as {@link Map#get(Object)}.
+     *
+     * @param key Key to search.
+     * @return Value associated with the key or {@code null} is it's not found.
+     */
+    public V get(String key) {
+        return map.get(key);
+    }
+
+    /**
+     * Same as {@link Map#remove(Object)}.
+     *
+     * @param key Key to remove.
+     * @return Previous value associated with the key or {@code null} if the 
map had no such key.
+     */
+    public V remove(String key) {
+        V res = map.remove(key);
+
+        if (res != null)
+            orderedKeys.remove(key);
+
+        return res;
+    }
+
+    /**
+     * Inserts a value into the map under the specified key. If the key was 
not present in the map, it will be ordered last.
+     * ordering index will be used.
+     *
+     * @param key Key to put.
+     * @param value Value associated with the key.
+     */
+    public void put(String key, V value) {
+        if (map.put(key, value) == null)
+            orderedKeys.add(key);
+    }
+
+    /**
+     * Inserts a value into the map under the specified key. The key will be 
positioned at the given index, shifting any
+     * existing values at that position to the right. Key must not be present 
in the map when the method is called.
+     *
+     * @param idx Ordering index for the key. Can't be negative. Every value 
bigger or equal than {@code size()} is
+     *            treated like {@code size()}.
+     * @param key Key to put.
+     * @param value Value associated with the key.
+     */
+    public void putByIndex(int idx, String key, V value) {
+        assert !map.containsKey(key) : key + " " + map;
+
+        if (idx >= orderedKeys.size())
+            orderedKeys.add(key);
+        else
+            orderedKeys.add(idx, key);
+
+        map.put(key, value);
+    }
+
+    /**
+     * Put value to the map at the position after the {@code precedingKey}. 
Key must not be present in the map when the
+     * method is called.
+     *
+     * @param precedingKey Previous key for the new key. Last key will be used 
if this one is missing from the map.
+     * @param key Key to put.
+     * @param value Value associated with the key.
+     */
+    public void putAfter(String precedingKey, String key, V value) {
+        assert map.containsKey(precedingKey) : precedingKey + " " + map;
+        assert !map.containsKey(key) : key + " " + map;
+
+        int idx = orderedKeys.indexOf(precedingKey);
+
+        putByIndex(idx + 1, key, value);
+    }
+
+    /**
+     * Re-associates the value under the {@code oldKey} to the {@code newKey}. 
Does nothing if the {@code oldKey}
+     * is not present in the map.
+     * was missing from the map.
+     *
+     * @param oldKey Old key.
+     * @param newKey New key.
+     *
+     * @throws IllegalArgumentException If both {@code oldKey} and {@code 
newKey} already exist in the map.
+     */
+    public void rename(String oldKey, String newKey) {
+        if (!map.containsKey(oldKey))
+            return;
+
+        if (map.containsKey(newKey))
+            throw new IllegalArgumentException();
+
+        int idx = orderedKeys.indexOf(oldKey);
+
+        orderedKeys.set(idx, newKey);
+
+        V value = map.remove(oldKey);
+
+        map.put(newKey, value);
+    }
+
+    /**
+     * @return List of keys.
+     */
+    public List<String> keys() {
+        return new ArrayList<>(orderedKeys);
+    }
+
+    /**
+     * Reorders keys in the map.
+     *
+     * @param orderedKeys List of keys in new order. Must have the same set of 
keys in it.
+     */
+    public void reorderKeys(List<String> orderedKeys) {
+        assert map.keySet().equals(Set.copyOf(orderedKeys)) : map.keySet() + " 
: " + orderedKeys;
+
+        this.orderedKeys.clear();
+
+        this.orderedKeys.addAll(orderedKeys);
+    }
+
+    /**
+     * @return Size of the map.
+     */
+    public int size() {
+        return map.size();
+    }
+
+    /**
+     * Clears the map.
+     */
+    public void clear() {
+        map.clear();
+
+        orderedKeys.clear();
+    }
+}
diff --git 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationFlattener.java
 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationFlattener.java
new file mode 100644
index 0000000..61600c9
--- /dev/null
+++ 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationFlattener.java
@@ -0,0 +1,212 @@
+/*
+ * 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.ignite.internal.configuration.util;
+
+import java.io.Serializable;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import org.apache.ignite.internal.configuration.SuperRoot;
+import org.apache.ignite.internal.configuration.tree.InnerNode;
+import org.apache.ignite.internal.configuration.tree.NamedListNode;
+
+/** Utility class that has {@link 
ConfigurationFlattener#createFlattenedUpdatesMap(SuperRoot, SuperRoot)} method. 
*/
+public class ConfigurationFlattener {
+    /**
+     * Convert a traversable tree to a map of qualified keys to values.
+     *
+     * @param curRoot Current root tree.
+     * @param updates Tree with updates.
+     * @return Map of changes.
+     */
+    public static Map<String, Serializable> 
createFlattenedUpdatesMap(SuperRoot curRoot, SuperRoot updates) {
+        // Resulting map.
+        Map<String, Serializable> resMap = new HashMap<>();
+
+        // This method traverses two trees at the same time. In order to reuse 
the visitor object it's decided to
+        // use an explicit stack for nodes of the "old" tree. We need to reuse 
the visitor object because it accumulates
+        // "full" keys in dot-separated notation. Please refer to 
KeysTrackingConfigurationVisitor for details..
+        Deque<InnerNode> oldInnerNodesStack = new ArrayDeque<>();
+
+        oldInnerNodesStack.push(curRoot);
+
+        // Explicit access to the children of super root guarantees that 
"oldInnerNodesStack" is never empty and thus
+        // we don't need null-checks when calling Deque#peek().
+        updates.traverseChildren(new FlattenerVisitor(oldInnerNodesStack, 
resMap));
+
+        assert oldInnerNodesStack.peek() == curRoot : oldInnerNodesStack;
+
+        return resMap;
+    }
+
+    /**
+     * @param node Named list node.
+     * @return Map that contains same keys and their positions as values.
+     */
+    private static Map<String, Integer> keysToOrderIdx(NamedListNode<?> node) {
+        Map<String, Integer> res = new HashMap<>();
+
+        int idx = 0;
+
+        for (String key : node.namedListKeys()) {
+            if (node.get(key) != null)
+                res.put(key, idx++);
+        }
+
+        return res;
+    }
+
+    /**
+     * Visitor that collects diff between "old" and "new" trees into a flat 
map.
+     */
+    private static class FlattenerVisitor extends 
KeysTrackingConfigurationVisitor<Object> {
+        /** Old nodes stack for recursion. */
+        private final Deque<InnerNode> oldInnerNodesStack;
+
+        /** Map with the result. */
+        private final Map<String, Serializable> resMap;
+
+        /** Flag indicates that "old" and "new" trees are literally the same 
at the moment. */
+        private boolean singleTreeTraversal;
+
+        /**
+         * Makes sense only if {@link #singleTreeTraversal} is {@code true}. 
Helps distinguishing creation from
+         * deletion. Always {@code false} if {@link #singleTreeTraversal} is 
{@code false}.
+         */
+        private boolean deletion;
+
+        FlattenerVisitor(Deque<InnerNode> oldInnerNodesStack, Map<String, 
Serializable> resMap) {
+            this.oldInnerNodesStack = oldInnerNodesStack;
+            this.resMap = resMap;
+        }
+
+        /** {@inheritDoc} */
+        @Override public Void doVisitLeafNode(String key, Serializable newVal) 
{
+            // Read same value from old tree.
+            Serializable oldVal = oldInnerNodesStack.peek().traverseChild(key, 
ConfigurationUtil.leafNodeVisitor());
+
+            // Do not put duplicates into the resulting map.
+            if (singleTreeTraversal || !Objects.deepEquals(oldVal, newVal))
+                resMap.put(currentKey(), deletion ? null : newVal);
+
+            return null;
+        }
+
+        /** {@inheritDoc} */
+        @Override public Void doVisitInnerNode(String key, InnerNode newNode) {
+            // Read same node from old tree.
+            InnerNode oldNode = oldInnerNodesStack.peek().traverseChild(key, 
ConfigurationUtil.innerNodeVisitor());
+
+            // Skip subtree that has not changed.
+            if (oldNode == newNode && !singleTreeTraversal)
+                return null;
+
+            if (oldNode == null)
+                visitAsymmetricInnerNode(newNode, false);
+            else {
+                oldInnerNodesStack.push(oldNode);
+
+                newNode.traverseChildren(this);
+
+                oldInnerNodesStack.pop();
+            }
+
+            return null;
+        }
+
+        /** {@inheritDoc} */
+        @Override public <N extends InnerNode> Void 
doVisitNamedListNode(String key, NamedListNode<N> newNode) {
+            // Read same named list node from old tree.
+            NamedListNode<?> oldNode = 
oldInnerNodesStack.peek().traverseChild(key, 
ConfigurationUtil.namedListNodeVisitor());
+
+            // Skip subtree that has not changed.
+            if (oldNode == newNode && !singleTreeTraversal)
+                return null;
+
+            // Old keys ordering can be ignored if we either create or delete 
everything.
+            Map<String, Integer> oldKeysToOrderIdxMap = singleTreeTraversal ? 
null
+                : keysToOrderIdx(oldNode);
+
+            // New keys ordering can be ignored if we delete everything.
+            Map<String, Integer> newKeysToOrderIdxMap = deletion ? null
+                : keysToOrderIdx(newNode);
+
+            for (String namedListKey : newNode.namedListKeys()) {
+                withTracking(namedListKey, true, false, () -> {
+                    InnerNode oldNamedElement = oldNode.get(namedListKey);
+                    InnerNode newNamedElement = newNode.get(namedListKey);
+
+                    // Deletion of nonexistent element.
+                    if (oldNamedElement == null && newNamedElement == null)
+                        return null;
+
+                    // Skip element that has not changed.
+                    // Its index can be different though, so we don't 
"continue" straight away.
+                    if (singleTreeTraversal || oldNamedElement != 
newNamedElement) {
+                        if (newNamedElement == null)
+                            visitAsymmetricInnerNode(oldNamedElement, true);
+                        else if (oldNamedElement == null)
+                            visitAsymmetricInnerNode(newNamedElement, false);
+                        else {
+                            oldInnerNodesStack.push(oldNamedElement);
+
+                            newNamedElement.traverseChildren(this);
+
+                            oldInnerNodesStack.pop();
+                        }
+                    }
+
+                    Integer newIdx = newKeysToOrderIdxMap == null ? null : 
newKeysToOrderIdxMap.get(namedListKey);
+                    Integer oldIdx = oldKeysToOrderIdxMap == null ? null : 
oldKeysToOrderIdxMap.get(namedListKey);
+
+                    // We should "persist" changed indexes only.
+                    if (newIdx != oldIdx || singleTreeTraversal || 
newNamedElement == null) {
+                        String orderKey = currentKey() + 
NamedListNode.ORDER_IDX;
+
+                        resMap.put(orderKey, deletion || newNamedElement == 
null ? null : newIdx);
+                    }
+
+                    return null;
+                });
+            }
+
+            return null;
+        }
+
+        /**
+         * Here we must list all joined keys belonging to deleted or created 
element. The only way to do it is to
+         * traverse the entire configuration tree unconditionally.
+         */
+        private void visitAsymmetricInnerNode(InnerNode node, boolean delete) {
+            assert !singleTreeTraversal;
+            assert node != null;
+
+            oldInnerNodesStack.push(node);
+            singleTreeTraversal = true;
+            deletion = delete;
+
+            node.traverseChildren(this);
+
+            deletion = false;
+            singleTreeTraversal = false;
+            oldInnerNodesStack.pop();
+        }
+    }
+}
diff --git 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationNotificationsUtil.java
 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationNotificationsUtil.java
index bbdb88b..34b0558 100644
--- 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationNotificationsUtil.java
+++ 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationNotificationsUtil.java
@@ -98,8 +98,8 @@ public class ConfigurationNotificationsUtil {
 
                     // This is optimization, we could use 
"NamedListConfiguration#get" directly, but we don't want to.
 
-                    Set<String> oldNames = oldNamedList.namedListKeys();
-                    Set<String> newNames = newNamedList.namedListKeys();
+                    List<String> oldNames = oldNamedList.namedListKeys();
+                    List<String> newNames = newNamedList.namedListKeys();
 
                     Map<String, ConfigurationProperty<?, ?>> 
namedListCfgMembers = namedListCfg.members();
 
diff --git 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationUtil.java
 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationUtil.java
index 8e89b16..58e3a9d 100644
--- 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationUtil.java
+++ 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationUtil.java
@@ -21,15 +21,13 @@ import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
-import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
-import java.util.Objects;
 import java.util.RandomAccess;
 import java.util.stream.Collectors;
+import org.apache.ignite.configuration.NamedListView;
 import org.apache.ignite.configuration.RootKey;
-import org.apache.ignite.internal.configuration.SuperRoot;
 import org.apache.ignite.internal.configuration.tree.ConfigurationSource;
 import org.apache.ignite.internal.configuration.tree.ConfigurationVisitor;
 import org.apache.ignite.internal.configuration.tree.ConstructableTreeNode;
@@ -252,22 +250,24 @@ public class ConfigurationUtil {
 
             /** {@inheritDoc} */
             @Override public void descend(ConstructableTreeNode node) {
+                if (node instanceof NamedListNode) {
+                    descendToNamedListNode((NamedListNode<?>)node);
+
+                    return;
+                }
+
                 for (Map.Entry<String, ?> entry : map.entrySet()) {
                     String key = entry.getKey();
                     Object val = entry.getValue();
 
                     assert val == null || val instanceof Map || val instanceof 
Serializable;
 
-                    if (val == null) {
-                        if (node instanceof NamedListNode) {
-                            // Given that this particular method is applied to 
modify existing trees rather than
-                            // creating new trees, a "hack" is required in 
this place. "construct" is designed to create
-                            // "change" objects, thus it would just nullify 
named list element instead of deleting it.
-                            ((NamedListNode<?>)node).forceDelete(key);
-                        }
-                        else
-                            node.construct(key, null);
-                    }
+                    // Ordering of indexes must be skipped here because they 
make no sense in this context.
+                    if (key.equals(NamedListNode.ORDER_IDX))
+                        continue;
+
+                    if (val == null)
+                        node.construct(key, null);
                     else if (val instanceof Map)
                         node.construct(key, new 
InnerConfigurationSource((Map<String, ?>)val));
                     else {
@@ -277,101 +277,73 @@ public class ConfigurationUtil {
                     }
                 }
             }
-        }
-
-        var src = new InnerConfigurationSource(prefixMap);
-
-        src.descend(node);
-    }
-
-    /**
-     * Convert a traversable tree to a map of qualified keys to values.
-     *
-     * @param curRoot Current root tree.
-     * @param updates Tree with updates.
-     * @return Map of changes.
-     */
-    public static Map<String, Serializable> nodeToFlatMap(
-        SuperRoot curRoot,
-        SuperRoot updates
-    ) {
-        Map<String, Serializable> values = new HashMap<>();
 
-        updates.traverseChildren(new KeysTrackingConfigurationVisitor<>() {
-            /** Write nulls instead of actual values. Makes sense for 
deletions from named lists. */
-            private boolean writeNulls;
-
-            /** {@inheritDoc} */
-            @Override public Map<String, Serializable> doVisitLeafNode(String 
key, Serializable val) {
-                if (val != null)
-                    values.put(currentKey(), writeNulls ? null : val);
+            /**
+             * Specific implementation of {@link 
#descend(ConstructableTreeNode)} that descends into named list node and
+             * sets a proper ordering to named list elements.
+             *
+             * @param node Named list node under construction.
+             */
+            private void descendToNamedListNode(NamedListNode<?> node) {
+                // This list must be mutable and RandomAccess.
+                var orderedKeys = new 
ArrayList<>(((NamedListView<?>)node).namedListKeys());
 
-                return values;
-            }
+                for (Map.Entry<String, ?> entry : map.entrySet()) {
+                    String key = entry.getKey();
+                    Object val = entry.getValue();
 
-            /** {@inheritDoc} */
-            @Override public Map<String, Serializable> doVisitInnerNode(String 
key, InnerNode node) {
-                if (node == null)
-                    return null;
+                    assert val == null || val instanceof Map || val instanceof 
Serializable;
 
-                node.traverseChildren(this);
+                    if (val == null) {
+                        // Given that this particular method is applied to 
modify existing trees rather than
+                        // creating new trees, a "hack" is required in this 
place. "construct" is designed to create
+                        // "change" objects, thus it would just nullify named 
list element instead of deleting it.
+                        node.forceDelete(key);
+                    }
+                    else if (val instanceof Map) {
+                        // For every named list entry modification we must 
take its index into account.
+                        // We do this by modifying "orderedKeys" when index is 
explicitly passed.
+                        Object idxObj = ((Map<?, 
?>)val).get(NamedListNode.ORDER_IDX);
 
-                return values;
-            }
+                        if (idxObj != null) {
+                            assert idxObj instanceof Integer : val;
 
-            /** {@inheritDoc} */
-            @Override public <N extends InnerNode> Map<String, Serializable> 
doVisitNamedListNode(String key, NamedListNode<N> node) {
-                for (String namedListKey : node.namedListKeys()) {
-                    N namedElement = node.get(namedListKey);
-
-                    withTracking(namedListKey, true, false, () -> {
-                        if (namedElement == null)
-                            visitDeletedNamedListElement();
-                        else
-                            namedElement.traverseChildren(this);
-
-                        return null;
-                    });
-                }
+                            int idx = (Integer)idxObj;
 
-                return values;
-            }
+                            if (idx >= orderedKeys.size()) {
+                                // Updates can come in arbitrary order. This 
means that array may be too small
+                                // during batch creation. In this case we have 
to insert enough nulls before
+                                // invoking "add" method for actual key.
+                                orderedKeys.ensureCapacity(idx + 1);
 
-            /**
-             * Here we must list all joined keys belonging to deleted element. 
The only way to do it is to traverse
-             * cached configuration tree and write {@code null} into all 
values.
-             */
-            private void visitDeletedNamedListElement() {
-                // It must be impossible to delete something inside of the 
element that we're currently deleting.
-                assert !writeNulls;
+                                while (idx != orderedKeys.size())
+                                    orderedKeys.add(null);
 
-                Object originalNamedElement = null;
+                                orderedKeys.add(key);
+                            }
+                            else
+                                orderedKeys.set(idx, key);
+                        }
 
-                List<String> currentPath = currentPath();
+                        node.construct(key, new 
InnerConfigurationSource((Map<String, ?>)val));
+                    }
+                    else {
+                        assert val instanceof Serializable;
 
-                try {
-                    // This code can in fact be better optimized for deletion 
scenario,
-                    // but there's no point in doing that, since the operation 
is so rare and it will
-                    // complicate code even more.
-                    originalNamedElement = find(currentPath, curRoot);
-                }
-                catch (KeyNotFoundException ignore) {
-                    // May happen, not a big deal. This means that element 
never existed in the first place.
+                        node.construct(key, new 
LeafConfigurationSource((Serializable)val));
+                    }
                 }
 
-                if (originalNamedElement != null) {
-                    assert originalNamedElement instanceof InnerNode : 
currentPath;
-
-                    writeNulls = true;
-
-                    ((InnerNode)originalNamedElement).traverseChildren(this);
-
-                    writeNulls = false;
-                }
+                node.reorderKeys(orderedKeys.size() > node.size()
+                    ? orderedKeys.subList(0, node.size())
+                    : orderedKeys
+                );
             }
-        });
+        }
 
-        return values;
+        var src = new InnerConfigurationSource(prefixMap);
+
+        src.descend(node);
     }
 
     /**
@@ -539,65 +511,6 @@ public class ConfigurationUtil {
         };
     }
 
-    /**
-     * Nullifies leaves in {@code newNode} node that are equal to the 
corresponding leaf values in {@code curNode}.
-     * In this context we view {@code curNode} as a full configuration node 
with all the data, while {@code newNode}
-     * contains only updates that we plan to apply to the {@code curNode} in 
the future.
-     * @param curNode Node to look for definitive values.
-     * @param newNode Node to nullify matching values.
-     */
-    public static void cleanupMatchingValues(InnerNode curNode, InnerNode 
newNode) {
-        if (curNode == null || newNode == null)
-            return;
-
-        assert curNode.getClass() == newNode.getClass();
-
-        newNode.traverseChildren(new ConfigurationVisitor<Void>() {
-            /** {@inheritDoc} */
-            @Override public Void visitLeafNode(String key, Serializable 
newVal) {
-                if (Objects.deepEquals(curNode.traverseChild(key, 
leafNodeVisitor()), newVal))
-                    newNode.construct(key, null);
-
-                return null;
-            }
-
-            /** {@inheritDoc} */
-            @Override public Void visitInnerNode(String key, InnerNode 
newInnerNode) {
-                InnerNode oldInnerNode = curNode.traverseChild(key, 
innerNodeVisitor());
-
-                if (oldInnerNode == newInnerNode)
-                    newNode.construct(key, null);
-                else
-                    cleanupMatchingValues(oldInnerNode, newInnerNode);
-
-                return null;
-            }
-
-            /** {@inheritDoc} */
-            @Override public <N extends InnerNode> Void 
visitNamedListNode(String key, NamedListNode<N> newNamedList) {
-                NamedListNode<?> curNamedList = curNode.traverseChild(key, 
namedListNodeVisitor());
-
-                if (curNamedList == newNamedList) {
-                    newNode.construct(key, null);
-
-                    return null;
-                }
-
-                for (String name : new 
LinkedHashSet<>(newNamedList.namedListKeys())) {
-                    InnerNode oldElement = curNamedList.get(name);
-                    N newElement = newNamedList.get(name);
-
-                    if (oldElement == newElement)
-                        newNamedList.forceDelete(name);
-                    else
-                        cleanupMatchingValues(oldElement, newElement);
-                }
-
-                return null;
-            }
-        });
-    }
-
     /** @see #patch(ConstructableTreeNode, TraversableTreeNode) */
     private static class PatchLeafConfigurationSource implements 
ConfigurationSource {
         /** */
@@ -644,6 +557,9 @@ public class ConfigurationUtil {
         @Override public void descend(ConstructableTreeNode dstNode) {
             assert srcNode.getClass() == dstNode.getClass() : 
srcNode.getClass() + " : " + dstNode.getClass();
 
+            if (srcNode == dstNode)
+                return;
+
             srcNode.traverseChildren(new ConfigurationVisitor<>() {
                 @Override public Void visitLeafNode(String key, Serializable 
val) {
                     if (val != null)
@@ -690,12 +606,15 @@ public class ConfigurationUtil {
         @Override public void descend(ConstructableTreeNode dstNode) {
             assert srcNode.getClass() == dstNode.getClass();
 
+            if (srcNode == dstNode)
+                return;
+
             for (String key : srcNode.namedListKeys()) {
                 InnerNode node = srcNode.get(key);
 
                 if (node == null)
                     ((NamedListNode<?>)dstNode).forceDelete(key); // Same as 
in fillFromPrefixMap.
-                else
+                else if (((NamedListView<?>)dstNode).get(key) != node)
                     dstNode.construct(key, new 
PatchInnerConfigurationSource(node));
             }
         }
diff --git 
a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/OrderedMapTest.java
 
b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/OrderedMapTest.java
new file mode 100644
index 0000000..580d181
--- /dev/null
+++ 
b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/OrderedMapTest.java
@@ -0,0 +1,150 @@
+/*
+ * 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.ignite.internal.configuration.tree;
+
+import java.util.List;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+/** Test with basic {@link OrderedMap} invariants. */
+public class OrderedMapTest {
+    /** Map instance. */
+    private final OrderedMap<String> map = new OrderedMap<>();
+
+    /** Tests put/get/remove consistency on a single key. */
+    @Test
+    public void putGetRemove() {
+        assertNull(map.get("key1"));
+
+        map.put("key", "value");
+
+        assertEquals(1, map.size());
+        assertEquals("value", map.get("key"));
+
+        map.remove("key");
+
+        assertNull(map.get("key1"));
+    }
+
+    /** Tests that {@link OrderedMap#put(String, Object)} preserves order. */
+    @Test
+    public void keysOrder() {
+        map.put("key1", "value1");
+        map.put("key2", "value2");
+
+        assertEquals(2, map.size());
+        assertEquals(List.of("key1", "key2"), map.keys());
+
+        map.clear();
+
+        assertEquals(0, map.size());
+        assertEquals(List.of(), map.keys());
+
+        map.put("key2", "value2");
+        map.put("key1", "value1");
+
+        assertEquals(2, map.size());
+        assertEquals(List.of("key2", "key1"), map.keys());
+
+        map.put("key2", "value3");
+        assertEquals(List.of("key2", "key1"), map.keys());
+    }
+
+    /** Tests that {@link OrderedMap#putByIndex(int, String, Object)} 
preserves order. */
+    @Test
+    public void putByIndex() {
+        map.putByIndex(0, "key1", "value1");
+
+        map.putByIndex(0, "key2", "value2");
+
+        assertEquals(List.of("key2", "key1"), map.keys());
+
+        map.putByIndex(1, "key3", "value3");
+
+        assertEquals(List.of("key2", "key3", "key1"), map.keys());
+
+        map.putByIndex(3, "key4", "value4");
+
+        assertEquals(List.of("key2", "key3", "key1", "key4"), map.keys());
+
+        map.putByIndex(5, "key5", "value5");
+
+        assertEquals(List.of("key2", "key3", "key1", "key4", "key5"), 
map.keys());
+    }
+
+    /** Tests that {@link OrderedMap#putAfter(String, String, Object)} 
preserves order. */
+    @Test
+    public void putAfter() {
+        map.put("key1", "value1");
+
+        assertEquals(List.of("key1"), map.keys());
+
+        map.putAfter("key1", "key2", "value2");
+
+        assertEquals(List.of("key1", "key2"), map.keys());
+
+        map.putAfter("key1", "key3", "value3");
+
+        assertEquals(List.of("key1", "key3", "key2"), map.keys());
+
+        map.putAfter("key2", "key4", "value4");
+
+        assertEquals(List.of("key1", "key3", "key2", "key4"), map.keys());
+    }
+
+    /** Tests basic invariants of {@link OrderedMap#rename(String, String)} 
method. */
+    @Test
+    public void rename() {
+        map.put("key1", "value1");
+        map.put("key2", "value2");
+        map.put("key3", "value3");
+
+        map.rename("key2", "key4");
+
+        assertEquals("value2", map.get("key4"));
+
+        assertFalse(map.containsKey("key2"));
+
+        assertEquals(List.of("key1", "key4", "key3"), map.keys());
+    }
+
+    /** Tests that {@link OrderedMap#reorderKeys(List)} reorders keys 
properly. */
+    @Test
+    public void reorderKeys() {
+        map.put("key1", "value1");
+        map.put("key2", "value2");
+        map.put("key3", "value3");
+
+        assertEquals(List.of("key1", "key2", "key3"), map.keys());
+
+        map.reorderKeys(List.of("key2", "key1", "key3"));
+
+        assertEquals(List.of("key2", "key1", "key3"), map.keys());
+
+        map.reorderKeys(List.of("key2", "key3", "key1"));
+
+        assertEquals(List.of("key2", "key3", "key1"), map.keys());
+
+        map.reorderKeys(List.of("key1", "key3", "key2"));
+
+        assertEquals(List.of("key1", "key3", "key2"), map.keys());
+    }
+}
diff --git 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/AbstractSchemaChangeTest.java
 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/AbstractSchemaChangeTest.java
index 8070888..fbe794c 100644
--- 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/AbstractSchemaChangeTest.java
+++ 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/AbstractSchemaChangeTest.java
@@ -119,7 +119,7 @@ abstract class AbstractSchemaChangeTest {
                         });
 
                     tblChanger.changeColumns(listChanger ->
-                        listChanger.update(colKey, colChanger -> 
colChanger.changeType(c -> c.changeType("STRING")))
+                        listChanger.createOrUpdate(colKey, colChanger -> 
colChanger.changeType(c -> c.changeType("STRING")))
                     );
                 })
             );
@@ -147,7 +147,7 @@ abstract class AbstractSchemaChangeTest {
                         });
 
                     tblChanger.changeColumns(listChanger ->
-                        listChanger.update(colKey, colChanger -> 
colChanger.changeNullable(false))
+                        listChanger.createOrUpdate(colKey, colChanger -> 
colChanger.changeNullable(false))
                     );
                 })
             );
@@ -175,7 +175,7 @@ abstract class AbstractSchemaChangeTest {
                         });
 
                     tblChanger.changeColumns(listChanger ->
-                        listChanger.update(colKey, colChanger -> 
colChanger.changeNullable(true))
+                        listChanger.createOrUpdate(colKey, colChanger -> 
colChanger.changeNullable(true))
                     );
                 })
             );
@@ -258,7 +258,7 @@ abstract class AbstractSchemaChangeTest {
                     });
 
                 tblChanger.changeColumns(listChanger ->
-                    listChanger.update(colKey, colChanger -> 
colChanger.changeName(newName))
+                    listChanger.createOrUpdate(colKey, colChanger -> 
colChanger.changeName(newName))
                 );
             })
         );
@@ -280,7 +280,7 @@ abstract class AbstractSchemaChangeTest {
                     });
 
                 tblChanger.changeColumns(listChanger ->
-                    listChanger.update(colKey, colChanger -> 
colChanger.changeDefaultValue(defSup.get().toString()))
+                    listChanger.createOrUpdate(colKey, colChanger -> 
colChanger.changeDefaultValue(defSup.get().toString()))
                 );
             })
         );
diff --git 
a/modules/schema/src/test/java/org/apache/ignite/internal/schema/configuration/SchemaConfigurationConverterTest.java
 
b/modules/schema/src/test/java/org/apache/ignite/internal/schema/configuration/SchemaConfigurationConverterTest.java
index 8ca9951..0237f60 100644
--- 
a/modules/schema/src/test/java/org/apache/ignite/internal/schema/configuration/SchemaConfigurationConverterTest.java
+++ 
b/modules/schema/src/test/java/org/apache/ignite/internal/schema/configuration/SchemaConfigurationConverterTest.java
@@ -83,7 +83,7 @@ public class SchemaConfigurationConverterTest {
         confRegistry.getConfiguration(TablesConfiguration.KEY).change(
             ch -> {
                 SchemaConfigurationConverter.createTable(tbl, ch)
-                    .changeTables(tblsCh -> tblsCh.create(tbl.canonicalName(),
+                    .changeTables(tblsCh -> 
tblsCh.createOrUpdate(tbl.canonicalName(),
                         tblCh -> tblCh.changeReplicas(1)));
             }).get();
     }
diff --git 
a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/TableManager.java
 
b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/TableManager.java
index 116d4ea..b8e8a7e 100644
--- 
a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/TableManager.java
+++ 
b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/TableManager.java
@@ -575,7 +575,7 @@ public class TableManager extends Producer<TableEvent, 
TableEventParameters> imp
         try {
             configurationMgr.configurationRegistry()
                 
.getConfiguration(TablesConfiguration.KEY).tables().change(change ->
-                change.update(name, tableChange)).get();
+                change.createOrUpdate(name, tableChange)).get();
         }
         catch (InterruptedException | ExecutionException e) {
             LOG.error("Table wasn't created [name=" + name + ']', e);

Reply via email to