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 2023/07/20 14:03:37 UTC

[ignite-3] branch main updated: IGNITE-19671 Save default configuration values to configuration storage (#2301)

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 3f89316ace IGNITE-19671 Save default configuration values to configuration storage (#2301)
3f89316ace is described below

commit 3f89316acea9d1ac9763147c14f33ef01090cdae
Author: Cyrill <cy...@gmail.com>
AuthorDate: Thu Jul 20 17:03:31 2023 +0300

    IGNITE-19671 Save default configuration values to configuration storage (#2301)
---
 .../configuration/ConfigurationChanger.java        | 118 ++++++++++++---------
 .../configuration/ConfigurationRegistry.java       |  18 +---
 .../configuration/ConfigurationChangerTest.java    |  15 +--
 .../asm/ConfigurationTreeGeneratorTest.java        |   1 -
 .../configuration/direct/DirectPropertiesTest.java |   2 -
 .../ConfigurationAnyListenerTest.java              |   2 -
 .../notifications/ConfigurationListenerTest.java   |   4 +-
 .../internal/configuration/sample/UsageTest.java   |   2 -
 .../configuration/tree/InternalIdTest.java         |   2 -
 ...ibutionZoneManagerConfigurationChangesTest.java |  12 +--
 .../DistributionZoneManagerScaleUpTest.java        |   4 +-
 .../DistributionZoneManagerTest.java               |   4 +-
 .../org/apache/ignite/internal/app/IgniteImpl.java |   2 +-
 .../storage/LocalFileConfigurationStorageTest.java | 117 +++++++++++++++++---
 .../DdlCommandHandlerExceptionHandlingTest.java    |   4 +-
 15 files changed, 189 insertions(+), 118 deletions(-)

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 ade0c4b9c4..eb0fed2e2c 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
@@ -49,7 +49,6 @@ import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ForkJoinPool;
 import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.concurrent.locks.ReadWriteLock;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
@@ -95,6 +94,9 @@ public abstract class ConfigurationChanger implements DynamicConfigurationChange
     /** Storage trees. */
     private volatile StorageRoots storageRoots;
 
+    /** Future that resolves after the defaults are persisted to the storage. */
+    private final CompletableFuture<Void> defaultsPersisted = new CompletableFuture<>();
+
     /** Configuration listener notification counter, must be incremented before each use of {@link #configurationUpdateListener}. */
     private final AtomicLong notificationListenerCnt = new AtomicLong();
 
@@ -124,6 +126,9 @@ public abstract class ConfigurationChanger implements DynamicConfigurationChange
      * Immutable data container to store version and all roots associated with the specific storage.
      */
     private static class StorageRoots {
+        /** Immutable forest, so to say. */
+        private final SuperRoot rootsWithoutDefaults;
+
         /** Immutable forest, so to say. */
         private final SuperRoot roots;
 
@@ -136,14 +141,17 @@ public abstract class ConfigurationChanger implements DynamicConfigurationChange
         /**
          * Constructor.
          *
-         * @param roots Forest.
+         * @param rootsWithoutDefaults Forest without the defaults
+         * @param roots Forest with the defaults filled in
          * @param version Version associated with the currently known storage state.
          */
-        private StorageRoots(SuperRoot roots, long version) {
+        private StorageRoots(SuperRoot rootsWithoutDefaults, SuperRoot roots, long version) {
+            this.rootsWithoutDefaults = rootsWithoutDefaults;
             this.roots = roots;
             this.version = version;
 
             makeImmutable(roots);
+            makeImmutable(rootsWithoutDefaults);
         }
     }
 
@@ -151,7 +159,7 @@ public abstract class ConfigurationChanger implements DynamicConfigurationChange
      * Makes the node immutable by calling {@link ConstructableTreeNode#makeImmutable()} on each sub-node recursively.
      */
     private static void makeImmutable(InnerNode node) {
-        if (!node.makeImmutable()) {
+        if (node == null || !node.makeImmutable()) {
             return;
         }
 
@@ -251,59 +259,56 @@ public abstract class ConfigurationChanger implements DynamicConfigurationChange
             superRoot.addRoot(rootKey, rootNode);
         }
 
-        //Workaround for distributed configuration.
+        SuperRoot superRootNoDefaults = superRoot.copy();
+
         addDefaults(superRoot);
 
         // Validate the restored configuration.
         validateConfiguration(superRoot);
-
-        storageRoots = new StorageRoots(superRoot, data.changeId());
+        // We store two configuration roots, one with the defaults set and another one without them.
+        // The root WITH the defaults is used when we calculate who to notify of a configuration change or
+        // when we provide the configuration outside.
+        // The root WITHOUT the defaults is used to calculate which properties to write to the underlying storage,
+        // in other words it allows us to persist the defaults from the code.
+        // After the storage listener fires for the first time both roots are supposed to become equal.
+        storageRoots = new StorageRoots(superRootNoDefaults, superRoot, data.changeId());
 
         storage.registerConfigurationListener(configurationStorageListener());
+
+        persistDefaults();
     }
 
     /**
-     * Initializes the configuration storage - reads data and sets default values for missing configuration properties.
+     * Persists default values to the storage.
      *
-     * @throws ConfigurationValidationException If configuration validation failed.
-     * @throws ConfigurationChangeException If configuration framework failed to add default values and save them to storage.
+     * <p>Specifically, this method runs a check whether there are
+     * default values that are not persisted to the storage and writes them if there are any.
      */
-    public void initializeDefaults() throws ConfigurationValidationException, ConfigurationChangeException {
-        try {
-            ConfigurationSource defaultsCfgSource = new ConfigurationSource() {
-                /** {@inheritDoc} */
-                @Override
-                public void descend(ConstructableTreeNode node) {
-                    addDefaults((InnerNode) node);
-                }
-            };
-
-            changeInternally(defaultsCfgSource).get(5, TimeUnit.SECONDS);
-        } catch (ExecutionException e) {
-            Throwable cause = e.getCause();
-
-            if (cause instanceof ConfigurationValidationException) {
-                throw (ConfigurationValidationException) cause;
-            }
-
-            if (cause instanceof ConfigurationChangeException) {
-                throw (ConfigurationChangeException) cause;
-            }
-
-            throw new ConfigurationChangeException(
-                    "Failed to write default configuration values into the storage " + storage.getClass(), e
-            );
-        } catch (InterruptedException | TimeoutException e) {
-            throw new ConfigurationChangeException(
-                    "Failed to initialize configuration storage " + storage.getClass(), e
-            );
-        }
+    private void persistDefaults() {
+        changeInternally(ConfigurationUtil.EMPTY_CFG_SRC, true)
+                .whenComplete((v, e) -> {
+                    if (e == null) {
+                        defaultsPersisted.complete(null);
+                    } else {
+                        defaultsPersisted.completeExceptionally(e);
+                    }
+                });
     }
 
     /** {@inheritDoc} */
     @Override
     public CompletableFuture<Void> change(ConfigurationSource source) {
-        return changeInternally(source);
+        if (storageRoots == null) {
+            throw new ComponentNotStartedException();
+        }
+        return defaultsPersisted.thenCompose(v -> changeInternally(source, false));
+    }
+
+    /**
+     * Returns a future that resolves after the defaults are persisted to the storage.
+     */
+    public CompletableFuture<Void> onDefaultsPersisted() {
+        return defaultsPersisted;
     }
 
     /** {@inheritDoc} */
@@ -474,6 +479,8 @@ public abstract class ConfigurationChanger implements DynamicConfigurationChange
     public void stop() {
         IgniteUtils.shutdownAndAwaitTermination(pool, 10, TimeUnit.SECONDS);
 
+        defaultsPersisted.completeExceptionally(new NodeStoppingException());
+
         StorageRoots roots = storageRoots;
 
         if (roots != null) {
@@ -509,19 +516,16 @@ public abstract class ConfigurationChanger implements DynamicConfigurationChange
      * Entry point for configuration changes.
      *
      * @param src Configuration source.
+     * @param onStartup if {@code true} this change is triggered right after startup
      * @return Future that will be completed after changes are written to the storage.
      * @throws ComponentNotStartedException if changer is not started.
      */
-    private CompletableFuture<Void> changeInternally(ConfigurationSource src) {
-        if (storageRoots == null) {
-            throw new ComponentNotStartedException();
-        }
-
+    private CompletableFuture<Void> changeInternally(ConfigurationSource src, boolean onStartup) {
         return storage.lastRevision()
             .thenComposeAsync(storageRevision -> {
                 assert storageRevision != null;
 
-                return changeInternally0(src, storageRevision);
+                return changeInternally0(src, storageRevision, onStartup);
             }, pool)
             .exceptionally(throwable -> {
                 Throwable cause = throwable.getCause();
@@ -539,10 +543,11 @@ public abstract class ConfigurationChanger implements DynamicConfigurationChange
      *
      * @param src Configuration source.
      * @param storageRevision Latest storage revision from the metastorage.
+     * @param onStartup if {@code true} this change is triggered right after startup
      * @return Future that will be completed after changes are written to the storage.
      */
-    private CompletableFuture<Void> changeInternally0(ConfigurationSource src, long storageRevision) {
-        // Read lock protects "storageRoots" field from being updated, thus guaranteeing a thread-safe read of configuration inside of the
+    private CompletableFuture<Void> changeInternally0(ConfigurationSource src, long storageRevision, boolean onStartup) {
+        // Read lock protects "storageRoots" field from being updated, thus guaranteeing a thread-safe read of configuration inside the
         // change closure.
         rwLock.readLock().lock();
 
@@ -553,7 +558,7 @@ public abstract class ConfigurationChanger implements DynamicConfigurationChange
 
             if (localRoots.version < storageRevision) {
                 // Need to wait for the configuration updates from the storage, then try to update again (loop).
-                return localRoots.changeFuture.thenCompose(v -> changeInternally(src));
+                return localRoots.changeFuture.thenCompose(v -> changeInternally(src, onStartup));
             }
 
             SuperRoot curRoots = localRoots.roots;
@@ -566,7 +571,11 @@ public abstract class ConfigurationChanger implements DynamicConfigurationChange
 
             addDefaults(changes);
 
-            Map<String, Serializable> allChanges = createFlattenedUpdatesMap(curRoots, changes);
+            Map<String, Serializable> allChanges = createFlattenedUpdatesMap(localRoots.rootsWithoutDefaults, changes);
+            if (allChanges.isEmpty() && onStartup) {
+                // We don't want an empty storage update if this is the initialization changer.
+                return CompletableFuture.completedFuture(null);
+            }
 
             dropNulls(changes);
 
@@ -583,7 +592,7 @@ public abstract class ConfigurationChanger implements DynamicConfigurationChange
                         } else {
                             // Here we go to next iteration of an implicit spin loop; we have to do it via recursion
                             // because we work with async code (futures).
-                            return localRoots.changeFuture.thenCompose(v -> changeInternally(src));
+                            return localRoots.changeFuture.thenCompose(v -> changeInternally(src, onStartup));
                         }
                     });
         } finally {
@@ -613,17 +622,20 @@ public abstract class ConfigurationChanger implements DynamicConfigurationChange
             StorageRoots oldStorageRoots = storageRoots;
 
             SuperRoot oldSuperRoot = oldStorageRoots.roots;
+            SuperRoot oldSuperRootNoDefaults = oldStorageRoots.rootsWithoutDefaults;
             SuperRoot newSuperRoot = oldSuperRoot.copy();
+            SuperRoot newSuperNoDefaults = oldSuperRootNoDefaults.copy();
 
             Map<String, ?> dataValuesPrefixMap = toPrefixMap(changedEntries.values());
 
             compressDeletedEntries(dataValuesPrefixMap);
 
             fillFromPrefixMap(newSuperRoot, dataValuesPrefixMap);
+            fillFromPrefixMap(newSuperNoDefaults, dataValuesPrefixMap);
 
             long newChangeId = changedEntries.changeId();
 
-            var newStorageRoots = new StorageRoots(newSuperRoot, newChangeId);
+            var newStorageRoots = new StorageRoots(newSuperNoDefaults, newSuperRoot, newChangeId);
 
             rwLock.writeLock().lock();
 
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 ce12346756..026939af14 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
@@ -21,7 +21,6 @@ import static java.util.concurrent.CompletableFuture.completedFuture;
 import static org.apache.ignite.internal.configuration.notifications.ConfigurationNotifier.notifyListeners;
 import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.checkConfigurationType;
 import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.innerNodeVisitor;
-import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.touch;
 
 import java.io.Serializable;
 import java.lang.annotation.Annotation;
@@ -68,9 +67,6 @@ public class ConfigurationRegistry implements IgniteComponent {
     /** Generated configuration implementations. Mapping: {@link RootKey#key} -> configuration implementation. */
     private final Map<String, DynamicConfiguration<?, ?>> configs = new HashMap<>();
 
-    /** Root keys. */
-    private final Collection<RootKey<?, ?>> rootKeys;
-
     /** Configuration change handler. */
     private final ConfigurationChanger changer;
 
@@ -92,8 +88,6 @@ public class ConfigurationRegistry implements IgniteComponent {
     ) {
         checkConfigurationType(rootKeys, storage);
 
-        this.rootKeys = rootKeys;
-
         changer = new ConfigurationChanger(notificationUpdateListener(), rootKeys, storage, configurationValidator) {
             @Override
             public InnerNode createRootNode(RootKey<?, ?> rootKey) {
@@ -137,16 +131,10 @@ public class ConfigurationRegistry implements IgniteComponent {
     }
 
     /**
-     * Initializes the configuration storage - reads data and sets default values for missing configuration properties.
+     * Returns a future that resolves after the defaults are persisted to the storage.
      */
-    public void initializeDefaults() {
-        changer.initializeDefaults();
-
-        for (RootKey<?, ?> rootKey : rootKeys) {
-            DynamicConfiguration<?, ?> dynCfg = configs.get(rootKey.key());
-
-            touch(dynCfg);
-        }
+    public CompletableFuture<Void> onDefaultsPersisted() {
+        return changer.onDefaultsPersisted();
     }
 
     /**
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/ConfigurationChangerTest.java b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/ConfigurationChangerTest.java
index ef821efcb0..7457145a44 100644
--- a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/ConfigurationChangerTest.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/ConfigurationChangerTest.java
@@ -23,6 +23,7 @@ import static java.util.concurrent.TimeUnit.SECONDS;
 import static org.apache.ignite.configuration.annotation.ConfigurationType.LOCAL;
 import static org.apache.ignite.internal.configuration.FirstConfiguration.KEY;
 import static org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willBe;
+import static org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willCompleteSuccessfully;
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.anEmptyMap;
@@ -315,8 +316,6 @@ public class ConfigurationChangerTest {
 
         changer.start();
 
-        changer.initializeDefaults();
-
         DefaultsView root = (DefaultsView) changer.getRootNode(DefaultsConfiguration.KEY);
 
         assertEquals("foo", root.defStr());
@@ -344,8 +343,6 @@ public class ConfigurationChangerTest {
 
         changer.start();
 
-        changer.initializeDefaults();
-
         ConfigurationSource source = source(
                 DefaultsConfiguration.KEY,
                 (DefaultsChange change) -> change.changeChildrenList(children -> children.create("name", child -> {
@@ -371,8 +368,6 @@ public class ConfigurationChangerTest {
 
         changer.start();
 
-        changer.initializeDefaults();
-
         DefaultsChildView childView = changer.getLatest(List.of(node("def"), node("child")));
 
         assertEquals("bar", childView.defStr());
@@ -396,8 +391,6 @@ public class ConfigurationChangerTest {
 
         changer.start();
 
-        changer.initializeDefaults();
-
         ConfigurationSource source = source(
                 DefaultsConfiguration.KEY,
                 (DefaultsChange change) -> change.changeChildrenList(children -> {
@@ -447,8 +440,6 @@ public class ConfigurationChangerTest {
 
         changer.start();
 
-        changer.initializeDefaults();
-
         ConfigurationSource source = source(
                 DefaultsConfiguration.KEY,
                 (DefaultsChange change) -> change.changeChildrenList(children -> children.create("name", child -> {
@@ -513,7 +504,7 @@ public class ConfigurationChangerTest {
 
         changer.start();
 
-        changer.initializeDefaults();
+        assertThat(changer.onDefaultsPersisted(), willCompleteSuccessfully());
 
         assertEquals(1, storage.lastRevision().get(1, SECONDS));
 
@@ -566,7 +557,7 @@ public class ConfigurationChangerTest {
 
         changer.start();
 
-        changer.initializeDefaults();
+        assertThat(changer.onDefaultsPersisted(), willCompleteSuccessfully());
 
         assertEquals(1, storage.lastRevision().get(1, SECONDS));
 
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/asm/ConfigurationTreeGeneratorTest.java b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/asm/ConfigurationTreeGeneratorTest.java
index d4ab2b1754..89b9c90126 100644
--- a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/asm/ConfigurationTreeGeneratorTest.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/asm/ConfigurationTreeGeneratorTest.java
@@ -121,7 +121,6 @@ public class ConfigurationTreeGeneratorTest {
         );
 
         changer.start();
-        changer.initializeDefaults();
     }
 
     @AfterEach
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/direct/DirectPropertiesTest.java b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/direct/DirectPropertiesTest.java
index 5cad618852..2c79d0df38 100644
--- a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/direct/DirectPropertiesTest.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/direct/DirectPropertiesTest.java
@@ -112,8 +112,6 @@ public class DirectPropertiesTest {
         );
 
         registry.start();
-
-        registry.initializeDefaults();
     }
 
     @AfterEach
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/notifications/ConfigurationAnyListenerTest.java b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/notifications/ConfigurationAnyListenerTest.java
index 23ba64373e..fe6fdd6133 100644
--- a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/notifications/ConfigurationAnyListenerTest.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/notifications/ConfigurationAnyListenerTest.java
@@ -170,8 +170,6 @@ public class ConfigurationAnyListenerTest {
 
         registry.start();
 
-        registry.initializeDefaults();
-
         rootConfig = registry.getConfiguration(RootConfiguration.KEY);
 
         // Add "regular" listeners.
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/notifications/ConfigurationListenerTest.java b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/notifications/ConfigurationListenerTest.java
index d3862690de..740001f539 100644
--- a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/notifications/ConfigurationListenerTest.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/notifications/ConfigurationListenerTest.java
@@ -31,6 +31,7 @@ import static org.apache.ignite.internal.configuration.notifications.Configurati
 import static org.apache.ignite.internal.configuration.notifications.ConfigurationListenerTestUtils.randomUuid;
 import static org.apache.ignite.internal.configuration.notifications.ConfigurationNotifier.notifyListeners;
 import static org.apache.ignite.internal.testframework.IgniteTestUtils.hasCause;
+import static org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willCompleteSuccessfully;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -178,8 +179,7 @@ public class ConfigurationListenerTest {
         );
 
         registry.start();
-
-        registry.initializeDefaults();
+        assertThat(registry.onDefaultsPersisted(), willCompleteSuccessfully());
 
         config = registry.getConfiguration(ParentConfiguration.KEY);
     }
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/sample/UsageTest.java b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/sample/UsageTest.java
index 1d7da15616..8cacadf5dc 100644
--- a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/sample/UsageTest.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/sample/UsageTest.java
@@ -57,8 +57,6 @@ public class UsageTest {
 
         registry.start();
 
-        registry.initializeDefaults();
-
         LocalConfiguration root = registry.getConfiguration(LocalConfiguration.KEY);
 
         root.change(local ->
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/InternalIdTest.java b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/InternalIdTest.java
index 36de4fd2e4..4aea167549 100644
--- a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/InternalIdTest.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/InternalIdTest.java
@@ -98,8 +98,6 @@ public class InternalIdTest {
         );
 
         registry.start();
-
-        registry.initializeDefaults();
     }
 
     @AfterEach
diff --git a/modules/distribution-zones/src/test/java/org/apache/ignite/internal/distributionzones/DistributionZoneManagerConfigurationChangesTest.java b/modules/distribution-zones/src/test/java/org/apache/ignite/internal/distributionzones/DistributionZoneManagerConfigurationChangesTest.java
index dc8f0b217d..713ee78a4e 100644
--- a/modules/distribution-zones/src/test/java/org/apache/ignite/internal/distributionzones/DistributionZoneManagerConfigurationChangesTest.java
+++ b/modules/distribution-zones/src/test/java/org/apache/ignite/internal/distributionzones/DistributionZoneManagerConfigurationChangesTest.java
@@ -180,9 +180,9 @@ public class DistributionZoneManagerConfigurationChangesTest extends IgniteAbstr
 
         assertDataNodesForZoneWithAttributes(1, nodes.stream().map(NodeWithAttributes::node).collect(toSet()), keyValueStorage);
 
-        assertZoneScaleUpChangeTriggerKey(1L, 1, keyValueStorage);
+        assertZoneScaleUpChangeTriggerKey(2L, 1, keyValueStorage);
 
-        assertZonesChangeTriggerKey(1, 1, keyValueStorage);
+        assertZonesChangeTriggerKey(2, 1, keyValueStorage);
     }
 
     @Test
@@ -205,10 +205,10 @@ public class DistributionZoneManagerConfigurationChangesTest extends IgniteAbstr
 
         distributionZoneManager.createZone(new DistributionZoneConfigurationParameters.Builder(NEW_ZONE_NAME).build()).get();
 
-        assertZoneScaleUpChangeTriggerKey(1L, 1, keyValueStorage);
-        assertZoneScaleUpChangeTriggerKey(2L, 2, keyValueStorage);
-        assertZonesChangeTriggerKey(1, 1, keyValueStorage);
-        assertZonesChangeTriggerKey(2, 2, keyValueStorage);
+        assertZoneScaleUpChangeTriggerKey(2L, 1, keyValueStorage);
+        assertZoneScaleUpChangeTriggerKey(3L, 2, keyValueStorage);
+        assertZonesChangeTriggerKey(2, 1, keyValueStorage);
+        assertZonesChangeTriggerKey(3, 2, keyValueStorage);
     }
 
     @Test
diff --git a/modules/distribution-zones/src/test/java/org/apache/ignite/internal/distributionzones/DistributionZoneManagerScaleUpTest.java b/modules/distribution-zones/src/test/java/org/apache/ignite/internal/distributionzones/DistributionZoneManagerScaleUpTest.java
index af7e98a271..e262f79101 100644
--- a/modules/distribution-zones/src/test/java/org/apache/ignite/internal/distributionzones/DistributionZoneManagerScaleUpTest.java
+++ b/modules/distribution-zones/src/test/java/org/apache/ignite/internal/distributionzones/DistributionZoneManagerScaleUpTest.java
@@ -470,7 +470,7 @@ public class DistributionZoneManagerScaleUpTest extends BaseDistributionZoneMana
 
         assertDataNodesForZone(ZONE_1_ID, Set.of(), keyValueStorage);
 
-        assertZoneScaleUpChangeTriggerKey(3L, ZONE_1_ID, keyValueStorage);
+        assertZoneScaleUpChangeTriggerKey(4L, ZONE_1_ID, keyValueStorage);
 
         doAnswer(invocation -> {
             If iif = invocation.getArgument(0);
@@ -516,7 +516,7 @@ public class DistributionZoneManagerScaleUpTest extends BaseDistributionZoneMana
 
         assertDataNodesForZone(ZONE_1_ID, Set.of(NODE_1), keyValueStorage);
 
-        assertZoneScaleDownChangeTriggerKey(5L, ZONE_1_ID, keyValueStorage);
+        assertZoneScaleDownChangeTriggerKey(6L, ZONE_1_ID, keyValueStorage);
 
         doAnswer(invocation -> {
             If iif = invocation.getArgument(0);
diff --git a/modules/distribution-zones/src/test/java/org/apache/ignite/internal/distributionzones/DistributionZoneManagerTest.java b/modules/distribution-zones/src/test/java/org/apache/ignite/internal/distributionzones/DistributionZoneManagerTest.java
index 91fb686962..5bd1324a97 100644
--- a/modules/distribution-zones/src/test/java/org/apache/ignite/internal/distributionzones/DistributionZoneManagerTest.java
+++ b/modules/distribution-zones/src/test/java/org/apache/ignite/internal/distributionzones/DistributionZoneManagerTest.java
@@ -23,6 +23,7 @@ import static org.apache.ignite.internal.distributionzones.DistributionZoneManag
 import static org.apache.ignite.internal.distributionzones.DistributionZoneManager.IMMEDIATE_TIMER_VALUE;
 import static org.apache.ignite.internal.distributionzones.DistributionZoneManager.INFINITE_TIMER_VALUE;
 import static org.apache.ignite.internal.testframework.IgniteTestUtils.assertThrowsWithCause;
+import static org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willCompleteSuccessfully;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -91,8 +92,7 @@ class DistributionZoneManagerTest extends IgniteAbstractTest {
         );
 
         registry.start();
-
-        registry.initializeDefaults();
+        assertThat(registry.onDefaultsPersisted(), willCompleteSuccessfully());
 
         DistributionZonesConfiguration zonesConfiguration = registry.getConfiguration(DistributionZonesConfiguration.KEY);
 
diff --git a/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java b/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
index 6ba3ece793..d89c111b1a 100644
--- a/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
+++ b/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
@@ -714,7 +714,6 @@ public class IgniteImpl implements Ignite {
                     restComponent,
                     raftMgr,
                     clusterStateStorage,
-                    distributedConfigurationUpdater,
                     cmgMgr
             );
 
@@ -770,6 +769,7 @@ public class IgniteImpl implements Ignite {
 
                         return recoverComponentsStateOnStart(startupExecutor);
                     }, startupExecutor)
+                    .thenComposeAsync(v -> clusterCfgMgr.configurationRegistry().onDefaultsPersisted(), startupExecutor)
                     .thenRunAsync(() -> {
                         try {
                             lifecycleManager.startComponent(distributedConfigurationUpdater);
diff --git a/modules/runner/src/test/java/org/apache/ignite/internal/configuration/storage/LocalFileConfigurationStorageTest.java b/modules/runner/src/test/java/org/apache/ignite/internal/configuration/storage/LocalFileConfigurationStorageTest.java
index 30162b0120..88dce5e3a5 100644
--- a/modules/runner/src/test/java/org/apache/ignite/internal/configuration/storage/LocalFileConfigurationStorageTest.java
+++ b/modules/runner/src/test/java/org/apache/ignite/internal/configuration/storage/LocalFileConfigurationStorageTest.java
@@ -17,6 +17,7 @@
 
 package org.apache.ignite.internal.configuration.storage;
 
+import static org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willCompleteSuccessfully;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.aMapWithSize;
 import static org.hamcrest.Matchers.allOf;
@@ -93,8 +94,6 @@ public class LocalFileConfigurationStorageTest {
                 treeGenerator,
                 new ConfigurationValidatorImpl(treeGenerator, Set.of())
         );
-
-        changer.start();
     }
 
     @AfterEach
@@ -125,6 +124,9 @@ public class LocalFileConfigurationStorageTest {
         // And
         var topConfiguration = (TopConfiguration) treeGenerator.instantiateCfg(TopConfiguration.KEY, changer);
 
+        changer.start();
+        assertThat(changer.onDefaultsPersisted(), willCompleteSuccessfully());
+
         topConfiguration.namedList().change(b -> b.create("name1", x -> {
             x.changeStrVal("strVal1");
             x.changeIntVal(-1);
@@ -141,12 +143,21 @@ public class LocalFileConfigurationStorageTest {
         // top.namedList.<ids>.name1             -> "<generatedUUID>"
         // top.namedList.<generatedUUID>.<order> -> 0
 
-        assertThat(storageValues, allOf(aMapWithSize(5), hasValue(-1)));
-        assertThat(storageValues, allOf(aMapWithSize(5), hasValue("strVal1")));
+        assertThat(storageValues, allOf(aMapWithSize(10), hasValue(-1)));
+        assertThat(storageValues, allOf(aMapWithSize(10), hasValue("strVal1")));
 
         // And
+        // Enriched with the defaults
         assertThat(configFileContent(), equalToCompressingWhiteSpace(
                 "top {\n"
+                        + "    inner {\n"
+                        + "        boolVal=false\n"
+                        + "        someConfigurationValue {\n"
+                        + "            intVal=1\n"
+                        + "            strVal=foo\n"
+                        + "        }\n"
+                        + "        strVal=foo\n"
+                        + "    }\n"
                         + "    namedList=[\n"
                         + "        {\n"
                         + "            intVal=-1\n"
@@ -154,6 +165,7 @@ public class LocalFileConfigurationStorageTest {
                         + "            strVal=strVal1\n"
                         + "        }\n"
                         + "    ]\n"
+                        + "    shortVal=1\n"
                         + "}"
         ));
 
@@ -166,14 +178,22 @@ public class LocalFileConfigurationStorageTest {
         storageValues = readAllLatest();
 
         // Then
-        assertThat(storageValues, allOf(aMapWithSize(10), hasValue(-2)));
-        assertThat(storageValues, allOf(aMapWithSize(10), hasValue("strVal2")));
+        assertThat(storageValues, allOf(aMapWithSize(15), hasValue(-2)));
+        assertThat(storageValues, allOf(aMapWithSize(15), hasValue("strVal2")));
         // And
-        assertThat(storageValues, allOf(aMapWithSize(10), hasValue(-1)));
-        assertThat(storageValues, allOf(aMapWithSize(10), hasValue("strVal1")));
+        assertThat(storageValues, allOf(aMapWithSize(15), hasValue(-1)));
+        assertThat(storageValues, allOf(aMapWithSize(15), hasValue("strVal1")));
         // And
         assertThat(configFileContent(), equalToCompressingWhiteSpace(
                 "top {\n"
+                        + "    inner {\n"
+                        + "        boolVal=false\n"
+                        + "        someConfigurationValue {\n"
+                        + "            intVal=1\n"
+                        + "            strVal=foo\n"
+                        + "        }\n"
+                        + "        strVal=foo\n"
+                        + "    }\n"
                         + "    namedList=[\n"
                         + "        {\n"
                         + "            intVal=-1\n"
@@ -186,6 +206,7 @@ public class LocalFileConfigurationStorageTest {
                         + "            strVal=strVal2\n"
                         + "        }\n"
                         + "    ]\n"
+                        + "    shortVal=1\n"
                         + "}\n"
         ));
     }
@@ -199,15 +220,26 @@ public class LocalFileConfigurationStorageTest {
         // When
         var topConfiguration = (TopConfiguration) treeGenerator.instantiateCfg(TopConfiguration.KEY, changer);
 
+        changer.start();
+        assertThat(changer.onDefaultsPersisted(), willCompleteSuccessfully());
+
         topConfiguration.shortVal().update((short) 3).get();
         // And
         var storageValues = readAllLatest();
 
         // Then
-        assertThat(storageValues, allOf(aMapWithSize(1), hasValue((short) 3)));
+        assertThat(storageValues, allOf(aMapWithSize(5), hasValue((short) 3)));
         // And
         assertThat(configFileContent(), equalToCompressingWhiteSpace(
                 "top {\n"
+                        + "    inner {\n"
+                        + "        boolVal=false\n"
+                        + "        someConfigurationValue {\n"
+                        + "            intVal=1\n"
+                        + "            strVal=foo\n"
+                        + "        }\n"
+                        + "        strVal=foo\n"
+                        + "    }\n"
                         + "    shortVal=3\n"
                         + "}\n"
         ));
@@ -219,11 +251,19 @@ public class LocalFileConfigurationStorageTest {
         storageValues = readAllLatest();
 
         // Then
-        assertThat(storageValues, allOf(aMapWithSize(6), hasValue(1)));
-        assertThat(storageValues, allOf(aMapWithSize(6), hasValue("foo")));
+        assertThat(storageValues, allOf(aMapWithSize(10), hasValue(1)));
+        assertThat(storageValues, allOf(aMapWithSize(10), hasValue("foo")));
         // And
         assertThat(configFileContent(), equalToCompressingWhiteSpace(
                 "top {\n"
+                        + "    inner {\n"
+                        + "        boolVal=false\n"
+                        + "        someConfigurationValue {\n"
+                        + "            intVal=1\n"
+                        + "            strVal=foo\n"
+                        + "        }\n"
+                        + "        strVal=foo\n"
+                        + "    }\n"
                         + "    namedList=[\n"
                         + "        {\n"
                         + "            intVal=1\n"
@@ -244,11 +284,19 @@ public class LocalFileConfigurationStorageTest {
         storageValues = readAllLatest();
 
         // Then
-        assertThat(storageValues, allOf(aMapWithSize(6), hasValue(-1)));
-        assertThat(storageValues, allOf(aMapWithSize(6), hasValue("strVal1")));
+        assertThat(storageValues, allOf(aMapWithSize(10), hasValue(-1)));
+        assertThat(storageValues, allOf(aMapWithSize(10), hasValue("strVal1")));
         // And
         assertThat(configFileContent(), equalToCompressingWhiteSpace(
                 "top {\n"
+                        + "    inner {\n"
+                        + "        boolVal=false\n"
+                        + "        someConfigurationValue {\n"
+                        + "            intVal=1\n"
+                        + "            strVal=foo\n"
+                        + "        }\n"
+                        + "        strVal=foo\n"
+                        + "    }\n"
                         + "    namedList=[\n"
                         + "        {\n"
                         + "            intVal=-1\n"
@@ -267,6 +315,9 @@ public class LocalFileConfigurationStorageTest {
         // Given
         var topConfiguration = (TopConfiguration) treeGenerator.instantiateCfg(TopConfiguration.KEY, changer);
 
+        changer.start();
+        assertThat(changer.onDefaultsPersisted(), willCompleteSuccessfully());
+
         topConfiguration.namedList().change(b -> {
             b.create("name1", x -> {
                 x.changeStrVal("strVal1");
@@ -282,6 +333,14 @@ public class LocalFileConfigurationStorageTest {
         // And values are saved to file
         assertThat(configFileContent(), equalToCompressingWhiteSpace(
                 "top {\n"
+                        + "    inner {\n"
+                        + "        boolVal=false\n"
+                        + "        someConfigurationValue {\n"
+                        + "            intVal=1\n"
+                        + "            strVal=foo\n"
+                        + "        }\n"
+                        + "        strVal=foo\n"
+                        + "    }\n"
                         + "    namedList=[\n"
                         + "        {\n"
                         + "            intVal=-1\n"
@@ -304,10 +363,18 @@ public class LocalFileConfigurationStorageTest {
         var storageValues = readAllLatest();
 
         // Then
-        assertThat(storageValues, allOf(aMapWithSize(6), Matchers.not(hasValue("strVal1"))));
+        assertThat(storageValues, allOf(aMapWithSize(10), Matchers.not(hasValue("strVal1"))));
         // And entity removed from file
         assertThat(configFileContent(), equalToCompressingWhiteSpace(
                 "top {\n"
+                        + "    inner {\n"
+                        + "        boolVal=false\n"
+                        + "        someConfigurationValue {\n"
+                        + "            intVal=1\n"
+                        + "            strVal=foo\n"
+                        + "        }\n"
+                        + "        strVal=foo\n"
+                        + "    }\n"
                         + "    namedList=[\n"
                         + "        {\n"
                         + "            intVal=-2\n"
@@ -325,10 +392,18 @@ public class LocalFileConfigurationStorageTest {
         storageValues = readAllLatest();
 
         // Then
-        assertThat(storageValues, allOf(aMapWithSize(1), hasValue((short) 3)));
+        assertThat(storageValues, allOf(aMapWithSize(5), hasValue((short) 3)));
         // And entity removed from file
         assertThat(configFileContent(), equalToCompressingWhiteSpace(
                 "top {\n"
+                        + "    inner {\n"
+                        + "        boolVal=false\n"
+                        + "        someConfigurationValue {\n"
+                        + "            intVal=1\n"
+                        + "            strVal=foo\n"
+                        + "        }\n"
+                        + "        strVal=foo\n"
+                        + "    }\n"
                         + "    shortVal=3\n"
                         + "}\n"
         ));
@@ -389,6 +464,9 @@ public class LocalFileConfigurationStorageTest {
         assertThat(Files.exists(getConfigFile()), is(false));
 
         // When update configuration
+        changer.start();
+        assertThat(changer.onDefaultsPersisted(), willCompleteSuccessfully());
+
         var topConfiguration = (TopConfiguration) treeGenerator.instantiateCfg(TopConfiguration.KEY, changer);
         topConfiguration.namedList().change(b -> b.create("name1", x -> {
             x.changeStrVal("strVal1");
@@ -398,6 +476,14 @@ public class LocalFileConfigurationStorageTest {
         // Then file is created
         assertThat(configFileContent(), equalToCompressingWhiteSpace(
                 "top {\n"
+                        + "    inner {\n"
+                        + "        boolVal=false\n"
+                        + "        someConfigurationValue {\n"
+                        + "            intVal=1\n"
+                        + "            strVal=foo\n"
+                        + "        }\n"
+                        + "        strVal=foo\n"
+                        + "    }\n"
                         + "    namedList=[\n"
                         + "        {\n"
                         + "            intVal=-1\n"
@@ -405,6 +491,7 @@ public class LocalFileConfigurationStorageTest {
                         + "            strVal=strVal1\n"
                         + "        }\n"
                         + "    ]\n"
+                        + "    shortVal=1\n"
                         + "}\n"
         ));
     }
diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/ddl/DdlCommandHandlerExceptionHandlingTest.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/ddl/DdlCommandHandlerExceptionHandlingTest.java
index f3e846119e..0c42f9fe49 100644
--- a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/ddl/DdlCommandHandlerExceptionHandlingTest.java
+++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/ddl/DdlCommandHandlerExceptionHandlingTest.java
@@ -18,6 +18,8 @@
 package org.apache.ignite.internal.sql.engine.exec.ddl;
 
 import static org.apache.ignite.configuration.annotation.ConfigurationType.DISTRIBUTED;
+import static org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willCompleteSuccessfully;
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
 import static org.mockito.Mockito.mock;
@@ -100,7 +102,7 @@ public class DdlCommandHandlerExceptionHandlingTest extends IgniteAbstractTest {
     void before() {
         registry.start();
 
-        registry.initializeDefaults();
+        assertThat(registry.onDefaultsPersisted(), willCompleteSuccessfully());
 
         DistributionZonesConfiguration zonesConfiguration = registry.getConfiguration(DistributionZonesConfiguration.KEY);