You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@ignite.apache.org by GitBox <gi...@apache.org> on 2022/03/31 15:40:50 UTC

[GitHub] [ignite-3] sashapolo commented on a change in pull request #739: IGNITE-16697 MV storage methods and reference implementation

sashapolo commented on a change in pull request #739:
URL: https://github.com/apache/ignite-3/pull/739#discussion_r839616044



##########
File path: modules/storage-api/src/main/java/org/apache/ignite/internal/storage/MvPartitionStorage.java
##########
@@ -0,0 +1,96 @@
+/*
+ * 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;
+
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Predicate;
+import org.apache.ignite.internal.schema.BinaryRow;
+import org.apache.ignite.internal.tx.Timestamp;
+import org.apache.ignite.internal.util.Cursor;
+import org.apache.ignite.lang.IgniteException;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Multi-versioned partition storage.
+ * POC version, that represents a combination between a replicated TX-aware MV storage and physical MV storage. Their API are very similar,
+ * althouh there are very important differences that will be addressed in the future.
+ */
+public interface MvPartitionStorage {
+    /**
+     * Exception class that describes the situation where two independant transactions attempting to write values for the same key.

Review comment:
       ```suggestion
        * Exception class that describes the situation when two independent transactions attempt to write values for the same key.
   ```

##########
File path: modules/storage-api/src/main/java/org/apache/ignite/internal/storage/MvPartitionStorage.java
##########
@@ -0,0 +1,96 @@
+/*
+ * 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;
+
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Predicate;
+import org.apache.ignite.internal.schema.BinaryRow;
+import org.apache.ignite.internal.tx.Timestamp;
+import org.apache.ignite.internal.util.Cursor;
+import org.apache.ignite.lang.IgniteException;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Multi-versioned partition storage.
+ * POC version, that represents a combination between a replicated TX-aware MV storage and physical MV storage. Their API are very similar,
+ * althouh there are very important differences that will be addressed in the future.
+ */
+public interface MvPartitionStorage {
+    /**
+     * Exception class that describes the situation where two independant transactions attempting to write values for the same key.
+     */
+    class TxIdMismatchException extends IgniteException {

Review comment:
       I would prefer extracting this class into an upper level.

##########
File path: modules/storage-api/src/main/java/org/apache/ignite/internal/storage/MvPartitionStorage.java
##########
@@ -0,0 +1,96 @@
+/*
+ * 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;
+
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Predicate;
+import org.apache.ignite.internal.schema.BinaryRow;
+import org.apache.ignite.internal.tx.Timestamp;
+import org.apache.ignite.internal.util.Cursor;
+import org.apache.ignite.lang.IgniteException;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Multi-versioned partition storage.
+ * POC version, that represents a combination between a replicated TX-aware MV storage and physical MV storage. Their API are very similar,
+ * althouh there are very important differences that will be addressed in the future.
+ */
+public interface MvPartitionStorage {
+    /**
+     * Exception class that describes the situation where two independant transactions attempting to write values for the same key.
+     */
+    class TxIdMismatchException extends IgniteException {
+    }
+
+    /**
+     * Reads the value from the storage as it was at the given timestamp.
+     *
+     * @param key Key.
+     * @param timestamp Timestamp.
+     * @return Binary row that corresponds to the key or {@code null} if value is not found.
+     */
+    @Nullable
+    BinaryRow read(BinaryRow key, @Nullable Timestamp timestamp);
+
+    /**
+     * Creates uncommited version, assigned to the passed transaction id..

Review comment:
       ```suggestion
        * Creates an uncommitted version, assigned to the given transaction id.
   ```

##########
File path: modules/storage-api/src/main/java/org/apache/ignite/internal/storage/MvPartitionStorage.java
##########
@@ -0,0 +1,96 @@
+/*
+ * 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;
+
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Predicate;
+import org.apache.ignite.internal.schema.BinaryRow;
+import org.apache.ignite.internal.tx.Timestamp;
+import org.apache.ignite.internal.util.Cursor;
+import org.apache.ignite.lang.IgniteException;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Multi-versioned partition storage.
+ * POC version, that represents a combination between a replicated TX-aware MV storage and physical MV storage. Their API are very similar,
+ * althouh there are very important differences that will be addressed in the future.
+ */
+public interface MvPartitionStorage {
+    /**
+     * Exception class that describes the situation where two independant transactions attempting to write values for the same key.
+     */
+    class TxIdMismatchException extends IgniteException {
+    }
+
+    /**
+     * Reads the value from the storage as it was at the given timestamp.

Review comment:
       You should also specify, what a `null` timestamp means

##########
File path: modules/storage-api/src/main/java/org/apache/ignite/internal/storage/MvPartitionStorage.java
##########
@@ -0,0 +1,96 @@
+/*
+ * 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;
+
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Predicate;
+import org.apache.ignite.internal.schema.BinaryRow;
+import org.apache.ignite.internal.tx.Timestamp;
+import org.apache.ignite.internal.util.Cursor;
+import org.apache.ignite.lang.IgniteException;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Multi-versioned partition storage.
+ * POC version, that represents a combination between a replicated TX-aware MV storage and physical MV storage. Their API are very similar,
+ * althouh there are very important differences that will be addressed in the future.

Review comment:
       ```suggestion
    * although there are very important differences that will be addressed in the future.
   ```

##########
File path: modules/storage-api/src/main/java/org/apache/ignite/internal/storage/MvPartitionStorage.java
##########
@@ -0,0 +1,96 @@
+/*
+ * 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;
+
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Predicate;
+import org.apache.ignite.internal.schema.BinaryRow;
+import org.apache.ignite.internal.tx.Timestamp;
+import org.apache.ignite.internal.util.Cursor;
+import org.apache.ignite.lang.IgniteException;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Multi-versioned partition storage.
+ * POC version, that represents a combination between a replicated TX-aware MV storage and physical MV storage. Their API are very similar,
+ * althouh there are very important differences that will be addressed in the future.
+ */
+public interface MvPartitionStorage {
+    /**
+     * Exception class that describes the situation where two independant transactions attempting to write values for the same key.
+     */
+    class TxIdMismatchException extends IgniteException {
+    }
+
+    /**
+     * Reads the value from the storage as it was at the given timestamp.
+     *
+     * @param key Key.
+     * @param timestamp Timestamp.
+     * @return Binary row that corresponds to the key or {@code null} if value is not found.
+     */
+    @Nullable
+    BinaryRow read(BinaryRow key, @Nullable Timestamp timestamp);
+
+    /**
+     * Creates uncommited version, assigned to the passed transaction id..
+     *
+     * @param row Binary row to update. Key only row means value removal.
+     * @param txId Transaction id.
+     * @throws TxIdMismatchException If there's another pending update associated with different transaction id.
+     * @throws StorageException If failed to write data to the storage.
+     */
+    void addWrite(BinaryRow row, UUID txId) throws TxIdMismatchException, StorageException;
+
+    /**
+     * Aborts a pending update of the ongoing uncommited transaction. Invoked during rollback.
+     *
+     * @param key Key.
+     * @throws StorageException If failed to write data to the storage.
+     */
+    void abortWrite(BinaryRow key) throws StorageException;
+
+    /**
+     * Commits a pending update of the ongoing transaction. Invoked during commit. Commited value will be versioned by the given timestamp.
+     *
+     * @param key Key.
+     * @param timestamp Timestamp to associate with commited value.
+     * @throws StorageException If failed to write data to the storage.
+     */
+    void commitWrite(BinaryRow key, Timestamp timestamp) throws StorageException;
+
+    /**
+     * Removes data associated with old timestamps.
+     *
+     * @param from Start of hashes range to process. Inclusive.
+     * @param to End of hashes range to process. Inclusive.
+     * @param timestamp Timestamp to remove all the data with a lesser timestamp.
+     * @return Future for the operation.
+     */
+    CompletableFuture<?> cleanup(int from, int to, Timestamp timestamp);

Review comment:
       IDEA tells me that this method is never used, which means it is not covered by tests

##########
File path: modules/storage-api/src/main/java/org/apache/ignite/internal/storage/MvPartitionStorage.java
##########
@@ -0,0 +1,96 @@
+/*
+ * 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;
+
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Predicate;
+import org.apache.ignite.internal.schema.BinaryRow;
+import org.apache.ignite.internal.tx.Timestamp;
+import org.apache.ignite.internal.util.Cursor;
+import org.apache.ignite.lang.IgniteException;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Multi-versioned partition storage.
+ * POC version, that represents a combination between a replicated TX-aware MV storage and physical MV storage. Their API are very similar,
+ * althouh there are very important differences that will be addressed in the future.
+ */
+public interface MvPartitionStorage {
+    /**
+     * Exception class that describes the situation where two independant transactions attempting to write values for the same key.
+     */
+    class TxIdMismatchException extends IgniteException {
+    }
+
+    /**
+     * Reads the value from the storage as it was at the given timestamp.
+     *
+     * @param key Key.
+     * @param timestamp Timestamp.
+     * @return Binary row that corresponds to the key or {@code null} if value is not found.
+     */
+    @Nullable
+    BinaryRow read(BinaryRow key, @Nullable Timestamp timestamp);
+
+    /**
+     * Creates uncommited version, assigned to the passed transaction id..
+     *
+     * @param row Binary row to update. Key only row means value removal.
+     * @param txId Transaction id.
+     * @throws TxIdMismatchException If there's another pending update associated with different transaction id.
+     * @throws StorageException If failed to write data to the storage.
+     */
+    void addWrite(BinaryRow row, UUID txId) throws TxIdMismatchException, StorageException;
+
+    /**
+     * Aborts a pending update of the ongoing uncommited transaction. Invoked during rollback.
+     *
+     * @param key Key.
+     * @throws StorageException If failed to write data to the storage.
+     */
+    void abortWrite(BinaryRow key) throws StorageException;
+
+    /**
+     * Commits a pending update of the ongoing transaction. Invoked during commit. Commited value will be versioned by the given timestamp.
+     *
+     * @param key Key.
+     * @param timestamp Timestamp to associate with commited value.
+     * @throws StorageException If failed to write data to the storage.
+     */
+    void commitWrite(BinaryRow key, Timestamp timestamp) throws StorageException;
+
+    /**
+     * Removes data associated with old timestamps.
+     *
+     * @param from Start of hashes range to process. Inclusive.

Review comment:
       What are these "hashes ranges"? How would one obtain a hash range?

##########
File path: modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/SortedIndexDescriptor.java
##########
@@ -166,7 +166,7 @@ public SortedIndexDescriptor(String name, TableView tableConfig) {
     /**
      * Creates a {@link SchemaDescriptor} from a list of index key columns.
      */
-    private static SchemaDescriptor createSchemaDescriptor(List<ColumnView> indexKeyColumnViews) {
+    public static SchemaDescriptor createSchemaDescriptor(List<ColumnView> indexKeyColumnViews) {

Review comment:
       Looks like this change is not needed

##########
File path: modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/SortedIndexMvStorage.java
##########
@@ -0,0 +1,95 @@
+/*
+ * 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 java.util.function.IntPredicate;
+import org.apache.ignite.internal.schema.BinaryRow;
+import org.apache.ignite.internal.tx.Timestamp;
+import org.apache.ignite.internal.util.Cursor;
+import org.intellij.lang.annotations.MagicConstant;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Storage for a sorted index.
+ * POC version, that represents a combination between a replicated TX-aware MV storage and physical MV storage. Real future implementation
+ * will be defined later. Things to notice here: TX-aware implementation should have a projection bitset instead of full row reading.
+ * Physical storage API will be enriched with append/remove methods, like in reference implementation.
+ */
+public interface SortedIndexMvStorage {
+    /** Exclude lower bound. */
+    int GREATER = 0;

Review comment:
       Again, IDEA tells me that both `GREATER` and `LESS` constants are never used, which means they are not covered by tests

##########
File path: modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/SortedIndexMvStorage.java
##########
@@ -0,0 +1,95 @@
+/*
+ * 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 java.util.function.IntPredicate;
+import org.apache.ignite.internal.schema.BinaryRow;
+import org.apache.ignite.internal.tx.Timestamp;
+import org.apache.ignite.internal.util.Cursor;
+import org.intellij.lang.annotations.MagicConstant;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Storage for a sorted index.
+ * POC version, that represents a combination between a replicated TX-aware MV storage and physical MV storage. Real future implementation
+ * will be defined later. Things to notice here: TX-aware implementation should have a projection bitset instead of full row reading.
+ * Physical storage API will be enriched with append/remove methods, like in reference implementation.
+ */
+public interface SortedIndexMvStorage {
+    /** Exclude lower bound. */
+    int GREATER = 0;
+
+    /** Include lower bound. */
+    int GREATER_OR_EQUAL = 1;
+
+    /** Exclude upper bound. */
+    int LESS = 0;
+
+    /** Include upper bound. */
+    int LESS_OR_EQUAL = 1 << 1;
+
+    /** Forward scan. */
+    int FORWARD = 0;
+
+    /** Backwards scan. */
+    int BACKWARDS = 1 << 2;
+
+    /**
+     * The sole purpose of this class is to avoid massive refactoring while changing the original IndexRow.
+     */
+    interface IndexRowEx {
+        /**
+         * Key-only binary row if index-only scan is supported, full binary row otherwise.
+         */
+        BinaryRow row();
+
+        /**
+         * Returns indexed column value.
+         *
+         * @param idx PK column index.
+         * @return Indexed column value.
+         */
+        Object value(int idx);
+    }
+
+    boolean supportsBackwardsScan();
+
+    boolean supportsIndexOnlyScan();
+
+    /**
+     * Returns a range of index values between the lower bound and the upper bound, consistent with the passed timestamp.
+     *
+     * @param lowerBound Lower bound. Exclusivity is controlled by a {@link #GREATER_OR_EQUAL} or {@link #GREATER} flag.
+     *      {@code null} means unbounded.
+     * @param upperBound Upper bound. Exclusivity is controlled by a {@link #LESS} or {@link #LESS_OR_EQUAL} flag.
+     *      {@code null} means unbounded.
+     * @param flags Control flags. {@link #GREATER} | {@link #LESS} | {@link #FORWARD} by default. Other available values
+     *      are {@link #GREATER_OR_EQUAL}, {@link #LESS_OR_EQUAL} and {@link #BACKWARDS}.
+     * @param timestamp Timestamp value for consistent multiversioned index scan.

Review comment:
       ```suggestion
        * @param timestamp Timestamp value for consistent multi-versioned index scan.
   ```

##########
File path: modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/SortedIndexMvStorage.java
##########
@@ -0,0 +1,95 @@
+/*
+ * 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 java.util.function.IntPredicate;
+import org.apache.ignite.internal.schema.BinaryRow;
+import org.apache.ignite.internal.tx.Timestamp;
+import org.apache.ignite.internal.util.Cursor;
+import org.intellij.lang.annotations.MagicConstant;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Storage for a sorted index.
+ * POC version, that represents a combination between a replicated TX-aware MV storage and physical MV storage. Real future implementation
+ * will be defined later. Things to notice here: TX-aware implementation should have a projection bitset instead of full row reading.
+ * Physical storage API will be enriched with append/remove methods, like in reference implementation.
+ */
+public interface SortedIndexMvStorage {
+    /** Exclude lower bound. */
+    int GREATER = 0;
+
+    /** Include lower bound. */
+    int GREATER_OR_EQUAL = 1;
+
+    /** Exclude upper bound. */
+    int LESS = 0;
+
+    /** Include upper bound. */
+    int LESS_OR_EQUAL = 1 << 1;
+
+    /** Forward scan. */
+    int FORWARD = 0;
+
+    /** Backwards scan. */
+    int BACKWARDS = 1 << 2;
+
+    /**
+     * The sole purpose of this class is to avoid massive refactoring while changing the original IndexRow.
+     */
+    interface IndexRowEx {
+        /**
+         * Key-only binary row if index-only scan is supported, full binary row otherwise.
+         */
+        BinaryRow row();
+
+        /**
+         * Returns indexed column value.
+         *
+         * @param idx PK column index.
+         * @return Indexed column value.
+         */
+        Object value(int idx);
+    }
+
+    boolean supportsBackwardsScan();
+
+    boolean supportsIndexOnlyScan();
+
+    /**
+     * Returns a range of index values between the lower bound and the upper bound, consistent with the passed timestamp.

Review comment:
       What does "consistent" mean here?

##########
File path: modules/storage-api/src/test/java/org/apache/ignite/internal/storage/BaseMvStoragesTest.java
##########
@@ -0,0 +1,180 @@
+/*
+ * 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;
+
+import java.util.Locale;
+import java.util.Objects;
+import org.apache.ignite.internal.schema.BinaryRow;
+import org.apache.ignite.internal.schema.Column;
+import org.apache.ignite.internal.schema.NativeTypes;
+import org.apache.ignite.internal.schema.SchemaDescriptor;
+import org.apache.ignite.internal.schema.marshaller.KvMarshaller;
+import org.apache.ignite.internal.schema.marshaller.MarshallerException;
+import org.apache.ignite.internal.schema.marshaller.MarshallerFactory;
+import org.apache.ignite.internal.schema.marshaller.reflection.ReflectionMarshallerFactory;
+import org.apache.ignite.internal.schema.row.Row;
+import org.apache.ignite.lang.IgniteException;
+import org.jetbrains.annotations.Nullable;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+
+/**
+ * Base test for MV storages, contains pojo classes, their descriptor and a marshaller instance.
+ */
+public class BaseMvStoragesTest {
+    /** Default reflection marshaller factory. */
+    protected static MarshallerFactory marshallerFactory;
+
+    /** Schema descriptor for tests. */
+    protected static SchemaDescriptor schemaDescriptor;
+
+    /** Key-value marshaller for tests. */
+    protected static KvMarshaller<TestKey, TestValue> kvMarshaller;
+
+    @BeforeAll
+    static void beforeAll() {
+        marshallerFactory = new ReflectionMarshallerFactory();
+
+        schemaDescriptor = new SchemaDescriptor(1, new Column[]{
+                new Column("intKey".toUpperCase(Locale.ROOT), NativeTypes.INT32, false),
+                new Column("strKey".toUpperCase(Locale.ROOT), NativeTypes.STRING, false),
+        }, new Column[]{
+                new Column("intVal".toUpperCase(Locale.ROOT), NativeTypes.INT32, false),
+                new Column("strVal".toUpperCase(Locale.ROOT), NativeTypes.STRING, false),
+        });
+
+        kvMarshaller = marshallerFactory.create(schemaDescriptor, TestKey.class, TestValue.class);
+    }
+
+    @AfterAll
+    static void afterAll() {
+        kvMarshaller = null;
+        schemaDescriptor = null;
+        marshallerFactory = null;
+    }
+
+    protected BinaryRow binaryKey(TestKey key) {

Review comment:
       Looks like all these methods can be `static`

##########
File path: modules/storage-api/src/test/java/org/apache/ignite/internal/storage/basic/TestMvPartitionStorage.java
##########
@@ -0,0 +1,172 @@
+/*
+ * 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.basic;
+
+import java.nio.ByteBuffer;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Predicate;
+import org.apache.ignite.internal.schema.BinaryRow;
+import org.apache.ignite.internal.storage.MvPartitionStorage;
+import org.apache.ignite.internal.tx.Timestamp;
+import org.apache.ignite.internal.util.Cursor;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Test implementation of MV partition storage.
+ */
+public class TestMvPartitionStorage implements MvPartitionStorage {
+    private static final VersionChain NULL = new VersionChain(null, null, null, null);
+
+    private final ConcurrentHashMap<ByteBuffer, VersionChain> map = new ConcurrentHashMap<>();
+
+    private final List<TestSortedIndexMvStorage> indexes;
+
+    public TestMvPartitionStorage(List<TestSortedIndexMvStorage> indexes) {
+        this.indexes = indexes;
+    }
+
+    private static class VersionChain {
+        final BinaryRow row;
+        final Timestamp begin;
+        final UUID txId;
+        final VersionChain next;
+
+        VersionChain(BinaryRow row, Timestamp begin, UUID txId, VersionChain next) {
+            this.row = row;
+            this.begin = begin;
+            this.txId = txId;
+            this.next = next;
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void addWrite(BinaryRow row, UUID txId) throws TxIdMismatchException {
+        //TODO Make it idempotent?
+        map.compute(row.keySlice(), (keyBuf, versionChain) -> {
+            if (versionChain != null && versionChain.begin == null && !txId.equals(versionChain.txId)) {
+                throw new TxIdMismatchException();
+            }
+
+            return new VersionChain(row, null, txId, versionChain);
+        });
+
+        for (TestSortedIndexMvStorage index : indexes) {
+            index.append(row);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void abortWrite(BinaryRow key) {
+        map.merge(key.keySlice(), NULL, (versionChain, ignored) -> {
+            assert versionChain != null;
+            assert versionChain.begin == null && versionChain.txId != null;
+
+            BinaryRow aborted = versionChain.row;
+
+            for (TestSortedIndexMvStorage index : indexes) {
+                abortWrite(versionChain.next, aborted, index);
+            }
+
+            return versionChain.next;
+        });
+    }
+
+    private void abortWrite(VersionChain head, BinaryRow aborted, TestSortedIndexMvStorage index) {
+        for (VersionChain cur = head; cur != null; cur = cur.next) {
+            if (index.matches(aborted, cur.row)) {
+                return;
+            }
+        }
+
+        index.remove(aborted);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void commitWrite(BinaryRow key, Timestamp timestamp) {
+        map.compute(key.keySlice(), (keyBuf, versionChain) -> {
+            assert versionChain != null;
+            assert versionChain.begin == null && versionChain.txId != null;
+
+            return new VersionChain(versionChain.row, timestamp, null, versionChain.next);
+        });
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    @Nullable
+    public BinaryRow read(BinaryRow key, @Nullable Timestamp timestamp) {
+        VersionChain versionChain = map.get(key.keySlice());
+
+        return read(versionChain, timestamp);
+    }
+
+    @Nullable
+    private BinaryRow read(VersionChain versionChain, @Nullable Timestamp timestamp) {
+        if (versionChain == null) {
+            return null;
+        }
+
+        if (timestamp == null) {
+            return versionChain.row;
+        }
+
+        VersionChain cur = versionChain;
+
+        if (cur.begin == null) {
+            cur = cur.next;
+        }
+
+        while (cur != null) {
+            if (timestamp.compareTo(cur.begin) >= 0) {
+                BinaryRow row = cur.row;
+
+                return row.hasValue() ? row : null;
+            }
+
+            cur = cur.next;
+        }
+
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Cursor<BinaryRow> scan(Predicate<BinaryRow> keyFilter, @Nullable Timestamp timestamp) {
+        Iterator<BinaryRow> iterator = map.values().stream()
+                .map(versionChain -> read(versionChain, timestamp))
+                .filter(Objects::nonNull)
+                .filter(keyFilter::test)

Review comment:
       ```suggestion
                   .filter(keyFilter)
   ```

##########
File path: modules/storage-api/src/test/java/org/apache/ignite/internal/storage/basic/TestMvPartitionStorage.java
##########
@@ -0,0 +1,172 @@
+/*
+ * 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.basic;
+
+import java.nio.ByteBuffer;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Predicate;
+import org.apache.ignite.internal.schema.BinaryRow;
+import org.apache.ignite.internal.storage.MvPartitionStorage;
+import org.apache.ignite.internal.tx.Timestamp;
+import org.apache.ignite.internal.util.Cursor;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Test implementation of MV partition storage.
+ */
+public class TestMvPartitionStorage implements MvPartitionStorage {
+    private static final VersionChain NULL = new VersionChain(null, null, null, null);
+
+    private final ConcurrentHashMap<ByteBuffer, VersionChain> map = new ConcurrentHashMap<>();
+
+    private final List<TestSortedIndexMvStorage> indexes;
+
+    public TestMvPartitionStorage(List<TestSortedIndexMvStorage> indexes) {
+        this.indexes = indexes;
+    }
+
+    private static class VersionChain {
+        final BinaryRow row;
+        final Timestamp begin;
+        final UUID txId;
+        final VersionChain next;
+
+        VersionChain(BinaryRow row, Timestamp begin, UUID txId, VersionChain next) {
+            this.row = row;
+            this.begin = begin;
+            this.txId = txId;
+            this.next = next;
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void addWrite(BinaryRow row, UUID txId) throws TxIdMismatchException {
+        //TODO Make it idempotent?
+        map.compute(row.keySlice(), (keyBuf, versionChain) -> {
+            if (versionChain != null && versionChain.begin == null && !txId.equals(versionChain.txId)) {
+                throw new TxIdMismatchException();
+            }
+
+            return new VersionChain(row, null, txId, versionChain);
+        });
+
+        for (TestSortedIndexMvStorage index : indexes) {
+            index.append(row);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void abortWrite(BinaryRow key) {
+        map.merge(key.keySlice(), NULL, (versionChain, ignored) -> {
+            assert versionChain != null;
+            assert versionChain.begin == null && versionChain.txId != null;
+
+            BinaryRow aborted = versionChain.row;
+
+            for (TestSortedIndexMvStorage index : indexes) {
+                abortWrite(versionChain.next, aborted, index);
+            }
+
+            return versionChain.next;
+        });
+    }
+
+    private void abortWrite(VersionChain head, BinaryRow aborted, TestSortedIndexMvStorage index) {
+        for (VersionChain cur = head; cur != null; cur = cur.next) {
+            if (index.matches(aborted, cur.row)) {
+                return;
+            }
+        }
+
+        index.remove(aborted);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void commitWrite(BinaryRow key, Timestamp timestamp) {
+        map.compute(key.keySlice(), (keyBuf, versionChain) -> {
+            assert versionChain != null;

Review comment:
       Should we actually throw an exception here? Should we also specify it in the interface?

##########
File path: modules/storage-api/src/test/java/org/apache/ignite/internal/storage/basic/TestSortedIndexMvStorage.java
##########
@@ -0,0 +1,260 @@
+/*
+ * 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.basic;
+
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NavigableSet;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentSkipListSet;
+import java.util.function.IntPredicate;
+import java.util.function.ToIntFunction;
+import org.apache.ignite.configuration.NamedListView;
+import org.apache.ignite.configuration.schemas.table.ColumnView;
+import org.apache.ignite.configuration.schemas.table.IndexColumnView;
+import org.apache.ignite.configuration.schemas.table.SortedIndexView;
+import org.apache.ignite.configuration.schemas.table.TableIndexView;
+import org.apache.ignite.configuration.schemas.table.TableView;
+import org.apache.ignite.internal.schema.BinaryRow;
+import org.apache.ignite.internal.schema.NativeType;
+import org.apache.ignite.internal.schema.SchemaDescriptor;
+import org.apache.ignite.internal.schema.configuration.SchemaConfigurationConverter;
+import org.apache.ignite.internal.schema.configuration.SchemaDescriptorConverter;
+import org.apache.ignite.internal.schema.row.Row;
+import org.apache.ignite.internal.storage.index.IndexRowPrefix;
+import org.apache.ignite.internal.storage.index.PrefixComparator;
+import org.apache.ignite.internal.storage.index.SortedIndexMvStorage;
+import org.apache.ignite.internal.tx.Timestamp;
+import org.apache.ignite.internal.util.Cursor;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Test implementation of MV sorted index storage.
+ */
+public class TestSortedIndexMvStorage implements SortedIndexMvStorage {
+    private final TableView tableCfg;

Review comment:
       this field is not used

##########
File path: modules/storage-api/src/test/java/org/apache/ignite/internal/storage/basic/TestSortedIndexMvStorage.java
##########
@@ -0,0 +1,260 @@
+/*
+ * 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.basic;
+
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NavigableSet;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentSkipListSet;
+import java.util.function.IntPredicate;
+import java.util.function.ToIntFunction;
+import org.apache.ignite.configuration.NamedListView;
+import org.apache.ignite.configuration.schemas.table.ColumnView;
+import org.apache.ignite.configuration.schemas.table.IndexColumnView;
+import org.apache.ignite.configuration.schemas.table.SortedIndexView;
+import org.apache.ignite.configuration.schemas.table.TableIndexView;
+import org.apache.ignite.configuration.schemas.table.TableView;
+import org.apache.ignite.internal.schema.BinaryRow;
+import org.apache.ignite.internal.schema.NativeType;
+import org.apache.ignite.internal.schema.SchemaDescriptor;
+import org.apache.ignite.internal.schema.configuration.SchemaConfigurationConverter;
+import org.apache.ignite.internal.schema.configuration.SchemaDescriptorConverter;
+import org.apache.ignite.internal.schema.row.Row;
+import org.apache.ignite.internal.storage.index.IndexRowPrefix;
+import org.apache.ignite.internal.storage.index.PrefixComparator;
+import org.apache.ignite.internal.storage.index.SortedIndexMvStorage;
+import org.apache.ignite.internal.tx.Timestamp;
+import org.apache.ignite.internal.util.Cursor;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Test implementation of MV sorted index storage.
+ */
+public class TestSortedIndexMvStorage implements SortedIndexMvStorage {
+    private final TableView tableCfg;
+
+    private final NavigableSet<BinaryRow> index;
+
+    private final SchemaDescriptor descriptor;
+    private final Map<Integer, TestMvPartitionStorage> pk;
+
+    private final int partitions;
+
+    private final IndexColumnView[] indexColumns;
+
+    private final int[] columnIndexes;
+
+    private final NativeType[] nativeTypes;
+
+    protected TestSortedIndexMvStorage(

Review comment:
       why `protected`?

##########
File path: modules/storage-api/src/main/java/org/apache/ignite/internal/storage/MvPartitionStorage.java
##########
@@ -0,0 +1,96 @@
+/*
+ * 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;
+
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Predicate;
+import org.apache.ignite.internal.schema.BinaryRow;
+import org.apache.ignite.internal.tx.Timestamp;
+import org.apache.ignite.internal.util.Cursor;
+import org.apache.ignite.lang.IgniteException;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Multi-versioned partition storage.
+ * POC version, that represents a combination between a replicated TX-aware MV storage and physical MV storage. Their API are very similar,
+ * althouh there are very important differences that will be addressed in the future.
+ */
+public interface MvPartitionStorage {
+    /**
+     * Exception class that describes the situation where two independant transactions attempting to write values for the same key.
+     */
+    class TxIdMismatchException extends IgniteException {
+    }
+
+    /**
+     * Reads the value from the storage as it was at the given timestamp.
+     *
+     * @param key Key.
+     * @param timestamp Timestamp.
+     * @return Binary row that corresponds to the key or {@code null} if value is not found.
+     */
+    @Nullable
+    BinaryRow read(BinaryRow key, @Nullable Timestamp timestamp);
+
+    /**
+     * Creates uncommited version, assigned to the passed transaction id..
+     *
+     * @param row Binary row to update. Key only row means value removal.
+     * @param txId Transaction id.
+     * @throws TxIdMismatchException If there's another pending update associated with different transaction id.
+     * @throws StorageException If failed to write data to the storage.
+     */
+    void addWrite(BinaryRow row, UUID txId) throws TxIdMismatchException, StorageException;
+
+    /**
+     * Aborts a pending update of the ongoing uncommited transaction. Invoked during rollback.
+     *
+     * @param key Key.
+     * @throws StorageException If failed to write data to the storage.
+     */
+    void abortWrite(BinaryRow key) throws StorageException;
+
+    /**
+     * Commits a pending update of the ongoing transaction. Invoked during commit. Commited value will be versioned by the given timestamp.

Review comment:
       ```suggestion
        * Commits a pending update of the ongoing transaction. Invoked during commit. Committed value will be versioned by the given timestamp.
   ```

##########
File path: modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/PrefixComparator.java
##########
@@ -76,7 +74,18 @@ int compare(BinaryRow binaryRow) {
      * Compares a particular column of a {@code row} with the given value.
      */
     private static int compare(Column column, Row row, @Nullable Object value) {
-        boolean nullRow = row.hasNullValue(column.schemaIndex(), column.type().spec());
+        int schemaIndex = column.schemaIndex();
+
+        NativeTypeSpec typeSpec = column.type().spec();
+
+        return compareColumns(row, schemaIndex, typeSpec, value);
+    }
+
+    /**
+     * Compares a particular column of a {@code row} with the given value.
+     */
+    public static int compareColumns(Row row, int schemaIndex, NativeTypeSpec typeSpec, Object value) {

Review comment:
       This is also a huge abstraction leak, can we get rid of it somehow?

##########
File path: modules/storage-api/src/main/java/org/apache/ignite/internal/storage/MvPartitionStorage.java
##########
@@ -0,0 +1,96 @@
+/*
+ * 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;
+
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Predicate;
+import org.apache.ignite.internal.schema.BinaryRow;
+import org.apache.ignite.internal.tx.Timestamp;
+import org.apache.ignite.internal.util.Cursor;
+import org.apache.ignite.lang.IgniteException;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Multi-versioned partition storage.
+ * POC version, that represents a combination between a replicated TX-aware MV storage and physical MV storage. Their API are very similar,
+ * althouh there are very important differences that will be addressed in the future.
+ */
+public interface MvPartitionStorage {
+    /**
+     * Exception class that describes the situation where two independant transactions attempting to write values for the same key.
+     */
+    class TxIdMismatchException extends IgniteException {
+    }
+
+    /**
+     * Reads the value from the storage as it was at the given timestamp.
+     *
+     * @param key Key.
+     * @param timestamp Timestamp.
+     * @return Binary row that corresponds to the key or {@code null} if value is not found.
+     */
+    @Nullable
+    BinaryRow read(BinaryRow key, @Nullable Timestamp timestamp);
+
+    /**
+     * Creates uncommited version, assigned to the passed transaction id..
+     *
+     * @param row Binary row to update. Key only row means value removal.
+     * @param txId Transaction id.
+     * @throws TxIdMismatchException If there's another pending update associated with different transaction id.
+     * @throws StorageException If failed to write data to the storage.
+     */
+    void addWrite(BinaryRow row, UUID txId) throws TxIdMismatchException, StorageException;
+
+    /**
+     * Aborts a pending update of the ongoing uncommited transaction. Invoked during rollback.

Review comment:
       ```suggestion
        * Aborts a pending update of the ongoing uncommitted transaction. Invoked during rollback.
   ```

##########
File path: modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/PrefixComparator.java
##########
@@ -76,7 +74,18 @@ int compare(BinaryRow binaryRow) {
      * Compares a particular column of a {@code row} with the given value.
      */
     private static int compare(Column column, Row row, @Nullable Object value) {
-        boolean nullRow = row.hasNullValue(column.schemaIndex(), column.type().spec());
+        int schemaIndex = column.schemaIndex();
+
+        NativeTypeSpec typeSpec = column.type().spec();
+
+        return compareColumns(row, schemaIndex, typeSpec, value);
+    }
+
+    /**
+     * Compares a particular column of a {@code row} with the given value.
+     */
+    public static int compareColumns(Row row, int schemaIndex, NativeTypeSpec typeSpec, Object value) {

Review comment:
       ```suggestion
       public static int compareColumns(Row row, int schemaIndex, NativeTypeSpec typeSpec, @Nullable Object value) {
   ```

##########
File path: modules/storage-api/src/main/java/org/apache/ignite/internal/storage/index/SortedIndexMvStorage.java
##########
@@ -0,0 +1,95 @@
+/*
+ * 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 java.util.function.IntPredicate;
+import org.apache.ignite.internal.schema.BinaryRow;
+import org.apache.ignite.internal.tx.Timestamp;
+import org.apache.ignite.internal.util.Cursor;
+import org.intellij.lang.annotations.MagicConstant;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Storage for a sorted index.
+ * POC version, that represents a combination between a replicated TX-aware MV storage and physical MV storage. Real future implementation
+ * will be defined later. Things to notice here: TX-aware implementation should have a projection bitset instead of full row reading.
+ * Physical storage API will be enriched with append/remove methods, like in reference implementation.
+ */
+public interface SortedIndexMvStorage {
+    /** Exclude lower bound. */
+    int GREATER = 0;
+
+    /** Include lower bound. */
+    int GREATER_OR_EQUAL = 1;
+
+    /** Exclude upper bound. */
+    int LESS = 0;
+
+    /** Include upper bound. */
+    int LESS_OR_EQUAL = 1 << 1;
+
+    /** Forward scan. */
+    int FORWARD = 0;
+
+    /** Backwards scan. */
+    int BACKWARDS = 1 << 2;
+
+    /**
+     * The sole purpose of this class is to avoid massive refactoring while changing the original IndexRow.
+     */
+    interface IndexRowEx {
+        /**
+         * Key-only binary row if index-only scan is supported, full binary row otherwise.
+         */
+        BinaryRow row();
+
+        /**
+         * Returns indexed column value.
+         *
+         * @param idx PK column index.
+         * @return Indexed column value.
+         */
+        Object value(int idx);
+    }
+
+    boolean supportsBackwardsScan();

Review comment:
       why are these methods needed?

##########
File path: modules/storage-api/src/main/java/org/apache/ignite/internal/storage/MvPartitionStorage.java
##########
@@ -0,0 +1,96 @@
+/*
+ * 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;
+
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Predicate;
+import org.apache.ignite.internal.schema.BinaryRow;
+import org.apache.ignite.internal.tx.Timestamp;
+import org.apache.ignite.internal.util.Cursor;
+import org.apache.ignite.lang.IgniteException;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Multi-versioned partition storage.
+ * POC version, that represents a combination between a replicated TX-aware MV storage and physical MV storage. Their API are very similar,
+ * althouh there are very important differences that will be addressed in the future.
+ */
+public interface MvPartitionStorage {
+    /**
+     * Exception class that describes the situation where two independant transactions attempting to write values for the same key.
+     */
+    class TxIdMismatchException extends IgniteException {
+    }
+
+    /**
+     * Reads the value from the storage as it was at the given timestamp.
+     *
+     * @param key Key.
+     * @param timestamp Timestamp.
+     * @return Binary row that corresponds to the key or {@code null} if value is not found.
+     */
+    @Nullable
+    BinaryRow read(BinaryRow key, @Nullable Timestamp timestamp);
+
+    /**
+     * Creates uncommited version, assigned to the passed transaction id..
+     *
+     * @param row Binary row to update. Key only row means value removal.
+     * @param txId Transaction id.
+     * @throws TxIdMismatchException If there's another pending update associated with different transaction id.
+     * @throws StorageException If failed to write data to the storage.
+     */
+    void addWrite(BinaryRow row, UUID txId) throws TxIdMismatchException, StorageException;
+
+    /**
+     * Aborts a pending update of the ongoing uncommited transaction. Invoked during rollback.
+     *
+     * @param key Key.
+     * @throws StorageException If failed to write data to the storage.
+     */
+    void abortWrite(BinaryRow key) throws StorageException;
+
+    /**
+     * Commits a pending update of the ongoing transaction. Invoked during commit. Commited value will be versioned by the given timestamp.
+     *
+     * @param key Key.
+     * @param timestamp Timestamp to associate with commited value.
+     * @throws StorageException If failed to write data to the storage.
+     */
+    void commitWrite(BinaryRow key, Timestamp timestamp) throws StorageException;
+
+    /**
+     * Removes data associated with old timestamps.
+     *
+     * @param from Start of hashes range to process. Inclusive.
+     * @param to End of hashes range to process. Inclusive.
+     * @param timestamp Timestamp to remove all the data with a lesser timestamp.

Review comment:
       ```suggestion
        * @param timestamp Timestamp to remove all data with a smaller timestamp.
   ```

##########
File path: modules/storage-api/src/main/java/org/apache/ignite/internal/storage/MvPartitionStorage.java
##########
@@ -0,0 +1,96 @@
+/*
+ * 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;
+
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Predicate;
+import org.apache.ignite.internal.schema.BinaryRow;
+import org.apache.ignite.internal.tx.Timestamp;
+import org.apache.ignite.internal.util.Cursor;
+import org.apache.ignite.lang.IgniteException;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Multi-versioned partition storage.
+ * POC version, that represents a combination between a replicated TX-aware MV storage and physical MV storage. Their API are very similar,
+ * althouh there are very important differences that will be addressed in the future.
+ */
+public interface MvPartitionStorage {
+    /**
+     * Exception class that describes the situation where two independant transactions attempting to write values for the same key.
+     */
+    class TxIdMismatchException extends IgniteException {
+    }
+
+    /**
+     * Reads the value from the storage as it was at the given timestamp.
+     *
+     * @param key Key.
+     * @param timestamp Timestamp.
+     * @return Binary row that corresponds to the key or {@code null} if value is not found.
+     */
+    @Nullable
+    BinaryRow read(BinaryRow key, @Nullable Timestamp timestamp);
+
+    /**
+     * Creates uncommited version, assigned to the passed transaction id..
+     *
+     * @param row Binary row to update. Key only row means value removal.
+     * @param txId Transaction id.
+     * @throws TxIdMismatchException If there's another pending update associated with different transaction id.
+     * @throws StorageException If failed to write data to the storage.
+     */
+    void addWrite(BinaryRow row, UUID txId) throws TxIdMismatchException, StorageException;
+
+    /**
+     * Aborts a pending update of the ongoing uncommited transaction. Invoked during rollback.
+     *
+     * @param key Key.
+     * @throws StorageException If failed to write data to the storage.
+     */
+    void abortWrite(BinaryRow key) throws StorageException;
+
+    /**
+     * Commits a pending update of the ongoing transaction. Invoked during commit. Commited value will be versioned by the given timestamp.
+     *
+     * @param key Key.
+     * @param timestamp Timestamp to associate with commited value.

Review comment:
       ```suggestion
        * @param timestamp Timestamp to associate with committed value.
   ```

##########
File path: modules/storage-api/src/test/java/org/apache/ignite/internal/storage/BaseMvStoragesTest.java
##########
@@ -0,0 +1,180 @@
+/*
+ * 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;
+
+import java.util.Locale;
+import java.util.Objects;
+import org.apache.ignite.internal.schema.BinaryRow;
+import org.apache.ignite.internal.schema.Column;
+import org.apache.ignite.internal.schema.NativeTypes;
+import org.apache.ignite.internal.schema.SchemaDescriptor;
+import org.apache.ignite.internal.schema.marshaller.KvMarshaller;
+import org.apache.ignite.internal.schema.marshaller.MarshallerException;
+import org.apache.ignite.internal.schema.marshaller.MarshallerFactory;
+import org.apache.ignite.internal.schema.marshaller.reflection.ReflectionMarshallerFactory;
+import org.apache.ignite.internal.schema.row.Row;
+import org.apache.ignite.lang.IgniteException;
+import org.jetbrains.annotations.Nullable;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+
+/**
+ * Base test for MV storages, contains pojo classes, their descriptor and a marshaller instance.
+ */
+public class BaseMvStoragesTest {
+    /** Default reflection marshaller factory. */
+    protected static MarshallerFactory marshallerFactory;
+
+    /** Schema descriptor for tests. */
+    protected static SchemaDescriptor schemaDescriptor;
+
+    /** Key-value marshaller for tests. */
+    protected static KvMarshaller<TestKey, TestValue> kvMarshaller;
+
+    @BeforeAll
+    static void beforeAll() {
+        marshallerFactory = new ReflectionMarshallerFactory();
+
+        schemaDescriptor = new SchemaDescriptor(1, new Column[]{
+                new Column("intKey".toUpperCase(Locale.ROOT), NativeTypes.INT32, false),
+                new Column("strKey".toUpperCase(Locale.ROOT), NativeTypes.STRING, false),
+        }, new Column[]{
+                new Column("intVal".toUpperCase(Locale.ROOT), NativeTypes.INT32, false),
+                new Column("strVal".toUpperCase(Locale.ROOT), NativeTypes.STRING, false),
+        });
+
+        kvMarshaller = marshallerFactory.create(schemaDescriptor, TestKey.class, TestValue.class);
+    }
+
+    @AfterAll
+    static void afterAll() {
+        kvMarshaller = null;
+        schemaDescriptor = null;
+        marshallerFactory = null;
+    }
+
+    protected BinaryRow binaryKey(TestKey key) {
+        try {
+            return kvMarshaller.marshal(key);
+        } catch (MarshallerException e) {
+            throw new IgniteException(e);
+        }
+    }
+
+    protected BinaryRow binaryRow(TestKey key, TestValue value) {
+        try {
+            return kvMarshaller.marshal(key, value);
+        } catch (MarshallerException e) {
+            throw new IgniteException(e);
+        }
+    }
+
+    @Nullable
+    protected TestKey key(BinaryRow binaryRow) {
+        try {
+            return kvMarshaller.unmarshalKey(new Row(schemaDescriptor, binaryRow));
+        } catch (MarshallerException e) {
+            throw new IgniteException(e);
+        }
+    }
+
+    @Nullable
+    protected TestValue value(BinaryRow binaryRow) {
+        try {
+            return kvMarshaller.unmarshalValue(new Row(schemaDescriptor, binaryRow));
+        } catch (MarshallerException e) {
+            throw new IgniteException(e);
+        }
+    }
+
+    /**
+     * Test pojo key.
+     */
+    protected static class TestKey {
+        public int intKey;
+
+        public String strKey;
+
+        public TestKey() {
+        }
+
+        public TestKey(int intKey, String strKey) {
+            this.intKey = intKey;
+            this.strKey = strKey;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            TestKey testKey = (TestKey) o;
+            return intKey == testKey.intKey && Objects.equals(strKey, testKey.strKey);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(intKey, strKey);
+        }
+    }
+
+    /**
+     * Test pojo value.
+     */
+    protected static class TestValue implements Comparable<TestValue> {
+        public Integer intVal;
+
+        public String strVal;
+
+        public TestValue() {
+        }
+
+        public TestValue(Integer intVal, String strVal) {
+            this.intVal = intVal;
+            this.strVal = strVal;
+        }
+
+        @Override
+        public int compareTo(TestValue o) {
+            //TODO Compare nuppable values.

Review comment:
       ```suggestion
               //TODO Compare nullable values.
   ```

##########
File path: modules/storage-api/src/test/java/org/apache/ignite/internal/storage/BaseMvStoragesTest.java
##########
@@ -0,0 +1,180 @@
+/*
+ * 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;
+
+import java.util.Locale;
+import java.util.Objects;
+import org.apache.ignite.internal.schema.BinaryRow;
+import org.apache.ignite.internal.schema.Column;
+import org.apache.ignite.internal.schema.NativeTypes;
+import org.apache.ignite.internal.schema.SchemaDescriptor;
+import org.apache.ignite.internal.schema.marshaller.KvMarshaller;
+import org.apache.ignite.internal.schema.marshaller.MarshallerException;
+import org.apache.ignite.internal.schema.marshaller.MarshallerFactory;
+import org.apache.ignite.internal.schema.marshaller.reflection.ReflectionMarshallerFactory;
+import org.apache.ignite.internal.schema.row.Row;
+import org.apache.ignite.lang.IgniteException;
+import org.jetbrains.annotations.Nullable;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+
+/**
+ * Base test for MV storages, contains pojo classes, their descriptor and a marshaller instance.
+ */
+public class BaseMvStoragesTest {
+    /** Default reflection marshaller factory. */
+    protected static MarshallerFactory marshallerFactory;
+
+    /** Schema descriptor for tests. */
+    protected static SchemaDescriptor schemaDescriptor;
+
+    /** Key-value marshaller for tests. */
+    protected static KvMarshaller<TestKey, TestValue> kvMarshaller;
+
+    @BeforeAll
+    static void beforeAll() {
+        marshallerFactory = new ReflectionMarshallerFactory();
+
+        schemaDescriptor = new SchemaDescriptor(1, new Column[]{
+                new Column("intKey".toUpperCase(Locale.ROOT), NativeTypes.INT32, false),
+                new Column("strKey".toUpperCase(Locale.ROOT), NativeTypes.STRING, false),
+        }, new Column[]{
+                new Column("intVal".toUpperCase(Locale.ROOT), NativeTypes.INT32, false),
+                new Column("strVal".toUpperCase(Locale.ROOT), NativeTypes.STRING, false),
+        });
+
+        kvMarshaller = marshallerFactory.create(schemaDescriptor, TestKey.class, TestValue.class);
+    }
+
+    @AfterAll
+    static void afterAll() {
+        kvMarshaller = null;
+        schemaDescriptor = null;
+        marshallerFactory = null;
+    }
+
+    protected BinaryRow binaryKey(TestKey key) {
+        try {
+            return kvMarshaller.marshal(key);
+        } catch (MarshallerException e) {
+            throw new IgniteException(e);
+        }
+    }
+
+    protected BinaryRow binaryRow(TestKey key, TestValue value) {
+        try {
+            return kvMarshaller.marshal(key, value);
+        } catch (MarshallerException e) {
+            throw new IgniteException(e);
+        }
+    }
+
+    @Nullable
+    protected TestKey key(BinaryRow binaryRow) {
+        try {
+            return kvMarshaller.unmarshalKey(new Row(schemaDescriptor, binaryRow));
+        } catch (MarshallerException e) {
+            throw new IgniteException(e);
+        }
+    }
+
+    @Nullable
+    protected TestValue value(BinaryRow binaryRow) {
+        try {
+            return kvMarshaller.unmarshalValue(new Row(schemaDescriptor, binaryRow));
+        } catch (MarshallerException e) {
+            throw new IgniteException(e);
+        }
+    }
+
+    /**
+     * Test pojo key.
+     */
+    protected static class TestKey {
+        public int intKey;
+
+        public String strKey;
+
+        public TestKey() {
+        }
+
+        public TestKey(int intKey, String strKey) {
+            this.intKey = intKey;
+            this.strKey = strKey;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            TestKey testKey = (TestKey) o;
+            return intKey == testKey.intKey && Objects.equals(strKey, testKey.strKey);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(intKey, strKey);
+        }
+    }
+
+    /**
+     * Test pojo value.
+     */
+    protected static class TestValue implements Comparable<TestValue> {
+        public Integer intVal;
+
+        public String strVal;
+
+        public TestValue() {
+        }
+
+        public TestValue(Integer intVal, String strVal) {
+            this.intVal = intVal;
+            this.strVal = strVal;
+        }
+
+        @Override
+        public int compareTo(TestValue o) {
+            //TODO Compare nuppable values.

Review comment:
       do we need a ticket?

##########
File path: modules/storage-api/src/test/java/org/apache/ignite/internal/storage/basic/TestMvPartitionStorage.java
##########
@@ -0,0 +1,172 @@
+/*
+ * 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.basic;
+
+import java.nio.ByteBuffer;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Predicate;
+import org.apache.ignite.internal.schema.BinaryRow;
+import org.apache.ignite.internal.storage.MvPartitionStorage;
+import org.apache.ignite.internal.tx.Timestamp;
+import org.apache.ignite.internal.util.Cursor;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Test implementation of MV partition storage.
+ */
+public class TestMvPartitionStorage implements MvPartitionStorage {
+    private static final VersionChain NULL = new VersionChain(null, null, null, null);
+
+    private final ConcurrentHashMap<ByteBuffer, VersionChain> map = new ConcurrentHashMap<>();
+
+    private final List<TestSortedIndexMvStorage> indexes;
+
+    public TestMvPartitionStorage(List<TestSortedIndexMvStorage> indexes) {
+        this.indexes = indexes;
+    }
+
+    private static class VersionChain {
+        final BinaryRow row;
+        final Timestamp begin;
+        final UUID txId;
+        final VersionChain next;
+
+        VersionChain(BinaryRow row, Timestamp begin, UUID txId, VersionChain next) {

Review comment:
       Looks like all these fields and parameters should be marked as `@Nullable`

##########
File path: modules/storage-api/src/test/java/org/apache/ignite/internal/storage/basic/TestSortedIndexMvStorage.java
##########
@@ -0,0 +1,260 @@
+/*
+ * 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.basic;
+
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NavigableSet;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentSkipListSet;
+import java.util.function.IntPredicate;
+import java.util.function.ToIntFunction;
+import org.apache.ignite.configuration.NamedListView;
+import org.apache.ignite.configuration.schemas.table.ColumnView;
+import org.apache.ignite.configuration.schemas.table.IndexColumnView;
+import org.apache.ignite.configuration.schemas.table.SortedIndexView;
+import org.apache.ignite.configuration.schemas.table.TableIndexView;
+import org.apache.ignite.configuration.schemas.table.TableView;
+import org.apache.ignite.internal.schema.BinaryRow;
+import org.apache.ignite.internal.schema.NativeType;
+import org.apache.ignite.internal.schema.SchemaDescriptor;
+import org.apache.ignite.internal.schema.configuration.SchemaConfigurationConverter;
+import org.apache.ignite.internal.schema.configuration.SchemaDescriptorConverter;
+import org.apache.ignite.internal.schema.row.Row;
+import org.apache.ignite.internal.storage.index.IndexRowPrefix;
+import org.apache.ignite.internal.storage.index.PrefixComparator;
+import org.apache.ignite.internal.storage.index.SortedIndexMvStorage;
+import org.apache.ignite.internal.tx.Timestamp;
+import org.apache.ignite.internal.util.Cursor;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Test implementation of MV sorted index storage.
+ */
+public class TestSortedIndexMvStorage implements SortedIndexMvStorage {
+    private final TableView tableCfg;
+
+    private final NavigableSet<BinaryRow> index;
+
+    private final SchemaDescriptor descriptor;
+    private final Map<Integer, TestMvPartitionStorage> pk;

Review comment:
       missing linebreak

##########
File path: modules/storage-api/src/test/java/org/apache/ignite/internal/storage/basic/TestSortedIndexMvStorage.java
##########
@@ -0,0 +1,260 @@
+/*
+ * 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.basic;
+
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NavigableSet;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentSkipListSet;
+import java.util.function.IntPredicate;
+import java.util.function.ToIntFunction;
+import org.apache.ignite.configuration.NamedListView;
+import org.apache.ignite.configuration.schemas.table.ColumnView;
+import org.apache.ignite.configuration.schemas.table.IndexColumnView;
+import org.apache.ignite.configuration.schemas.table.SortedIndexView;
+import org.apache.ignite.configuration.schemas.table.TableIndexView;
+import org.apache.ignite.configuration.schemas.table.TableView;
+import org.apache.ignite.internal.schema.BinaryRow;
+import org.apache.ignite.internal.schema.NativeType;
+import org.apache.ignite.internal.schema.SchemaDescriptor;
+import org.apache.ignite.internal.schema.configuration.SchemaConfigurationConverter;
+import org.apache.ignite.internal.schema.configuration.SchemaDescriptorConverter;
+import org.apache.ignite.internal.schema.row.Row;
+import org.apache.ignite.internal.storage.index.IndexRowPrefix;
+import org.apache.ignite.internal.storage.index.PrefixComparator;
+import org.apache.ignite.internal.storage.index.SortedIndexMvStorage;
+import org.apache.ignite.internal.tx.Timestamp;
+import org.apache.ignite.internal.util.Cursor;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Test implementation of MV sorted index storage.
+ */
+public class TestSortedIndexMvStorage implements SortedIndexMvStorage {
+    private final TableView tableCfg;
+
+    private final NavigableSet<BinaryRow> index;
+
+    private final SchemaDescriptor descriptor;
+    private final Map<Integer, TestMvPartitionStorage> pk;
+
+    private final int partitions;
+
+    private final IndexColumnView[] indexColumns;
+
+    private final int[] columnIndexes;
+
+    private final NativeType[] nativeTypes;
+
+    protected TestSortedIndexMvStorage(
+            String name,
+            TableView tableCfg,
+            SchemaDescriptor descriptor,
+            Map<Integer, TestMvPartitionStorage> pk
+    ) {
+        this.tableCfg = tableCfg;
+
+        this.descriptor = descriptor;
+
+        this.pk = pk;
+
+        partitions = tableCfg.partitions();
+
+        index = new ConcurrentSkipListSet<>((l, r) -> {
+            int cmp = compareColumns(l, r);
+
+            if (cmp != 0) {
+                return cmp;
+            }
+
+            return l.keySlice().compareTo(r.keySlice());
+        });
+
+        // Init columns.
+        NamedListView<? extends ColumnView> tblColumns = tableCfg.columns();
+
+        TableIndexView idxCfg = tableCfg.indices().get(name);
+
+        assert idxCfg instanceof SortedIndexView;
+
+        SortedIndexView sortedIdxCfg = (SortedIndexView) idxCfg;
+
+        NamedListView<? extends IndexColumnView> columns = sortedIdxCfg.columns();
+
+        int length = columns.size();
+
+        this.indexColumns = new IndexColumnView[length];
+        this.columnIndexes = new int[length];
+        this.nativeTypes = new NativeType[length];
+
+        for (int i = 0; i < length; i++) {
+            IndexColumnView idxColumn = columns.get(i);
+
+            indexColumns[i] = idxColumn;
+
+            int columnIndex = tblColumns.namedListKeys().indexOf(idxColumn.name());
+
+            columnIndexes[i] = columnIndex;
+
+            nativeTypes[i] = SchemaDescriptorConverter.convert(SchemaConfigurationConverter.convert(tblColumns.get(columnIndex).type()));
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean supportsBackwardsScan() {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean supportsIndexOnlyScan() {
+        return false;
+    }
+
+    private int compareColumns(BinaryRow l, BinaryRow r) {
+        Row leftRow = new Row(descriptor, l);

Review comment:
       Can we reuse `BinaryRowComparator` logic here? For example, there's a `binaryRowComparator` method there...

##########
File path: modules/storage-api/src/test/java/org/apache/ignite/internal/storage/basic/TestSortedIndexMvStorage.java
##########
@@ -0,0 +1,260 @@
+/*
+ * 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.basic;
+
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NavigableSet;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentSkipListSet;
+import java.util.function.IntPredicate;
+import java.util.function.ToIntFunction;
+import org.apache.ignite.configuration.NamedListView;
+import org.apache.ignite.configuration.schemas.table.ColumnView;
+import org.apache.ignite.configuration.schemas.table.IndexColumnView;
+import org.apache.ignite.configuration.schemas.table.SortedIndexView;
+import org.apache.ignite.configuration.schemas.table.TableIndexView;
+import org.apache.ignite.configuration.schemas.table.TableView;
+import org.apache.ignite.internal.schema.BinaryRow;
+import org.apache.ignite.internal.schema.NativeType;
+import org.apache.ignite.internal.schema.SchemaDescriptor;
+import org.apache.ignite.internal.schema.configuration.SchemaConfigurationConverter;
+import org.apache.ignite.internal.schema.configuration.SchemaDescriptorConverter;
+import org.apache.ignite.internal.schema.row.Row;
+import org.apache.ignite.internal.storage.index.IndexRowPrefix;
+import org.apache.ignite.internal.storage.index.PrefixComparator;
+import org.apache.ignite.internal.storage.index.SortedIndexMvStorage;
+import org.apache.ignite.internal.tx.Timestamp;
+import org.apache.ignite.internal.util.Cursor;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Test implementation of MV sorted index storage.
+ */
+public class TestSortedIndexMvStorage implements SortedIndexMvStorage {
+    private final TableView tableCfg;
+
+    private final NavigableSet<BinaryRow> index;
+
+    private final SchemaDescriptor descriptor;
+    private final Map<Integer, TestMvPartitionStorage> pk;
+
+    private final int partitions;
+
+    private final IndexColumnView[] indexColumns;
+
+    private final int[] columnIndexes;
+
+    private final NativeType[] nativeTypes;
+
+    protected TestSortedIndexMvStorage(
+            String name,
+            TableView tableCfg,
+            SchemaDescriptor descriptor,
+            Map<Integer, TestMvPartitionStorage> pk
+    ) {
+        this.tableCfg = tableCfg;
+
+        this.descriptor = descriptor;
+
+        this.pk = pk;
+
+        partitions = tableCfg.partitions();
+
+        index = new ConcurrentSkipListSet<>((l, r) -> {
+            int cmp = compareColumns(l, r);
+
+            if (cmp != 0) {
+                return cmp;
+            }
+
+            return l.keySlice().compareTo(r.keySlice());
+        });
+
+        // Init columns.
+        NamedListView<? extends ColumnView> tblColumns = tableCfg.columns();
+
+        TableIndexView idxCfg = tableCfg.indices().get(name);
+
+        assert idxCfg instanceof SortedIndexView;
+
+        SortedIndexView sortedIdxCfg = (SortedIndexView) idxCfg;
+
+        NamedListView<? extends IndexColumnView> columns = sortedIdxCfg.columns();
+
+        int length = columns.size();
+
+        this.indexColumns = new IndexColumnView[length];
+        this.columnIndexes = new int[length];
+        this.nativeTypes = new NativeType[length];
+
+        for (int i = 0; i < length; i++) {
+            IndexColumnView idxColumn = columns.get(i);
+
+            indexColumns[i] = idxColumn;
+
+            int columnIndex = tblColumns.namedListKeys().indexOf(idxColumn.name());
+
+            columnIndexes[i] = columnIndex;
+
+            nativeTypes[i] = SchemaDescriptorConverter.convert(SchemaConfigurationConverter.convert(tblColumns.get(columnIndex).type()));
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean supportsBackwardsScan() {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean supportsIndexOnlyScan() {
+        return false;
+    }
+
+    private int compareColumns(BinaryRow l, BinaryRow r) {
+        Row leftRow = new Row(descriptor, l);
+        Row rightRow = new Row(descriptor, r);
+
+        for (int i = 0; i < indexColumns.length; i++) {
+            int columnIndex = columnIndexes[i];
+
+            int cmp = PrefixComparator.compareColumns(leftRow, columnIndex, nativeTypes[i].spec(), rightRow.value(columnIndex));
+
+            if (cmp != 0) {
+                return indexColumns[i].asc() ? cmp : -cmp;
+            }
+        }
+
+        return 0;
+    }
+
+    public void append(BinaryRow row) {
+        index.add(row);
+    }
+
+    public void remove(BinaryRow row) {
+        index.remove(row);
+    }
+
+    public boolean matches(BinaryRow aborted, BinaryRow existing) {
+        return compareColumns(aborted, existing) == 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Cursor<IndexRowEx> scan(
+            @Nullable IndexRowPrefix lowerBound,
+            @Nullable IndexRowPrefix upperBound,
+            int flags,
+            Timestamp timestamp,
+            @Nullable IntPredicate partitionFilter
+    ) {
+        boolean includeLower = (flags & GREATER_OR_EQUAL) != 0;
+        boolean includeUpper = (flags & LESS_OR_EQUAL) != 0;
+
+        NavigableSet<BinaryRow> index = this.index;
+        int direction = 1;
+
+        // Swap bounds and flip index for backwards scan.
+        if ((flags & BACKWARDS) != 0) {
+            index = index.descendingSet();
+            direction = -1;
+
+            boolean tempBoolean = includeLower;
+            includeLower = includeUpper;
+            includeUpper = tempBoolean;
+
+            IndexRowPrefix tempBound = lowerBound;
+            lowerBound = upperBound;
+            upperBound = tempBound;
+        }
+
+        ToIntFunction<BinaryRow> lowerCmp = lowerBound == null ? row -> 1 : boundComparator(lowerBound, direction, includeLower ? 0 : -1);
+        ToIntFunction<BinaryRow> upperCmp = upperBound == null ? row -> -1 : boundComparator(upperBound, direction, includeUpper ? 0 : 1);
+
+        Iterator<IndexRowEx> iterator = index.stream()
+                .dropWhile(binaryRow -> {
+                    return lowerCmp.applyAsInt(binaryRow) < 0;

Review comment:
       can be written shorter:
   ```
   .dropWhile(binaryRow -> lowerCmp.applyAsInt(binaryRow) < 0)
   .takeWhile(binaryRow -> upperCmp.applyAsInt(binaryRow) <= 0)
   ```

##########
File path: modules/storage-api/src/test/java/org/apache/ignite/internal/storage/basic/TestSortedIndexMvStorage.java
##########
@@ -0,0 +1,260 @@
+/*
+ * 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.basic;
+
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NavigableSet;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentSkipListSet;
+import java.util.function.IntPredicate;
+import java.util.function.ToIntFunction;
+import org.apache.ignite.configuration.NamedListView;
+import org.apache.ignite.configuration.schemas.table.ColumnView;
+import org.apache.ignite.configuration.schemas.table.IndexColumnView;
+import org.apache.ignite.configuration.schemas.table.SortedIndexView;
+import org.apache.ignite.configuration.schemas.table.TableIndexView;
+import org.apache.ignite.configuration.schemas.table.TableView;
+import org.apache.ignite.internal.schema.BinaryRow;
+import org.apache.ignite.internal.schema.NativeType;
+import org.apache.ignite.internal.schema.SchemaDescriptor;
+import org.apache.ignite.internal.schema.configuration.SchemaConfigurationConverter;
+import org.apache.ignite.internal.schema.configuration.SchemaDescriptorConverter;
+import org.apache.ignite.internal.schema.row.Row;
+import org.apache.ignite.internal.storage.index.IndexRowPrefix;
+import org.apache.ignite.internal.storage.index.PrefixComparator;
+import org.apache.ignite.internal.storage.index.SortedIndexMvStorage;
+import org.apache.ignite.internal.tx.Timestamp;
+import org.apache.ignite.internal.util.Cursor;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Test implementation of MV sorted index storage.
+ */
+public class TestSortedIndexMvStorage implements SortedIndexMvStorage {
+    private final TableView tableCfg;
+
+    private final NavigableSet<BinaryRow> index;
+
+    private final SchemaDescriptor descriptor;
+    private final Map<Integer, TestMvPartitionStorage> pk;
+
+    private final int partitions;
+
+    private final IndexColumnView[] indexColumns;
+
+    private final int[] columnIndexes;
+
+    private final NativeType[] nativeTypes;
+
+    protected TestSortedIndexMvStorage(
+            String name,
+            TableView tableCfg,
+            SchemaDescriptor descriptor,
+            Map<Integer, TestMvPartitionStorage> pk
+    ) {
+        this.tableCfg = tableCfg;
+
+        this.descriptor = descriptor;
+
+        this.pk = pk;
+
+        partitions = tableCfg.partitions();
+
+        index = new ConcurrentSkipListSet<>((l, r) -> {
+            int cmp = compareColumns(l, r);
+
+            if (cmp != 0) {
+                return cmp;
+            }
+
+            return l.keySlice().compareTo(r.keySlice());
+        });
+
+        // Init columns.
+        NamedListView<? extends ColumnView> tblColumns = tableCfg.columns();
+
+        TableIndexView idxCfg = tableCfg.indices().get(name);
+
+        assert idxCfg instanceof SortedIndexView;
+
+        SortedIndexView sortedIdxCfg = (SortedIndexView) idxCfg;
+
+        NamedListView<? extends IndexColumnView> columns = sortedIdxCfg.columns();
+
+        int length = columns.size();
+
+        this.indexColumns = new IndexColumnView[length];

Review comment:
       Do you really need all these fields? Can they be replaced with a `SortedIndexDescriptor`?

##########
File path: modules/storage-api/src/test/java/org/apache/ignite/internal/storage/basic/TestSortedIndexMvStorage.java
##########
@@ -0,0 +1,260 @@
+/*
+ * 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.basic;
+
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NavigableSet;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentSkipListSet;
+import java.util.function.IntPredicate;
+import java.util.function.ToIntFunction;
+import org.apache.ignite.configuration.NamedListView;
+import org.apache.ignite.configuration.schemas.table.ColumnView;
+import org.apache.ignite.configuration.schemas.table.IndexColumnView;
+import org.apache.ignite.configuration.schemas.table.SortedIndexView;
+import org.apache.ignite.configuration.schemas.table.TableIndexView;
+import org.apache.ignite.configuration.schemas.table.TableView;
+import org.apache.ignite.internal.schema.BinaryRow;
+import org.apache.ignite.internal.schema.NativeType;
+import org.apache.ignite.internal.schema.SchemaDescriptor;
+import org.apache.ignite.internal.schema.configuration.SchemaConfigurationConverter;
+import org.apache.ignite.internal.schema.configuration.SchemaDescriptorConverter;
+import org.apache.ignite.internal.schema.row.Row;
+import org.apache.ignite.internal.storage.index.IndexRowPrefix;
+import org.apache.ignite.internal.storage.index.PrefixComparator;
+import org.apache.ignite.internal.storage.index.SortedIndexMvStorage;
+import org.apache.ignite.internal.tx.Timestamp;
+import org.apache.ignite.internal.util.Cursor;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Test implementation of MV sorted index storage.
+ */
+public class TestSortedIndexMvStorage implements SortedIndexMvStorage {
+    private final TableView tableCfg;
+
+    private final NavigableSet<BinaryRow> index;
+
+    private final SchemaDescriptor descriptor;
+    private final Map<Integer, TestMvPartitionStorage> pk;
+
+    private final int partitions;
+
+    private final IndexColumnView[] indexColumns;
+
+    private final int[] columnIndexes;
+
+    private final NativeType[] nativeTypes;
+
+    protected TestSortedIndexMvStorage(
+            String name,
+            TableView tableCfg,
+            SchemaDescriptor descriptor,
+            Map<Integer, TestMvPartitionStorage> pk
+    ) {
+        this.tableCfg = tableCfg;
+
+        this.descriptor = descriptor;
+
+        this.pk = pk;
+
+        partitions = tableCfg.partitions();
+
+        index = new ConcurrentSkipListSet<>((l, r) -> {
+            int cmp = compareColumns(l, r);

Review comment:
       can be replaced with `((Comparator<BinaryRow>) this::compareColumns).thenComparing(BinaryRow::keySlice)`




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@ignite.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org