You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by tk...@apache.org on 2023/01/24 10:42:11 UTC

[ignite-3] branch main updated: IGNITE-18531 Destroy indices when destroying VolatilePageMemoryMvPartitionStorage (#1559)

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

tkalkirill 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 e3cfedb1d1 IGNITE-18531 Destroy indices when destroying VolatilePageMemoryMvPartitionStorage (#1559)
e3cfedb1d1 is described below

commit e3cfedb1d1173ebed0d169f1cafc306f6dae7455
Author: Roman Puchkovskiy <ro...@gmail.com>
AuthorDate: Tue Jan 24 14:42:06 2023 +0400

    IGNITE-18531 Destroy indices when destroying VolatilePageMemoryMvPartitionStorage (#1559)
---
 .../ignite/internal/pagememory/tree/BplusTree.java |   4 +-
 .../internal/pagememory/tree/io/BplusIo.java       |   5 +-
 .../storage/AbstractMvTableStorageTest.java        |   4 +-
 .../pagememory/VolatilePageMemoryTableStorage.java |  20 ++--
 .../index/hash/PageMemoryHashIndexStorage.java     |  44 +++++++-
 .../index/hash/io/HashIndexTreeLeafIo.java         |  21 ++++
 .../index/sorted/PageMemorySortedIndexStorage.java |  38 +++++++
 .../index/sorted/io/SortedIndexTreeLeafIo.java     |  21 ++++
 .../mv/AbstractPageMemoryMvPartitionStorage.java   |  30 ++++--
 .../mv/PersistentPageMemoryMvPartitionStorage.java |   7 +-
 .../mv/VolatilePageMemoryMvPartitionStorage.java   |  75 +++++++++++--
 .../pagememory/mv/io/VersionChainLeafIo.java       |   2 +-
 .../VolatilePageMemoryMvTableStorageTest.java      | 117 ++++++++++++++++++++-
 13 files changed, 351 insertions(+), 37 deletions(-)

diff --git a/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/tree/BplusTree.java b/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/tree/BplusTree.java
index b0e1172866..f92854d7ab 100644
--- a/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/tree/BplusTree.java
+++ b/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/tree/BplusTree.java
@@ -2828,7 +2828,7 @@ public abstract class BplusTree<L, T extends L> extends DataStructure implements
                 }
 
                 if (c != null && io.isLeaf()) {
-                    io.visit(pageAddr, c);
+                    io.visit(this, pageAddr, c);
                 }
 
                 bag.addFreePage(recyclePage(pageId, pageAddr));
@@ -6734,7 +6734,7 @@ public abstract class BplusTree<L, T extends L> extends DataStructure implements
                             readChildrenPageIdsAndDescend(pageAddr, io, cnt);
                         } else {
                             if (actOnEachElement != null) {
-                                io.visit(pageAddr, actOnEachElement);
+                                io.visit(BplusTree.this, pageAddr, actOnEachElement);
 
                                 workDone += io.getCount(pageAddr);
                             }
diff --git a/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/tree/io/BplusIo.java b/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/tree/io/BplusIo.java
index faccda01b4..d465616c91 100644
--- a/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/tree/io/BplusIo.java
+++ b/modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/tree/io/BplusIo.java
@@ -519,10 +519,11 @@ public abstract class BplusIo<L> extends PageIo {
     /**
      * Visits the page.
      *
+     * @param tree The tree to which the page belongs.
      * @param pageAddr Page address.
-     * @param c Consumer.
+     * @param c Consumer triggered for each element stored in the page.
      */
-    public void visit(long pageAddr, Consumer<L> c) {
+    public void visit(BplusTree<L, ?> tree, long pageAddr, Consumer<L> c) {
         // No-op.
     }
 
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 8715cfe9b6..3a70adbfef 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
@@ -97,9 +97,9 @@ public abstract class AbstractMvTableStorageTest extends BaseMvStoragesTest {
 
     protected MvTableStorage tableStorage;
 
-    private TableIndexView sortedIdx;
+    protected TableIndexView sortedIdx;
 
-    private TableIndexView hashIdx;
+    protected TableIndexView hashIdx;
 
     protected StorageEngine storageEngine;
 
diff --git a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/VolatilePageMemoryTableStorage.java b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/VolatilePageMemoryTableStorage.java
index 12d48656c9..66aeb50a45 100644
--- a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/VolatilePageMemoryTableStorage.java
+++ b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/VolatilePageMemoryTableStorage.java
@@ -149,20 +149,22 @@ public class VolatilePageMemoryTableStorage extends AbstractPageMemoryTableStora
     CompletableFuture<Void> clearStorageAndUpdateDataStructures(AbstractPageMemoryMvPartitionStorage mvPartitionStorage) {
         VolatilePageMemoryMvPartitionStorage volatilePartitionStorage = ((VolatilePageMemoryMvPartitionStorage) mvPartitionStorage);
 
-        return volatilePartitionStorage.destroyStructures().thenAccept(unused -> {
-            int partitionId = mvPartitionStorage.partitionId();
-            TableView tableView = tableCfg.value();
+        volatilePartitionStorage.cleanStructuresData();
 
-            volatilePartitionStorage.updateDataStructuresOnRebalance(
-                    createVersionChainTree(partitionId, tableView),
-                    createIndexMetaTree(partitionId, tableView)
-            );
-        });
+        int partitionId = mvPartitionStorage.partitionId();
+        TableView tableView = tableCfg.value();
+
+        volatilePartitionStorage.updateDataStructuresOnRebalance(
+                createVersionChainTree(partitionId, tableView),
+                createIndexMetaTree(partitionId, tableView)
+        );
+
+        return completedFuture(null);
     }
 
     @Override
     CompletableFuture<Void> destroyMvPartitionStorage(AbstractPageMemoryMvPartitionStorage mvPartitionStorage) {
-        mvPartitionStorage.close();
+        mvPartitionStorage.closeForDestruction();
 
         VolatilePageMemoryMvPartitionStorage volatilePartitionStorage = (VolatilePageMemoryMvPartitionStorage) mvPartitionStorage;
 
diff --git a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/hash/PageMemoryHashIndexStorage.java b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/hash/PageMemoryHashIndexStorage.java
index 8641c96d17..21fd68173f 100644
--- a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/hash/PageMemoryHashIndexStorage.java
+++ b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/hash/PageMemoryHashIndexStorage.java
@@ -24,6 +24,10 @@ import static org.apache.ignite.internal.storage.util.StorageUtils.throwExceptio
 
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Supplier;
+import org.apache.ignite.internal.logger.IgniteLogger;
+import org.apache.ignite.internal.logger.Loggers;
+import org.apache.ignite.internal.pagememory.util.GradualTaskExecutor;
+import org.apache.ignite.internal.pagememory.util.PageIdUtils;
 import org.apache.ignite.internal.schema.BinaryTuple;
 import org.apache.ignite.internal.storage.RowId;
 import org.apache.ignite.internal.storage.StorageException;
@@ -43,6 +47,8 @@ import org.apache.ignite.lang.IgniteStringFormatter;
  * Implementation of Hash index storage using Page Memory.
  */
 public class PageMemoryHashIndexStorage implements HashIndexStorage {
+    private static final IgniteLogger LOG = Loggers.forClass(PageMemoryHashIndexStorage.class);
+
     /** Index descriptor. */
     private final HashIndexDescriptor descriptor;
 
@@ -74,7 +80,11 @@ public class PageMemoryHashIndexStorage implements HashIndexStorage {
      * @param freeList Free list to store index columns.
      * @param hashIndexTree Hash index tree instance.
      */
-    public PageMemoryHashIndexStorage(HashIndexDescriptor descriptor, IndexColumnsFreeList freeList, HashIndexTree hashIndexTree) {
+    public PageMemoryHashIndexStorage(
+            HashIndexDescriptor descriptor,
+            IndexColumnsFreeList freeList,
+            HashIndexTree hashIndexTree
+    ) {
         this.descriptor = descriptor;
         this.freeList = freeList;
         this.hashIndexTree = hashIndexTree;
@@ -183,6 +193,38 @@ public class PageMemoryHashIndexStorage implements HashIndexStorage {
         throw new UnsupportedOperationException();
     }
 
+    /**
+     * Starts destruction of the data stored by this index partition.
+     *
+     * @param executor {@link GradualTaskExecutor} on which to destroy.
+     * @throws StorageException If something goes wrong.
+     */
+    public void startDestructionOn(GradualTaskExecutor executor) throws StorageException {
+        try {
+            executor.execute(
+                    hashIndexTree.startGradualDestruction(rowKey -> removeIndexColumns((HashIndexRow) rowKey), false)
+            ).whenComplete((res, ex) -> {
+                if (ex != null) {
+                    LOG.error("Hash index " + descriptor.id() + " destruction has failed", ex);
+                }
+            });
+        } catch (IgniteInternalCheckedException e) {
+            throw new StorageException("Cannot destroy hash index " + indexDescriptor().id(), e);
+        }
+    }
+
+    private void removeIndexColumns(HashIndexRow indexRow) {
+        if (indexRow.indexColumns().link() != PageIdUtils.NULL_LINK) {
+            try {
+                freeList.removeDataRowByLink(indexRow.indexColumns().link());
+            } catch (IgniteInternalCheckedException e) {
+                throw new StorageException("Cannot destroy hash index " + indexDescriptor().id(), e);
+            }
+
+            indexRow.indexColumns().link(PageIdUtils.NULL_LINK);
+        }
+    }
+
     /**
      * Closes the hash index storage.
      */
diff --git a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/hash/io/HashIndexTreeLeafIo.java b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/hash/io/HashIndexTreeLeafIo.java
index 8207446a9e..6896ae1177 100644
--- a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/hash/io/HashIndexTreeLeafIo.java
+++ b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/hash/io/HashIndexTreeLeafIo.java
@@ -22,6 +22,7 @@ import static org.apache.ignite.internal.storage.pagememory.index.IndexPageTypes
 import static org.apache.ignite.internal.storage.pagememory.index.InlineUtils.MAX_BINARY_TUPLE_INLINE_SIZE;
 
 import java.util.List;
+import java.util.function.Consumer;
 import java.util.stream.IntStream;
 import org.apache.ignite.internal.pagememory.io.IoVersions;
 import org.apache.ignite.internal.pagememory.tree.BplusTree;
@@ -29,9 +30,11 @@ import org.apache.ignite.internal.pagememory.tree.io.BplusIo;
 import org.apache.ignite.internal.pagememory.tree.io.BplusLeafIo;
 import org.apache.ignite.internal.schema.BinaryTuple;
 import org.apache.ignite.internal.storage.pagememory.index.InlineUtils;
+import org.apache.ignite.internal.storage.pagememory.index.hash.HashIndexRow;
 import org.apache.ignite.internal.storage.pagememory.index.hash.HashIndexRowKey;
 import org.apache.ignite.internal.storage.pagememory.index.hash.HashIndexTree;
 import org.apache.ignite.lang.IgniteInternalCheckedException;
+import org.apache.ignite.lang.IgniteInternalException;
 
 /**
  * {@link BplusLeafIo} implementation for {@link HashIndexTree}.
@@ -68,4 +71,22 @@ public class HashIndexTreeLeafIo extends BplusLeafIo<HashIndexRowKey> implements
 
         return getRow(hashIndexTree.dataPageReader(), hashIndexTree.partitionId(), pageAddr, idx);
     }
+
+    @Override
+    public void visit(BplusTree<HashIndexRowKey, ?> tree, long pageAddr, Consumer<HashIndexRowKey> c) {
+        HashIndexTree hashIndexTree = (HashIndexTree) tree;
+
+        int count = getCount(pageAddr);
+
+        for (int i = 0; i < count; i++) {
+            HashIndexRow indexRow;
+            try {
+                indexRow = getRow(hashIndexTree.dataPageReader(), hashIndexTree.partitionId(), pageAddr, i);
+            } catch (IgniteInternalCheckedException e) {
+                throw new IgniteInternalException(e);
+            }
+
+            c.accept(indexRow);
+        }
+    }
 }
diff --git a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/sorted/PageMemorySortedIndexStorage.java b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/sorted/PageMemorySortedIndexStorage.java
index 45b8b98dcf..79cde00a2e 100644
--- a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/sorted/PageMemorySortedIndexStorage.java
+++ b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/sorted/PageMemorySortedIndexStorage.java
@@ -28,6 +28,10 @@ import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Function;
 import java.util.function.Supplier;
 import org.apache.ignite.internal.binarytuple.BinaryTupleCommon;
+import org.apache.ignite.internal.logger.IgniteLogger;
+import org.apache.ignite.internal.logger.Loggers;
+import org.apache.ignite.internal.pagememory.util.GradualTaskExecutor;
+import org.apache.ignite.internal.pagememory.util.PageIdUtils;
 import org.apache.ignite.internal.schema.BinaryTuple;
 import org.apache.ignite.internal.schema.BinaryTuplePrefix;
 import org.apache.ignite.internal.storage.RowId;
@@ -52,6 +56,8 @@ import org.jetbrains.annotations.Nullable;
  * Implementation of Sorted index storage using Page Memory.
  */
 public class PageMemorySortedIndexStorage implements SortedIndexStorage {
+    private static final IgniteLogger LOG = Loggers.forClass(PageMemorySortedIndexStorage.class);
+
     /** Index descriptor. */
     private final SortedIndexDescriptor descriptor;
 
@@ -432,4 +438,36 @@ public class PageMemorySortedIndexStorage implements SortedIndexStorage {
     private String createStorageInfo() {
         return IgniteStringFormatter.format("indexId={}, partitionId={}", descriptor.id(), partitionId);
     }
+
+    /**
+     * Starts destruction of the data stored by this index partition.
+     *
+     * @param executor {@link GradualTaskExecutor} on which to destroy.
+     * @throws StorageException If something goes wrong.
+     */
+    public void startDestructionOn(GradualTaskExecutor executor) throws StorageException {
+        try {
+            executor.execute(
+                    sortedIndexTree.startGradualDestruction(rowKey -> removeIndexColumns((SortedIndexRow) rowKey), false)
+            ).whenComplete((res, ex) -> {
+                if (ex != null) {
+                    LOG.error("Sorted index " + descriptor.id() + " destruction has failed", ex);
+                }
+            });
+        } catch (IgniteInternalCheckedException e) {
+            throw new StorageException("Cannot destroy sorted index " + indexDescriptor().id(), e);
+        }
+    }
+
+    private void removeIndexColumns(SortedIndexRow indexRow) {
+        if (indexRow.indexColumns().link() != PageIdUtils.NULL_LINK) {
+            try {
+                freeList.removeDataRowByLink(indexRow.indexColumns().link());
+            } catch (IgniteInternalCheckedException e) {
+                throw new StorageException("Cannot destroy sorted index " + indexDescriptor().id(), e);
+            }
+
+            indexRow.indexColumns().link(PageIdUtils.NULL_LINK);
+        }
+    }
 }
diff --git a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/sorted/io/SortedIndexTreeLeafIo.java b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/sorted/io/SortedIndexTreeLeafIo.java
index 31bb799003..d351ad4fdc 100644
--- a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/sorted/io/SortedIndexTreeLeafIo.java
+++ b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/index/sorted/io/SortedIndexTreeLeafIo.java
@@ -22,6 +22,7 @@ import static org.apache.ignite.internal.storage.pagememory.index.IndexPageTypes
 import static org.apache.ignite.internal.storage.pagememory.index.InlineUtils.MAX_BINARY_TUPLE_INLINE_SIZE;
 
 import java.util.List;
+import java.util.function.Consumer;
 import java.util.stream.IntStream;
 import org.apache.ignite.internal.pagememory.io.IoVersions;
 import org.apache.ignite.internal.pagememory.tree.BplusTree;
@@ -29,9 +30,11 @@ import org.apache.ignite.internal.pagememory.tree.io.BplusIo;
 import org.apache.ignite.internal.pagememory.tree.io.BplusLeafIo;
 import org.apache.ignite.internal.schema.BinaryTuple;
 import org.apache.ignite.internal.storage.pagememory.index.InlineUtils;
+import org.apache.ignite.internal.storage.pagememory.index.sorted.SortedIndexRow;
 import org.apache.ignite.internal.storage.pagememory.index.sorted.SortedIndexRowKey;
 import org.apache.ignite.internal.storage.pagememory.index.sorted.SortedIndexTree;
 import org.apache.ignite.lang.IgniteInternalCheckedException;
+import org.apache.ignite.lang.IgniteInternalException;
 
 /**
  * {@link BplusLeafIo} implementation for {@link SortedIndexTree}.
@@ -69,4 +72,22 @@ public class SortedIndexTreeLeafIo extends BplusLeafIo<SortedIndexRowKey> implem
 
         return getRow(sortedIndexTree.dataPageReader(), sortedIndexTree.partitionId(), pageAddr, idx);
     }
+
+    @Override
+    public void visit(BplusTree<SortedIndexRowKey, ?> tree, long pageAddr, Consumer<SortedIndexRowKey> c) {
+        SortedIndexTree sortedIndexTree = (SortedIndexTree) tree;
+
+        int count = getCount(pageAddr);
+
+        for (int i = 0; i < count; i++) {
+            SortedIndexRow indexRow;
+            try {
+                indexRow = getRow(sortedIndexTree.dataPageReader(), sortedIndexTree.partitionId(), pageAddr, i);
+            } catch (IgniteInternalCheckedException e) {
+                throw new IgniteInternalException(e);
+            }
+
+            c.accept(indexRow);
+        }
+    }
 }
diff --git a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/AbstractPageMemoryMvPartitionStorage.java b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/AbstractPageMemoryMvPartitionStorage.java
index 1af00faf88..323d368648 100644
--- a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/AbstractPageMemoryMvPartitionStorage.java
+++ b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/AbstractPageMemoryMvPartitionStorage.java
@@ -149,9 +149,7 @@ public abstract class AbstractPageMemoryMvPartitionStorage implements MvPartitio
             try (Cursor<IndexMeta> cursor = indexMetaTree.find(null, null)) {
                 NamedListView<TableIndexView> indexesCfgView = tableStorage.tablesConfiguration().indexes().value();
 
-                while (cursor.hasNext()) {
-                    IndexMeta indexMeta = cursor.next();
-
+                for (IndexMeta indexMeta : cursor) {
                     TableIndexView indexCfgView = getByInternalId(indexesCfgView, indexMeta.id());
 
                     if (indexCfgView instanceof HashIndexView) {
@@ -987,8 +985,24 @@ public abstract class AbstractPageMemoryMvPartitionStorage implements MvPartitio
         }
     }
 
+    /**
+     * Closes the partition in preparation for its destruction.
+     */
+    public void closeForDestruction() {
+        close(true);
+    }
+
     @Override
     public void close() {
+        close(false);
+    }
+
+    /**
+     * Closes the storage.
+     *
+     * @param goingToDestroy If the closure is in preparation for destruction.
+     */
+    private void close(boolean goingToDestroy) {
         if (!state.compareAndSet(StorageState.RUNNABLE, StorageState.CLOSED)) {
             StorageState state = this.state.get();
 
@@ -1000,7 +1014,7 @@ public abstract class AbstractPageMemoryMvPartitionStorage implements MvPartitio
         busyLock.block();
 
         try {
-            IgniteUtils.closeAll(getResourcesToClose());
+            IgniteUtils.closeAll(getResourcesToClose(goingToDestroy));
         } catch (Exception e) {
             throw new StorageException(e);
         }
@@ -1008,8 +1022,10 @@ public abstract class AbstractPageMemoryMvPartitionStorage implements MvPartitio
 
     /**
      * Returns resources that should be closed on {@link #close()}.
+     *
+     * @param goingToDestroy If the closure is in preparation for destruction.
      */
-    protected List<AutoCloseable> getResourcesToClose() {
+    protected List<AutoCloseable> getResourcesToClose(boolean goingToDestroy) {
         List<AutoCloseable> resources = new ArrayList<>();
 
         resources.add(versionChainTree::close);
@@ -1018,8 +1034,8 @@ public abstract class AbstractPageMemoryMvPartitionStorage implements MvPartitio
         hashIndexes.values().forEach(index -> resources.add(index::close));
         sortedIndexes.values().forEach(index -> resources.add(index::close));
 
-        resources.add(hashIndexes::clear);
-        resources.add(sortedIndexes::clear);
+        // We do not clear hashIndexes and sortedIndexes here because we leave the decision about when to clear them
+        // to the subclasses.
 
         return resources;
     }
diff --git a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/PersistentPageMemoryMvPartitionStorage.java b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/PersistentPageMemoryMvPartitionStorage.java
index 0fd7a18b44..2c3c54d9a5 100644
--- a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/PersistentPageMemoryMvPartitionStorage.java
+++ b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/PersistentPageMemoryMvPartitionStorage.java
@@ -290,8 +290,11 @@ public class PersistentPageMemoryMvPartitionStorage extends AbstractPageMemoryMv
     }
 
     @Override
-    protected List<AutoCloseable> getResourcesToClose() {
-        List<AutoCloseable> resourcesToClose = super.getResourcesToClose();
+    protected List<AutoCloseable> getResourcesToClose(boolean goingToDestroy) {
+        List<AutoCloseable> resourcesToClose = super.getResourcesToClose(goingToDestroy);
+
+        resourcesToClose.add(hashIndexes::clear);
+        resourcesToClose.add(sortedIndexes::clear);
 
         resourcesToClose.add(() -> checkpointManager.removeCheckpointListener(checkpointListener));
 
diff --git a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/VolatilePageMemoryMvPartitionStorage.java b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/VolatilePageMemoryMvPartitionStorage.java
index 88f9a06182..362a3c3529 100644
--- a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/VolatilePageMemoryMvPartitionStorage.java
+++ b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/VolatilePageMemoryMvPartitionStorage.java
@@ -25,6 +25,8 @@ import java.util.List;
 import java.util.concurrent.CompletableFuture;
 import java.util.function.Predicate;
 import org.apache.ignite.internal.hlc.HybridTimestamp;
+import org.apache.ignite.internal.logger.IgniteLogger;
+import org.apache.ignite.internal.logger.Loggers;
 import org.apache.ignite.internal.pagememory.tree.BplusTree;
 import org.apache.ignite.internal.pagememory.util.GradualTaskExecutor;
 import org.apache.ignite.internal.pagememory.util.PageIdUtils;
@@ -45,6 +47,8 @@ import org.jetbrains.annotations.Nullable;
  * Implementation of {@link MvPartitionStorage} based on a {@link BplusTree} for in-memory case.
  */
 public class VolatilePageMemoryMvPartitionStorage extends AbstractPageMemoryMvPartitionStorage {
+    private static final IgniteLogger LOG = Loggers.forClass(VolatilePageMemoryMvPartitionStorage.class);
+
     private static final Predicate<HybridTimestamp> NEVER_LOAD_VALUE = ts -> false;
 
     private final GradualTaskExecutor destructionExecutor;
@@ -148,18 +152,63 @@ public class VolatilePageMemoryMvPartitionStorage extends AbstractPageMemoryMvPa
         this.lastAppliedTerm = lastAppliedTerm;
     }
 
+    @Override
+    protected List<AutoCloseable> getResourcesToClose(boolean goingToDestroy) {
+        List<AutoCloseable> resourcesToClose = super.getResourcesToClose(goingToDestroy);
+
+        if (!goingToDestroy) {
+            // If we are going to destroy after closure, we should retain indices because the destruction logic
+            // will need to destroy them as well. It will clean the maps after it starts the destruction.
+
+            resourcesToClose.add(hashIndexes::clear);
+            resourcesToClose.add(sortedIndexes::clear);
+        }
+
+        return resourcesToClose;
+    }
+
+    /**
+     * Cleans data backing this partition. Indices are destroyed, but index desscriptors are
+     * not removed from this partition so that they can be refilled with data later.
+     */
+    public void cleanStructuresData() {
+        destroyStructures(false);
+    }
+
+    /**
+     * Destroys internal structures (including indices) backing this partition.
+     */
+    public void destroyStructures() {
+        destroyStructures(true);
+    }
+
     /**
-     * Destroys internal structures backing this partition.
+     * Destroys internal structures (including indices) backing this partition.
      *
-     * @return future that completes when the destruction completes.
+     * @param removeIndexDescriptors Whether indices should be completely removed, not just their contents destroyed.
      */
-    public CompletableFuture<Void> destroyStructures() {
-        // TODO: IGNITE-18531 - destroy indices.
+    private void destroyStructures(boolean removeIndexDescriptors) {
+        startMvDataDestruction();
+        startIndexMetaTreeDestruction();
+
+        hashIndexes.values().forEach(indexStorage -> indexStorage.startDestructionOn(destructionExecutor));
+        sortedIndexes.values().forEach(indexStorage -> indexStorage.startDestructionOn(destructionExecutor));
+
+        if (removeIndexDescriptors) {
+            hashIndexes.clear();
+            sortedIndexes.clear();
+        }
+    }
 
+    private void startMvDataDestruction() {
         try {
-            return destructionExecutor.execute(
+            destructionExecutor.execute(
                     versionChainTree.startGradualDestruction(chainKey -> destroyVersionChain((VersionChain) chainKey), false)
-            );
+            ).whenComplete((res, ex) -> {
+                if (ex != null) {
+                    LOG.error("Version chains destruction failed in group={}, partition={}", ex, groupId, partitionId);
+                }
+            });
         } catch (IgniteInternalCheckedException e) {
             throw new StorageException("Cannot destroy MV partition in group=" + groupId + ", partition=" + partitionId, e);
         }
@@ -185,6 +234,20 @@ public class VolatilePageMemoryMvPartitionStorage extends AbstractPageMemoryMvPa
         }
     }
 
+    private void startIndexMetaTreeDestruction() {
+        try {
+            destructionExecutor.execute(
+                    indexMetaTree.startGradualDestruction(null, false)
+            ).whenComplete((res, ex) -> {
+                if (ex != null) {
+                    LOG.error("Index meta tree destruction failed in group={}, partition={}", ex, groupId, partitionId);
+                }
+            });
+        } catch (IgniteInternalCheckedException e) {
+            throw new StorageException("Cannot destroy index meta tree in group=" + groupId + ", partition=" + partitionId, e);
+        }
+    }
+
     @Override
     List<AutoCloseable> getResourcesToCloseOnRebalance() {
         return List.of(versionChainTree::close, indexMetaTree::close);
diff --git a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/io/VersionChainLeafIo.java b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/io/VersionChainLeafIo.java
index b0a98ddd3a..d21972b67a 100644
--- a/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/io/VersionChainLeafIo.java
+++ b/modules/storage-page-memory/src/main/java/org/apache/ignite/internal/storage/pagememory/mv/io/VersionChainLeafIo.java
@@ -67,7 +67,7 @@ public final class VersionChainLeafIo extends BplusLeafIo<VersionChainKey> imple
     }
 
     @Override
-    public void visit(long pageAddr, Consumer<VersionChainKey> c) {
+    public void visit(BplusTree<VersionChainKey, ?> tree, long pageAddr, Consumer<VersionChainKey> c) {
         int partitionId = getPartitionId(pageAddr);
 
         int count = getCount(pageAddr);
diff --git a/modules/storage-page-memory/src/test/java/org/apache/ignite/internal/storage/pagememory/VolatilePageMemoryMvTableStorageTest.java b/modules/storage-page-memory/src/test/java/org/apache/ignite/internal/storage/pagememory/VolatilePageMemoryMvTableStorageTest.java
index b701c8963d..f9b75766d2 100644
--- a/modules/storage-page-memory/src/test/java/org/apache/ignite/internal/storage/pagememory/VolatilePageMemoryMvTableStorageTest.java
+++ b/modules/storage-page-memory/src/test/java/org/apache/ignite/internal/storage/pagememory/VolatilePageMemoryMvTableStorageTest.java
@@ -23,19 +23,28 @@ import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
 
+import java.nio.ByteBuffer;
+import org.apache.ignite.internal.binarytuple.BinaryTupleBuilder;
 import org.apache.ignite.internal.configuration.testframework.ConfigurationExtension;
 import org.apache.ignite.internal.configuration.testframework.InjectConfiguration;
 import org.apache.ignite.internal.pagememory.evict.PageEvictionTracker;
 import org.apache.ignite.internal.pagememory.evict.PageEvictionTrackerNoOp;
 import org.apache.ignite.internal.pagememory.io.PageIoRegistry;
+import org.apache.ignite.internal.schema.BinaryTuple;
+import org.apache.ignite.internal.schema.BinaryTupleSchema;
+import org.apache.ignite.internal.schema.BinaryTupleSchema.Element;
+import org.apache.ignite.internal.schema.NativeTypes;
 import org.apache.ignite.internal.schema.TableRow;
 import org.apache.ignite.internal.schema.configuration.TablesConfiguration;
 import org.apache.ignite.internal.storage.AbstractMvTableStorageTest;
 import org.apache.ignite.internal.storage.MvPartitionStorage;
 import org.apache.ignite.internal.storage.RowId;
+import org.apache.ignite.internal.storage.index.HashIndexStorage;
+import org.apache.ignite.internal.storage.index.IndexRowImpl;
+import org.apache.ignite.internal.storage.index.SortedIndexStorage;
 import org.apache.ignite.internal.storage.pagememory.configuration.schema.VolatilePageMemoryStorageEngineConfiguration;
 import org.apache.ignite.lang.IgniteInternalCheckedException;
 import org.junit.jupiter.api.BeforeEach;
@@ -75,16 +84,19 @@ public class VolatilePageMemoryMvTableStorageTest extends AbstractMvTableStorage
 
         assertThat(tableStorage.destroyPartition(0), willSucceedFast());
 
-        assertDestructionCompletes(emptyDataPagesBeforeDestroy);
+        assertMvDataDestructionCompletes(emptyDataPagesBeforeDestroy);
     }
 
-    private void assertDestructionCompletes(long emptyDataPagesBeforeDestroy) throws InterruptedException, IgniteInternalCheckedException {
+    private void assertMvDataDestructionCompletes(long emptyDataPagesBeforeDestroy)
+            throws InterruptedException, IgniteInternalCheckedException {
         assertTrue(waitForCondition(
                 () -> dataRegion().rowVersionFreeList().emptyDataPages() > emptyDataPagesBeforeDestroy,
                 5_000
         ));
 
-        verify(pageEvictionTracker, times(1)).forgetPage(anyLong());
+        // Make sure that some page storing row versions gets emptied (so we can be sure that row versions
+        // get removed).
+        verify(pageEvictionTracker, timeout(5_000)).forgetPage(anyLong());
     }
 
     private void insertOneRow(MvPartitionStorage partitionStorage) {
@@ -107,7 +119,102 @@ public class VolatilePageMemoryMvTableStorageTest extends AbstractMvTableStorage
 
         assertThat(tableStorage.destroy(), willSucceedFast());
 
-        assertDestructionCompletes(emptyDataPagesBeforeDestroy);
+        assertMvDataDestructionCompletes(emptyDataPagesBeforeDestroy);
+    }
+
+    @Test
+    void partitionDestructionFreesHashIndexPages() throws Exception {
+        tableStorage.getOrCreateMvPartition(0);
+
+        HashIndexStorage indexStorage = tableStorage.getOrCreateHashIndex(0, hashIdx.id());
+
+        indexStorage.put(nonInlinableIndexRow());
+
+        // Using RowVersionFreeList to track removal because RowVersionFreeList is used as a ReuseList for IndexColumnsFreeList.
+        long emptyIndexPagesBeforeDestroy = dataRegion().rowVersionFreeList().emptyDataPages();
+
+        assertThat(tableStorage.destroyPartition(0), willSucceedFast());
+
+        assertIndexDataDestructionCompletes(emptyIndexPagesBeforeDestroy);
+    }
+
+    private static IndexRowImpl nonInlinableIndexRow() {
+        RowId rowId = new RowId(PARTITION_ID);
+
+        BinaryTupleSchema schema = BinaryTupleSchema.create(new Element[]{
+                new Element(NativeTypes.INT32, false),
+                new Element(NativeTypes.stringOf(300), false)
+        });
+
+        ByteBuffer buffer = new BinaryTupleBuilder(schema.elementCount(), schema.hasNullableElements())
+                .appendInt(1)
+                .appendString("a".repeat(300))
+                .build();
+
+        BinaryTuple tuple = new BinaryTuple(schema, buffer);
+
+        return new IndexRowImpl(tuple, rowId);
+    }
+
+    private void assertIndexDataDestructionCompletes(long emptyIndexPagesBeforeDestroy)
+            throws InterruptedException, IgniteInternalCheckedException {
+        // Using RowVersionFreeList to track removal because RowVersionFreeList is used as a ReuseList for IndexColumnsFreeList.
+        assertTrue(waitForCondition(
+                () -> dataRegion().rowVersionFreeList().emptyDataPages() > emptyIndexPagesBeforeDestroy,
+                5_000
+        ));
+
+        // Make sure that some page storing index columns gets emptied (so we can be sure that index columns
+        // get removed).
+        verify(pageEvictionTracker, timeout(5_000)).forgetPage(anyLong());
+    }
+
+    @Test
+    void partitionDestructionFreesSortedIndexPages() throws Exception {
+        tableStorage.getOrCreateMvPartition(0);
+
+        SortedIndexStorage indexStorage = tableStorage.getOrCreateSortedIndex(0, sortedIdx.id());
+
+        indexStorage.put(nonInlinableIndexRow());
+
+        // Using RowVersionFreeList to track removal because RowVersionFreeList is used as a ReuseList for IndexColumnsFreeList.
+        long emptyIndexPagesBeforeDestroy = dataRegion().rowVersionFreeList().emptyDataPages();
+
+        assertThat(tableStorage.destroyPartition(0), willSucceedFast());
+
+        assertIndexDataDestructionCompletes(emptyIndexPagesBeforeDestroy);
+    }
+
+    @Test
+    void tableStorageDestructionFreesHashIndexPages() throws Exception {
+        tableStorage.getOrCreateMvPartition(0);
+
+        HashIndexStorage indexStorage = tableStorage.getOrCreateHashIndex(0, hashIdx.id());
+
+        indexStorage.put(nonInlinableIndexRow());
+
+        // Using RowVersionFreeList to track removal because RowVersionFreeList is used as a ReuseList for IndexColumnsFreeList.
+        long emptyIndexPagesBeforeDestroy = dataRegion().rowVersionFreeList().emptyDataPages();
+
+        assertThat(tableStorage.destroy(), willSucceedFast());
+
+        assertIndexDataDestructionCompletes(emptyIndexPagesBeforeDestroy);
+    }
+
+    @Test
+    void tableStorageDestructionFreesSortedIndexPages() throws Exception {
+        tableStorage.getOrCreateMvPartition(0);
+
+        SortedIndexStorage indexStorage = tableStorage.getOrCreateSortedIndex(0, sortedIdx.id());
+
+        indexStorage.put(nonInlinableIndexRow());
+
+        // Using RowVersionFreeList to track removal because RowVersionFreeList is used as a ReuseList for IndexColumnsFreeList.
+        long emptyIndexPagesBeforeDestroy = dataRegion().rowVersionFreeList().emptyDataPages();
+
+        assertThat(tableStorage.destroy(), willSucceedFast());
+
+        assertIndexDataDestructionCompletes(emptyIndexPagesBeforeDestroy);
     }
 
     private VolatilePageMemoryDataRegion dataRegion() {