You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by ib...@apache.org on 2021/08/16 10:39:02 UTC

[ignite-3] branch main updated: IGNITE-15285 Properly splitting ConfigurationManager instances (node and cluster) (#273)

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 b82c1cb  IGNITE-15285 Properly splitting ConfigurationManager instances (node and cluster) (#273)
b82c1cb is described below

commit b82c1cb70ef8603568991bff01e528e6917e488d
Author: Kirill Tkalenko <tk...@yandex.ru>
AuthorDate: Mon Aug 16 13:38:58 2021 +0300

    IGNITE-15285 Properly splitting ConfigurationManager instances (node and cluster) (#273)
---
 .../internal/affinity/AffinityManagerTest.java     |  12 +-
 .../org/apache/ignite/cli/ConfigCommandTest.java   |   7 +-
 .../cli/builtins/config/ConfigurationClient.java   |  17 +-
 .../apache/ignite/cli/spec/ConfigCommandSpec.java  |  31 ++-
 .../apache/ignite/cli/IgniteCliInterfaceTest.java  |  31 ++-
 .../handler/ClientHandlerIntegrationTest.java      |  11 +-
 .../apache/ignite/client/AbstractClientTest.java   |  11 +-
 .../configuration/ConfigurationChangerTest.java    |  90 ++++----
 .../configuration/TestConfigurationChanger.java    |  33 ++-
 .../configuration/hocon/HoconConverterTest.java    |  14 +-
 .../notifications/ConfigurationListenerTest.java   |  16 +-
 .../internal/configuration/sample/UsageTest.java   |  20 +-
 .../configuration/tree/NamedListNodeTest.java      |  10 +-
 .../configuration/util/ConfigurationUtilTest.java  |  98 +++++++-
 .../org/apache/ignite/configuration/RootKey.java   |  18 +-
 .../configuration/ConfigurationChanger.java        | 247 +++++++--------------
 .../configuration/ConfigurationManager.java        |  55 ++---
 .../configuration/ConfigurationRegistry.java       |  94 ++++----
 .../configuration/DynamicConfiguration.java        |   2 +-
 .../internal/configuration/DynamicProperty.java    |   2 +-
 .../configuration/util/ConfigurationUtil.java      |  17 ++
 .../java/org/apache/ignite/rest/RestModule.java    | 199 +++++++++++------
 .../rest/presentation/hocon/HoconPresentation.java |   2 +-
 .../ConfigurationPresentationTest.java             |   2 +-
 .../ITDistributedConfigurationStorageTest.java     |  12 +-
 .../apache/ignite/internal/app/IgnitionImpl.java   |  60 ++---
 .../storage/LocalConfigurationStorage.java         |   2 +-
 .../SchemaConfigurationConverterTest.java          |  16 +-
 .../internal/table/distributed/TableManager.java   |  57 ++---
 .../ignite/internal/table/TableManagerTest.java    |  60 ++---
 30 files changed, 701 insertions(+), 545 deletions(-)

diff --git a/modules/affinity/src/test/java/org/apache/ignite/internal/affinity/AffinityManagerTest.java b/modules/affinity/src/test/java/org/apache/ignite/internal/affinity/AffinityManagerTest.java
index 48c814d..152c900 100644
--- a/modules/affinity/src/test/java/org/apache/ignite/internal/affinity/AffinityManagerTest.java
+++ b/modules/affinity/src/test/java/org/apache/ignite/internal/affinity/AffinityManagerTest.java
@@ -21,10 +21,10 @@ import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
 import org.apache.ignite.configuration.RootKey;
-import org.apache.ignite.configuration.annotation.ConfigurationType;
 import org.apache.ignite.configuration.schemas.table.TablesConfiguration;
 import org.apache.ignite.internal.affinity.event.AffinityEvent;
 import org.apache.ignite.internal.affinity.event.AffinityEventParameters;
@@ -50,6 +50,7 @@ import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
+import static org.apache.ignite.configuration.annotation.ConfigurationType.DISTRIBUTED;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
@@ -80,8 +81,11 @@ public class AffinityManagerTest {
     @BeforeEach
     void setUp() {
         try {
-            cfrMgr = new ConfigurationManager(rootConfigurationKeys(), Arrays.asList(
-                new TestConfigurationStorage(ConfigurationType.DISTRIBUTED)));
+            cfrMgr = new ConfigurationManager(
+                rootConfigurationKeys(),
+                Map.of(),
+                new TestConfigurationStorage(DISTRIBUTED)
+            );
 
             cfrMgr.start();
 
@@ -117,7 +121,7 @@ public class AffinityManagerTest {
                 "         }\n" +
                 "      }\n" +
                 "   }\n" +
-                "}", ConfigurationType.DISTRIBUTED);
+                "}");
         }
         catch (Exception e) {
             LOG.error("Failed to bootstrap the test configuration manager.", e);
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/ConfigCommandTest.java b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/ConfigCommandTest.java
index 63d0fd0..5d8d470 100644
--- a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/ConfigCommandTest.java
+++ b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/ConfigCommandTest.java
@@ -85,6 +85,7 @@ public class ConfigCommandTest extends AbstractCliTest {
             "set",
             "--node-endpoint",
             "localhost:" + restPort,
+            "--type", "node", //TODO: Fix in https://issues.apache.org/jira/browse/IGNITE-15306
             "node.metastorageNodes=[\"localhost1\"]"
         );
 
@@ -103,7 +104,8 @@ public class ConfigCommandTest extends AbstractCliTest {
             "config",
             "get",
             "--node-endpoint",
-            "localhost:" + restPort
+            "localhost:" + restPort,
+            "--type", "node" //TODO: Fix in https://issues.apache.org/jira/browse/IGNITE-15306
         );
 
         assertEquals(0, exitCode);
@@ -123,7 +125,8 @@ public class ConfigCommandTest extends AbstractCliTest {
             "--node-endpoint",
             "localhost:" + restPort,
             "--selector",
-            "network"
+            "network",
+            "--type", "node" //TODO: Fix in https://issues.apache.org/jira/browse/IGNITE-15306
         );
 
         assertEquals(0, exitCode);
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/config/ConfigurationClient.java b/modules/cli/src/main/java/org/apache/ignite/cli/builtins/config/ConfigurationClient.java
index 4a87abe..019e481 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/config/ConfigurationClient.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/builtins/config/ConfigurationClient.java
@@ -69,21 +69,24 @@ public class ConfigurationClient {
      * @param host String representation of server node host.
      * @param port Host REST port.
      * @param rawHoconPath HOCON dot-delimited path of requested configuration.
+     * @param type Configuration type: {@code node} or {@code cluster}.
      * @return JSON string with node configuration.
      */
+    //TODO: Fix in https://issues.apache.org/jira/browse/IGNITE-15306
     public String get(
         String host,
         int port,
-        @Nullable String rawHoconPath) {
+        @Nullable String rawHoconPath,
+        String type
+    ) {
         var req = HttpRequest
             .newBuilder()
             .header("Content-Type", "application/json");
 
         if (rawHoconPath == null)
-            req.uri(URI.create("http://" + host + ":" + port + GET_URL));
+            req.uri(URI.create("http://" + host + ":" + port + GET_URL + type + "/"));
         else
-            req.uri(URI.create("http://" + host + ":" + port + GET_URL +
-                rawHoconPath));
+            req.uri(URI.create("http://" + host + ":" + port + GET_URL + type + "/" + rawHoconPath));
 
         try {
             HttpResponse<String> res =
@@ -109,13 +112,15 @@ public class ConfigurationClient {
      * @param rawHoconData Valid HOCON represented as a string.
      * @param out PrintWriter for printing user messages.
      * @param cs ColorScheme to enrich user messages.
+     * @param type Configuration type: {@code node} or {@code cluster}.
      */
-    public void set(String host, int port, String rawHoconData, PrintWriter out, ColorScheme cs) {
+    //TODO: Fix in https://issues.apache.org/jira/browse/IGNITE-15306
+    public void set(String host, int port, String rawHoconData, PrintWriter out, ColorScheme cs, String type) {
         var req = HttpRequest
             .newBuilder()
             .PUT(HttpRequest.BodyPublishers.ofString(renderJsonFromHocon(rawHoconData)))
             .header("Content-Type", "application/json")
-            .uri(URI.create("http://" + host + ":" + port + SET_URL))
+            .uri(URI.create("http://" + host + ":" + port + SET_URL + type + "/"))
             .build();
 
         try {
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/spec/ConfigCommandSpec.java b/modules/cli/src/main/java/org/apache/ignite/cli/spec/ConfigCommandSpec.java
index 2be0cf1..63d8a96 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/spec/ConfigCommandSpec.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/spec/ConfigCommandSpec.java
@@ -54,10 +54,20 @@ public class ConfigCommandSpec extends CategorySpec {
         )
         private String selector;
 
+        /** Configuration type: {@code node} or {@code cluster}. */
+        @CommandLine.Option(
+            names = "--type",
+            description = "Configuration type (\"node\" or \"cluster\")",
+            required = true
+        )
+        //TODO: Fix in https://issues.apache.org/jira/browse/IGNITE-15306
+        private String type;
+
         /** {@inheritDoc} */
         @Override public void run() {
             spec.commandLine().getOut().println(
-                configurationClient.get(cfgHostnameOptions.host(), cfgHostnameOptions.port(), selector));
+                configurationClient.get(cfgHostnameOptions.host(), cfgHostnameOptions.port(), selector, type)
+            );
         }
     }
 
@@ -81,10 +91,25 @@ public class ConfigCommandSpec extends CategorySpec {
         @CommandLine.Mixin
         private CfgHostnameOptions cfgHostnameOptions;
 
+        /** Configuration type: {@code node} or {@code cluster}. */
+        //TODO: Fix in https://issues.apache.org/jira/browse/IGNITE-15306
+        @CommandLine.Option(
+            names = "--type",
+            description = "Configuration type (\"node\" or \"cluster\")",
+            required = true
+        )
+        private String type;
+
         /** {@inheritDoc} */
         @Override public void run() {
-            configurationClient.set(cfgHostnameOptions.host(), cfgHostnameOptions.port(), cfg,
-                spec.commandLine().getOut(), spec.commandLine().getColorScheme());
+            configurationClient.set(
+                cfgHostnameOptions.host(),
+                cfgHostnameOptions.port(),
+                cfg,
+                spec.commandLine().getOut(),
+                spec.commandLine().getColorScheme(),
+                type
+            );
         }
     }
 
diff --git a/modules/cli/src/test/java/org/apache/ignite/cli/IgniteCliInterfaceTest.java b/modules/cli/src/test/java/org/apache/ignite/cli/IgniteCliInterfaceTest.java
index d787009..7d416c0 100644
--- a/modules/cli/src/test/java/org/apache/ignite/cli/IgniteCliInterfaceTest.java
+++ b/modules/cli/src/test/java/org/apache/ignite/cli/IgniteCliInterfaceTest.java
@@ -484,19 +484,20 @@ public class IgniteCliInterfaceTest extends AbstractCliTest {
         }
 
         /** */
+        //TODO: Fix in https://issues.apache.org/jira/browse/IGNITE-15306
         @Test
-        @DisplayName("get --node-endpoint localhost:8081")
+        @DisplayName("get --node-endpoint localhost:8081 --type node")
         void get() throws IOException, InterruptedException {
             when(res.statusCode()).thenReturn(HttpURLConnection.HTTP_OK);
             when(res.body()).thenReturn("{\"baseline\":{\"autoAdjust\":{\"enabled\":true}}}");
             when(httpClient.<String>send(any(), any())).thenReturn(res);
 
             var exitCode =
-                cmd(ctx).execute("config get --node-endpoint localhost:8081".split(" "));
+                cmd(ctx).execute("config get --node-endpoint localhost:8081 --type node".split(" "));
 
             Assertions.assertEquals(0, exitCode);
             verify(httpClient).send(
-                argThat(r -> "http://localhost:8081/management/v1/configuration/".equals(r.uri().toString()) &&
+                argThat(r -> "http://localhost:8081/management/v1/configuration/node/".equals(r.uri().toString()) &&
                     "application/json".equals(r.headers().firstValue("Content-Type").get())),
                 any());
             assertEquals("{\n" +
@@ -509,8 +510,9 @@ public class IgniteCliInterfaceTest extends AbstractCliTest {
         }
 
         /** */
+        //TODO: Fix in https://issues.apache.org/jira/browse/IGNITE-15306
         @Test
-        @DisplayName("get --node-endpoint localhost:8081 --selector local.baseline")
+        @DisplayName("get --node-endpoint localhost:8081 --selector local.baseline --type node")
         void getSubtree() throws IOException, InterruptedException {
             when(res.statusCode()).thenReturn(HttpURLConnection.HTTP_OK);
             when(res.body()).thenReturn("{\"autoAdjust\":{\"enabled\":true}}");
@@ -518,12 +520,12 @@ public class IgniteCliInterfaceTest extends AbstractCliTest {
 
             var exitCode =
                 cmd(ctx).execute(("config get --node-endpoint localhost:8081 " +
-                    "--selector local.baseline").split(" "));
+                    "--selector local.baseline --type node").split(" "));
 
             Assertions.assertEquals(0, exitCode);
             verify(httpClient).send(
                 argThat(r ->
-                    "http://localhost:8081/management/v1/configuration/local.baseline".equals(r.uri().toString()) &&
+                    "http://localhost:8081/management/v1/configuration/node/local.baseline".equals(r.uri().toString()) &&
                     "application/json".equals(r.headers().firstValue("Content-Type").get())),
                 any());
             assertEquals("{\n" +
@@ -534,8 +536,9 @@ public class IgniteCliInterfaceTest extends AbstractCliTest {
         }
 
         /** */
+        //TODO: Fix in https://issues.apache.org/jira/browse/IGNITE-15306
         @Test
-        @DisplayName("set --node-endpoint localhost:8081 local.baseline.autoAdjust.enabled=true")
+        @DisplayName("set --node-endpoint localhost:8081 local.baseline.autoAdjust.enabled=true --type node")
         void setHocon() throws IOException, InterruptedException {
             when(res.statusCode()).thenReturn(HttpURLConnection.HTTP_OK);
             when(httpClient.<String>send(any(), any())).thenReturn(res);
@@ -545,12 +548,12 @@ public class IgniteCliInterfaceTest extends AbstractCliTest {
             var cmd = cmd(ctx);
             var exitCode =
                 cmd.execute(("config set --node-endpoint localhost:8081 " +
-                    "local.baseline.autoAdjust.enabled=true"
+                    "local.baseline.autoAdjust.enabled=true --type node"
                     ).split(" "));
 
             Assertions.assertEquals(0, exitCode);
             verify(httpClient).send(
-                argThat(r -> "http://localhost:8081/management/v1/configuration/".equals(r.uri().toString()) &&
+                argThat(r -> "http://localhost:8081/management/v1/configuration/node/".equals(r.uri().toString()) &&
                     "PUT".equals(r.method()) &&
                     r.bodyPublisher().get().contentLength() == expSentContent.getBytes().length &&
                     "application/json".equals(r.headers().firstValue("Content-Type").get())),
@@ -561,8 +564,12 @@ public class IgniteCliInterfaceTest extends AbstractCliTest {
         }
 
         /** */
+        //TODO: Fix in https://issues.apache.org/jira/browse/IGNITE-15306
         @Test
-        @DisplayName("set --node-endpoint localhost:8081 {\"local\":{\"baseline\":{\"autoAdjust\":{\"enabled\":true}}}}")
+        @DisplayName(
+            "set --node-endpoint localhost:8081 {\"local\":{\"baseline\":{\"autoAdjust\":{\"enabled\":true}}}} " +
+                "--type node"
+        )
         void setJson() throws IOException, InterruptedException {
             when(res.statusCode()).thenReturn(HttpURLConnection.HTTP_OK);
             when(httpClient.<String>send(any(), any())).thenReturn(res);
@@ -572,12 +579,12 @@ public class IgniteCliInterfaceTest extends AbstractCliTest {
             var cmd = cmd(ctx);
             var exitCode =
                 cmd.execute(("config set --node-endpoint localhost:8081 " +
-                    "local.baseline.autoAdjust.enabled=true"
+                    "local.baseline.autoAdjust.enabled=true --type node"
                 ).split(" "));
 
             Assertions.assertEquals(0, exitCode);
             verify(httpClient).send(
-                argThat(r -> "http://localhost:8081/management/v1/configuration/".equals(r.uri().toString()) &&
+                argThat(r -> "http://localhost:8081/management/v1/configuration/node/".equals(r.uri().toString()) &&
                     "PUT".equals(r.method()) &&
                     r.bodyPublisher().get().contentLength() == expSentContent.getBytes().length &&
                     "application/json".equals(r.headers().firstValue("Content-Type").get())),
diff --git a/modules/client-handler/src/test/java/org/apache/ignite/client/handler/ClientHandlerIntegrationTest.java b/modules/client-handler/src/test/java/org/apache/ignite/client/handler/ClientHandlerIntegrationTest.java
index 29733db..5294065 100644
--- a/modules/client-handler/src/test/java/org/apache/ignite/client/handler/ClientHandlerIntegrationTest.java
+++ b/modules/client-handler/src/test/java/org/apache/ignite/client/handler/ClientHandlerIntegrationTest.java
@@ -21,10 +21,10 @@ import java.io.IOException;
 import java.io.OutputStream;
 import java.net.InetSocketAddress;
 import java.net.Socket;
-import java.util.Collections;
+import java.util.List;
+import java.util.Map;
 import io.netty.channel.ChannelFuture;
 import org.apache.ignite.app.Ignite;
-import org.apache.ignite.configuration.annotation.ConfigurationType;
 import org.apache.ignite.configuration.schemas.clientconnector.ClientConnectorConfiguration;
 import org.apache.ignite.internal.configuration.ConfigurationRegistry;
 import org.apache.ignite.internal.configuration.storage.TestConfigurationStorage;
@@ -34,6 +34,7 @@ import org.junit.jupiter.api.Test;
 import org.msgpack.core.MessagePack;
 import org.slf4j.helpers.NOPLogger;
 
+import static org.apache.ignite.configuration.annotation.ConfigurationType.LOCAL;
 import static org.junit.jupiter.api.Assertions.assertArrayEquals;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -179,9 +180,9 @@ public class ClientHandlerIntegrationTest {
 
     private ChannelFuture startServer() throws InterruptedException {
         configurationRegistry = new ConfigurationRegistry(
-                Collections.singletonList(ClientConnectorConfiguration.KEY),
-                Collections.emptyMap(),
-                Collections.singletonList(new TestConfigurationStorage(ConfigurationType.LOCAL))
+            List.of(ClientConnectorConfiguration.KEY),
+            Map.of(),
+            new TestConfigurationStorage(LOCAL)
         );
 
         configurationRegistry.start();
diff --git a/modules/client/src/test/java/org/apache/ignite/client/AbstractClientTest.java b/modules/client/src/test/java/org/apache/ignite/client/AbstractClientTest.java
index 416101c..64d7855 100644
--- a/modules/client/src/test/java/org/apache/ignite/client/AbstractClientTest.java
+++ b/modules/client/src/test/java/org/apache/ignite/client/AbstractClientTest.java
@@ -18,13 +18,13 @@
 package org.apache.ignite.client;
 
 import java.net.InetSocketAddress;
-import java.util.Collections;
+import java.util.List;
+import java.util.Map;
 import io.netty.channel.ChannelFuture;
 import io.netty.util.ResourceLeakDetector;
 import org.apache.ignite.app.Ignite;
 import org.apache.ignite.client.fakes.FakeIgnite;
 import org.apache.ignite.client.handler.ClientHandlerModule;
-import org.apache.ignite.configuration.annotation.ConfigurationType;
 import org.apache.ignite.configuration.schemas.clientconnector.ClientConnectorConfiguration;
 import org.apache.ignite.internal.configuration.ConfigurationRegistry;
 import org.apache.ignite.internal.configuration.storage.TestConfigurationStorage;
@@ -34,6 +34,7 @@ import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.BeforeEach;
 import org.slf4j.helpers.NOPLogger;
 
+import static org.apache.ignite.configuration.annotation.ConfigurationType.LOCAL;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNull;
 
@@ -89,9 +90,9 @@ public abstract class AbstractClientTest {
 
     public static ChannelFuture startServer(String host) throws InterruptedException {
         configurationRegistry = new ConfigurationRegistry(
-                Collections.singletonList(ClientConnectorConfiguration.KEY),
-                Collections.emptyMap(),
-                Collections.singletonList(new TestConfigurationStorage(ConfigurationType.LOCAL))
+            List.of(ClientConnectorConfiguration.KEY),
+            Map.of(),
+            new TestConfigurationStorage(LOCAL)
         );
 
         configurationRegistry.start();
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 f7cc95d..b1e69fb 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
@@ -22,6 +22,7 @@ import java.lang.annotation.Target;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
 import org.apache.ignite.configuration.ConfigurationChangeException;
@@ -29,7 +30,6 @@ 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;
-import org.apache.ignite.configuration.annotation.ConfigurationType;
 import org.apache.ignite.configuration.annotation.NamedConfigValue;
 import org.apache.ignite.configuration.annotation.Value;
 import org.apache.ignite.configuration.validation.Immutable;
@@ -47,6 +47,7 @@ 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.TimeUnit.SECONDS;
+import static org.apache.ignite.configuration.annotation.ConfigurationType.LOCAL;
 import static org.apache.ignite.internal.configuration.AConfiguration.KEY;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNull;
@@ -63,7 +64,7 @@ public class ConfigurationChangerTest {
     }
 
     /** */
-    @ConfigurationRoot(rootName = "key", type = ConfigurationType.LOCAL)
+    @ConfigurationRoot(rootName = "key", type = LOCAL)
     public static class AConfigurationSchema {
         /** */
         @ConfigValue
@@ -108,16 +109,15 @@ public class ConfigurationChangerTest {
      */
     @Test
     public void testSimpleConfigurationChange() throws Exception {
-        var storage = new TestConfigurationStorage(ConfigurationType.LOCAL);
+        var storage = new TestConfigurationStorage(LOCAL);
 
-        ConfigurationChanger changer = new TestConfigurationChanger(cgen);
-        changer.addRootKey(KEY);
-        changer.register(storage);
+        ConfigurationChanger changer = new TestConfigurationChanger(cgen, List.of(KEY), Map.of(), storage);
+        changer.start();
 
         changer.change(source(KEY, (AChange parent) -> parent
             .changeChild(change -> change.changeIntCfg(1).changeStrCfg("1"))
             .changeElements(change -> change.create("a", element -> element.changeStrCfg("1")))
-        ), null).get(1, SECONDS);
+        )).get(1, SECONDS);
 
         AView newRoot = (AView)changer.getRootNode(KEY);
 
@@ -131,20 +131,18 @@ public class ConfigurationChangerTest {
      */
     @Test
     public void testModifiedFromAnotherStorage() throws Exception {
-        var storage = new TestConfigurationStorage(ConfigurationType.LOCAL);
+        var storage = new TestConfigurationStorage(LOCAL);
 
-        ConfigurationChanger changer1 = new TestConfigurationChanger(cgen);
-        changer1.addRootKey(KEY);
-        changer1.register(storage);
+        ConfigurationChanger changer1 = new TestConfigurationChanger(cgen, List.of(KEY), Map.of(), storage);
+        changer1.start();
 
-        ConfigurationChanger changer2 = new TestConfigurationChanger(cgen);
-        changer2.addRootKey(KEY);
-        changer2.register(storage);
+        ConfigurationChanger changer2 = new TestConfigurationChanger(cgen, List.of(KEY), Map.of(), storage);
+        changer2.start();
 
         changer1.change(source(KEY, (AChange parent) -> parent
             .changeChild(change -> change.changeIntCfg(1).changeStrCfg("1"))
             .changeElements(change -> change.create("a", element -> element.changeStrCfg("1")))
-        ), null).get(1, SECONDS);
+        )).get(1, SECONDS);
 
         changer2.change(source(KEY, (AChange parent) -> parent
             .changeChild(change -> change.changeIntCfg(2).changeStrCfg("2"))
@@ -152,7 +150,7 @@ public class ConfigurationChangerTest {
                 .createOrUpdate("a", element -> element.changeStrCfg("2"))
                 .create("b", element -> element.changeStrCfg("2"))
             )
-        ), null).get(1, SECONDS);
+        )).get(1, SECONDS);
 
         AView newRoot1 = (AView)changer1.getRootNode(KEY);
 
@@ -174,26 +172,31 @@ public class ConfigurationChangerTest {
      */
     @Test
     public void testModifiedFromAnotherStorageWithIncompatibleChanges() throws Exception {
-        var storage = new TestConfigurationStorage(ConfigurationType.LOCAL);
+        var storage = new TestConfigurationStorage(LOCAL);
 
-        ConfigurationChanger changer1 = new TestConfigurationChanger(cgen);
-        changer1.addRootKey(KEY);
-        changer1.register(storage);
+        ConfigurationChanger changer1 = new TestConfigurationChanger(cgen, List.of(KEY), Map.of(), storage);
+        changer1.start();
 
-        ConfigurationChanger changer2 = new TestConfigurationChanger(cgen);
-        changer2.addRootKey(KEY);
-        changer2.register(storage);
+        Validator<MaybeInvalid, Object> validator = new Validator<>() {
+            /** {@inheritDoc} */
+            @Override public void validate(MaybeInvalid annotation, ValidationContext<Object> ctx) {
+                ctx.addIssue(new ValidationIssue("foo"));
+            }
+        };
+
+        ConfigurationChanger changer2 = new TestConfigurationChanger(
+            cgen,
+            List.of(KEY),
+            Map.of(MaybeInvalid.class, Set.of(validator)),
+            storage
+        );
+
+        changer2.start();
 
         changer1.change(source(KEY, (AChange parent) -> parent
             .changeChild(change -> change.changeIntCfg(1).changeStrCfg("1"))
             .changeElements(change -> change.create("a", element -> element.changeStrCfg("1")))
-        ), null).get(1, SECONDS);
-
-        changer2.addValidator(MaybeInvalid.class, new Validator<MaybeInvalid, Object>() {
-            @Override public void validate(MaybeInvalid annotation, ValidationContext<Object> ctx) {
-                ctx.addIssue(new ValidationIssue("foo"));
-            }
-        });
+        )).get(1, SECONDS);
 
         assertThrows(ExecutionException.class, () -> changer2.change(source(KEY, (AChange parent) -> parent
             .changeChild(change -> change.changeIntCfg(2).changeStrCfg("2"))
@@ -201,7 +204,7 @@ public class ConfigurationChangerTest {
                 .create("a", element -> element.changeStrCfg("2"))
                 .create("b", element -> element.changeStrCfg("2"))
             )
-        ), null).get(1, SECONDS));
+        )).get(1, SECONDS));
 
         AView newRoot = (AView)changer2.getRootNode(KEY);
 
@@ -215,24 +218,23 @@ public class ConfigurationChangerTest {
      */
     @Test
     public void testFailedToWrite() {
-        var storage = new TestConfigurationStorage(ConfigurationType.LOCAL);
+        var storage = new TestConfigurationStorage(LOCAL);
 
-        ConfigurationChanger changer = new TestConfigurationChanger(cgen);
-        changer.addRootKey(KEY);
+        ConfigurationChanger changer = new TestConfigurationChanger(cgen, List.of(KEY), Map.of(), storage);
 
         storage.fail(true);
 
-        assertThrows(ConfigurationChangeException.class, () -> changer.register(storage));
+        assertThrows(ConfigurationChangeException.class, changer::start);
 
         storage.fail(false);
 
-        changer.register(storage);
+        changer.start();
 
         storage.fail(true);
 
         assertThrows(ExecutionException.class, () -> changer.change(source(KEY, (AChange parent) -> parent
             .changeChild(child -> child.changeIntCfg(1))
-        ), null).get(1, SECONDS));
+        )).get(1, SECONDS));
 
         storage.fail(false);
 
@@ -246,7 +248,7 @@ public class ConfigurationChangerTest {
     }
 
     /** */
-    @ConfigurationRoot(rootName = "def", type = ConfigurationType.LOCAL)
+    @ConfigurationRoot(rootName = "def", type = LOCAL)
     public static class DefaultsConfigurationSchema {
         /** */
         @ConfigValue
@@ -275,15 +277,13 @@ public class ConfigurationChangerTest {
 
     @Test
     public void defaultsOnInit() throws Exception {
-        var changer = new TestConfigurationChanger(cgen);
-
-        changer.addRootKey(DefaultsConfiguration.KEY);
+        var storage = new TestConfigurationStorage(LOCAL);
 
-        var storage = new TestConfigurationStorage(ConfigurationType.LOCAL);
+        var changer = new TestConfigurationChanger(cgen, List.of(DefaultsConfiguration.KEY), Map.of(), storage);
 
-        changer.register(storage);
+        changer.start();
 
-        changer.initialize(storage.type());
+        changer.initializeDefaults();
 
         DefaultsView root = (DefaultsView)changer.getRootNode(DefaultsConfiguration.KEY);
 
@@ -295,7 +295,7 @@ public class ConfigurationChangerTest {
             .changeChildsList(childs ->
                 childs.create("name", child -> {})
             )
-        ), null).get(1, SECONDS);
+        )).get(1, SECONDS);
 
         root = (DefaultsView)changer.getRootNode(DefaultsConfiguration.KEY);
 
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
index 23f27d3..0d80082 100644
--- 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
@@ -17,8 +17,14 @@
 
 package org.apache.ignite.internal.configuration;
 
+import java.lang.annotation.Annotation;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
 import org.apache.ignite.configuration.RootKey;
+import org.apache.ignite.configuration.validation.Validator;
 import org.apache.ignite.internal.configuration.asm.ConfigurationAsmGenerator;
+import org.apache.ignite.internal.configuration.storage.ConfigurationStorage;
 import org.apache.ignite.internal.configuration.tree.InnerNode;
 
 import static java.util.concurrent.CompletableFuture.completedFuture;
@@ -29,19 +35,30 @@ public class TestConfigurationChanger extends ConfigurationChanger {
     private final ConfigurationAsmGenerator cgen;
 
     /**
+     * Constructor.
+     *
      * @param cgen Runtime implementations generator for node classes. Will be used to instantiate nodes objects.
+     * @param rootKeys Configuration root keys.
+     * @param validators Validators.
+     * @param storage Configuration storage.
+     * @throws IllegalArgumentException If the configuration type of the root keys is not equal to the storage type.
      */
-    public TestConfigurationChanger(ConfigurationAsmGenerator cgen) {
-        super((oldRoot, newRoot, revision) -> completedFuture(null));
+    public TestConfigurationChanger(
+        ConfigurationAsmGenerator cgen,
+        Collection<RootKey<?, ?>> rootKeys,
+        Map<Class<? extends Annotation>, Set<Validator<?, ?>>> validators,
+        ConfigurationStorage storage
+    ) {
+        super(
+            (oldRoot, newRoot, revision) -> completedFuture(null),
+            rootKeys,
+            validators,
+            storage
+        );
 
         this.cgen = cgen;
-    }
-
-    /** {@inheritDoc} */
-    @Override public void addRootKey(RootKey<?, ?> rootKey) {
-        super.addRootKey(rootKey);
 
-        cgen.compileRootSchema(rootKey.schemaClass());
+        rootKeys.forEach(key -> cgen.compileRootSchema(key.schemaClass()));
     }
 
     /** {@inheritDoc} */
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/hocon/HoconConverterTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/hocon/HoconConverterTest.java
index 5b151d5..85174e8 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/hocon/HoconConverterTest.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/hocon/HoconConverterTest.java
@@ -18,8 +18,8 @@
 package org.apache.ignite.internal.configuration.hocon;
 
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.ExecutionException;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
@@ -28,7 +28,6 @@ import com.typesafe.config.ConfigRenderOptions;
 import com.typesafe.config.ConfigValue;
 import org.apache.ignite.configuration.annotation.Config;
 import org.apache.ignite.configuration.annotation.ConfigurationRoot;
-import org.apache.ignite.configuration.annotation.ConfigurationType;
 import org.apache.ignite.configuration.annotation.NamedConfigValue;
 import org.apache.ignite.configuration.annotation.Value;
 import org.apache.ignite.internal.configuration.ConfigurationRegistry;
@@ -40,6 +39,7 @@ import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.function.Executable;
 
 import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.apache.ignite.configuration.annotation.ConfigurationType.LOCAL;
 import static org.apache.ignite.internal.configuration.hocon.HoconConverter.hoconSource;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.is;
@@ -53,7 +53,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
  */
 public class HoconConverterTest {
     /** */
-    @ConfigurationRoot(rootName = "root", type = ConfigurationType.LOCAL)
+    @ConfigurationRoot(rootName = "root", type = LOCAL)
     public static class HoconRootConfigurationSchema {
         /** */
         @NamedConfigValue(syntheticKeyName = "a")
@@ -158,9 +158,9 @@ public class HoconConverterTest {
     @BeforeAll
     public static void beforeAll() {
         registry = new ConfigurationRegistry(
-            Collections.singletonList(HoconRootConfiguration.KEY),
-            Collections.emptyMap(),
-            Collections.singletonList(new TestConfigurationStorage(ConfigurationType.LOCAL))
+            List.of(HoconRootConfiguration.KEY),
+            Map.of(),
+            new TestConfigurationStorage(LOCAL)
         );
 
         registry.start();
@@ -553,7 +553,7 @@ public class HoconConverterTest {
      */
     private void change(String hocon) throws Throwable {
         try {
-            registry.change(hoconSource(ConfigFactory.parseString(hocon).root()), null).get(1, SECONDS);
+            registry.change(hoconSource(ConfigFactory.parseString(hocon).root())).get(1, SECONDS);
         }
         catch (ExecutionException e) {
             throw e.getCause();
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 bb631c0..77753dd 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
@@ -18,15 +18,14 @@
 package org.apache.ignite.internal.configuration.notifications;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Future;
 import org.apache.ignite.configuration.annotation.Config;
 import org.apache.ignite.configuration.annotation.ConfigValue;
 import org.apache.ignite.configuration.annotation.ConfigurationRoot;
-import org.apache.ignite.configuration.annotation.ConfigurationType;
 import org.apache.ignite.configuration.annotation.NamedConfigValue;
 import org.apache.ignite.configuration.annotation.Value;
 import org.apache.ignite.configuration.notifications.ConfigurationNamedListListener;
@@ -39,6 +38,7 @@ import org.junit.jupiter.api.Test;
 
 import static java.util.concurrent.CompletableFuture.completedFuture;
 import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.apache.ignite.configuration.annotation.ConfigurationType.LOCAL;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNull;
@@ -48,7 +48,7 @@ import static org.junit.jupiter.api.Assertions.fail;
 /** */
 public class ConfigurationListenerTest {
     /** */
-    @ConfigurationRoot(rootName = "parent", type = ConfigurationType.LOCAL)
+    @ConfigurationRoot(rootName = "parent", type = LOCAL)
     public static class ParentConfigurationSchema {
         /** */
         @ConfigValue
@@ -76,17 +76,13 @@ public class ConfigurationListenerTest {
     /** */
     @BeforeEach
     public void before() {
-        var testConfigurationStorage = new TestConfigurationStorage(ConfigurationType.LOCAL);
+        var testConfigurationStorage = new TestConfigurationStorage(LOCAL);
 
-        registry = new ConfigurationRegistry(
-            Collections.singletonList(ParentConfiguration.KEY),
-            Collections.emptyMap(),
-            Collections.singletonList(testConfigurationStorage)
-        );
+        registry = new ConfigurationRegistry(List.of(ParentConfiguration.KEY), Map.of(), testConfigurationStorage);
 
         registry.start();
 
-        registry.startStorageConfigurations(testConfigurationStorage.type());
+        registry.initializeDefaults();
 
         configuration = registry.getConfiguration(ParentConfiguration.KEY);
     }
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/sample/UsageTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/sample/UsageTest.java
index df521e7..72a8f0a 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/sample/UsageTest.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/sample/UsageTest.java
@@ -17,15 +17,15 @@
 
 package org.apache.ignite.internal.configuration.sample;
 
-import java.util.Arrays;
-import java.util.Collections;
-import org.apache.ignite.configuration.annotation.ConfigurationType;
+import java.util.List;
+import java.util.Map;
 import org.apache.ignite.internal.configuration.ConfigurationRegistry;
 import org.apache.ignite.internal.configuration.storage.TestConfigurationStorage;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Test;
 
 import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.apache.ignite.configuration.annotation.ConfigurationType.LOCAL;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNull;
@@ -50,14 +50,14 @@ public class UsageTest {
     @Test
     public void test() throws Exception {
         registry = new ConfigurationRegistry(
-            Collections.singletonList(LocalConfiguration.KEY),
-            Collections.emptyMap(),
-            Collections.singletonList(new TestConfigurationStorage(ConfigurationType.LOCAL))
+            List.of(LocalConfiguration.KEY),
+            Map.of(),
+            new TestConfigurationStorage(LOCAL)
         );
 
         registry.start();
 
-        registry.startStorageConfigurations(ConfigurationType.LOCAL);
+        registry.initializeDefaults();
 
         LocalConfiguration root = registry.getConfiguration(LocalConfiguration.KEY);
 
@@ -110,9 +110,9 @@ public class UsageTest {
         long autoAdjustTimeout = 30_000L;
 
         registry = new ConfigurationRegistry(
-            Arrays.asList(NetworkConfiguration.KEY, LocalConfiguration.KEY),
-            Collections.emptyMap(),
-            Collections.singletonList(new TestConfigurationStorage(ConfigurationType.LOCAL))
+            List.of(NetworkConfiguration.KEY, LocalConfiguration.KEY),
+            Map.of(),
+            new TestConfigurationStorage(LOCAL)
         );
 
         registry.start();
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/tree/NamedListNodeTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/tree/NamedListNodeTest.java
index 6f87b3a..e1b1491 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/tree/NamedListNodeTest.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/tree/NamedListNodeTest.java
@@ -18,10 +18,10 @@
 package org.apache.ignite.internal.configuration.tree;
 
 import java.io.Serializable;
+import java.util.List;
 import java.util.Map;
 import org.apache.ignite.configuration.annotation.Config;
 import org.apache.ignite.configuration.annotation.ConfigurationRoot;
-import org.apache.ignite.configuration.annotation.ConfigurationType;
 import org.apache.ignite.configuration.annotation.NamedConfigValue;
 import org.apache.ignite.configuration.annotation.Value;
 import org.apache.ignite.internal.configuration.TestConfigurationChanger;
@@ -35,6 +35,7 @@ import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
 import static java.lang.String.format;
+import static org.apache.ignite.configuration.annotation.ConfigurationType.LOCAL;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.aMapWithSize;
 import static org.hamcrest.Matchers.anEmptyMap;
@@ -87,11 +88,10 @@ public class NamedListNodeTest {
     /** */
     @BeforeEach
     public void before() {
-        storage = new TestConfigurationStorage(ConfigurationType.LOCAL);
+        storage = new TestConfigurationStorage(LOCAL);
 
-        changer = new TestConfigurationChanger(cgen);
-        changer.addRootKey(AConfiguration.KEY);
-        changer.register(storage);
+        changer = new TestConfigurationChanger(cgen, List.of(AConfiguration.KEY), Map.of(), storage);
+        changer.start();
     }
 
     /** */
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 9bf04cc..fa72dff 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
@@ -21,14 +21,15 @@ import java.io.Serializable;
 import java.util.List;
 import java.util.Map;
 import java.util.function.Consumer;
+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;
-import org.apache.ignite.configuration.annotation.ConfigurationType;
 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.storage.TestConfigurationStorage;
 import org.apache.ignite.internal.configuration.tree.ConfigurationSource;
 import org.apache.ignite.internal.configuration.tree.InnerNode;
 import org.apache.ignite.internal.configuration.tree.TraversableTreeNode;
@@ -38,9 +39,12 @@ import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 
 import static java.util.Collections.singletonMap;
+import static org.apache.ignite.configuration.annotation.ConfigurationType.DISTRIBUTED;
+import static org.apache.ignite.configuration.annotation.ConfigurationType.LOCAL;
 import static org.apache.ignite.internal.configuration.tree.NamedListNode.NAME;
 import static org.apache.ignite.internal.configuration.tree.NamedListNode.ORDER_IDX;
 import static org.apache.ignite.internal.configuration.util.ConfigurationFlattener.createFlattenedUpdatesMap;
+import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.checkConfigurationType;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.aMapWithSize;
 import static org.hamcrest.Matchers.allOf;
@@ -112,7 +116,7 @@ public class ConfigurationUtilTest {
     }
 
     /** */
-    @ConfigurationRoot(rootName = "root", type = ConfigurationType.LOCAL)
+    @ConfigurationRoot(rootName = "root", type = LOCAL)
     public static class ParentConfigurationSchema {
         /** */
         @NamedConfigValue
@@ -341,6 +345,56 @@ public class ConfigurationUtilTest {
         );
     }
 
+    /** */
+    @Test
+    void testCheckConfigurationTypeMixedTypes() {
+        List<RootKey<?, ?>> rootKeys = List.of(LocalFirstConfiguration.KEY, DistributedFirstConfiguration.KEY);
+
+        assertThrows(
+            IllegalArgumentException.class,
+            () -> checkConfigurationType(rootKeys, new TestConfigurationStorage(LOCAL))
+        );
+
+        assertThrows(
+            IllegalArgumentException.class,
+            () -> checkConfigurationType(rootKeys, new TestConfigurationStorage(DISTRIBUTED))
+        );
+    }
+
+    /** */
+    @Test
+    void testCheckConfigurationTypeOppositeTypes() {
+        assertThrows(
+            IllegalArgumentException.class,
+            () -> checkConfigurationType(
+                List.of(DistributedFirstConfiguration.KEY, DistributedSecondConfiguration.KEY),
+                new TestConfigurationStorage(LOCAL)
+            )
+        );
+
+        assertThrows(
+            IllegalArgumentException.class,
+            () -> checkConfigurationType(
+                List.of(LocalFirstConfiguration.KEY, LocalSecondConfiguration.KEY),
+                new TestConfigurationStorage(DISTRIBUTED)
+            )
+        );
+    }
+
+    /** */
+    @Test
+    void testCheckConfigurationTypeNoError() {
+        checkConfigurationType(
+            List.of(LocalFirstConfiguration.KEY, LocalSecondConfiguration.KEY),
+            new TestConfigurationStorage(LOCAL)
+        );
+
+        checkConfigurationType(
+            List.of(DistributedFirstConfiguration.KEY, DistributedSecondConfiguration.KEY),
+            new TestConfigurationStorage(DISTRIBUTED)
+        );
+    }
+
     /**
      * Patches super root and returns flat representation of the changes. Passed {@code superRoot} object will contain
      * patched tree when method execution is completed.
@@ -362,4 +416,44 @@ public class ConfigurationUtilTest {
         // Create flat diff between two super trees.
         return createFlattenedUpdatesMap(originalSuperRoot, superRoot);
     }
+
+    /**
+     * First local configuration.
+     */
+    @ConfigurationRoot(rootName = "localFirst", type = LOCAL)
+    public static class LocalFirstConfigurationSchema {
+        /** String field. */
+        @Value(hasDefault = true)
+        public String str = "str";
+    }
+
+    /**
+     * Second local configuration.
+     */
+    @ConfigurationRoot(rootName = "localSecond", type = LOCAL)
+    public static class LocalSecondConfigurationSchema {
+        /** String field. */
+        @Value(hasDefault = true)
+        public String str = "str";
+    }
+
+    /**
+     * First distributed configuration.
+     */
+    @ConfigurationRoot(rootName = "distributedFirst", type = DISTRIBUTED)
+    public static class DistributedFirstConfigurationSchema {
+        /** String field. */
+        @Value(hasDefault = true)
+        public String str = "str";
+    }
+
+    /**
+     * Second distributed configuration.
+     */
+    @ConfigurationRoot(rootName = "distributedSecond", type = DISTRIBUTED)
+    public static class DistributedSecondConfigurationSchema {
+        /** String field. */
+        @Value(hasDefault = true)
+        public String str = "str";
+    }
 }
diff --git a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/RootKey.java b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/RootKey.java
index 93ec620..3ff15be 100644
--- a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/RootKey.java
+++ b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/RootKey.java
@@ -19,6 +19,7 @@ package org.apache.ignite.configuration;
 
 import org.apache.ignite.configuration.annotation.ConfigurationRoot;
 import org.apache.ignite.configuration.annotation.ConfigurationType;
+import org.apache.ignite.internal.tostring.S;
 
 /**
  * Configuration root selector.
@@ -27,19 +28,13 @@ import org.apache.ignite.configuration.annotation.ConfigurationType;
  * @param <VIEW> Type of the immutable snapshot view associated with the tree.
  */
 public class RootKey<T extends ConfigurationTree<VIEW, ?>, VIEW> {
-    /**
-     * Name of the configuration root.
-     */
+    /** Name of the configuration root. */
     private final String rootName;
 
-    /**
-     * Configuration type of the root.
-     */
+    /** Configuration type of the root. */
     private final ConfigurationType storageType;
 
-    /**
-     * Schema class for the root.
-     */
+    /** Schema class for the root. */
     private final Class<?> schemaClass;
 
     /**
@@ -84,4 +79,9 @@ public class RootKey<T extends ConfigurationTree<VIEW, ?>, VIEW> {
     public Class<?> schemaClass() {
         return schemaClass;
     }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(RootKey.class, this);
+    }
 }
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 153da64..b64f291 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
@@ -18,22 +18,17 @@ package org.apache.ignite.internal.configuration;
 
 import java.io.Serializable;
 import java.lang.annotation.Annotation;
-import java.util.HashMap;
-import java.util.HashSet;
+import java.util.Collection;
 import java.util.List;
 import java.util.Map;
-import java.util.NoSuchElementException;
 import java.util.Set;
-import java.util.TreeMap;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ForkJoinPool;
 import java.util.function.Function;
-import java.util.stream.Collectors;
 import org.apache.ignite.configuration.ConfigurationChangeException;
 import org.apache.ignite.configuration.RootKey;
-import org.apache.ignite.configuration.annotation.ConfigurationType;
 import org.apache.ignite.configuration.validation.ConfigurationValidationException;
 import org.apache.ignite.configuration.validation.ValidationIssue;
 import org.apache.ignite.configuration.validation.Validator;
@@ -47,12 +42,13 @@ import org.apache.ignite.internal.configuration.validation.MemberKey;
 import org.apache.ignite.internal.configuration.validation.ValidationUtil;
 import org.apache.ignite.lang.NodeStoppingException;
 import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
 
 import static java.util.concurrent.CompletableFuture.completedFuture;
-import static java.util.stream.Collectors.toList;
+import static java.util.function.Function.identity;
+import static java.util.stream.Collectors.toMap;
 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.checkConfigurationType;
 import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.dropNulls;
 import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.fillFromPrefixMap;
 import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.toPrefixMap;
@@ -61,26 +57,36 @@ import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.to
  * Class that handles configuration changes, by validating them, passing to storage and listening to storage updates.
  */
 public abstract class ConfigurationChanger {
-    /** */
+    /** Thread pool. */
     private final ForkJoinPool pool = new ForkJoinPool(2);
 
-    /** */
-    private final Map<String, RootKey<?, ?>> rootKeys = new TreeMap<>();
+    /** Lazy annotations cache for configuration schema fields. */
+    private final Map<MemberKey, Annotation[]> cachedAnnotations = new ConcurrentHashMap<>();
+
+    /** Closure to execute when an update from the storage is received. */
+    private final Notificator notificator;
+
+    /** Root keys. Mapping: {@link RootKey#key()} -> identity (itself). */
+    private final Map<String, RootKey<?, ?>> rootKeys;
 
-    /** Map that has all the trees in accordance to their storages. */
-    private final Map<ConfigurationType, StorageRoots> storagesRootsMap = new ConcurrentHashMap<>();
+    /** Validators. */
+    private final Map<Class<? extends Annotation>, Set<Validator<?, ?>>> validators;
 
-    /** Annotation classes mapped to validator objects. */
-    private Map<Class<? extends Annotation>, Set<Validator<?, ?>>> validators = new HashMap<>();
+    /** Configuration storage. */
+    private final ConfigurationStorage storage;
+
+    /** Storage trees. */
+    private volatile StorageRoots storageRoots;
 
     /**
-     * Closure interface to be used by the configuration changer. An instance of this closure is passed into the constructor and
-     * invoked every time when there's an update from any of the storages.
+     * Closure interface to be used by the configuration changer. An instance of this closure is passed into
+     * the constructor and invoked every time when there's an update from any of the storages.
      */
     @FunctionalInterface
     public interface Notificator {
         /**
          * Invoked every time when the configuration is updated.
+         *
          * @param oldRoot Old roots values. All these roots always belong to a single storage.
          * @param newRoot New values for the same roots as in {@code oldRoot}.
          * @param storageRevision Revision of the storage.
@@ -90,9 +96,6 @@ public abstract class ConfigurationChanger {
         @NotNull CompletableFuture<Void> notify(SuperRoot oldRoot, SuperRoot newRoot, long storageRevision);
     }
 
-    /** Closure to execute when an update from the storage is received. */
-    private final Notificator notificator;
-
     /**
      * Immutable data container to store version and all roots associated with the specific storage.
      */
@@ -106,61 +109,45 @@ public abstract class ConfigurationChanger {
         /** Future that signifies update of current configuration. */
         private final CompletableFuture<Void> changeFuture = new CompletableFuture<>();
 
-        /** */
+        /**
+         * Constructor.
+         *
+         * @param roots Forest.
+         * @param version Version associated with the currently known storage state.
+         */
         private StorageRoots(SuperRoot roots, long version) {
             this.roots = roots;
             this.version = version;
         }
     }
 
-    /** Lazy annotations cache for configuration schema fields. */
-    private final Map<MemberKey, Annotation[]> cachedAnnotations = new ConcurrentHashMap<>();
-
-    /** Storage instances by their classes. Comes in handy when all you have is {@link RootKey}. */
-    private final Map<ConfigurationType, ConfigurationStorage> storageInstances = new HashMap<>();
-
     /**
+     * Constructor.
+     *
      * @param notificator Closure to execute when update from the storage is received.
+     * @param rootKeys Configuration root keys.
+     * @param validators Validators.
+     * @param storage Configuration storage.
+     * @throws IllegalArgumentException If the configuration type of the root keys is not equal to the storage type.
      */
-    public ConfigurationChanger(Notificator notificator) {
-        this.notificator = notificator;
-    }
-
-    /**
-     * Adds a single validator instance.
-     * @param annotationType Annotation type for validated fields.
-     * @param validator Validator instance for this annotation.
-     * @param <A> Annotation type.
-     */
-    public <A extends Annotation> void addValidator(Class<A> annotationType, Validator<A, ?> validator) {
-        validators
-            .computeIfAbsent(annotationType, a -> new HashSet<>())
-            .add(validator);
-    }
-
-    /**
-     * Adds multiple validators instances.
-     * @param annotationType Annotation type for validated fields.
-     * @param validators Set of validator instancec for this annotation.
-     */
-    public void addValidators(Class<? extends Annotation> annotationType, Set<Validator<? extends Annotation, ?>> validators) {
-        this.validators
-            .computeIfAbsent(annotationType, a -> new HashSet<>())
-            .addAll(validators);
-    }
+    public ConfigurationChanger(
+        Notificator notificator,
+        Collection<RootKey<?, ?>> rootKeys,
+        Map<Class<? extends Annotation>, Set<Validator<?, ?>>> validators,
+        ConfigurationStorage storage
+    ) {
+        checkConfigurationType(rootKeys, storage);
 
-    /**
-     * Registers an additional root key.
-     * @param rootKey Root key instance.
-     */
-    public void addRootKey(RootKey<?, ?> rootKey) {
-        assert !storageInstances.containsKey(rootKey.type());
+        this.notificator = notificator;
+        this.validators = validators;
+        this.storage = storage;
 
-        rootKeys.put(rootKey.key(), rootKey);
+        this.rootKeys = rootKeys.stream().collect(toMap(RootKey::key, identity()));
     }
 
     /**
-     * Created new {@code Node} object that corresponds to passed root keys root configuration node.
+     * Creates new {@code Node} object that corresponds to passed root keys root configuration node.
+     *
      * @param rootKey Root key.
      * @return New {@link InnerNode} instance that represents root.
      */
@@ -168,9 +155,10 @@ public abstract class ConfigurationChanger {
 
     /**
      * Utility method to create {@link SuperRoot} parameter value.
+     *
      * @return Function that creates root node by root name or returns {@code null} if root name is not found.
      */
-    @NotNull private Function<String, InnerNode> rootCreator() {
+    private Function<String, InnerNode> rootCreator() {
         return key -> {
             RootKey<?, ?> rootKey = rootKeys.get(key);
 
@@ -179,21 +167,14 @@ public abstract class ConfigurationChanger {
     }
 
     /**
-     * Registers a storage.
-     * @param configurationStorage Configuration storage instance.
+     * Start component.
      */
     // ConfigurationChangeException, really?
-    public void register(ConfigurationStorage configurationStorage) throws ConfigurationChangeException {
-        storageInstances.put(configurationStorage.type(), configurationStorage);
-
-        Set<RootKey<?, ?>> storageRootKeys = rootKeys.values().stream().filter(
-            rootKey -> configurationStorage.type() == rootKey.type()
-        ).collect(Collectors.toSet());
-
+    public void start() throws ConfigurationChangeException {
         Data data;
 
         try {
-            data = configurationStorage.readAll();
+            data = storage.readAll();
         }
         catch (StorageException e) {
             throw new ConfigurationChangeException("Failed to initialize configuration: " + e.getMessage(), e);
@@ -203,7 +184,7 @@ public abstract class ConfigurationChanger {
 
         Map<String, ?> dataValuesPrefixMap = toPrefixMap(data.values());
 
-        for (RootKey<?, ?> rootKey : storageRootKeys) {
+        for (RootKey<?, ?> rootKey : rootKeys.values()) {
             Map<String, ?> rootPrefixMap = (Map<String, ?>)dataValuesPrefixMap.get(rootKey.key());
 
             InnerNode rootNode = createRootNode(rootKey);
@@ -214,36 +195,27 @@ public abstract class ConfigurationChanger {
             superRoot.addRoot(rootKey, rootNode);
         }
 
-        StorageRoots storageRoots = new StorageRoots(superRoot, data.changeId());
+        storageRoots = new StorageRoots(superRoot, data.changeId());
 
-        storagesRootsMap.put(configurationStorage.type(), storageRoots);
-
-        configurationStorage.registerConfigurationListener(changedEntries -> updateFromListener(
-            configurationStorage.type(),
-            changedEntries
-        ));
+        storage.registerConfigurationListener(this::updateFromListener);
     }
 
     /**
      * Initializes the configuration storage - reads data and sets default values for missing configuration properties.
-     * @param storageType Storage type.
+     *
      * @throws ConfigurationValidationException If configuration validation failed.
-     * @throws ConfigurationChangeException If configuration framework failed to add default values and save them to
-     *      storage.
+     * @throws ConfigurationChangeException If configuration framework failed to add default values and save them to storage.
      */
-    public void initialize(ConfigurationType storageType) throws ConfigurationValidationException, ConfigurationChangeException {
-        ConfigurationStorage configurationStorage = storageInstances.get(storageType);
-
-        assert configurationStorage != null : storageType;
-
+    public void initializeDefaults() throws ConfigurationValidationException, ConfigurationChangeException {
         try {
             ConfigurationSource defaultsCfgSource = new ConfigurationSource() {
+                /** {@inheritDoc} */
                 @Override public void descend(ConstructableTreeNode node) {
                     addDefaults((InnerNode)node);
                 }
             };
 
-            changeInternally(defaultsCfgSource, configurationStorage).get();
+            changeInternally(defaultsCfgSource).get();
         }
         catch (ExecutionException e) {
             Throwable cause = e.getCause();
@@ -255,75 +227,34 @@ public abstract class ConfigurationChanger {
                 throw (ConfigurationChangeException)cause;
 
             throw new ConfigurationChangeException(
-                "Failed to write defalut configuration values into the storage " + configurationStorage.getClass(), e
+                "Failed to write default configuration values into the storage " + storage.getClass(), e
             );
         }
         catch (InterruptedException e) {
             throw new ConfigurationChangeException(
-                "Failed to initialize configuration storage " + configurationStorage.getClass(), e
+                "Failed to initialize configuration storage " + storage.getClass(), e
             );
         }
     }
 
     /**
      * Changes the configuration.
+     *
      * @param source Configuration source to create patch from.
-     * @param storage Expected storage for the changes. If null, a derived storage will be used
-     * unconditionaly.
      * @return Future that is completed on change completion.
      */
-    public CompletableFuture<Void> change(
-        ConfigurationSource source,
-        @Nullable ConfigurationStorage storage
-    ) {
-        Set<ConfigurationType> storagesTypes = new HashSet<>();
-
-        ConstructableTreeNode collector = new ConstructableTreeNode() {
-            @Override public void construct(String key, ConfigurationSource src) throws NoSuchElementException {
-                RootKey<?, ?> rootKey = rootKeys.get(key);
-
-                if (rootKey == null)
-                    throw new NoSuchElementException(key);
-
-                storagesTypes.add(rootKey.type());
-            }
-
-            @Override public ConstructableTreeNode copy() {
-                throw new UnsupportedOperationException("copy");
-            }
-        };
-
-        source.reset();
-
-        source.descend(collector);
-
-        assert !storagesTypes.isEmpty();
-
-        if (storagesTypes.size() != 1) {
-            return CompletableFuture.failedFuture(
-                new ConfigurationChangeException(
-                    "Cannot handle change request with configuration patches belonging to different storages."
-                )
-            );
-        }
-
-        ConfigurationStorage actualStorage = storageInstances.get(storagesTypes.iterator().next());
-
-        if (storage != null && storage != actualStorage) {
-            return CompletableFuture.failedFuture(
-                new ConfigurationChangeException("Mismatched storage passed.")
-            );
-        }
-
-        return changeInternally(source, actualStorage);
+    public CompletableFuture<Void> change(ConfigurationSource source) {
+        return changeInternally(source);
     }
 
     /** Stop component. */
     public void stop() {
         pool.shutdownNow();
 
-        for (StorageRoots storageRoots : storagesRootsMap.values())
-            storageRoots.changeFuture.completeExceptionally(new NodeStoppingException());
+        StorageRoots roots = storageRoots;
+
+        if (roots != null)
+            roots.changeFuture.completeExceptionally(new NodeStoppingException());
     }
 
     /**
@@ -333,32 +264,30 @@ public abstract class ConfigurationChanger {
      * @return Root node.
      */
     public InnerNode getRootNode(RootKey<?, ?> rootKey) {
-        return storagesRootsMap.get(rootKey.type()).roots.getRoot(rootKey);
+        return storageRoots.roots.getRoot(rootKey);
     }
 
     /**
-     * @return Super root chat contains roots belonging to all storages.
+     * Get storage super root.
+     *
+     * @return Super root storage.
      */
-    public SuperRoot mergedSuperRoot() {
-        return new SuperRoot(rootCreator(), storagesRootsMap.values().stream().map(roots -> roots.roots).collect(toList()));
+    public SuperRoot superRoot() {
+        return storageRoots.roots;
     }
 
     /**
      * Internal configuration change method that completes provided future.
      *
      * @param src Configuration source.
-     * @param storage Storage instance.
      * @return fut Future that will be completed after changes are written to the storage.
      */
-    private CompletableFuture<Void> changeInternally(
-        ConfigurationSource src,
-        ConfigurationStorage storage
-    ) {
-        StorageRoots storageRoots = storagesRootsMap.get(storage.type());
+    private CompletableFuture<Void> changeInternally(ConfigurationSource src) {
+        StorageRoots localRoots = storageRoots;
 
         return CompletableFuture
             .supplyAsync(() -> {
-                SuperRoot curRoots = storageRoots.roots;
+                SuperRoot curRoots = localRoots.roots;
 
                 SuperRoot changes = curRoots.copy();
 
@@ -393,12 +322,12 @@ public abstract class ConfigurationChanger {
                 if (allChanges == null)
                     return completedFuture(null);
 
-                return storage.write(allChanges, storageRoots.version)
+                return storage.write(allChanges, localRoots.version)
                     .thenCompose(casResult -> {
                         if (casResult)
-                            return storageRoots.changeFuture;
+                            return localRoots.changeFuture;
                         else
-                            return storageRoots.changeFuture.thenCompose(v -> changeInternally(src, storage));
+                            return localRoots.changeFuture.thenCompose(v -> changeInternally(src));
                     })
                     .exceptionally(throwable -> {
                         throw new ConfigurationChangeException("Failed to change configuration", throwable);
@@ -409,15 +338,11 @@ public abstract class ConfigurationChanger {
     /**
      * Updates configuration from storage listener.
      *
-     * @param storageType Type of the storage that propagated these changes.
      * @param changedEntries Changed data.
      * @return Future that signifies update completion.
      */
-    private CompletableFuture<Void> updateFromListener(
-        ConfigurationType storageType,
-        Data changedEntries
-    ) {
-        StorageRoots oldStorageRoots = this.storagesRootsMap.get(storageType);
+    private CompletableFuture<Void> updateFromListener(Data changedEntries) {
+        StorageRoots oldStorageRoots = storageRoots;
 
         Map<String, ?> dataValuesPrefixMap = toPrefixMap(changedEntries.values());
 
@@ -430,9 +355,7 @@ public abstract class ConfigurationChanger {
 
         long newChangeId = changedEntries.changeId();
 
-        StorageRoots newStorageRoots = new StorageRoots(newSuperRoot, newChangeId);
-
-        storagesRootsMap.put(storageType, newStorageRoots);
+        storageRoots = new StorageRoots(newSuperRoot, newChangeId);
 
         return notificator.notify(oldSuperRoot, newSuperRoot, newChangeId)
             .whenComplete((v, t) -> {
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 f38da2c..c1c6056 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
@@ -19,97 +19,74 @@ package org.apache.ignite.internal.configuration;
 
 import java.lang.annotation.Annotation;
 import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import com.typesafe.config.ConfigFactory;
 import com.typesafe.config.ConfigObject;
 import org.apache.ignite.configuration.RootKey;
-import org.apache.ignite.configuration.annotation.ConfigurationType;
 import org.apache.ignite.configuration.validation.Validator;
 import org.apache.ignite.internal.configuration.hocon.HoconConverter;
 import org.apache.ignite.internal.configuration.storage.ConfigurationStorage;
 import org.apache.ignite.internal.manager.IgniteComponent;
 
+import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.checkConfigurationType;
+
 /**
  * Configuration manager is responsible for handling configuration lifecycle and provides configuration API.
  */
 public class ConfigurationManager implements IgniteComponent {
     /** Configuration registry. */
-    private final ConfigurationRegistry confRegistry;
-
-    /** Type mapped to configuration storage. */
-    private final Map<ConfigurationType, ConfigurationStorage> configurationStorages;
+    private final ConfigurationRegistry registry;
 
     /**
-     * The constructor.
+     * Constructor.
      *
      * @param rootKeys Configuration root keys.
      * @param validators Validators.
-     * @param configurationStorages Configuration storages.
+     * @param storage Configuration storage.
+     * @throws IllegalArgumentException If the configuration type of the root keys is not equal to the storage type.
      */
     public ConfigurationManager(
         Collection<RootKey<?, ?>> rootKeys,
         Map<Class<? extends Annotation>, Set<Validator<? extends Annotation, ?>>> validators,
-        Collection<ConfigurationStorage> configurationStorages
+        ConfigurationStorage storage
     ) {
-        HashMap<ConfigurationType, ConfigurationStorage> storageByType = new HashMap<>();
-
-        for (ConfigurationStorage storage : configurationStorages) {
-            assert !storageByType.containsKey(storage.type()) : "Two or more storage have the same configuration type [type=" + storage.type() + ']';
-
-            storageByType.put(storage.type(), storage);
-        }
+        checkConfigurationType(rootKeys, storage);
 
-        this.configurationStorages = Map.copyOf(storageByType);
-
-        confRegistry = new ConfigurationRegistry(rootKeys, validators, configurationStorages);
+        registry = new ConfigurationRegistry(rootKeys, validators, storage);
     }
 
     /** {@inheritDoc} */
     @Override public void start() {
-        confRegistry.start();
+        registry.start();
     }
 
     /** {@inheritDoc} */
     @Override public void stop() {
         // TODO: IGNITE-15161 Implement component's stop.
-        confRegistry.stop();
-    }
-
-    /**
-     * Constructor.
-     *
-     * @param rootKeys Configuration root keys.
-     * @param configurationStorages Configuration storages.
-     */
-    public ConfigurationManager(
-        Collection<RootKey<?, ?>> rootKeys,
-        Collection<ConfigurationStorage> configurationStorages
-    ) {
-        this(rootKeys, Collections.emptyMap(), configurationStorages);
+        registry.stop();
     }
 
     /**
      * Bootstrap configuration manager with customer user cfg.
      *
      * @param hoconStr Customer configuration in hocon format.
-     * @param type Configuration type.
      * @throws InterruptedException If thread is interrupted during bootstrap.
      * @throws ExecutionException If configuration update failed for some reason.
      */
-    public void bootstrap(String hoconStr, ConfigurationType type) throws InterruptedException, ExecutionException {
+    public void bootstrap(String hoconStr) throws InterruptedException, ExecutionException {
         ConfigObject hoconCfg = ConfigFactory.parseString(hoconStr).root();
 
-        confRegistry.change(HoconConverter.hoconSource(hoconCfg), configurationStorages.get(type)).get();
+        registry.change(HoconConverter.hoconSource(hoconCfg)).get();
     }
 
     /**
+     * Get configuration registry.
+     *
      * @return Configuration registry.
      */
     public ConfigurationRegistry configurationRegistry() {
-        return confRegistry;
+        return registry;
     }
 }
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationRegistry.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationRegistry.java
index bb0935f..8ef663e 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationRegistry.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationRegistry.java
@@ -22,6 +22,7 @@ import java.lang.annotation.Annotation;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -29,7 +30,6 @@ import java.util.concurrent.CompletableFuture;
 import java.util.function.Function;
 import org.apache.ignite.configuration.ConfigurationTree;
 import org.apache.ignite.configuration.RootKey;
-import org.apache.ignite.configuration.annotation.ConfigurationType;
 import org.apache.ignite.configuration.validation.Immutable;
 import org.apache.ignite.configuration.validation.Max;
 import org.apache.ignite.configuration.validation.Min;
@@ -48,10 +48,9 @@ import org.apache.ignite.internal.configuration.validation.MaxValidator;
 import org.apache.ignite.internal.configuration.validation.MinValidator;
 import org.apache.ignite.internal.manager.IgniteComponent;
 import org.apache.ignite.lang.IgniteLogger;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
 
 import static org.apache.ignite.internal.configuration.util.ConfigurationNotificationsUtil.notifyListeners;
+import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.checkConfigurationType;
 import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.innerNodeVisitor;
 
 /** */
@@ -65,16 +64,10 @@ public class ConfigurationRegistry implements IgniteComponent {
     /** Root keys. */
     private final Collection<RootKey<?, ?>> rootKeys;
 
-    /** Validators. */
-    private final Map<Class<? extends Annotation>, Set<Validator<? extends Annotation, ?>>> validators;
+    /** Configuration change handler. */
+    private final ConfigurationChanger changer;
 
-    /** Configuration storages. */
-    private final Collection<ConfigurationStorage> configurationStorages;
-
-    /** */
-    private volatile ConfigurationChanger changer;
-
-    /** */
+    /** Configuration generator. */
     private final ConfigurationAsmGenerator cgen = new ConfigurationAsmGenerator();
 
     /**
@@ -82,44 +75,43 @@ public class ConfigurationRegistry implements IgniteComponent {
      *
      * @param rootKeys Configuration root keys.
      * @param validators Validators.
-     * @param configurationStorages Configuration Storages.
+     * @param storage Configuration storage.
+     * @throws IllegalArgumentException If the configuration type of the root keys is not equal to the storage type.
      */
     public ConfigurationRegistry(
         Collection<RootKey<?, ?>> rootKeys,
         Map<Class<? extends Annotation>, Set<Validator<? extends Annotation, ?>>> validators,
-        Collection<ConfigurationStorage> configurationStorages
+        ConfigurationStorage storage
     ) {
+        checkConfigurationType(rootKeys, storage);
+
         this.rootKeys = rootKeys;
-        this.validators = validators;
-        this.configurationStorages = configurationStorages;
-    }
 
-    /** {@inheritDoc} */
-    @Override public void start() {
-        this.changer = new ConfigurationChanger(this::notificator) {
+        Map<Class<? extends Annotation>, Set<Validator<?, ?>>> validators0 = new HashMap<>(validators);
+
+        validators0.computeIfAbsent(Min.class, a -> new HashSet<>()).add(new MinValidator());
+        validators0.computeIfAbsent(Max.class, a -> new HashSet<>()).add(new MaxValidator());
+        validators0.computeIfAbsent(Immutable.class, a -> new HashSet<>()).add(new ImmutableValidator());
+
+        changer = new ConfigurationChanger(this::notificator, rootKeys, validators0, storage) {
             /** {@inheritDoc} */
             @Override public InnerNode createRootNode(RootKey<?, ?> rootKey) {
                 return cgen.instantiateNode(rootKey.schemaClass());
             }
         };
+    }
 
-        changer.addValidator(Min.class, new MinValidator());
-        changer.addValidator(Max.class, new MaxValidator());
-        changer.addValidator(Immutable.class, new ImmutableValidator());
-
+    /** {@inheritDoc} */
+    @Override public void start() {
         rootKeys.forEach(rootKey -> {
             cgen.compileRootSchema(rootKey.schemaClass());
 
-            changer.addRootKey(rootKey);
-
             DynamicConfiguration<?, ?> cfg = cgen.instantiateCfg(rootKey, changer);
 
             configs.put(rootKey.key(), cfg);
         });
 
-        validators.forEach(changer::addValidators);
-
-        configurationStorages.forEach(changer::register);
+        changer.start();
     }
 
     /** {@inheritDoc} */
@@ -129,16 +121,12 @@ public class ConfigurationRegistry implements IgniteComponent {
     }
 
     /**
-     * Starts storage configurations.
-     * @param storageType Storage type.
+     * Initializes the configuration storage - reads data and sets default values for missing configuration properties.
      */
-    public void startStorageConfigurations(ConfigurationType storageType) {
-        changer.initialize(storageType);
+    public void initializeDefaults() {
+        changer.initializeDefaults();
 
         for (RootKey<?, ?> rootKey : rootKeys) {
-            if (rootKey.type() != storageType)
-                continue;
-
             DynamicConfiguration<?, ?> dynCfg = configs.get(rootKey.key());
 
             ConfigurationNotificationsUtil.touch(dynCfg);
@@ -147,6 +135,7 @@ public class ConfigurationRegistry implements IgniteComponent {
 
     /**
      * Gets the public configuration tree.
+     *
      * @param rootKey Root key.
      * @param <V> View type.
      * @param <C> Change type.
@@ -167,11 +156,11 @@ public class ConfigurationRegistry implements IgniteComponent {
      * @throws IllegalArgumentException If {@code path} is not found in current configuration.
      */
     public <T> T represent(List<String> path, ConfigurationVisitor<T> visitor) throws IllegalArgumentException {
-        SuperRoot mergedSuperRoot = changer.mergedSuperRoot();
+        SuperRoot superRoot = changer.superRoot();
 
         Object node;
         try {
-            node = ConfigurationUtil.find(path, mergedSuperRoot);
+            node = ConfigurationUtil.find(path, superRoot);
         }
         catch (KeyNotFoundException e) {
             throw new IllegalArgumentException(e.getMessage());
@@ -187,20 +176,27 @@ public class ConfigurationRegistry implements IgniteComponent {
 
     /**
      * Change configuration.
-     * @param changesSource Configuration source to create patch from it.
-     * @param storage Expected storage for the changes. Can be null, this will mean that derived storage will be used
-     * unconditionaly.
+     *
+     * @param changesSrc Configuration source to create patch from it.
      * @return Future that is completed on change completion.
      */
-    public CompletableFuture<Void> change(ConfigurationSource changesSource, @Nullable ConfigurationStorage storage) {
-        return changer.change(changesSource, storage);
+    public CompletableFuture<Void> change(ConfigurationSource changesSrc) {
+        return changer.change(changesSrc);
     }
 
-    /** */
-    private @NotNull CompletableFuture<Void> notificator(SuperRoot oldSuperRoot, SuperRoot newSuperRoot, long storageRevision) {
+    /**
+     * Configuration change notifier.
+     *
+     * @param oldSuperRoot Old roots values. All these roots always belong to a single storage.
+     * @param newSuperRoot New values for the same roots as in {@code oldRoot}.
+     * @param storageRevision Revision of the storage.
+     * @return Future that must signify when processing is completed.
+     */
+    private CompletableFuture<Void> notificator(SuperRoot oldSuperRoot, SuperRoot newSuperRoot, long storageRevision) {
         List<CompletableFuture<?>> futures = new ArrayList<>();
 
         newSuperRoot.traverseChildren(new ConfigurationVisitor<Void>() {
+            /** {@inheritDoc} */
             @Override public Void visitInnerNode(String key, InnerNode newRoot) {
                 InnerNode oldRoot = oldSuperRoot.traverseChild(key, innerNodeVisitor());
 
@@ -215,15 +211,13 @@ public class ConfigurationRegistry implements IgniteComponent {
             }
         });
 
-        // Map futures into a "suppressed" future that won't throw any exceptions on completion.
-        Function<CompletableFuture<?>, CompletableFuture<?>> mapping = fut -> fut.handle((res, throwable) -> {
+        // Map futures is only for logging errors.
+        Function<CompletableFuture<?>, CompletableFuture<?>> mapping = fut -> fut.whenComplete((res, throwable) -> {
             if (throwable != null)
                 LOG.error("Failed to notify configuration listener.", throwable);
-
-            return res;
         });
 
-        CompletableFuture[] resultFutures = futures.stream().map(mapping).toArray(CompletableFuture[]::new);
+        CompletableFuture<?>[] resultFutures = futures.stream().map(mapping).toArray(CompletableFuture[]::new);
 
         return CompletableFuture.allOf(resultFutures);
     }
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/DynamicConfiguration.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/DynamicConfiguration.java
index 94b7eee..010f34d 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/DynamicConfiguration.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/DynamicConfiguration.java
@@ -88,7 +88,7 @@ public abstract class DynamicConfiguration<VIEW, CHANGE> extends ConfigurationNo
         };
 
         // Use resulting tree as update request for the storage.
-        return changer.change(src, null);
+        return changer.change(src);
     }
 
     /** {@inheritDoc} */
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/DynamicProperty.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/DynamicProperty.java
index 6f4b85d..769c489 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/DynamicProperty.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/DynamicProperty.java
@@ -82,7 +82,7 @@ public class DynamicProperty<T extends Serializable> extends ConfigurationNode<T
         };
 
         // Use resulting tree as update request for the storage.
-        return changer.change(src, null);
+        return changer.change(src);
     }
 
     /** {@inheritDoc} */
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 5b33d68..8c1cdb5 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
@@ -20,6 +20,7 @@ package org.apache.ignite.internal.configuration.util;
 import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -27,6 +28,8 @@ import java.util.NoSuchElementException;
 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.storage.ConfigurationStorage;
 import org.apache.ignite.internal.configuration.tree.ConfigurationSource;
 import org.apache.ignite.internal.configuration.tree.ConfigurationVisitor;
 import org.apache.ignite.internal.configuration.tree.ConstructableTreeNode;
@@ -502,4 +505,18 @@ public class ConfigurationUtil {
             }
         };
     }
+
+    /**
+     * Checks that the configuration type of root keys is equal to the storage type.
+     *
+     * @throws IllegalArgumentException If the configuration type of the root keys is not equal to the storage type.
+     */
+    public static void checkConfigurationType(Collection<RootKey<?, ?>> rootKeys, ConfigurationStorage storage) {
+        for (RootKey<?, ?> key : rootKeys) {
+            if (key.type() != storage.type()) {
+                throw new IllegalArgumentException("Invalid root key configuration type [key=" + key +
+                    ", storage=" + storage.getClass().getName() + ", storageType=" + storage.type() + "]");
+            }
+        }
+    }
 }
diff --git a/modules/rest/src/main/java/org/apache/ignite/rest/RestModule.java b/modules/rest/src/main/java/org/apache/ignite/rest/RestModule.java
index 6849c5e..59d18d1 100644
--- a/modules/rest/src/main/java/org/apache/ignite/rest/RestModule.java
+++ b/modules/rest/src/main/java/org/apache/ignite/rest/RestModule.java
@@ -18,7 +18,6 @@
 package org.apache.ignite.rest;
 
 import java.net.BindException;
-import java.nio.charset.StandardCharsets;
 import java.util.Map;
 import io.netty.bootstrap.ServerBootstrap;
 import io.netty.channel.Channel;
@@ -28,7 +27,6 @@ import io.netty.channel.ChannelOption;
 import io.netty.channel.EventLoopGroup;
 import io.netty.channel.nio.NioEventLoopGroup;
 import io.netty.channel.socket.nio.NioServerSocketChannel;
-import io.netty.handler.codec.http.HttpHeaderValues;
 import io.netty.handler.logging.LogLevel;
 import io.netty.handler.logging.LoggingHandler;
 import org.apache.ignite.configuration.schemas.rest.RestConfiguration;
@@ -39,12 +37,16 @@ import org.apache.ignite.internal.configuration.ConfigurationRegistry;
 import org.apache.ignite.internal.manager.IgniteComponent;
 import org.apache.ignite.lang.IgniteException;
 import org.apache.ignite.lang.IgniteLogger;
+import org.apache.ignite.rest.netty.RestApiHttpRequest;
+import org.apache.ignite.rest.netty.RestApiHttpResponse;
 import org.apache.ignite.rest.netty.RestApiInitializer;
 import org.apache.ignite.rest.presentation.ConfigurationPresentation;
 import org.apache.ignite.rest.presentation.hocon.HoconPresentation;
 import org.apache.ignite.rest.routes.Router;
 
+import static io.netty.handler.codec.http.HttpHeaderValues.APPLICATION_JSON;
 import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
+import static java.nio.charset.StandardCharsets.UTF_8;
 
 /**
  * Rest module is responsible for starting a REST endpoints for accessing and managing configuration.
@@ -53,90 +55,89 @@ import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
  * Refer to default config file in resources for the example.
  */
 public class RestModule implements IgniteComponent {
-    /** */
+    /** Default port. */
     public static final int DFLT_PORT = 10300;
 
-    /** */
-    private static final String CONF_URL = "/management/v1/configuration/";
+    /** Node configuration route. */
+    private static final String NODE_CFG_URL = "/management/v1/configuration/node/";
 
-    /** */
+    /** Cluster configuration route. */
+    private static final String CLUSTER_CFG_URL = "/management/v1/configuration/cluster/";
+
+    /** Path parameter. */
     private static final String PATH_PARAM = "selector";
 
     /** Ignite logger. */
     private final IgniteLogger LOG = IgniteLogger.forClass(RestModule.class);
 
-    /** */
-    private final ConfigurationRegistry sysConf;
+    /** Node configuration register. */
+    private final ConfigurationRegistry nodeCfgRegistry;
+
+    /** Presentation of node configuration. */
+    private final ConfigurationPresentation<String> nodeCfgPresentation;
 
-    /** */
-    private volatile ConfigurationPresentation<String> presentation;
+    /** Presentation of cluster configuration. */
+    private final ConfigurationPresentation<String> clusterCfgPresentation;
 
     /**
      * Creates a new instance of REST module.
      *
      * @param nodeCfgMgr Node configuration manager.
+     * @param clusterCfgMgr Cluster configuration manager.
      */
-    public RestModule(ConfigurationManager nodeCfgMgr) {
-        sysConf = nodeCfgMgr.configurationRegistry();
+    public RestModule(
+        ConfigurationManager nodeCfgMgr,
+        ConfigurationManager clusterCfgMgr
+    ) {
+        nodeCfgRegistry = nodeCfgMgr.configurationRegistry();
+
+        nodeCfgPresentation = new HoconPresentation(nodeCfgMgr.configurationRegistry());
+        clusterCfgPresentation = new HoconPresentation(clusterCfgMgr.configurationRegistry());
     }
 
     /** {@inheritDoc} */
     @Override public void start() {
-        presentation = new HoconPresentation(sysConf);
-
         var router = new Router();
 
         router
-            .get(CONF_URL, (req, resp) -> {
-                resp.json(presentation.represent());
-            })
-            .get(CONF_URL + ":" + PATH_PARAM, (req, resp) -> {
-                String cfgPath = req.queryParams().get(PATH_PARAM);
-                try {
-                    resp.json(presentation.representByPath(cfgPath));
-                }
-                catch (IllegalArgumentException pathE) {
-                    ErrorResult eRes = new ErrorResult("CONFIG_PATH_UNRECOGNIZED", pathE.getMessage());
-
-                    resp.status(BAD_REQUEST);
-                    resp.json(Map.of("error", eRes));
-                }
-            })
-            .put(CONF_URL, HttpHeaderValues.APPLICATION_JSON, (req, resp) -> {
-                try {
-                    presentation.update(
-                        req
-                            .request()
-                            .content()
-                            .readCharSequence(req.request().content().readableBytes(), StandardCharsets.UTF_8)
-                            .toString());
-                }
-                catch (IllegalArgumentException e) {
-                    ErrorResult eRes = new ErrorResult("INVALID_CONFIG_FORMAT", e.getMessage());
-
-                    resp.status(BAD_REQUEST);
-                    resp.json(Map.of("error", eRes));
-                }
-                catch (ConfigurationValidationException e) {
-                    ErrorResult eRes = new ErrorResult("VALIDATION_EXCEPTION", e.getMessage());
-
-                    resp.status(BAD_REQUEST);
-                    resp.json(Map.of("error", eRes));
-                }
-                catch (IgniteException e) {
-                    ErrorResult eRes = new ErrorResult("APPLICATION_EXCEPTION", e.getMessage());
-
-                    resp.status(BAD_REQUEST);
-                    resp.json(Map.of("error", eRes));
-                }
-            });
+            .get(
+                NODE_CFG_URL,
+                (req, resp) -> resp.json(nodeCfgPresentation.represent())
+            )
+            .get(
+                CLUSTER_CFG_URL,
+                (req, resp) -> resp.json(clusterCfgPresentation.represent())
+            )
+            .get(
+                NODE_CFG_URL + ":" + PATH_PARAM,
+                (req, resp) -> handleRepresentByPath(req, resp, nodeCfgPresentation)
+            )
+            .get(
+                CLUSTER_CFG_URL + ":" + PATH_PARAM,
+                (req, resp) -> handleRepresentByPath(req, resp, clusterCfgPresentation)
+            )
+            .put(
+                NODE_CFG_URL,
+                APPLICATION_JSON,
+                (req, resp) -> handleUpdate(req, resp, nodeCfgPresentation)
+            )
+            .put(
+                CLUSTER_CFG_URL,
+                APPLICATION_JSON,
+                (req, resp) -> handleUpdate(req, resp, clusterCfgPresentation)
+            );
 
         startRestEndpoint(router);
     }
 
-    /** */
+    /**
+     * Start endpoint.
+     *
+     * @param router Dispatcher of http requests.
+     * @return Future which will be notified when this channel is closed.
+     */
     private ChannelFuture startRestEndpoint(Router router) {
-        RestView restConfigurationView = sysConf.getConfiguration(RestConfiguration.KEY).value();
+        RestView restConfigurationView = nodeCfgRegistry.getConfiguration(RestConfiguration.KEY).value();
 
         int desiredPort = restConfigurationView.port();
         int portRange = restConfigurationView.portRange();
@@ -151,31 +152,36 @@ public class RestModule implements IgniteComponent {
         var hnd = new RestApiInitializer(router);
 
         // TODO: IGNITE-15132 Rest module must reuse netty infrastructure from network module
-        ServerBootstrap b = new ServerBootstrap();
-        b.option(ChannelOption.SO_BACKLOG, 1024);
-        b.group(parentGrp, childGrp)
+        ServerBootstrap b = new ServerBootstrap()
+            .option(ChannelOption.SO_BACKLOG, 1024)
+            .group(parentGrp, childGrp)
             .channel(NioServerSocketChannel.class)
             .handler(new LoggingHandler(LogLevel.INFO))
             .childHandler(hnd);
 
         for (int portCandidate = desiredPort; portCandidate <= desiredPort + portRange; portCandidate++) {
             ChannelFuture bindRes = b.bind(portCandidate).awaitUninterruptibly();
+
             if (bindRes.isSuccess()) {
                 ch = bindRes.channel();
 
                 ch.closeFuture().addListener(new ChannelFutureListener() {
+                    /** {@inheritDoc} */
                     @Override public void operationComplete(ChannelFuture fut) {
                         parentGrp.shutdownGracefully();
                         childGrp.shutdownGracefully();
+
                         LOG.error("REST component was stopped", fut.cause());
                     }
                 });
+
                 port = portCandidate;
                 break;
             }
             else if (!(bindRes.cause() instanceof BindException)) {
                 parentGrp.shutdownGracefully();
                 childGrp.shutdownGracefully();
+
                 throw new RuntimeException(bindRes.cause());
             }
         }
@@ -192,7 +198,8 @@ public class RestModule implements IgniteComponent {
             throw new RuntimeException(msg);
         }
 
-        LOG.info("REST protocol started successfully on port " + port);
+        if (LOG.isInfoEnabled())
+            LOG.info("REST protocol started successfully on port " + port);
 
         return ch.closeFuture();
     }
@@ -200,4 +207,70 @@ public class RestModule implements IgniteComponent {
     /** {@inheritDoc} */
     @Override public void stop() throws Exception {
     }
+
+    /**
+     * Handle a request to get the configuration by {@link #PATH_PARAM path}.
+     *
+     * @param req Rest request.
+     * @param res Rest response.
+     * @param presentation Configuration presentation.
+     */
+    private void handleRepresentByPath(
+        RestApiHttpRequest req,
+        RestApiHttpResponse res,
+        ConfigurationPresentation<String> presentation
+    ) {
+        try {
+            String cfgPath = req.queryParams().get(PATH_PARAM);
+
+            res.json(presentation.representByPath(cfgPath));
+        }
+        catch (IllegalArgumentException pathE) {
+            ErrorResult eRes = new ErrorResult("CONFIG_PATH_UNRECOGNIZED", pathE.getMessage());
+
+            res.status(BAD_REQUEST);
+            res.json(Map.of("error", eRes));
+        }
+    }
+
+    /**
+     * Handle a configuration update request as json.
+     *
+     * @param req Rest request.
+     * @param res Rest response.
+     * @param presentation Configuration presentation.
+     */
+    private void handleUpdate(
+        RestApiHttpRequest req,
+        RestApiHttpResponse res,
+        ConfigurationPresentation<String> presentation
+    ) {
+        try {
+            String updateReq = req
+                .request()
+                .content()
+                .readCharSequence(req.request().content().readableBytes(), UTF_8)
+                .toString();
+
+            presentation.update(updateReq);
+        }
+        catch (IllegalArgumentException e) {
+            ErrorResult eRes = new ErrorResult("INVALID_CONFIG_FORMAT", e.getMessage());
+
+            res.status(BAD_REQUEST);
+            res.json(Map.of("error", eRes));
+        }
+        catch (ConfigurationValidationException e) {
+            ErrorResult eRes = new ErrorResult("VALIDATION_EXCEPTION", e.getMessage());
+
+            res.status(BAD_REQUEST);
+            res.json(Map.of("error", eRes));
+        }
+        catch (IgniteException e) {
+            ErrorResult eRes = new ErrorResult("APPLICATION_EXCEPTION", e.getMessage());
+
+            res.status(BAD_REQUEST);
+            res.json(Map.of("error", eRes));
+        }
+    }
 }
diff --git a/modules/rest/src/main/java/org/apache/ignite/rest/presentation/hocon/HoconPresentation.java b/modules/rest/src/main/java/org/apache/ignite/rest/presentation/hocon/HoconPresentation.java
index 9f6d5bf..88329c5 100644
--- a/modules/rest/src/main/java/org/apache/ignite/rest/presentation/hocon/HoconPresentation.java
+++ b/modules/rest/src/main/java/org/apache/ignite/rest/presentation/hocon/HoconPresentation.java
@@ -62,7 +62,7 @@ public class HoconPresentation implements ConfigurationPresentation<String> {
             throw new IllegalArgumentException("Empty configuration");
 
         try {
-            registry.change(HoconConverter.hoconSource(parseString(cfgUpdate).root()), null).get();
+            registry.change(HoconConverter.hoconSource(parseString(cfgUpdate).root())).get();
         }
         catch (IllegalArgumentException | ConfigurationValidationException e) {
             throw e;
diff --git a/modules/rest/src/test/java/org/apache/ignite/rest/presentation/ConfigurationPresentationTest.java b/modules/rest/src/test/java/org/apache/ignite/rest/presentation/ConfigurationPresentationTest.java
index 951110c..d54669e 100644
--- a/modules/rest/src/test/java/org/apache/ignite/rest/presentation/ConfigurationPresentationTest.java
+++ b/modules/rest/src/test/java/org/apache/ignite/rest/presentation/ConfigurationPresentationTest.java
@@ -70,7 +70,7 @@ public class ConfigurationPresentationTest {
         cfgRegistry = new ConfigurationRegistry(
             List.of(TestRootConfiguration.KEY),
             Map.of(Value.class, Set.of(validator)),
-            List.of(new TestConfigurationStorage(LOCAL))
+            new TestConfigurationStorage(LOCAL)
         );
 
         cfgRegistry.start();
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/storage/ITDistributedConfigurationStorageTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/storage/ITDistributedConfigurationStorageTest.java
index 88df9de..b7c5646 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/storage/ITDistributedConfigurationStorageTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/storage/ITDistributedConfigurationStorageTest.java
@@ -24,10 +24,8 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.stream.Stream;
 import org.apache.ignite.configuration.RootKey;
-import org.apache.ignite.configuration.annotation.ConfigurationType;
 import org.apache.ignite.configuration.schemas.runner.NodeConfiguration;
 import org.apache.ignite.internal.configuration.ConfigurationManager;
-import org.apache.ignite.internal.configuration.storage.ConfigurationStorage;
 import org.apache.ignite.internal.configuration.storage.Data;
 import org.apache.ignite.internal.manager.IgniteComponent;
 import org.apache.ignite.internal.metastorage.MetaStorageManager;
@@ -94,9 +92,11 @@ public class ITDistributedConfigurationStorageTest {
 
             List<RootKey<?, ?>> rootKeys = List.of(NodeConfiguration.KEY);
 
-            List<ConfigurationStorage> localStorage = List.of(new LocalConfigurationStorage(vaultManager));
-
-            cfgManager = new ConfigurationManager(rootKeys, localStorage);
+            cfgManager = new ConfigurationManager(
+                rootKeys,
+                Map.of(),
+                new LocalConfigurationStorage(vaultManager)
+            );
 
             metaStorageManager = new MetaStorageManager(
                 vaultManager,
@@ -131,7 +131,7 @@ public class ITDistributedConfigurationStorageTest {
             // metastorage configuration
             var config = String.format("{\"node\": {\"metastorageNodes\": [ \"%s\" ]}}", addr);
 
-            cfgManager.bootstrap(config, ConfigurationType.LOCAL);
+            cfgManager.bootstrap(config);
 
             Stream.of(clusterService, raftManager, metaStorageManager).forEach(IgniteComponent::start);
 
diff --git a/modules/runner/src/main/java/org/apache/ignite/internal/app/IgnitionImpl.java b/modules/runner/src/main/java/org/apache/ignite/internal/app/IgnitionImpl.java
index a3407f2..8836dd5 100644
--- a/modules/runner/src/main/java/org/apache/ignite/internal/app/IgnitionImpl.java
+++ b/modules/runner/src/main/java/org/apache/ignite/internal/app/IgnitionImpl.java
@@ -24,32 +24,31 @@ import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicBoolean;
 import org.apache.ignite.app.Ignite;
 import org.apache.ignite.app.Ignition;
 import org.apache.ignite.configuration.RootKey;
-import org.apache.ignite.configuration.annotation.ConfigurationType;
 import org.apache.ignite.configuration.schemas.network.NetworkConfiguration;
 import org.apache.ignite.configuration.schemas.network.NetworkView;
 import org.apache.ignite.configuration.schemas.rest.RestConfiguration;
 import org.apache.ignite.configuration.schemas.runner.ClusterConfiguration;
 import org.apache.ignite.configuration.schemas.runner.NodeConfiguration;
+import org.apache.ignite.configuration.schemas.table.TableValidator;
 import org.apache.ignite.configuration.schemas.table.TablesConfiguration;
 import org.apache.ignite.internal.affinity.AffinityManager;
 import org.apache.ignite.internal.baseline.BaselineManager;
 import org.apache.ignite.internal.configuration.ConfigurationManager;
-import org.apache.ignite.internal.configuration.storage.ConfigurationStorage;
 import org.apache.ignite.internal.manager.IgniteComponent;
 import org.apache.ignite.internal.metastorage.MetaStorageManager;
 import org.apache.ignite.internal.processors.query.calcite.SqlQueryProcessor;
 import org.apache.ignite.internal.raft.Loza;
 import org.apache.ignite.internal.schema.SchemaManager;
+import org.apache.ignite.internal.schema.configuration.SchemaTableValidatorImpl;
 import org.apache.ignite.internal.storage.DistributedConfigurationStorage;
 import org.apache.ignite.internal.storage.LocalConfigurationStorage;
 import org.apache.ignite.internal.table.distributed.TableManager;
@@ -208,37 +207,36 @@ public class IgnitionImpl implements Ignition {
 
             vaultMgr.putName(nodeName).join();
 
-            List<RootKey<?, ?>> rootKeys = Arrays.asList(
+            List<RootKey<?, ?>> nodeRootKeys = List.of(
                 NetworkConfiguration.KEY,
                 NodeConfiguration.KEY,
-                ClusterConfiguration.KEY,
-                TablesConfiguration.KEY,
                 RestConfiguration.KEY
             );
 
-            List<ConfigurationStorage> cfgStorages =
-                new ArrayList<>(Collections.singletonList(new LocalConfigurationStorage(vaultMgr)));
-
             // Bootstrap node configuration manager.
-            ConfigurationManager nodeConfigurationMgr = doStartComponent(
+            ConfigurationManager nodeCfgMgr = doStartComponent(
                 nodeName,
                 startedComponents,
-                new ConfigurationManager(rootKeys, cfgStorages)
+                new ConfigurationManager(
+                    nodeRootKeys,
+                    Map.of(),
+                    new LocalConfigurationStorage(vaultMgr)
+                )
             );
 
             if (cfgContent != null) {
                 try {
-                    nodeConfigurationMgr.bootstrap(cfgContent, ConfigurationType.LOCAL);
+                    nodeCfgMgr.bootstrap(cfgContent);
                 }
                 catch (Exception e) {
                     LOG.warn("Unable to parse user-specific configuration, default configuration will be used: {}", e.getMessage());
                 }
             }
             else
-                nodeConfigurationMgr.configurationRegistry().startStorageConfigurations(ConfigurationType.LOCAL);
+                nodeCfgMgr.configurationRegistry().initializeDefaults();
 
             NetworkView netConfigurationView =
-                nodeConfigurationMgr.configurationRegistry().getConfiguration(NetworkConfiguration.KEY).value();
+                nodeCfgMgr.configurationRegistry().getConfiguration(NetworkConfiguration.KEY).value();
 
             var serializationRegistry = new MessageSerializationRegistryImpl();
 
@@ -271,20 +269,28 @@ public class IgnitionImpl implements Ignition {
                 startedComponents,
                 new MetaStorageManager(
                     vaultMgr,
-                    nodeConfigurationMgr,
+                    nodeCfgMgr,
                     clusterNetSvc,
                     raftMgr
                 )
             );
 
             // TODO IGNITE-14578 Bootstrap configuration manager with distributed configuration.
-            cfgStorages.add(new DistributedConfigurationStorage(metaStorageMgr, vaultMgr));
+
+            List<RootKey<?, ?>> clusterRootKeys = List.of(
+                ClusterConfiguration.KEY,
+                TablesConfiguration.KEY
+            );
 
             // Start cluster configuration manager.
-            ConfigurationManager clusterConfigurationMgr = doStartComponent(
+            ConfigurationManager clusterCfgMgr = doStartComponent(
                 nodeName,
                 startedComponents,
-                new ConfigurationManager(rootKeys, cfgStorages)
+                new ConfigurationManager(
+                    clusterRootKeys,
+                    Map.of(TableValidator.class, Set.of(SchemaTableValidatorImpl.INSTANCE)),
+                    new DistributedConfigurationStorage(metaStorageMgr, vaultMgr)
+                )
             );
 
             // Baseline manager startup.
@@ -292,7 +298,7 @@ public class IgnitionImpl implements Ignition {
                 nodeName,
                 startedComponents,
                 new BaselineManager(
-                    clusterConfigurationMgr,
+                    clusterCfgMgr,
                     metaStorageMgr,
                     clusterNetSvc
                 )
@@ -303,7 +309,7 @@ public class IgnitionImpl implements Ignition {
                 nodeName,
                 startedComponents,
                 new AffinityManager(
-                    clusterConfigurationMgr,
+                    clusterCfgMgr,
                     metaStorageMgr,
                     baselineMgr
                 )
@@ -314,7 +320,7 @@ public class IgnitionImpl implements Ignition {
                 nodeName,
                 startedComponents,
                 new SchemaManager(
-                    clusterConfigurationMgr,
+                    clusterCfgMgr,
                     metaStorageMgr,
                     vaultMgr
                 )
@@ -325,7 +331,8 @@ public class IgnitionImpl implements Ignition {
                 nodeName,
                 startedComponents,
                 new TableManager(
-                    clusterConfigurationMgr,
+                    nodeCfgMgr,
+                    clusterCfgMgr,
                     metaStorageMgr,
                     schemaMgr,
                     affinityMgr,
@@ -346,7 +353,8 @@ public class IgnitionImpl implements Ignition {
             doStartComponent(
                 nodeName,
                 startedComponents,
-                new RestModule(nodeConfigurationMgr));
+                new RestModule(nodeCfgMgr, clusterCfgMgr)
+            );
 
             // Deploy all resisted watches cause all components are ready and have registered their listeners.
             metaStorageMgr.deployWatches();
@@ -375,8 +383,8 @@ public class IgnitionImpl implements Ignition {
                 nodeName,
                 distributedTblMgr,
                 qryProc,
-                nodeConfigurationMgr,
-                clusterConfigurationMgr
+                nodeCfgMgr,
+                clusterCfgMgr
             );
         }
         catch (Exception e) {
diff --git a/modules/runner/src/main/java/org/apache/ignite/internal/storage/LocalConfigurationStorage.java b/modules/runner/src/main/java/org/apache/ignite/internal/storage/LocalConfigurationStorage.java
index 103fcf2..e119637 100644
--- a/modules/runner/src/main/java/org/apache/ignite/internal/storage/LocalConfigurationStorage.java
+++ b/modules/runner/src/main/java/org/apache/ignite/internal/storage/LocalConfigurationStorage.java
@@ -52,7 +52,7 @@ public class LocalConfigurationStorage implements ConfigurationStorage {
     private ConfigurationStorageListener lsnr;
 
     /** Storage version. */
-    private AtomicLong ver = new AtomicLong(0L);
+    private final AtomicLong ver = new AtomicLong(0L);
 
     /** Start key in range for searching local configuration keys. */
     private static final ByteArray LOC_KEYS_START_RANGE = ByteArray.fromString(LOC_PREFIX);
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 89634d8..4ec9da5 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
@@ -19,9 +19,11 @@ package org.apache.ignite.internal.schema.configuration;
 
 import java.util.Collection;
 import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.stream.Collectors;
-import org.apache.ignite.configuration.annotation.ConfigurationType;
 import org.apache.ignite.configuration.schemas.table.TableConfiguration;
 import org.apache.ignite.configuration.schemas.table.TableValidator;
 import org.apache.ignite.configuration.schemas.table.TablesConfiguration;
@@ -45,6 +47,7 @@ import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
+import static org.apache.ignite.configuration.annotation.ConfigurationType.DISTRIBUTED;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 
@@ -68,13 +71,14 @@ public class SchemaConfigurationConverterTest {
     @BeforeEach
     public void createRegistry() throws ExecutionException, InterruptedException {
         confRegistry = new ConfigurationRegistry(
-            Collections.singleton(TablesConfiguration.KEY),
-            Collections.singletonMap(TableValidator.class, Collections.singleton(SchemaTableValidatorImpl.INSTANCE)),
-            Collections.singleton(new TestConfigurationStorage(ConfigurationType.DISTRIBUTED)));
+            List.of(TablesConfiguration.KEY),
+            Map.of(TableValidator.class, Set.of(SchemaTableValidatorImpl.INSTANCE)),
+            new TestConfigurationStorage(DISTRIBUTED)
+        );
 
         confRegistry.start();
 
-        tblBuilder = SchemaBuilders.tableBuilder("SNAME","TNAME")
+        tblBuilder = SchemaBuilders.tableBuilder("SNAME", "TNAME")
             .columns(
                 SchemaBuilders.column("COL1", ColumnType.DOUBLE).build(),
                 SchemaBuilders.column("COL2", ColumnType.DOUBLE).build(),
@@ -105,7 +109,7 @@ public class SchemaConfigurationConverterTest {
     public void testConvertHashIndex() throws ExecutionException, InterruptedException {
         HashIndexBuilder builder = SchemaBuilders.hashIndex("testHI")
             .withColumns("A", "B", "C")
-            .withHints(Collections.singletonMap("param","value"));
+            .withHints(Collections.singletonMap("param", "value"));
         HashIndex idx = builder.build();
 
         getTbl().change(ch -> SchemaConfigurationConverter.addIndex(idx, ch)).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 d1bddb3..28961cd 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
@@ -101,8 +101,11 @@ public class TableManager extends Producer<TableEvent, TableEventParameters> imp
     /** Meta storage service. */
     private final MetaStorageManager metaStorageMgr;
 
-    /** Configuration manager. */
-    private final ConfigurationManager configurationMgr;
+    /** Node configuration manager. */
+    private final ConfigurationManager nodeCfgMgr;
+
+    /** Cluster configuration manager. */
+    private final ConfigurationManager clusterCfgMgr;
 
     /** Raft manager. */
     private final Loza raftMgr;
@@ -125,7 +128,8 @@ public class TableManager extends Producer<TableEvent, TableEventParameters> imp
     /**
      * Creates a new table manager.
      *
-     * @param configurationMgr Configuration manager.
+     * @param nodeCfgMgr Node configuration manager.
+     * @param clusterCfgMgr Cluster configuration manager.
      * @param metaStorageMgr Meta storage manager.
      * @param schemaMgr Schema manager.
      * @param affMgr Affinity manager.
@@ -133,14 +137,16 @@ public class TableManager extends Producer<TableEvent, TableEventParameters> imp
      * @param partitionsStoreDir Partitions store directory.
      */
     public TableManager(
-        ConfigurationManager configurationMgr,
+        ConfigurationManager nodeCfgMgr,
+        ConfigurationManager clusterCfgMgr,
         MetaStorageManager metaStorageMgr,
         SchemaManager schemaMgr,
         AffinityManager affMgr,
         Loza raftMgr,
         Path partitionsStoreDir
     ) {
-        this.configurationMgr = configurationMgr;
+        this.nodeCfgMgr = nodeCfgMgr;
+        this.clusterCfgMgr = clusterCfgMgr;
         this.metaStorageMgr = metaStorageMgr;
         this.affMgr = affMgr;
         this.raftMgr = raftMgr;
@@ -151,7 +157,7 @@ public class TableManager extends Producer<TableEvent, TableEventParameters> imp
     /** {@inheritDoc} */
     @Override public void start() {
         //TODO: IGNITE-14652 Change a metastorage update in listeners to multi-invoke
-        configurationMgr.configurationRegistry().getConfiguration(TablesConfiguration.KEY).tables().listen(ctx -> {
+        clusterCfgMgr.configurationRegistry().getConfiguration(TablesConfiguration.KEY).tables().listen(ctx -> {
             return onConfigurationChanged(ctx.storageRevision(), ctx.oldValue(), ctx.newValue());
         });
     }
@@ -342,7 +348,7 @@ public class TableManager extends Producer<TableEvent, TableEventParameters> imp
      * @return Table creation futures.
      */
     private List<CompletableFuture<Boolean>> startTables(Set<String> tbls, long rev, NamedListView<TableView> cfgs) {
-        boolean hasMetastorageLocally = metaStorageMgr.hasMetastorageLocally(configurationMgr);
+        boolean hasMetastorageLocally = metaStorageMgr.hasMetastorageLocally(nodeCfgMgr);
 
         List<CompletableFuture<Boolean>> futs = new ArrayList<>();
 
@@ -426,7 +432,7 @@ public class TableManager extends Producer<TableEvent, TableEventParameters> imp
      * @return Table drop futures.
      */
     private List<CompletableFuture<Boolean>> stopTables(Set<String> tbls) {
-        boolean hasMetastorageLocally = metaStorageMgr.hasMetastorageLocally(configurationMgr);
+        boolean hasMetastorageLocally = metaStorageMgr.hasMetastorageLocally(nodeCfgMgr);
 
         List<CompletableFuture<Boolean>> futs = new ArrayList<>();
 
@@ -481,7 +487,7 @@ public class TableManager extends Producer<TableEvent, TableEventParameters> imp
         @NotNull NamedListView<TableView> oldCfg,
         @NotNull NamedListView<TableView> newCfg
     ) {
-        boolean hasMetastorageLocally = metaStorageMgr.hasMetastorageLocally(configurationMgr);
+        boolean hasMetastorageLocally = metaStorageMgr.hasMetastorageLocally(nodeCfgMgr);
 
         List<CompletableFuture<Boolean>> futs = new ArrayList<>();
 
@@ -594,13 +600,14 @@ public class TableManager extends Producer<TableEvent, TableEventParameters> imp
                     removeListener(TableEvent.CREATE, clo);
             } else {
                 try {
-                    configurationMgr
-                            .configurationRegistry()
-                            .getConfiguration(TablesConfiguration.KEY)
-                            .tables()
-                            .change(change -> change.create(name, tableInitChange))
-                            .get();
-                } catch (InterruptedException | ExecutionException e) {
+                    clusterCfgMgr
+                        .configurationRegistry()
+                        .getConfiguration(TablesConfiguration.KEY)
+                        .tables()
+                        .change(change -> change.create(name, tableInitChange))
+                        .get();
+                }
+                catch (InterruptedException | ExecutionException e) {
                     LOG.error("Table wasn't created [name=" + name + ']', e);
 
                     removeListener(TableEvent.CREATE, clo, new IgniteInternalCheckedException(e));
@@ -641,9 +648,9 @@ public class TableManager extends Producer<TableEvent, TableEventParameters> imp
         });
 
         try {
-            configurationMgr.configurationRegistry()
-                    .getConfiguration(TablesConfiguration.KEY).tables().change(change ->
-                    change.createOrUpdate(name, tableChange)).get();
+            clusterCfgMgr.configurationRegistry()
+                .getConfiguration(TablesConfiguration.KEY).tables().change(change ->
+                change.createOrUpdate(name, tableChange)).get();
         }
         catch (InterruptedException | ExecutionException e) {
             LOG.error("Table wasn't created [name=" + name + ']', e);
@@ -698,12 +705,12 @@ public class TableManager extends Producer<TableEvent, TableEventParameters> imp
         }
         else {
             try {
-                configurationMgr
-                        .configurationRegistry()
-                        .getConfiguration(TablesConfiguration.KEY)
-                        .tables()
-                        .change(change -> change.delete(name))
-                        .get();
+                clusterCfgMgr
+                    .configurationRegistry()
+                    .getConfiguration(TablesConfiguration.KEY)
+                    .tables()
+                    .change(change -> change.delete(name))
+                    .get();
             }
             catch (InterruptedException | ExecutionException e) {
                 LOG.error("Table wasn't dropped [name=" + name + ']', e);
diff --git a/modules/table/src/test/java/org/apache/ignite/internal/table/TableManagerTest.java b/modules/table/src/test/java/org/apache/ignite/internal/table/TableManagerTest.java
index a4cbb97..d5fa621 100644
--- a/modules/table/src/test/java/org/apache/ignite/internal/table/TableManagerTest.java
+++ b/modules/table/src/test/java/org/apache/ignite/internal/table/TableManagerTest.java
@@ -20,16 +20,14 @@ package org.apache.ignite.internal.table;
 import java.lang.reflect.Method;
 import java.nio.file.Path;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.Phaser;
 import java.util.concurrent.atomic.AtomicBoolean;
-import org.apache.ignite.configuration.RootKey;
-import org.apache.ignite.configuration.annotation.ConfigurationType;
 import org.apache.ignite.configuration.schemas.runner.ClusterConfiguration;
 import org.apache.ignite.configuration.schemas.runner.NodeConfiguration;
 import org.apache.ignite.configuration.schemas.table.TablesConfiguration;
@@ -76,6 +74,8 @@ import org.mockito.junit.jupiter.MockitoExtension;
 import org.mockito.junit.jupiter.MockitoSettings;
 import org.mockito.quality.Strictness;
 
+import static org.apache.ignite.configuration.annotation.ConfigurationType.DISTRIBUTED;
+import static org.apache.ignite.configuration.annotation.ConfigurationType.LOCAL;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -121,8 +121,11 @@ public class TableManagerTest {
     /** Node name. */
     private static final String NODE_NAME = "node1";
 
-    /** Configuration manager. */
-    private ConfigurationManager cfrMgr;
+    /** Node configuration manager. */
+    private ConfigurationManager nodeCfgMgr;
+
+    /** Cluster configuration manager. */
+    private ConfigurationManager clusterCfgMgr;
 
     /** MetaStorage manager. */
     @Mock(lenient = true)
@@ -154,21 +157,30 @@ public class TableManagerTest {
     @BeforeEach
     void setUp() {
         try {
-            cfrMgr = new ConfigurationManager(rootConfigurationKeys(), Arrays.asList(
-                new TestConfigurationStorage(ConfigurationType.LOCAL),
-                new TestConfigurationStorage(ConfigurationType.DISTRIBUTED)));
+            nodeCfgMgr = new ConfigurationManager(
+                List.of(NodeConfiguration.KEY),
+                Map.of(),
+                new TestConfigurationStorage(LOCAL)
+            );
 
-            cfrMgr.start();
+            clusterCfgMgr = new ConfigurationManager(
+                List.of(ClusterConfiguration.KEY, TablesConfiguration.KEY),
+                Map.of(),
+                new TestConfigurationStorage(DISTRIBUTED)
+            );
 
-            cfrMgr.bootstrap("{\n" +
+            nodeCfgMgr.start();
+            clusterCfgMgr.start();
+
+            nodeCfgMgr.bootstrap("{\n" +
                 "   \"node\":{\n" +
                 "      \"metastorageNodes\":[\n" +
                 "         \"" + NODE_NAME + "\"\n" +
                 "      ]\n" +
                 "   }\n" +
-                "}", ConfigurationType.LOCAL);
+                "}");
 
-            cfrMgr.bootstrap("{\n" +
+            clusterCfgMgr.bootstrap("{\n" +
                 "   \"cluster\":{\n" +
                 "   \"metastorageNodes\":[\n" +
                 "      \"" + NODE_NAME + "\"\n" +
@@ -205,7 +217,7 @@ public class TableManagerTest {
                 "         }\n" +
                 "      }\n" +
                 "   }\n" +
-                "}", ConfigurationType.DISTRIBUTED);
+                "}");
         }
         catch (Exception e) {
             LOG.error("Failed to bootstrap the test configuration manager.", e);
@@ -217,7 +229,8 @@ public class TableManagerTest {
     /** Stop configuration manager. */
     @AfterEach
     void tearDown() {
-        cfrMgr.stop();
+        nodeCfgMgr.stop();
+        clusterCfgMgr.stop();
     }
 
     /**
@@ -226,7 +239,7 @@ public class TableManagerTest {
     @Disabled("https://issues.apache.org/jira/browse/IGNITE-15255")
     @Test
     public void testStaticTableConfigured() {
-        TableManager tableManager = new TableManager(cfrMgr, mm, sm, am, rm, workDir);
+        TableManager tableManager = new TableManager(nodeCfgMgr, clusterCfgMgr, mm, sm, am, rm, workDir);
 
         assertEquals(1, tableManager.tables().size());
 
@@ -461,7 +474,7 @@ public class TableManagerTest {
             return null;
         }).when(am).listen(same(AffinityEvent.CALCULATED), any());
 
-        TableManager tableManager = new TableManager(cfrMgr, mm, sm, am, rm, workDir);
+        TableManager tableManager = new TableManager(nodeCfgMgr, clusterCfgMgr, mm, sm, am, rm, workDir);
 
         TableImpl tbl2;
 
@@ -480,7 +493,7 @@ public class TableManagerTest {
 
             int tablesBeforeCreation = tableManager.tables().size();
 
-            cfrMgr.configurationRegistry().getConfiguration(TablesConfiguration.KEY).tables().listen(ctx -> {
+            clusterCfgMgr.configurationRegistry().getConfiguration(TablesConfiguration.KEY).tables().listen(ctx -> {
                 boolean createTbl = ctx.newValue().get(schemaTable.canonicalName()) != null &&
                     ctx.oldValue().get(schemaTable.canonicalName()) == null;
 
@@ -532,17 +545,4 @@ public class TableManagerTest {
 
         return tbl2;
     }
-
-    /**
-     * Gets a list of configuration keys to use in the test scenario.
-     *
-     * @return List of root configuration keys.
-     */
-    private static List<RootKey<?, ?>> rootConfigurationKeys() {
-        return Arrays.asList(
-            NodeConfiguration.KEY,
-            ClusterConfiguration.KEY,
-            TablesConfiguration.KEY
-        );
-    }
 }