You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by ap...@apache.org on 2024/09/05 15:22:46 UTC

(ignite-3) branch main updated: IGNITE-23146 Add an ability to create indexes in AVAILABLE state (#4335)

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

apolovtsev 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 6420277dfb IGNITE-23146 Add an ability to create indexes in AVAILABLE state (#4335)
6420277dfb is described below

commit 6420277dfbdae4e42b1f4c1f4b17fd648cca6851
Author: Alexander Polovtcev <al...@gmail.com>
AuthorDate: Thu Sep 5 18:22:42 2024 +0300

    IGNITE-23146 Add an ability to create indexes in AVAILABLE state (#4335)
---
 .../commands/AbstractCreateIndexCommand.java       | 21 ++++-
 .../AbstractCreateIndexCommandBuilder.java         |  3 +
 .../catalog/commands/CreateHashIndexCommand.java   | 32 +++++---
 .../catalog/commands/CreateSortedIndexCommand.java | 29 +++++--
 .../catalog/commands/CreateTableCommand.java       | 12 +--
 .../descriptors/CatalogHashIndexDescriptor.java    | 21 +++--
 .../descriptors/CatalogIndexDescriptor.java        | 21 ++++-
 .../descriptors/CatalogSortedIndexDescriptor.java  | 21 +++--
 .../storage/AbstractChangeIndexStatusEntry.java    |  6 +-
 .../internal/catalog/storage/RenameIndexEntry.java |  6 +-
 .../ignite/internal/catalog/CatalogIndexTest.java  | 19 -----
 ...ractChangeIndexStatusCommandValidationTest.java |  3 +-
 .../catalog/commands/CatalogUtilsTest.java         |  3 +-
 .../storage/CatalogEntrySerializationTest.java     |  4 +-
 .../internal/index/IndexBuildController.java       |  2 +-
 .../sql/engine/framework/TestBuilders.java         | 68 ++++++++++------
 .../storage/index/StorageHashIndexDescriptor.java  | 14 ++--
 .../storage/index/StorageIndexDescriptor.java      |  9 ++-
 .../index/StorageSortedIndexDescriptor.java        | 14 ++--
 .../storage/index/StorageIndexDescriptorTest.java  | 92 ----------------------
 .../storage/AbstractMvTableStorageTest.java        | 23 ++++--
 .../index/AbstractHashIndexStorageTest.java        | 17 ++--
 .../storage/index/AbstractIndexStorageTest.java    | 21 ++++-
 .../index/AbstractSortedIndexStorageTest.java      | 24 +++---
 .../index/impl/AbstractTestIndexStorage.java       | 19 +++--
 .../storage/pagememory/mv/IndexStorageFactory.java |  2 +-
 .../storage/rocksdb/RocksDbMetaStorage.java        | 21 ++---
 .../rocksdb/index/AbstractRocksDbIndexStorage.java | 33 +++++---
 .../rocksdb/index/RocksDbHashIndexStorage.java     |  7 +-
 .../rocksdb/index/RocksDbSortedIndexStorage.java   | 11 +--
 .../table/distributed/index/IndexMeta.java         |  3 +-
 .../internal/table/distributed/IndexBaseTest.java  |  6 +-
 .../raft/PartitionCommandListenerTest.java         |  2 +-
 .../PartitionReplicaListenerIndexLockingTest.java  |  2 +-
 .../replication/PartitionReplicaListenerTest.java  |  2 +-
 .../apache/ignite/distributed/ItTxTestCluster.java |  6 +-
 .../table/impl/DummyInternalTableImpl.java         |  1 -
 37 files changed, 314 insertions(+), 286 deletions(-)

diff --git a/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/AbstractCreateIndexCommand.java b/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/AbstractCreateIndexCommand.java
index 3a7336c3db..8419872cf3 100644
--- a/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/AbstractCreateIndexCommand.java
+++ b/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/AbstractCreateIndexCommand.java
@@ -31,6 +31,7 @@ import java.util.Set;
 import org.apache.ignite.internal.catalog.Catalog;
 import org.apache.ignite.internal.catalog.CatalogValidationException;
 import org.apache.ignite.internal.catalog.descriptors.CatalogIndexDescriptor;
+import org.apache.ignite.internal.catalog.descriptors.CatalogIndexStatus;
 import org.apache.ignite.internal.catalog.descriptors.CatalogSchemaDescriptor;
 import org.apache.ignite.internal.catalog.descriptors.CatalogTableDescriptor;
 import org.apache.ignite.internal.catalog.storage.NewIndexEntry;
@@ -49,10 +50,19 @@ public abstract class AbstractCreateIndexCommand extends AbstractIndexCommand {
 
     protected final List<String> columns;
 
+    protected final boolean isCreatedWithTable;
+
     private final boolean ifNotExists;
 
-    AbstractCreateIndexCommand(String schemaName, String indexName, boolean ifNotExists, String tableName, boolean unique,
-            List<String> columns) throws CatalogValidationException {
+    AbstractCreateIndexCommand(
+            String schemaName,
+            String indexName,
+            boolean ifNotExists,
+            String tableName,
+            boolean unique,
+            List<String> columns,
+            boolean isCreatedWithTable
+    ) throws CatalogValidationException {
         super(schemaName, indexName);
 
         validate(tableName, columns);
@@ -61,13 +71,14 @@ public abstract class AbstractCreateIndexCommand extends AbstractIndexCommand {
         this.tableName = tableName;
         this.unique = unique;
         this.columns = copyOrNull(columns);
+        this.isCreatedWithTable = isCreatedWithTable;
     }
 
     public boolean ifNotExists() {
         return ifNotExists;
     }
 
-    protected abstract CatalogIndexDescriptor createDescriptor(int indexId, int tableId);
+    protected abstract CatalogIndexDescriptor createDescriptor(int indexId, int tableId, CatalogIndexStatus status);
 
     @Override
     public List<UpdateEntry> get(Catalog catalog) {
@@ -90,8 +101,10 @@ public abstract class AbstractCreateIndexCommand extends AbstractIndexCommand {
             throw new CatalogValidationException("Unique index must include all colocation columns");
         }
 
+        CatalogIndexStatus status = isCreatedWithTable ? CatalogIndexStatus.AVAILABLE : CatalogIndexStatus.REGISTERED;
+
         return List.of(
-                new NewIndexEntry(createDescriptor(catalog.objectIdGenState(), table.id())),
+                new NewIndexEntry(createDescriptor(catalog.objectIdGenState(), table.id(), status)),
                 new ObjectIdGenUpdateEntry(1)
         );
     }
diff --git a/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/AbstractCreateIndexCommandBuilder.java b/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/AbstractCreateIndexCommandBuilder.java
index 970ed633a9..49e8ff5653 100644
--- a/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/AbstractCreateIndexCommandBuilder.java
+++ b/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/AbstractCreateIndexCommandBuilder.java
@@ -32,4 +32,7 @@ interface AbstractCreateIndexCommandBuilder<T extends AbstractIndexCommandBuilde
 
     /** List of the columns to index. There must be at least one column. */
     T columns(List<String> columns);
+
+    /** Flag indicating that this index has been created at the same time as its table. */
+    T isCreatedWithTable(boolean isCreatedWithTable);
 }
diff --git a/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/CreateHashIndexCommand.java b/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/CreateHashIndexCommand.java
index 2ea72d8498..3be6a9888c 100644
--- a/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/CreateHashIndexCommand.java
+++ b/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/CreateHashIndexCommand.java
@@ -22,6 +22,7 @@ import org.apache.ignite.internal.catalog.CatalogCommand;
 import org.apache.ignite.internal.catalog.CatalogValidationException;
 import org.apache.ignite.internal.catalog.descriptors.CatalogHashIndexDescriptor;
 import org.apache.ignite.internal.catalog.descriptors.CatalogIndexDescriptor;
+import org.apache.ignite.internal.catalog.descriptors.CatalogIndexStatus;
 
 /**
  * A command that adds a new hash index to the catalog.
@@ -43,16 +44,21 @@ public class CreateHashIndexCommand extends AbstractCreateIndexCommand {
      * @param columns List of the indexed columns. There should be at least one column.
      * @throws CatalogValidationException if any of restrictions above is violated.
      */
-    private CreateHashIndexCommand(String schemaName, String indexName, boolean ifNotExists, String tableName, boolean unique,
-            List<String> columns) throws CatalogValidationException {
-        super(schemaName, indexName, ifNotExists, tableName, unique, columns);
+    private CreateHashIndexCommand(
+            String schemaName,
+            String indexName,
+            boolean ifNotExists,
+            String tableName,
+            boolean unique,
+            List<String> columns,
+            boolean isCreatedWithTable
+    ) throws CatalogValidationException {
+        super(schemaName, indexName, ifNotExists, tableName, unique, columns, isCreatedWithTable);
     }
 
     @Override
-    protected CatalogIndexDescriptor createDescriptor(int indexId, int tableId) {
-        return new CatalogHashIndexDescriptor(
-                indexId, indexName, tableId, unique, columns
-        );
+    protected CatalogIndexDescriptor createDescriptor(int indexId, int tableId, CatalogIndexStatus status) {
+        return new CatalogHashIndexDescriptor(indexId, indexName, tableId, unique, status, columns, isCreatedWithTable);
     }
 
     private static class Builder implements CreateHashIndexCommandBuilder {
@@ -62,6 +68,7 @@ public class CreateHashIndexCommand extends AbstractCreateIndexCommand {
         private String tableName;
         private List<String> columns;
         private boolean unique;
+        private boolean isCreatedWithTable;
 
         @Override
         public Builder tableName(String tableName) {
@@ -105,11 +112,16 @@ public class CreateHashIndexCommand extends AbstractCreateIndexCommand {
             return this;
         }
 
+        @Override
+        public CreateHashIndexCommandBuilder isCreatedWithTable(boolean isCreatedWithTable) {
+            this.isCreatedWithTable = isCreatedWithTable;
+
+            return this;
+        }
+
         @Override
         public CatalogCommand build() {
-            return new CreateHashIndexCommand(
-                    schemaName, indexName, ifNotExists, tableName, unique, columns
-            );
+            return new CreateHashIndexCommand(schemaName, indexName, ifNotExists, tableName, unique, columns, isCreatedWithTable);
         }
     }
 }
diff --git a/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/CreateSortedIndexCommand.java b/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/CreateSortedIndexCommand.java
index d21661dc98..ddd6359803 100644
--- a/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/CreateSortedIndexCommand.java
+++ b/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/CreateSortedIndexCommand.java
@@ -27,6 +27,7 @@ import org.apache.ignite.internal.catalog.CatalogValidationException;
 import org.apache.ignite.internal.catalog.descriptors.CatalogColumnCollation;
 import org.apache.ignite.internal.catalog.descriptors.CatalogIndexColumnDescriptor;
 import org.apache.ignite.internal.catalog.descriptors.CatalogIndexDescriptor;
+import org.apache.ignite.internal.catalog.descriptors.CatalogIndexStatus;
 import org.apache.ignite.internal.catalog.descriptors.CatalogSortedIndexDescriptor;
 
 /**
@@ -52,9 +53,17 @@ public class CreateSortedIndexCommand extends AbstractCreateIndexCommand {
      * @param collations List of the columns collations. The size of this list should much size of the columns.
      * @throws CatalogValidationException if any of restrictions above is violated.
      */
-    private CreateSortedIndexCommand(String schemaName, String indexName, boolean ifNotExists, String tableName, boolean unique,
-            List<String> columns, List<CatalogColumnCollation> collations) throws CatalogValidationException {
-        super(schemaName, indexName, ifNotExists, tableName, unique, columns);
+    private CreateSortedIndexCommand(
+            String schemaName,
+            String indexName,
+            boolean ifNotExists,
+            String tableName,
+            boolean unique,
+            List<String> columns,
+            List<CatalogColumnCollation> collations,
+            boolean isCreatedWithTable
+    ) throws CatalogValidationException {
+        super(schemaName, indexName, ifNotExists, tableName, unique, columns, isCreatedWithTable);
 
         this.collations = copyOrNull(collations);
 
@@ -62,7 +71,7 @@ public class CreateSortedIndexCommand extends AbstractCreateIndexCommand {
     }
 
     @Override
-    protected CatalogIndexDescriptor createDescriptor(int indexId, int tableId) {
+    protected CatalogIndexDescriptor createDescriptor(int indexId, int tableId, CatalogIndexStatus status) {
         var indexColumnDescriptors = new ArrayList<CatalogIndexColumnDescriptor>(columns.size());
 
         for (int i = 0; i < columns.size(); i++) {
@@ -72,7 +81,7 @@ public class CreateSortedIndexCommand extends AbstractCreateIndexCommand {
         }
 
         return new CatalogSortedIndexDescriptor(
-                indexId, indexName, tableId, unique, indexColumnDescriptors
+                indexId, indexName, tableId, unique, status, indexColumnDescriptors, isCreatedWithTable
         );
     }
 
@@ -94,6 +103,7 @@ public class CreateSortedIndexCommand extends AbstractCreateIndexCommand {
         private List<String> columns;
         private List<CatalogColumnCollation> collations;
         private boolean unique;
+        private boolean isCreatedWithTable;
 
         @Override
         public Builder tableName(String tableName) {
@@ -144,10 +154,17 @@ public class CreateSortedIndexCommand extends AbstractCreateIndexCommand {
             return this;
         }
 
+        @Override
+        public CreateSortedIndexCommandBuilder isCreatedWithTable(boolean isCreatedWithTable) {
+            this.isCreatedWithTable = isCreatedWithTable;
+
+            return this;
+        }
+
         @Override
         public CatalogCommand build() {
             return new CreateSortedIndexCommand(
-                    schemaName, indexName, ifNotExists, tableName, unique, columns, collations
+                    schemaName, indexName, ifNotExists, tableName, unique, columns, collations, isCreatedWithTable
             );
         }
     }
diff --git a/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/CreateTableCommand.java b/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/CreateTableCommand.java
index 79fa986daa..c7b29a7f1b 100644
--- a/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/CreateTableCommand.java
+++ b/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/CreateTableCommand.java
@@ -43,7 +43,6 @@ import org.apache.ignite.internal.catalog.descriptors.CatalogSchemaDescriptor;
 import org.apache.ignite.internal.catalog.descriptors.CatalogSortedIndexDescriptor;
 import org.apache.ignite.internal.catalog.descriptors.CatalogTableDescriptor;
 import org.apache.ignite.internal.catalog.descriptors.CatalogZoneDescriptor;
-import org.apache.ignite.internal.catalog.storage.MakeIndexAvailableEntry;
 import org.apache.ignite.internal.catalog.storage.NewIndexEntry;
 import org.apache.ignite.internal.catalog.storage.NewTableEntry;
 import org.apache.ignite.internal.catalog.storage.ObjectIdGenUpdateEntry;
@@ -147,12 +146,11 @@ public class CreateTableCommand extends AbstractTableCommand {
 
         ensureNoTableIndexOrSysViewExistsWithGivenName(schema, indexName);
 
-        CatalogIndexDescriptor pkIndex = createIndexDescriptor(indexName, pkIndexId, tableId);
+        CatalogIndexDescriptor pkIndex = createPkIndexDescriptor(indexName, pkIndexId, tableId);
 
         return List.of(
                 new NewTableEntry(table),
                 new NewIndexEntry(pkIndex),
-                new MakeIndexAvailableEntry(pkIndexId),
                 new ObjectIdGenUpdateEntry(id - catalog.objectIdGenState())
         );
     }
@@ -203,7 +201,7 @@ public class CreateTableCommand extends AbstractTableCommand {
         }
     }
 
-    private CatalogIndexDescriptor createIndexDescriptor(String indexName, int pkIndexId, int tableId) {
+    private CatalogIndexDescriptor createPkIndexDescriptor(String indexName, int pkIndexId, int tableId) {
         CatalogIndexDescriptor pkIndex;
 
         if (primaryKey instanceof TableSortedPrimaryKey) {
@@ -223,7 +221,8 @@ public class CreateTableCommand extends AbstractTableCommand {
                     tableId,
                     true,
                     AVAILABLE,
-                    indexColumns
+                    indexColumns,
+                    true
             );
         } else if (primaryKey instanceof TableHashPrimaryKey) {
             TableHashPrimaryKey hashPrimaryKey = (TableHashPrimaryKey) primaryKey;
@@ -233,7 +232,8 @@ public class CreateTableCommand extends AbstractTableCommand {
                     tableId,
                     true,
                     AVAILABLE,
-                    hashPrimaryKey.columns()
+                    hashPrimaryKey.columns(),
+                    true
             );
         } else {
             throw new IllegalArgumentException("Unexpected primary key type: " + primaryKey);
diff --git a/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/descriptors/CatalogHashIndexDescriptor.java b/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/descriptors/CatalogHashIndexDescriptor.java
index b77d49430d..4df0614392 100644
--- a/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/descriptors/CatalogHashIndexDescriptor.java
+++ b/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/descriptors/CatalogHashIndexDescriptor.java
@@ -44,10 +44,11 @@ public class CatalogHashIndexDescriptor extends CatalogIndexDescriptor {
      * @param tableId Id of the table index belongs to.
      * @param unique Unique flag.
      * @param columns A list of indexed columns. Must not contains duplicates.
+     *
      * @throws IllegalArgumentException If columns list contains duplicates.
      */
     public CatalogHashIndexDescriptor(int id, String name, int tableId, boolean unique, List<String> columns) {
-        this(id, name, tableId, unique, CatalogIndexStatus.REGISTERED, columns, INITIAL_CAUSALITY_TOKEN);
+        this(id, name, tableId, unique, CatalogIndexStatus.REGISTERED, columns, INITIAL_CAUSALITY_TOKEN, false);
     }
 
     /**
@@ -59,6 +60,8 @@ public class CatalogHashIndexDescriptor extends CatalogIndexDescriptor {
      * @param unique Unique flag.
      * @param status Index status.
      * @param columns A list of indexed columns. Must not contains duplicates.
+     * @param isCreatedWithTable Flag indicating that this index has been created at the same time as its table.
+     *
      * @throws IllegalArgumentException If columns list contains duplicates.
      */
     public CatalogHashIndexDescriptor(
@@ -67,9 +70,10 @@ public class CatalogHashIndexDescriptor extends CatalogIndexDescriptor {
             int tableId,
             boolean unique,
             CatalogIndexStatus status,
-            List<String> columns
+            List<String> columns,
+            boolean isCreatedWithTable
     ) {
-        this(id, name, tableId, unique, status, columns, INITIAL_CAUSALITY_TOKEN);
+        this(id, name, tableId, unique, status, columns, INITIAL_CAUSALITY_TOKEN, isCreatedWithTable);
     }
 
     /**
@@ -82,6 +86,8 @@ public class CatalogHashIndexDescriptor extends CatalogIndexDescriptor {
      * @param status Index status.
      * @param columns A list of indexed columns. Must not contains duplicates.
      * @param causalityToken Token of the update of the descriptor.
+     * @param isCreatedWithTable Flag indicating that this index has been created at the same time as its table.
+     *
      * @throws IllegalArgumentException If columns list contains duplicates.
      */
     private CatalogHashIndexDescriptor(
@@ -91,9 +97,10 @@ public class CatalogHashIndexDescriptor extends CatalogIndexDescriptor {
             boolean unique,
             CatalogIndexStatus status,
             List<String> columns,
-            long causalityToken
+            long causalityToken,
+            boolean isCreatedWithTable
     ) {
-        super(CatalogIndexDescriptorType.HASH, id, name, tableId, unique, status, causalityToken);
+        super(CatalogIndexDescriptorType.HASH, id, name, tableId, unique, status, causalityToken, isCreatedWithTable);
 
         this.columns = List.copyOf(Objects.requireNonNull(columns, "columns"));
     }
@@ -117,9 +124,10 @@ public class CatalogHashIndexDescriptor extends CatalogIndexDescriptor {
             int tableId = input.readInt();
             boolean unique = input.readBoolean();
             CatalogIndexStatus status = CatalogIndexStatus.forId(input.readByte());
+            boolean isCreatedWithTable = input.readBoolean();
             List<String> columns = readStringCollection(input, ArrayList::new);
 
-            return new CatalogHashIndexDescriptor(id, name, tableId, unique, status, columns, updateToken);
+            return new CatalogHashIndexDescriptor(id, name, tableId, unique, status, columns, updateToken, isCreatedWithTable);
         }
 
         @Override
@@ -130,6 +138,7 @@ public class CatalogHashIndexDescriptor extends CatalogIndexDescriptor {
             output.writeInt(descriptor.tableId());
             output.writeBoolean(descriptor.unique());
             output.writeByte(descriptor.status().id());
+            output.writeBoolean(descriptor.isCreatedWithTable());
             writeStringCollection(descriptor.columns(), output);
         }
     }
diff --git a/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/descriptors/CatalogIndexDescriptor.java b/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/descriptors/CatalogIndexDescriptor.java
index 38c6aaa6b2..7e1da7bac2 100644
--- a/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/descriptors/CatalogIndexDescriptor.java
+++ b/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/descriptors/CatalogIndexDescriptor.java
@@ -34,6 +34,18 @@ public abstract class CatalogIndexDescriptor extends CatalogObjectDescriptor {
     /** Index descriptor type. */
     private final CatalogIndexDescriptorType indexType;
 
+    /**
+     * Flag indicating that this index has been created at the same time as its table.
+     *
+     * <p>This flag is used by the underlying index storage to determine whether the index should be built in the background (in case
+     * this flag {@code false}) or it will be filled with data alongside its table (in case this flag is {@code true}).
+     *
+     * <p>Unlike the {@code status} field, which may change during index lifecycle, the value of this field is constant, which allows to
+     * handle a case when an AVAILABLE index is being rebalanced to a new node and the new storage can decide if it needs to be built
+     * or not.
+     */
+    private final boolean createdWithTable;
+
     CatalogIndexDescriptor(
             CatalogIndexDescriptorType indexType,
             int id,
@@ -41,13 +53,15 @@ public abstract class CatalogIndexDescriptor extends CatalogObjectDescriptor {
             int tableId,
             boolean unique,
             CatalogIndexStatus status,
-            long causalityToken
+            long causalityToken,
+            boolean createdWithTable
     ) {
         super(id, Type.INDEX, name, causalityToken);
         this.indexType = indexType;
         this.tableId = tableId;
         this.unique = unique;
         this.status = Objects.requireNonNull(status, "status");
+        this.createdWithTable = createdWithTable;
     }
 
     /** Gets table ID. */
@@ -70,6 +84,11 @@ public abstract class CatalogIndexDescriptor extends CatalogObjectDescriptor {
         return indexType;
     }
 
+    /** Returns a flag indicating that this index has been created at the same time as its table. */
+    public boolean isCreatedWithTable() {
+        return createdWithTable;
+    }
+
     @Override
     public String toString() {
         return S.toString(CatalogIndexDescriptor.class, this, super.toString());
diff --git a/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/descriptors/CatalogSortedIndexDescriptor.java b/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/descriptors/CatalogSortedIndexDescriptor.java
index d535ac9679..aac9af8d06 100644
--- a/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/descriptors/CatalogSortedIndexDescriptor.java
+++ b/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/descriptors/CatalogSortedIndexDescriptor.java
@@ -45,6 +45,7 @@ public class CatalogSortedIndexDescriptor extends CatalogIndexDescriptor {
      * @param tableId Id of the table index belongs to.
      * @param unique Unique flag.
      * @param columns A list of columns descriptors.
+     *
      * @throws IllegalArgumentException If columns list contains duplicates or columns size doesn't match the collations size.
      */
     public CatalogSortedIndexDescriptor(
@@ -54,7 +55,7 @@ public class CatalogSortedIndexDescriptor extends CatalogIndexDescriptor {
             boolean unique,
             List<CatalogIndexColumnDescriptor> columns
     ) {
-        this(id, name, tableId, unique, REGISTERED, columns);
+        this(id, name, tableId, unique, REGISTERED, columns, false);
     }
 
     /**
@@ -66,6 +67,8 @@ public class CatalogSortedIndexDescriptor extends CatalogIndexDescriptor {
      * @param unique Unique flag.
      * @param status Index status.
      * @param columns A list of columns descriptors.
+     * @param isCreatedWithTable Flag indicating that this index has been created at the same time as its table.
+     *
      * @throws IllegalArgumentException If columns list contains duplicates or columns size doesn't match the collations size.
      */
     public CatalogSortedIndexDescriptor(
@@ -74,9 +77,10 @@ public class CatalogSortedIndexDescriptor extends CatalogIndexDescriptor {
             int tableId,
             boolean unique,
             CatalogIndexStatus status,
-            List<CatalogIndexColumnDescriptor> columns
+            List<CatalogIndexColumnDescriptor> columns,
+            boolean isCreatedWithTable
     ) {
-        this(id, name, tableId, unique, status, columns, INITIAL_CAUSALITY_TOKEN);
+        this(id, name, tableId, unique, status, columns, INITIAL_CAUSALITY_TOKEN, isCreatedWithTable);
     }
 
     /**
@@ -89,6 +93,8 @@ public class CatalogSortedIndexDescriptor extends CatalogIndexDescriptor {
      * @param status Index status.
      * @param columns A list of columns descriptors.
      * @param causalityToken Token of the update of the descriptor.
+     * @param isCreatedWithTable Flag indicating that this index has been created at the same time as its table.
+     *
      * @throws IllegalArgumentException If columns list contains duplicates or columns size doesn't match the collations size.
      */
     private CatalogSortedIndexDescriptor(
@@ -98,9 +104,10 @@ public class CatalogSortedIndexDescriptor extends CatalogIndexDescriptor {
             boolean unique,
             CatalogIndexStatus status,
             List<CatalogIndexColumnDescriptor> columns,
-            long causalityToken
+            long causalityToken,
+            boolean isCreatedWithTable
     ) {
-        super(CatalogIndexDescriptorType.SORTED, id, name, tableId, unique, status, causalityToken);
+        super(CatalogIndexDescriptorType.SORTED, id, name, tableId, unique, status, causalityToken, isCreatedWithTable);
 
         this.columns = Objects.requireNonNull(columns, "columns");
     }
@@ -124,9 +131,10 @@ public class CatalogSortedIndexDescriptor extends CatalogIndexDescriptor {
             int tableId = input.readInt();
             boolean unique = input.readBoolean();
             CatalogIndexStatus status = CatalogIndexStatus.forId(input.readByte());
+            boolean isCreatedWithTable = input.readBoolean();
             List<CatalogIndexColumnDescriptor> columns = readList(CatalogIndexColumnDescriptor.SERIALIZER, input);
 
-            return new CatalogSortedIndexDescriptor(id, name, tableId, unique, status, columns, updateToken);
+            return new CatalogSortedIndexDescriptor(id, name, tableId, unique, status, columns, updateToken, isCreatedWithTable);
         }
 
         @Override
@@ -137,6 +145,7 @@ public class CatalogSortedIndexDescriptor extends CatalogIndexDescriptor {
             output.writeInt(descriptor.tableId());
             output.writeBoolean(descriptor.unique());
             output.writeByte(descriptor.status().id());
+            output.writeBoolean(descriptor.isCreatedWithTable());
             writeList(descriptor.columns(), CatalogIndexColumnDescriptor.SERIALIZER, output);
         }
     }
diff --git a/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/storage/AbstractChangeIndexStatusEntry.java b/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/storage/AbstractChangeIndexStatusEntry.java
index 01aaf56234..44d169e259 100644
--- a/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/storage/AbstractChangeIndexStatusEntry.java
+++ b/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/storage/AbstractChangeIndexStatusEntry.java
@@ -97,7 +97,8 @@ abstract class AbstractChangeIndexStatusEntry implements UpdateEntry {
                 index.tableId(),
                 index.unique(),
                 newStatus,
-                index.columns()
+                index.columns(),
+                index.isCreatedWithTable()
         );
     }
 
@@ -108,7 +109,8 @@ abstract class AbstractChangeIndexStatusEntry implements UpdateEntry {
                 index.tableId(),
                 index.unique(),
                 newStatus,
-                index.columns()
+                index.columns(),
+                index.isCreatedWithTable()
         );
     }
 }
diff --git a/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/storage/RenameIndexEntry.java b/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/storage/RenameIndexEntry.java
index c1e36aa70f..803cbd8c30 100644
--- a/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/storage/RenameIndexEntry.java
+++ b/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/storage/RenameIndexEntry.java
@@ -100,7 +100,8 @@ public class RenameIndexEntry implements UpdateEntry {
                 index.tableId(),
                 index.unique(),
                 index.status(),
-                index.columns()
+                index.columns(),
+                index.isCreatedWithTable()
         );
     }
 
@@ -111,7 +112,8 @@ public class RenameIndexEntry implements UpdateEntry {
                 index.tableId(),
                 index.unique(),
                 index.status(),
-                index.columns()
+                index.columns(),
+                index.isCreatedWithTable()
         );
     }
 
diff --git a/modules/catalog/src/test/java/org/apache/ignite/internal/catalog/CatalogIndexTest.java b/modules/catalog/src/test/java/org/apache/ignite/internal/catalog/CatalogIndexTest.java
index 07dc76930e..27ce986cca 100644
--- a/modules/catalog/src/test/java/org/apache/ignite/internal/catalog/CatalogIndexTest.java
+++ b/modules/catalog/src/test/java/org/apache/ignite/internal/catalog/CatalogIndexTest.java
@@ -483,7 +483,6 @@ public class CatalogIndexTest extends BaseCatalogManagerTest {
         // Create table with PK index.
         assertThat(manager.execute(simpleTable(TABLE_NAME)), willCompleteSuccessfully());
         verify(eventListener).notify(any(CreateIndexEventParameters.class));
-        verify(eventListener).notify(any(MakeIndexAvailableEventParameters.class));
 
         verifyNoMoreInteractions(eventListener);
         clearInvocations(eventListener);
@@ -601,24 +600,6 @@ public class CatalogIndexTest extends BaseCatalogManagerTest {
         assertThat(fireEventFuture, willCompleteSuccessfully());
     }
 
-    @Test
-    public void testPkAvailableIndexEvent() {
-        String tableName = TABLE_NAME + "_new";
-
-        var fireEventFuture = new CompletableFuture<Void>();
-
-        manager.listen(CatalogEvent.INDEX_AVAILABLE, fromConsumer(fireEventFuture, (MakeIndexAvailableEventParameters parameters) -> {
-            CatalogIndexDescriptor catalogIndexDescriptor = manager.index(parameters.indexId(), parameters.catalogVersion());
-
-            assertNotNull(catalogIndexDescriptor);
-            assertEquals(pkIndexName(tableName), catalogIndexDescriptor.name());
-        }));
-
-        createSomeTable(tableName);
-
-        assertThat(fireEventFuture, willCompleteSuccessfully());
-    }
-
     @Test
     public void testPkAvailableOnCreateIndexEvent() {
         var fireEventFuture = new CompletableFuture<Void>();
diff --git a/modules/catalog/src/test/java/org/apache/ignite/internal/catalog/commands/AbstractChangeIndexStatusCommandValidationTest.java b/modules/catalog/src/test/java/org/apache/ignite/internal/catalog/commands/AbstractChangeIndexStatusCommandValidationTest.java
index f52a73dddf..043f45da72 100644
--- a/modules/catalog/src/test/java/org/apache/ignite/internal/catalog/commands/AbstractChangeIndexStatusCommandValidationTest.java
+++ b/modules/catalog/src/test/java/org/apache/ignite/internal/catalog/commands/AbstractChangeIndexStatusCommandValidationTest.java
@@ -87,7 +87,8 @@ public abstract class AbstractChangeIndexStatusCommandValidationTest extends Abs
                                 tableId,
                                 false,
                                 invalidPreviousIndexStatus,
-                                List.of(columnName)
+                                List.of(columnName),
+                                false
                         )
                 },
                 new CatalogSystemViewDescriptor[]{}
diff --git a/modules/catalog/src/test/java/org/apache/ignite/internal/catalog/commands/CatalogUtilsTest.java b/modules/catalog/src/test/java/org/apache/ignite/internal/catalog/commands/CatalogUtilsTest.java
index 8feb82909c..15d943cb20 100644
--- a/modules/catalog/src/test/java/org/apache/ignite/internal/catalog/commands/CatalogUtilsTest.java
+++ b/modules/catalog/src/test/java/org/apache/ignite/internal/catalog/commands/CatalogUtilsTest.java
@@ -141,7 +141,8 @@ public class CatalogUtilsTest extends BaseIgniteAbstractTest {
                 fooIndex.tableId(),
                 fooIndex.unique(),
                 fooIndex.status(),
-                fooIndex.columns()
+                fooIndex.columns(),
+                fooIndex.isCreatedWithTable()
         );
 
         CatalogSchemaDescriptor updatedSchema = replaceIndex(schema, bazIndex);
diff --git a/modules/catalog/src/test/java/org/apache/ignite/internal/catalog/storage/CatalogEntrySerializationTest.java b/modules/catalog/src/test/java/org/apache/ignite/internal/catalog/storage/CatalogEntrySerializationTest.java
index 0585f5e54a..5f0d30a6d9 100644
--- a/modules/catalog/src/test/java/org/apache/ignite/internal/catalog/storage/CatalogEntrySerializationTest.java
+++ b/modules/catalog/src/test/java/org/apache/ignite/internal/catalog/storage/CatalogEntrySerializationTest.java
@@ -446,12 +446,12 @@ public class CatalogEntrySerializationTest extends BaseIgniteAbstractTest {
         CatalogIndexColumnDescriptor idxCol4 = new CatalogIndexColumnDescriptor("C4", CatalogColumnCollation.ASC_NULLS_LAST);
 
         return new CatalogSortedIndexDescriptor(
-                1, name, 12, false, CatalogIndexStatus.AVAILABLE, List.of(idxCol1, idxCol2, idxCol3, idxCol4));
+                1, name, 12, false, CatalogIndexStatus.AVAILABLE, List.of(idxCol1, idxCol2, idxCol3, idxCol4), true);
     }
 
     private static CatalogHashIndexDescriptor newHashIndexDescriptor(String name) {
         return new CatalogHashIndexDescriptor(
-                1, name, 12, true, CatalogIndexStatus.REGISTERED, List.of("C1", "C2"));
+                1, name, 12, true, CatalogIndexStatus.REGISTERED, List.of("C1", "C2"), true);
     }
 
     private static CatalogTableDescriptor newTableDescriptor(String name, List<CatalogTableColumnDescriptor> columns) {
diff --git a/modules/index/src/main/java/org/apache/ignite/internal/index/IndexBuildController.java b/modules/index/src/main/java/org/apache/ignite/internal/index/IndexBuildController.java
index a75028a63b..218679b9b9 100644
--- a/modules/index/src/main/java/org/apache/ignite/internal/index/IndexBuildController.java
+++ b/modules/index/src/main/java/org/apache/ignite/internal/index/IndexBuildController.java
@@ -365,7 +365,7 @@ class IndexBuildController implements ManuallyCloseable {
         return replicaMeta.getStartTime().longValue();
     }
 
-    private static @Nullable MvPartitionStorage mvPartitionStorage(MvTableStorage mvTableStorage, TablePartitionId replicaId) {
+    private static MvPartitionStorage mvPartitionStorage(MvTableStorage mvTableStorage, TablePartitionId replicaId) {
         MvPartitionStorage mvPartition = mvTableStorage.getMvPartition(replicaId.partitionId());
 
         assert mvPartition != null : "Partition storage is missing, replicaId=" + replicaId;
diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestBuilders.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestBuilders.java
index 614c968941..8384c71362 100644
--- a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestBuilders.java
+++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestBuilders.java
@@ -17,13 +17,17 @@
 
 package org.apache.ignite.internal.sql.engine.framework;
 
+import static java.util.stream.Collectors.toCollection;
 import static org.apache.ignite.internal.lang.IgniteStringFormatter.format;
 import static org.apache.ignite.internal.sql.engine.exec.ExecutionServiceImplTest.PLANNING_THREAD_COUNT;
 import static org.apache.ignite.internal.sql.engine.exec.ExecutionServiceImplTest.PLANNING_TIMEOUT;
 import static org.apache.ignite.internal.testframework.IgniteTestUtils.await;
 import static org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willCompleteSuccessfully;
 import static org.apache.ignite.internal.util.CollectionUtils.nullOrEmpty;
+import static org.apache.ignite.internal.util.CompletableFutures.trueCompletedFuture;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -38,7 +42,7 @@ import java.util.Objects;
 import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Flow.Publisher;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Consumer;
@@ -66,6 +70,7 @@ import org.apache.ignite.internal.catalog.commands.TableHashPrimaryKey;
 import org.apache.ignite.internal.catalog.commands.TablePrimaryKey;
 import org.apache.ignite.internal.catalog.descriptors.CatalogColumnCollation;
 import org.apache.ignite.internal.catalog.descriptors.CatalogIndexDescriptor;
+import org.apache.ignite.internal.catalog.descriptors.CatalogIndexStatus;
 import org.apache.ignite.internal.catalog.events.CatalogEvent;
 import org.apache.ignite.internal.catalog.events.CreateIndexEventParameters;
 import org.apache.ignite.internal.catalog.events.MakeIndexAvailableEventParameters;
@@ -772,48 +777,67 @@ public class TestBuilders {
                     .flatMap(builder -> builder.build().stream())
                     .collect(Collectors.toList());
 
-            CompletableFuture<Boolean> indicesReadyFut = new CompletableFuture<>();
-            CopyOnWriteArraySet<Integer> initialIndices = new CopyOnWriteArraySet<>();
+            CompletableFuture<Boolean> indicesReadyFut;
 
             // Make indices registered via builder API available on startup.
             if (!tableBuilders.isEmpty()) {
-                Consumer<MakeIndexAvailableEventParameters> indexAvailableHandler = params -> {
-                    initialIndices.remove(params.indexId());
+                Set<String> initialIndices = tableBuilders.stream()
+                        .flatMap(builder -> builder.indexBuilders.stream())
+                        .map(builder -> builder.name)
+                        .collect(toCollection(ConcurrentHashMap::newKeySet));
 
-                    if (initialIndices.isEmpty()) {
-                        indicesReadyFut.complete(true);
-                    }
-                };
+                if (initialIndices.isEmpty()) {
+                    indicesReadyFut = trueCompletedFuture();
+                } else {
+                    indicesReadyFut = new CompletableFuture<>();
+
+                    Consumer<MakeIndexAvailableEventParameters> indexAvailableHandler = params -> {
+                        CatalogIndexDescriptor index = catalogManager.index(params.indexId(), params.catalogVersion());
+
+                        assertThat(index, is(notNullValue()));
+
+                        initialIndices.remove(index.name());
+
+                        if (initialIndices.isEmpty()) {
+                            indicesReadyFut.complete(true);
+                        }
+                    };
 
-                EventListener<MakeIndexAvailableEventParameters> listener = EventListener.fromConsumer(indexAvailableHandler);
-                catalogManager.listen(CatalogEvent.INDEX_AVAILABLE, listener);
+                    EventListener<MakeIndexAvailableEventParameters> listener = EventListener.fromConsumer(indexAvailableHandler);
+                    catalogManager.listen(CatalogEvent.INDEX_AVAILABLE, listener);
 
-                // Remove listener, when all indices become available.
-                indicesReadyFut.whenComplete((r, t) -> {
-                    catalogManager.removeListener(CatalogEvent.INDEX_AVAILABLE, listener);
-                });
+                    // Remove listener, when all indices become available.
+                    indicesReadyFut.whenComplete((r, t) -> {
+                        catalogManager.removeListener(CatalogEvent.INDEX_AVAILABLE, listener);
+                    });
+                }
             } else {
-                indicesReadyFut.complete(true);
+                indicesReadyFut = trueCompletedFuture();
             }
 
             // Every time an index is created add `start building `and `make available` commands
             // to make that index accessible to the SQL engine.
             Consumer<CreateIndexEventParameters> createIndexHandler = (params) -> {
                 CatalogIndexDescriptor index = params.indexDescriptor();
+
+                if (index.status() == CatalogIndexStatus.AVAILABLE) {
+                    return;
+                }
+
                 int indexId = index.id();
 
                 CatalogCommand startBuildIndexCommand = StartBuildingIndexCommand.builder().indexId(indexId).build();
                 CatalogCommand makeIndexAvailableCommand = MakeIndexAvailableCommand.builder().indexId(indexId).build();
 
-                // Collect initial indexes only if catalog init future has not completed.
-                if (!indicesReadyFut.isDone()) {
-                    initialIndices.add(indexId);
-                }
-
                 LOG.info("Index has been created. Sending commands to make index available. id: {}, name: {}, status: {}",
                         indexId, index.name(), index.status());
 
-                catalogManager.execute(List.of(startBuildIndexCommand, makeIndexAvailableCommand));
+                catalogManager.execute(List.of(startBuildIndexCommand, makeIndexAvailableCommand))
+                        .whenComplete((v, e) -> {
+                            if (e != null) {
+                                LOG.error("Catalog command execution error", e);
+                            }
+                        });
             };
             catalogManager.listen(CatalogEvent.INDEX_CREATE, EventListener.fromConsumer(createIndexHandler));
 
diff --git a/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/StorageHashIndexDescriptor.java b/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/StorageHashIndexDescriptor.java
index c27cf389eb..c37a2e453c 100644
--- a/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/StorageHashIndexDescriptor.java
+++ b/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/StorageHashIndexDescriptor.java
@@ -84,7 +84,7 @@ public class StorageHashIndexDescriptor implements StorageIndexDescriptor {
     @IgniteToStringInclude
     private final List<StorageHashIndexColumnDescriptor> columns;
 
-    private final boolean pk;
+    private final boolean mustBeBuilt;
 
     /**
      * Constructor.
@@ -93,7 +93,7 @@ public class StorageHashIndexDescriptor implements StorageIndexDescriptor {
      * @param index Catalog index descriptor.
      */
     public StorageHashIndexDescriptor(CatalogTableDescriptor table, CatalogHashIndexDescriptor index) {
-        this(index.id(), extractIndexColumnsConfiguration(table, index), table.primaryKeyIndexId() == index.id());
+        this(index.id(), extractIndexColumnsConfiguration(table, index), !index.isCreatedWithTable());
     }
 
     /**
@@ -101,12 +101,12 @@ public class StorageHashIndexDescriptor implements StorageIndexDescriptor {
      *
      * @param indexId Index id.
      * @param columns Columns descriptors.
-     * @param pk Primary index flag.
+     * @param mustBeBuilt Flag indicating that this index must be built by a background task.
      */
-    public StorageHashIndexDescriptor(int indexId, List<StorageHashIndexColumnDescriptor> columns, boolean pk) {
+    public StorageHashIndexDescriptor(int indexId, List<StorageHashIndexColumnDescriptor> columns, boolean mustBeBuilt) {
         this.id = indexId;
         this.columns = columns;
-        this.pk = pk;
+        this.mustBeBuilt = mustBeBuilt;
     }
 
     @Override
@@ -120,8 +120,8 @@ public class StorageHashIndexDescriptor implements StorageIndexDescriptor {
     }
 
     @Override
-    public boolean isPk() {
-        return pk;
+    public boolean mustBeBuilt() {
+        return mustBeBuilt;
     }
 
     @Override
diff --git a/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/StorageIndexDescriptor.java b/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/StorageIndexDescriptor.java
index ccc0b488b4..31ab1028cd 100644
--- a/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/StorageIndexDescriptor.java
+++ b/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/StorageIndexDescriptor.java
@@ -60,8 +60,13 @@ public interface StorageIndexDescriptor {
      */
     List<? extends StorageColumnDescriptor> columns();
 
-    /** Returns {@code true} for the primary index. */
-    boolean isPk();
+    /**
+     * Returns {@code true} if this index must be built by a background task and {@code false} if this index will be built when inserting
+     * data into its table.
+     *
+     * <p>The value of this field is deduced based on the {@link CatalogIndexDescriptor#isCreatedWithTable} flag value.
+     */
+    boolean mustBeBuilt();
 
     /**
      * Creates an index description based on the catalog descriptors.
diff --git a/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/StorageSortedIndexDescriptor.java b/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/StorageSortedIndexDescriptor.java
index 3da5f1ec45..cca8568adf 100644
--- a/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/StorageSortedIndexDescriptor.java
+++ b/modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/StorageSortedIndexDescriptor.java
@@ -102,7 +102,7 @@ public class StorageSortedIndexDescriptor implements StorageIndexDescriptor {
 
     private final BinaryTupleSchema binaryTupleSchema;
 
-    private final boolean pk;
+    private final boolean mustBeBuilt;
 
     /**
      * Constructor.
@@ -111,7 +111,7 @@ public class StorageSortedIndexDescriptor implements StorageIndexDescriptor {
      * @param index Catalog index descriptor.
      */
     public StorageSortedIndexDescriptor(CatalogTableDescriptor table, CatalogSortedIndexDescriptor index) {
-        this(index.id(), extractIndexColumnsConfiguration(table, index), table.primaryKeyIndexId() == index.id());
+        this(index.id(), extractIndexColumnsConfiguration(table, index), !index.isCreatedWithTable());
     }
 
     /**
@@ -119,13 +119,13 @@ public class StorageSortedIndexDescriptor implements StorageIndexDescriptor {
      *
      * @param indexId Index ID.
      * @param columnDescriptors Column descriptors.
-     * @param pk Primary index flag.
+     * @param mustBeBuilt Flag indicating that this index must be built by a background task.
      */
-    public StorageSortedIndexDescriptor(int indexId, List<StorageSortedIndexColumnDescriptor> columnDescriptors, boolean pk) {
+    public StorageSortedIndexDescriptor(int indexId, List<StorageSortedIndexColumnDescriptor> columnDescriptors, boolean mustBeBuilt) {
         this.id = indexId;
         this.columns = List.copyOf(columnDescriptors);
         this.binaryTupleSchema = createSchema(columns);
-        this.pk = pk;
+        this.mustBeBuilt = mustBeBuilt;
     }
 
     private static BinaryTupleSchema createSchema(List<StorageSortedIndexColumnDescriptor> columns) {
@@ -147,8 +147,8 @@ public class StorageSortedIndexDescriptor implements StorageIndexDescriptor {
     }
 
     @Override
-    public boolean isPk() {
-        return pk;
+    public boolean mustBeBuilt() {
+        return mustBeBuilt;
     }
 
     @Override
diff --git a/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/index/StorageIndexDescriptorTest.java b/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/index/StorageIndexDescriptorTest.java
deleted file mode 100644
index 48a4a228fb..0000000000
--- a/modules/storage-api/src/test/java/org/apache/ignite/internal/storage/index/StorageIndexDescriptorTest.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.storage.index;
-
-import static org.apache.ignite.internal.catalog.CatalogService.DEFAULT_STORAGE_PROFILE;
-import static org.apache.ignite.internal.catalog.descriptors.CatalogColumnCollation.ASC_NULLS_FIRST;
-import static org.apache.ignite.internal.catalog.descriptors.CatalogIndexStatus.AVAILABLE;
-import static org.apache.ignite.sql.ColumnType.INT32;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-import java.util.List;
-import java.util.concurrent.atomic.AtomicInteger;
-import org.apache.ignite.internal.catalog.descriptors.CatalogHashIndexDescriptor;
-import org.apache.ignite.internal.catalog.descriptors.CatalogIndexColumnDescriptor;
-import org.apache.ignite.internal.catalog.descriptors.CatalogSortedIndexDescriptor;
-import org.apache.ignite.internal.catalog.descriptors.CatalogTableColumnDescriptor;
-import org.apache.ignite.internal.catalog.descriptors.CatalogTableDescriptor;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.ValueSource;
-
-/** For {@link StorageHashIndexDescriptor} testing. */
-public class StorageIndexDescriptorTest {
-    private static final String COLUMN_NAME = "INT_VAL";
-
-    private final AtomicInteger nextId = new AtomicInteger();
-
-    @ParameterizedTest
-    @ValueSource(booleans = {true, false})
-    void testIsPk(boolean pk) {
-        CatalogTableDescriptor tableDescriptor = createTableDescriptor();
-
-        int tableId = tableDescriptor.id();
-        int indexId = pk ? tableDescriptor.primaryKeyIndexId() : nextId.getAndIncrement();
-
-        CatalogHashIndexDescriptor pkHashIndexDescriptor = createHashIndexDescriptor(indexId, tableId);
-        CatalogSortedIndexDescriptor pkSortedIndexDescriptor = createSortedIndexDescriptor(indexId, tableId);
-
-        assertEquals(pk, StorageIndexDescriptor.create(tableDescriptor, pkHashIndexDescriptor).isPk());
-        assertEquals(pk, StorageIndexDescriptor.create(tableDescriptor, pkSortedIndexDescriptor).isPk());
-    }
-
-    private CatalogTableDescriptor createTableDescriptor() {
-        return new CatalogTableDescriptor(
-                nextId.getAndIncrement(),
-                nextId.getAndIncrement(),
-                nextId.getAndIncrement(),
-                "TABLE_NAME",
-                nextId.getAndIncrement(),
-                List.of(new CatalogTableColumnDescriptor(COLUMN_NAME, INT32, false, 0, 0, 0, null)),
-                List.of(COLUMN_NAME),
-                List.of(COLUMN_NAME),
-                DEFAULT_STORAGE_PROFILE
-        );
-    }
-
-    private static CatalogHashIndexDescriptor createHashIndexDescriptor(int indexId, int tableId) {
-        return new CatalogHashIndexDescriptor(
-                indexId,
-                "INDEX_NAME_" + indexId,
-                tableId,
-                false,
-                AVAILABLE,
-                List.of(COLUMN_NAME)
-        );
-    }
-
-    private static CatalogSortedIndexDescriptor createSortedIndexDescriptor(int indexId, int tableId) {
-        return new CatalogSortedIndexDescriptor(
-                indexId,
-                "INDEX_NAME_" + indexId,
-                tableId,
-                false,
-                AVAILABLE,
-                List.of(new CatalogIndexColumnDescriptor(COLUMN_NAME, ASC_NULLS_FIRST))
-        );
-    }
-}
diff --git a/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/AbstractMvTableStorageTest.java b/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/AbstractMvTableStorageTest.java
index 5f854e1176..9ee0693184 100644
--- a/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/AbstractMvTableStorageTest.java
+++ b/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/AbstractMvTableStorageTest.java
@@ -367,7 +367,8 @@ public abstract class AbstractMvTableStorageTest extends BaseMvStoragesTest {
                 catalogTableDescriptor.id(),
                 false,
                 AVAILABLE,
-                List.of(new CatalogIndexColumnDescriptor("STRKEY", ASC_NULLS_LAST))
+                List.of(new CatalogIndexColumnDescriptor("STRKEY", ASC_NULLS_LAST)),
+                true
         );
 
         var catalogSortedIndex2 = new CatalogSortedIndexDescriptor(
@@ -376,7 +377,8 @@ public abstract class AbstractMvTableStorageTest extends BaseMvStoragesTest {
                 catalogTableDescriptor.id(),
                 false,
                 AVAILABLE,
-                List.of(new CatalogIndexColumnDescriptor("STRKEY", ASC_NULLS_LAST))
+                List.of(new CatalogIndexColumnDescriptor("STRKEY", ASC_NULLS_LAST)),
+                true
         );
 
         var sortedIndexDescriptor1 = new StorageSortedIndexDescriptor(catalogTableDescriptor, catalogSortedIndex1);
@@ -418,7 +420,8 @@ public abstract class AbstractMvTableStorageTest extends BaseMvStoragesTest {
                 catalogTableDescriptor.id(),
                 true,
                 AVAILABLE,
-                List.of("STRKEY")
+                List.of("STRKEY"),
+                true
         );
 
         var catalogHashIndex2 = new CatalogHashIndexDescriptor(
@@ -427,7 +430,8 @@ public abstract class AbstractMvTableStorageTest extends BaseMvStoragesTest {
                 catalogTableDescriptor.id(),
                 true,
                 AVAILABLE,
-                List.of("STRKEY")
+                List.of("STRKEY"),
+                true
         );
 
         var hashIndexDescriptor1 = new StorageHashIndexDescriptor(catalogTableDescriptor, catalogHashIndex1);
@@ -1054,7 +1058,8 @@ public abstract class AbstractMvTableStorageTest extends BaseMvStoragesTest {
                 tableId,
                 false,
                 AVAILABLE,
-                List.of(new CatalogIndexColumnDescriptor("STRKEY", ASC_NULLS_LAST))
+                List.of(new CatalogIndexColumnDescriptor("STRKEY", ASC_NULLS_LAST)),
+                false
         );
 
         CatalogHashIndexDescriptor hashIndex = new CatalogHashIndexDescriptor(
@@ -1063,7 +1068,8 @@ public abstract class AbstractMvTableStorageTest extends BaseMvStoragesTest {
                 tableId,
                 true,
                 AVAILABLE,
-                List.of("STRKEY")
+                List.of("STRKEY"),
+                false
         );
 
         CatalogIndexDescriptor pkIndex = new CatalogHashIndexDescriptor(
@@ -1072,7 +1078,8 @@ public abstract class AbstractMvTableStorageTest extends BaseMvStoragesTest {
                 tableId,
                 true,
                 AVAILABLE,
-                List.of(pkColumnName)
+                List.of(pkColumnName),
+                true
         );
 
         when(catalogService.table(eq(TABLE_NAME), anyLong())).thenReturn(tableDescriptor);
@@ -1581,7 +1588,7 @@ public abstract class AbstractMvTableStorageTest extends BaseMvStoragesTest {
         checkCursorAfterStartRebalance(sortedIndexStorageTolerantScanCursor);
     }
 
-    @SuppressWarnings({"resource", "deprecation"})
+    @SuppressWarnings("resource")
     private void checkMvPartitionStorageMethodsAfterStartRebalance(MvPartitionStorage storage) {
         checkLastApplied(storage, REBALANCE_IN_PROGRESS, REBALANCE_IN_PROGRESS);
 
diff --git a/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/index/AbstractHashIndexStorageTest.java b/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/index/AbstractHashIndexStorageTest.java
index 9a996cb11f..179c588176 100644
--- a/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/index/AbstractHashIndexStorageTest.java
+++ b/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/index/AbstractHashIndexStorageTest.java
@@ -46,18 +46,12 @@ public abstract class AbstractHashIndexStorageTest extends AbstractIndexStorageT
         int tableId = tableDescriptor.id();
         int indexId = catalogId.getAndIncrement();
 
-        CatalogHashIndexDescriptor indexDescriptor = createCatalogIndexDescriptor(tableId, indexId, name, columnTypes);
+        CatalogHashIndexDescriptor indexDescriptor = createCatalogIndexDescriptor(tableId, indexId, name, built, columnTypes);
 
-        HashIndexStorage indexStorage = tableStorage.getOrCreateHashIndex(
+        return tableStorage.getOrCreateHashIndex(
                 TEST_PARTITION,
                 new StorageHashIndexDescriptor(tableDescriptor, indexDescriptor)
         );
-
-        if (built) {
-            completeBuildIndex(indexStorage);
-        }
-
-        return indexStorage;
     }
 
     @Override
@@ -66,14 +60,17 @@ public abstract class AbstractHashIndexStorageTest extends AbstractIndexStorageT
     }
 
     @Override
-    CatalogHashIndexDescriptor createCatalogIndexDescriptor(int tableId, int indexId, String indexName, ColumnType... columnTypes) {
+    CatalogHashIndexDescriptor createCatalogIndexDescriptor(
+            int tableId, int indexId, String indexName, boolean built, ColumnType... columnTypes
+    ) {
         var indexDescriptor = new CatalogHashIndexDescriptor(
                 indexId,
                 indexName,
                 tableId,
                 false,
                 AVAILABLE,
-                Stream.of(columnTypes).map(AbstractIndexStorageTest::columnName).collect(toList())
+                Stream.of(columnTypes).map(AbstractIndexStorageTest::columnName).collect(toList()),
+                built
         );
 
         addToCatalog(indexDescriptor);
diff --git a/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/index/AbstractIndexStorageTest.java b/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/index/AbstractIndexStorageTest.java
index 69cfce1177..c9ab8295ce 100644
--- a/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/index/AbstractIndexStorageTest.java
+++ b/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/index/AbstractIndexStorageTest.java
@@ -31,6 +31,7 @@ import static org.hamcrest.Matchers.contains;
 import static org.hamcrest.Matchers.containsInAnyOrder;
 import static org.hamcrest.Matchers.empty;
 import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.nullValue;
 import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -173,7 +174,7 @@ public abstract class AbstractIndexStorageTest<S extends IndexStorage, D extends
         when(catalogService.table(eq(TABLE_NAME), anyLong())).thenReturn(tableDescriptor);
         when(catalogService.table(eq(tableId), anyInt())).thenReturn(tableDescriptor);
 
-        createCatalogIndexDescriptor(tableId, pkIndexId, PK_INDEX_NAME, pkColumn.type());
+        createCatalogIndexDescriptor(tableId, pkIndexId, PK_INDEX_NAME, true, pkColumn.type());
     }
 
     /**
@@ -204,7 +205,9 @@ public abstract class AbstractIndexStorageTest<S extends IndexStorage, D extends
      */
     protected abstract D indexDescriptor(S index);
 
-    abstract CatalogIndexDescriptor createCatalogIndexDescriptor(int tableId, int indexId, String indexName, ColumnType... columnTypes);
+    abstract CatalogIndexDescriptor createCatalogIndexDescriptor(
+            int tableId, int indexId, String indexName, boolean built, ColumnType... columnTypes
+    );
 
     /**
      * Tests the {@link IndexStorage#get} method.
@@ -393,6 +396,20 @@ public abstract class AbstractIndexStorageTest<S extends IndexStorage, D extends
         assertEquals(newNextRowIdToBuild, newNextRowIdToBuild);
     }
 
+    @Test
+    void testInitialRowIdToBuildWithBuildIndex() {
+        IndexStorage indexStorage = createIndexStorage(INDEX_NAME, true, ColumnType.INT32);
+
+        assertThat(indexStorage.getNextRowIdToBuild(), is(nullValue()));
+    }
+
+    @Test
+    void testInitialRowIdToBuildWithNotBuiltIndex() {
+        IndexStorage indexStorage = createIndexStorage(INDEX_NAME, false, ColumnType.INT32);
+
+        assertThat(indexStorage.getNextRowIdToBuild(), is(initialRowIdToBuild(TEST_PARTITION)));
+    }
+
     @Test
     void testGetFromPkIndex() {
         S pkIndex = createPkIndexStorage();
diff --git a/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/index/AbstractSortedIndexStorageTest.java b/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/index/AbstractSortedIndexStorageTest.java
index ac871f28f9..f4ecaee2bb 100644
--- a/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/index/AbstractSortedIndexStorageTest.java
+++ b/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/index/AbstractSortedIndexStorageTest.java
@@ -140,18 +140,12 @@ public abstract class AbstractSortedIndexStorageTest extends AbstractIndexStorag
         int tableId = tableDescriptor.id();
         int indexId = catalogId.getAndIncrement();
 
-        CatalogSortedIndexDescriptor indexDescriptor = createCatalogIndexDescriptor(tableId, indexId, name, columns);
+        CatalogSortedIndexDescriptor indexDescriptor = createCatalogIndexDescriptor(tableId, indexId, name, built, columns);
 
-        SortedIndexStorage indexStorage = tableStorage.getOrCreateSortedIndex(
+        return tableStorage.getOrCreateSortedIndex(
                 TEST_PARTITION,
                 new StorageSortedIndexDescriptor(tableDescriptor, indexDescriptor)
         );
-
-        if (built) {
-            completeBuildIndex(indexStorage);
-        }
-
-        return indexStorage;
     }
 
     /**
@@ -171,14 +165,21 @@ public abstract class AbstractSortedIndexStorageTest extends AbstractIndexStorag
     }
 
     @Override
-    CatalogSortedIndexDescriptor createCatalogIndexDescriptor(int tableId, int indexId, String indexName, ColumnType... columnTypes) {
-        return createCatalogIndexDescriptor(tableId, indexId, indexName, toCatalogIndexColumnDescriptors(columnTypes));
+    CatalogSortedIndexDescriptor createCatalogIndexDescriptor(
+            int tableId,
+            int indexId,
+            String indexName,
+            boolean built,
+            ColumnType... columnTypes
+    ) {
+        return createCatalogIndexDescriptor(tableId, indexId, indexName, built, toCatalogIndexColumnDescriptors(columnTypes));
     }
 
     private CatalogSortedIndexDescriptor createCatalogIndexDescriptor(
             int tableId,
             int indexId,
             String indexName,
+            boolean built,
             CatalogIndexColumnDescriptor... columns
     ) {
         var indexDescriptor = new CatalogSortedIndexDescriptor(
@@ -187,7 +188,8 @@ public abstract class AbstractSortedIndexStorageTest extends AbstractIndexStorag
                 tableId,
                 false,
                 AVAILABLE,
-                List.of(columns)
+                List.of(columns),
+                built
         );
 
         addToCatalog(indexDescriptor);
diff --git a/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/index/impl/AbstractTestIndexStorage.java b/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/index/impl/AbstractTestIndexStorage.java
index 175b77fdd7..4725638837 100644
--- a/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/index/impl/AbstractTestIndexStorage.java
+++ b/modules/storage-api/src/testFixtures/java/org/apache/ignite/internal/storage/index/impl/AbstractTestIndexStorage.java
@@ -17,8 +17,6 @@
 
 package org.apache.ignite.internal.storage.index.impl;
 
-import static org.apache.ignite.internal.storage.util.StorageUtils.initialRowIdToBuild;
-
 import java.util.Iterator;
 import java.util.concurrent.atomic.AtomicInteger;
 import org.apache.ignite.internal.lang.IgniteStringFormatter;
@@ -44,19 +42,20 @@ abstract class AbstractTestIndexStorage implements IndexStorage {
 
     protected final int partitionId;
 
-    private final boolean pk;
-
-    private final int indexId;
+    private final StorageIndexDescriptor descriptor;
 
     /** Amount of cursors that opened and still do not close. */
     protected final AtomicInteger pendingCursors = new AtomicInteger();
 
     AbstractTestIndexStorage(int partitionId, StorageIndexDescriptor descriptor) {
         this.partitionId = partitionId;
-        this.pk = descriptor.isPk();
-        this.indexId = descriptor.id();
+        this.descriptor = descriptor;
+
+        nextRowIdToBuild = initialRowIdToBuild();
+    }
 
-        nextRowIdToBuild = pk ? null : initialRowIdToBuild(partitionId);
+    private @Nullable RowId initialRowIdToBuild() {
+        return descriptor.mustBeBuilt() ? StorageUtils.initialRowIdToBuild(partitionId) : null;
     }
 
     /**
@@ -126,7 +125,7 @@ abstract class AbstractTestIndexStorage implements IndexStorage {
     private void clearAndReset() {
         clear0();
 
-        nextRowIdToBuild = pk ? null : initialRowIdToBuild(partitionId);
+        nextRowIdToBuild = initialRowIdToBuild();
     }
 
     public void destroy() {
@@ -191,7 +190,7 @@ abstract class AbstractTestIndexStorage implements IndexStorage {
     }
 
     private String createStorageInfo() {
-        return IgniteStringFormatter.format("indexId={}, partitionId={}", indexId, partitionId);
+        return IgniteStringFormatter.format("indexId={}, partitionId={}", descriptor.id(), partitionId);
     }
 
     void throwExceptionIfIndexIsNotBuilt() {
diff --git a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/IndexStorageFactory.java b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/IndexStorageFactory.java
index c4ec79b0ee..ce7b9bb09f 100644
--- a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/IndexStorageFactory.java
+++ b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/IndexStorageFactory.java
@@ -279,7 +279,7 @@ class IndexStorageFactory {
 
             IndexType indexType = descriptor instanceof StorageHashIndexDescriptor ? IndexType.HASH : IndexType.SORTED;
 
-            UUID nextRowIdUuidToBuild = descriptor.isPk() ? null : initialRowIdToBuild(partitionId).uuid();
+            UUID nextRowIdUuidToBuild = descriptor.mustBeBuilt() ? initialRowIdToBuild(partitionId).uuid() : null;
 
             var indexMeta = new IndexMeta(descriptor.id(), indexType, metaPageId, nextRowIdUuidToBuild);
 
diff --git a/modules/storage-rocksdb/src/main/java/org/apache/ignite/internal/storage/rocksdb/RocksDbMetaStorage.java b/modules/storage-rocksdb/src/main/java/org/apache/ignite/internal/storage/rocksdb/RocksDbMetaStorage.java
index eccd613afa..19282b3ae6 100644
--- a/modules/storage-rocksdb/src/main/java/org/apache/ignite/internal/storage/rocksdb/RocksDbMetaStorage.java
+++ b/modules/storage-rocksdb/src/main/java/org/apache/ignite/internal/storage/rocksdb/RocksDbMetaStorage.java
@@ -22,8 +22,6 @@ import static org.apache.ignite.internal.storage.rocksdb.RocksDbStorageUtils.ROW
 import static org.apache.ignite.internal.storage.rocksdb.RocksDbStorageUtils.createKey;
 import static org.apache.ignite.internal.storage.rocksdb.RocksDbStorageUtils.getRowIdUuid;
 import static org.apache.ignite.internal.storage.rocksdb.RocksDbStorageUtils.putRowIdUuid;
-import static org.apache.ignite.internal.storage.util.StorageUtils.initialRowIdToBuild;
-import static org.apache.ignite.internal.util.ArrayUtils.BYTE_EMPTY_ARRAY;
 
 import java.nio.ByteBuffer;
 import org.apache.ignite.internal.rocksdb.ColumnFamily;
@@ -85,17 +83,12 @@ public class RocksDbMetaStorage {
      *
      * @param indexId Index ID.
      * @param partitionId Partition ID.
-     * @param pk Primary index flag.
      */
-    public @Nullable RowId getNextRowIdToBuild(int tableId, int indexId, int partitionId, boolean pk) {
+    public @Nullable RowId getNextRowIdToBuild(int tableId, int indexId, int partitionId) {
         try {
             byte[] lastBuiltRowIdBytes = metaColumnFamily.get(createKey(INDEX_ROW_ID_PREFIX, tableId, indexId, partitionId));
 
             if (lastBuiltRowIdBytes == null) {
-                return pk ? null : initialRowIdToBuild(partitionId);
-            }
-
-            if (lastBuiltRowIdBytes.length == 0) {
                 return null;
             }
 
@@ -121,7 +114,11 @@ public class RocksDbMetaStorage {
         try {
             byte[] key = createKey(INDEX_ROW_ID_PREFIX, tableId, indexId, partitionId);
 
-            writeBatch.put(metaColumnFamily.handle(), key, indexLastBuildRowId(rowId));
+            if (rowId == null) {
+                writeBatch.delete(metaColumnFamily.handle(), key);
+            } else {
+                writeBatch.put(metaColumnFamily.handle(), key, indexLastBuildRowId(rowId));
+            }
         } catch (RocksDBException e) {
             throw new StorageException(
                     "Failed to save next row ID to build: [partitionId={}, indexId={}, rowId={}]",
@@ -146,11 +143,7 @@ public class RocksDbMetaStorage {
         }
     }
 
-    private static byte[] indexLastBuildRowId(@Nullable RowId rowId) {
-        if (rowId == null) {
-            return BYTE_EMPTY_ARRAY;
-        }
-
+    private static byte[] indexLastBuildRowId(RowId rowId) {
         ByteBuffer buffer = ByteBuffer.allocate(ROW_ID_SIZE).order(KEY_BYTE_ORDER);
 
         putRowIdUuid(buffer, rowId.uuid());
diff --git a/modules/storage-rocksdb/src/main/java/org/apache/ignite/internal/storage/rocksdb/index/AbstractRocksDbIndexStorage.java b/modules/storage-rocksdb/src/main/java/org/apache/ignite/internal/storage/rocksdb/index/AbstractRocksDbIndexStorage.java
index 169c381189..e65cddb8a6 100644
--- a/modules/storage-rocksdb/src/main/java/org/apache/ignite/internal/storage/rocksdb/index/AbstractRocksDbIndexStorage.java
+++ b/modules/storage-rocksdb/src/main/java/org/apache/ignite/internal/storage/rocksdb/index/AbstractRocksDbIndexStorage.java
@@ -42,6 +42,7 @@ import org.apache.ignite.internal.storage.StorageException;
 import org.apache.ignite.internal.storage.StorageRebalanceException;
 import org.apache.ignite.internal.storage.index.IndexStorage;
 import org.apache.ignite.internal.storage.index.PeekCursor;
+import org.apache.ignite.internal.storage.index.StorageIndexDescriptor;
 import org.apache.ignite.internal.storage.rocksdb.PartitionDataHelper;
 import org.apache.ignite.internal.storage.rocksdb.RocksDbMetaStorage;
 import org.apache.ignite.internal.storage.util.StorageState;
@@ -65,11 +66,11 @@ public abstract class AbstractRocksDbIndexStorage implements IndexStorage {
 
     private final int tableId;
 
-    protected final int indexId;
+    private final int indexId;
 
-    protected final int partitionId;
+    protected final StorageIndexDescriptor descriptor;
 
-    private final boolean pk;
+    protected final int partitionId;
 
     private final RocksDbMetaStorage indexMetaStorage;
 
@@ -82,14 +83,20 @@ public abstract class AbstractRocksDbIndexStorage implements IndexStorage {
     /** Row ID for which the index needs to be built, {@code null} means that the index building has completed. */
     private volatile @Nullable RowId nextRowIdToBuild;
 
-    AbstractRocksDbIndexStorage(int tableId, int indexId, int partitionId, RocksDbMetaStorage indexMetaStorage, boolean pk) {
+    AbstractRocksDbIndexStorage(StorageIndexDescriptor descriptor, int tableId, int partitionId, RocksDbMetaStorage indexMetaStorage) {
         this.tableId = tableId;
-        this.indexId = indexId;
+        this.indexId = descriptor.id();
+        this.descriptor = descriptor;
         this.indexMetaStorage = indexMetaStorage;
         this.partitionId = partitionId;
-        this.pk = pk;
 
-        nextRowIdToBuild = indexMetaStorage.getNextRowIdToBuild(tableId, indexId, partitionId, pk);
+        RowId rowIdFromMeta = indexMetaStorage.getNextRowIdToBuild(tableId, indexId, partitionId);
+
+        if (rowIdFromMeta == null && descriptor.mustBeBuilt()) {
+            rowIdFromMeta = initialRowIdToBuild(partitionId);
+        }
+
+        nextRowIdToBuild = rowIdFromMeta;
     }
 
     @Override
@@ -257,9 +264,17 @@ public abstract class AbstractRocksDbIndexStorage implements IndexStorage {
     public final void destroyData(WriteBatch writeBatch) throws RocksDBException {
         clearIndex(writeBatch);
 
-        indexMetaStorage.removeNextRowIdToBuild(writeBatch, tableId, indexId, partitionId);
+        if (descriptor.mustBeBuilt()) {
+            RowId initialRowId = initialRowIdToBuild(partitionId);
+
+            indexMetaStorage.putNextRowIdToBuild(writeBatch, tableId, indexId, partitionId, initialRowId);
 
-        nextRowIdToBuild = pk ? null : initialRowIdToBuild(partitionId);
+            nextRowIdToBuild = initialRowId;
+        } else {
+            indexMetaStorage.removeNextRowIdToBuild(writeBatch, tableId, indexId, partitionId);
+
+            nextRowIdToBuild = null;
+        }
     }
 
     /** Method that needs to be overridden by the inheritors to remove all implementation specific data for this index. */
diff --git a/modules/storage-rocksdb/src/main/java/org/apache/ignite/internal/storage/rocksdb/index/RocksDbHashIndexStorage.java b/modules/storage-rocksdb/src/main/java/org/apache/ignite/internal/storage/rocksdb/index/RocksDbHashIndexStorage.java
index 89c9ed68fb..9b6663fe23 100644
--- a/modules/storage-rocksdb/src/main/java/org/apache/ignite/internal/storage/rocksdb/index/RocksDbHashIndexStorage.java
+++ b/modules/storage-rocksdb/src/main/java/org/apache/ignite/internal/storage/rocksdb/index/RocksDbHashIndexStorage.java
@@ -59,8 +59,6 @@ public class RocksDbHashIndexStorage extends AbstractRocksDbIndexStorage impleme
     /** Length of the fixed part of the key: Table ID + Index ID + Partition ID + Hash. */
     public static final int FIXED_PREFIX_LENGTH = PREFIX_WITH_IDS_LENGTH + Integer.BYTES;
 
-    private final StorageHashIndexDescriptor descriptor;
-
     private final ColumnFamily indexCf;
 
     /** Constant prefix of every index key. */
@@ -82,9 +80,8 @@ public class RocksDbHashIndexStorage extends AbstractRocksDbIndexStorage impleme
             ColumnFamily indexCf,
             RocksDbMetaStorage indexMetaStorage
     ) {
-        super(tableId, descriptor.id(), partitionId, indexMetaStorage, descriptor.isPk());
+        super(descriptor, tableId, partitionId, indexMetaStorage);
 
-        this.descriptor = descriptor;
         this.indexCf = indexCf;
 
         this.constantPrefix = ByteBuffer.allocate(PREFIX_WITH_IDS_LENGTH)
@@ -97,7 +94,7 @@ public class RocksDbHashIndexStorage extends AbstractRocksDbIndexStorage impleme
 
     @Override
     public StorageHashIndexDescriptor indexDescriptor() {
-        return descriptor;
+        return (StorageHashIndexDescriptor) descriptor;
     }
 
     @Override
diff --git a/modules/storage-rocksdb/src/main/java/org/apache/ignite/internal/storage/rocksdb/index/RocksDbSortedIndexStorage.java b/modules/storage-rocksdb/src/main/java/org/apache/ignite/internal/storage/rocksdb/index/RocksDbSortedIndexStorage.java
index d78ba99f85..378ac0f686 100644
--- a/modules/storage-rocksdb/src/main/java/org/apache/ignite/internal/storage/rocksdb/index/RocksDbSortedIndexStorage.java
+++ b/modules/storage-rocksdb/src/main/java/org/apache/ignite/internal/storage/rocksdb/index/RocksDbSortedIndexStorage.java
@@ -65,8 +65,6 @@ import org.rocksdb.WriteBatchWithIndex;
  * <p>We use an empty array as values, because all required information can be extracted from the key.
  */
 public class RocksDbSortedIndexStorage extends AbstractRocksDbIndexStorage implements SortedIndexStorage {
-    private final StorageSortedIndexDescriptor descriptor;
-
     private final ColumnFamily indexCf;
     private final byte[] partitionStartPrefix;
     private final byte[] partitionEndPrefix;
@@ -87,15 +85,14 @@ public class RocksDbSortedIndexStorage extends AbstractRocksDbIndexStorage imple
             ColumnFamily indexCf,
             RocksDbMetaStorage indexMetaStorage
     ) {
-        super(tableId, descriptor.id(), partitionId, indexMetaStorage, descriptor.isPk());
+        super(descriptor, tableId, partitionId, indexMetaStorage);
 
-        this.descriptor = descriptor;
         this.indexCf = indexCf;
 
         this.partitionStartPrefix = ByteBuffer.allocate(PREFIX_WITH_IDS_LENGTH)
                 .order(KEY_BYTE_ORDER)
                 .putInt(tableId)
-                .putInt(indexId)
+                .putInt(descriptor.id())
                 .putShort((short) partitionId)
                 .array();
 
@@ -104,7 +101,7 @@ public class RocksDbSortedIndexStorage extends AbstractRocksDbIndexStorage imple
 
     @Override
     public StorageSortedIndexDescriptor indexDescriptor() {
-        return descriptor;
+        return (StorageSortedIndexDescriptor) descriptor;
     }
 
     @Override
@@ -281,7 +278,7 @@ public class RocksDbSortedIndexStorage extends AbstractRocksDbIndexStorage imple
     private IndexRow decodeRow(ByteBuffer bytes) {
         assert bytes.getShort(PREFIX_WITH_IDS_LENGTH - PARTITION_ID_SIZE) == partitionId;
 
-        var tuple = new BinaryTuple(descriptor.binaryTupleSchema().elementCount(), binaryTupleSlice(bytes));
+        var tuple = new BinaryTuple(indexDescriptor().binaryTupleSchema().elementCount(), binaryTupleSlice(bytes));
 
         return new IndexRowImpl(tuple, decodeRowId(bytes));
     }
diff --git a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/index/IndexMeta.java b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/index/IndexMeta.java
index 4109268848..d973b35e8d 100644
--- a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/index/IndexMeta.java
+++ b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/index/IndexMeta.java
@@ -166,7 +166,8 @@ public class IndexMeta implements Serializable {
      * @see Catalog#time()
      */
     IndexMeta status(MetaIndexStatus newStatus, int catalogVersion, long activationTs) {
-        assert !statusChanges.containsKey(newStatus) : "newStatus=" + newStatus + ", catalogVersion=" + catalogVersion;
+        assert !statusChanges.containsKey(newStatus) :
+                String.format("newStatus=%s, catalogVersion=%d, indexName=%s", newStatus, catalogVersion, indexName);
 
         var newStatuses = new EnumMap<>(statusChanges);
         newStatuses.put(newStatus, new MetaIndexStatusChange(catalogVersion, activationTs));
diff --git a/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/IndexBaseTest.java b/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/IndexBaseTest.java
index d66e019af8..96ab1b86ff 100644
--- a/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/IndexBaseTest.java
+++ b/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/IndexBaseTest.java
@@ -112,7 +112,7 @@ public abstract class IndexBaseTest extends BaseMvStoragesTest {
                                 new StorageHashIndexColumnDescriptor("INTKEY", NativeTypes.INT32, false),
                                 new StorageHashIndexColumnDescriptor("STRKEY", NativeTypes.STRING, false)
                         ),
-                        true
+                        false
                 )
         );
 
@@ -130,7 +130,7 @@ public abstract class IndexBaseTest extends BaseMvStoragesTest {
                                 new StorageSortedIndexColumnDescriptor("INTVAL", NativeTypes.INT32, false, true),
                                 new StorageSortedIndexColumnDescriptor("STRVAL", NativeTypes.STRING, false, true)
                         ),
-                        false
+                        true
                 )
         );
 
@@ -148,7 +148,7 @@ public abstract class IndexBaseTest extends BaseMvStoragesTest {
                                 new StorageHashIndexColumnDescriptor("INTVAL", NativeTypes.INT32, false),
                                 new StorageHashIndexColumnDescriptor("STRVAL", NativeTypes.STRING, false)
                         ),
-                        false
+                        true
                 )
         );
 
diff --git a/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/raft/PartitionCommandListenerTest.java b/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/raft/PartitionCommandListenerTest.java
index 249168b7ec..6985b4d9af 100644
--- a/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/raft/PartitionCommandListenerTest.java
+++ b/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/raft/PartitionCommandListenerTest.java
@@ -177,7 +177,7 @@ public class PartitionCommandListenerTest extends BaseIgniteAbstractTest {
                     new StorageHashIndexDescriptor(
                             TABLE_ID,
                             List.of(new StorageHashIndexColumnDescriptor("key", NativeTypes.INT32, false)),
-                            true
+                            false
                     )
             ),
             BinaryRowConverter.keyExtractor(SCHEMA)
diff --git a/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/replication/PartitionReplicaListenerIndexLockingTest.java b/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/replication/PartitionReplicaListenerIndexLockingTest.java
index b9491c5853..ca6532cc13 100644
--- a/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/replication/PartitionReplicaListenerIndexLockingTest.java
+++ b/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/replication/PartitionReplicaListenerIndexLockingTest.java
@@ -177,7 +177,7 @@ public class PartitionReplicaListenerIndexLockingTest extends IgniteAbstractTest
         StorageHashIndexDescriptor pkIndexDescriptor = new StorageHashIndexDescriptor(
                 PK_INDEX_ID,
                 List.of(new StorageHashIndexColumnDescriptor("ID", NativeTypes.INT32, false)),
-                true
+                false
         );
 
         TableSchemaAwareIndexStorage hashIndexStorage = new TableSchemaAwareIndexStorage(
diff --git a/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/replication/PartitionReplicaListenerTest.java b/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/replication/PartitionReplicaListenerTest.java
index c865bd856e..1a25e03421 100644
--- a/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/replication/PartitionReplicaListenerTest.java
+++ b/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/replication/PartitionReplicaListenerTest.java
@@ -512,7 +512,7 @@ public class PartitionReplicaListenerTest extends IgniteAbstractTest {
                 pkIndexId,
                 new TestHashIndexStorage(
                         PART_ID,
-                        new StorageHashIndexDescriptor(pkIndexId, List.of(), true)
+                        new StorageHashIndexDescriptor(pkIndexId, List.of(), false)
                 ),
                 row2Tuple
         ));
diff --git a/modules/table/src/testFixtures/java/org/apache/ignite/distributed/ItTxTestCluster.java b/modules/table/src/testFixtures/java/org/apache/ignite/distributed/ItTxTestCluster.java
index 964c01197d..4078df5c34 100644
--- a/modules/table/src/testFixtures/java/org/apache/ignite/distributed/ItTxTestCluster.java
+++ b/modules/table/src/testFixtures/java/org/apache/ignite/distributed/ItTxTestCluster.java
@@ -579,7 +579,7 @@ public class ItTxTestCluster {
      *
      * @param tableName Table name.
      * @param schemaDescriptor Schema descriptor.
-     * @return Groups map.
+     * @return Started instance.
      */
     public TableViewInternal startTable(String tableName, SchemaDescriptor schemaDescriptor) throws Exception {
         int tableId = globalCatalogId.getAndIncrement();
@@ -677,7 +677,6 @@ public class ItTxTestCluster {
                 ColumnsExtractor row2Tuple = BinaryRowConverter.keyExtractor(schemaDescriptor);
 
                 StorageHashIndexDescriptor pkIndexDescriptor = mock(StorageHashIndexDescriptor.class);
-                when(pkIndexDescriptor.isPk()).thenReturn(true);
 
                 when(pkIndexDescriptor.columns()).then(invocation -> Collections.nCopies(
                         schemaDescriptor.keyColumns().size(),
@@ -856,7 +855,7 @@ public class ItTxTestCluster {
         );
     }
 
-    private LogicalTopologyService logicalTopologyService(ClusterService clusterService) {
+    private static LogicalTopologyService logicalTopologyService(ClusterService clusterService) {
         return new LogicalTopologyService() {
             private final UUID clusterId = UUID.randomUUID();
 
@@ -914,7 +913,6 @@ public class ItTxTestCluster {
 
     /**
      * Shutdowns all cluster nodes after each test.
-     *
      */
     public void shutdownCluster() {
         assertThat(stopAsync(new ComponentContext(), cluster), willCompleteSuccessfully());
diff --git a/modules/table/src/testFixtures/java/org/apache/ignite/internal/table/impl/DummyInternalTableImpl.java b/modules/table/src/testFixtures/java/org/apache/ignite/internal/table/impl/DummyInternalTableImpl.java
index ef8c5822f6..bd20375686 100644
--- a/modules/table/src/testFixtures/java/org/apache/ignite/internal/table/impl/DummyInternalTableImpl.java
+++ b/modules/table/src/testFixtures/java/org/apache/ignite/internal/table/impl/DummyInternalTableImpl.java
@@ -345,7 +345,6 @@ public class DummyInternalTableImpl extends InternalTableImpl {
         ColumnsExtractor row2Tuple = BinaryRowConverter.keyExtractor(schema);
 
         StorageHashIndexDescriptor pkIndexDescriptor = mock(StorageHashIndexDescriptor.class);
-        when(pkIndexDescriptor.isPk()).thenReturn(true);
 
         when(pkIndexDescriptor.columns()).then(
                 invocation -> Collections.nCopies(schema.keyColumns().size(), mock(StorageHashIndexColumnDescriptor.class))