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

tkalkirill pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


The following commit(s) were added to refs/heads/main by this push:
     new 2526e8dab7 IGNITE-23283 Throw CompactedException for single get 
metastorage methods (#4524)
2526e8dab7 is described below

commit 2526e8dab7aedd3a78d8f45e51ea0ba6d94a5cc8
Author: Kirill Tkalenko <[email protected]>
AuthorDate: Wed Oct 9 11:14:20 2024 +0300

    IGNITE-23283 Throw CompactedException for single get metastorage methods 
(#4524)
---
 .../internal/metastorage/MetaStorageManager.java   | 114 +++++++++++++++++-
 .../metastorage/exceptions/CompactedException.java |  20 +++-
 .../internal/metastorage/impl/EntryImpl.java       |  10 ++
 .../metastorage/impl/MetaStorageManagerImpl.java   |  30 +----
 .../metastorage/server/KeyValueStorage.java        |  51 +++++++-
 .../metastorage/server/KeyValueStorageUtils.java   |  38 +++++-
 .../server/persistence/RocksDbKeyValueStorage.java |  77 +++++++-----
 .../AbstractCompactionKeyValueStorageTest.java     | 130 ++++++++++++++++++++-
 .../server/KeyValueStorageUtilsTest.java           |  50 ++++++--
 .../server/SimpleInMemoryKeyValueStorage.java      |  49 +++++---
 10 files changed, 474 insertions(+), 95 deletions(-)

diff --git 
a/modules/metastorage-api/src/main/java/org/apache/ignite/internal/metastorage/MetaStorageManager.java
 
b/modules/metastorage-api/src/main/java/org/apache/ignite/internal/metastorage/MetaStorageManager.java
index 053bbeb61d..c83eeadc8d 100644
--- 
a/modules/metastorage-api/src/main/java/org/apache/ignite/internal/metastorage/MetaStorageManager.java
+++ 
b/modules/metastorage-api/src/main/java/org/apache/ignite/internal/metastorage/MetaStorageManager.java
@@ -55,12 +55,72 @@ public interface MetaStorageManager extends IgniteComponent 
{
     long appliedRevision();
 
     /**
-     * Retrieves an entry for the given key.
+     * Returns a future of getting the latest version of an entry by key from 
the metastore leader.
+     *
+     * <p>Never completes with a {@link CompactedException}.</p>
+     *
+     * <p>Future may complete with {@link NodeStoppingException} if the node 
is in the process of stopping.</p>
+     *
+     * @param key The key.
      */
     CompletableFuture<Entry> get(ByteArray key);
 
     /**
-     * Retrieves an entry for the given key and the revision upper bound.
+     * Returns a future of getting an entry for the given key and the revision 
upper bound from the metastore leader.
+     *
+     * <p>Future may complete with exceptions:</p>
+     * <ul>
+     *     <li>{@link NodeStoppingException} - if the node is in the process 
of stopping.</li>
+     *     <li>{@link CompactedException} - If the requested entry was not 
found and the {@code revUpperBound} is less than or equal to the
+     *     last compacted one.</li>
+     * </ul>
+     *
+     * <p>Let's consider examples of the work of the method and compaction of 
the metastore. Let's assume that we have keys with revisions
+     * "foo" [1, 2] and "bar" [1, 2 (tombstone)], and the key "some" has never 
been in the metastore.</p>
+     * <ul>
+     *     <li>Compaction revision is {@code 1}.
+     *     <ul>
+     *         <li>get("foo", 1) - a {@link CompactedException} will be 
thrown.</li>
+     *         <li>get("foo", 2) - will return a single value with revision 
2.</li>
+     *         <li>get("foo", 3) - will return a single value with revision 
2.</li>
+     *         <li>get("bar", 1) - a {@link CompactedException} will be 
thrown.</li>
+     *         <li>get("bar", 2) - will return a single value with revision 
2.</li>
+     *         <li>get("bar", 3) - will return a single value with revision 
2.</li>
+     *         <li>get("some", 1) - a {@link CompactedException} will be 
thrown.</li>
+     *         <li>get("some", 2) - will return an empty value.</li>
+     *         <li>get("some", 3) - will return an empty value.</li>
+     *     </ul>
+     *     </li>
+     *     <li>Compaction revision is {@code 2}.
+     *     <ul>
+     *         <li>get("foo", 1) - a {@link CompactedException} will be 
thrown.</li>
+     *         <li>get("foo", 2) - will return a single value with revision 
2.</li>
+     *         <li>get("foo", 3) - will return a single value with revision 
2.</li>
+     *         <li>get("bar", 1) - a {@link CompactedException} will be 
thrown.</li>
+     *         <li>get("bar", 2) - a {@link CompactedException} will be 
thrown.</li>
+     *         <li>get("bar", 3) - will return a single value with revision 
2.</li>
+     *         <li>get("some", 1) - a {@link CompactedException} will be 
thrown.</li>
+     *         <li>get("some", 2) - a {@link CompactedException} will be 
thrown.</li>
+     *         <li>get("some", 3) - will return an empty value.</li>
+     *     </ul>
+     *     </li>
+     *     <li>Compaction revision is {@code 3}.
+     *     <ul>
+     *         <li>get("foo", 1) - a {@link CompactedException} will be 
thrown.</li>
+     *         <li>get("foo", 2) - will return a single value with revision 
2.</li>
+     *         <li>get("foo", 3) - will return a single value with revision 
2.</li>
+     *         <li>get("bar", 1) - a {@link CompactedException} will be 
thrown.</li>
+     *         <li>get("bar", 2) - a {@link CompactedException} will be 
thrown.</li>
+     *         <li>get("bar", 3) - a {@link CompactedException} will be 
thrown.</li>
+     *         <li>get("some", 1) - a {@link CompactedException} will be 
thrown.</li>
+     *         <li>get("some", 2) - a {@link CompactedException} will be 
thrown.</li>
+     *         <li>get("some", 3) - a {@link CompactedException} will be 
thrown.</li>
+     *     </ul>
+     *     </li>
+     * </ul>
+     *
+     * @param key The key.
+     * @param revUpperBound The upper bound of revision.
      */
     CompletableFuture<Entry> get(ByteArray key, long revUpperBound);
 
@@ -82,15 +142,61 @@ public interface MetaStorageManager extends 
IgniteComponent {
     List<Entry> getLocally(byte[] key, long revLowerBound, long revUpperBound);
 
     /**
-     * Returns an entry by the given key and bounded by the given revision. 
The entry is obtained
-     * from the local storage.
+     * Returns an entry for the given key and the revision upper bound locally.
      *
      * <p>This method doesn't wait for the storage's revision to become 
greater or equal to the revUpperBound parameter, so it is
      * up to user to wait for the appropriate time to call this method.
      *
+     * <p>Let's consider examples of the work of the method and compaction of 
the metastore. Let's assume that we have keys with revisions
+     * "foo" [1, 2] and "bar" [1, 2 (tombstone)], and the key "some" has never 
been in the metastore.</p>
+     * <ul>
+     *     <li>Compaction revision is {@code 1}.
+     *     <ul>
+     *         <li>get("foo", 1) - a {@link CompactedException} will be 
thrown.</li>
+     *         <li>get("foo", 2) - will return a single value with revision 
2.</li>
+     *         <li>get("foo", 3) - will return a single value with revision 
2.</li>
+     *         <li>get("bar", 1) - a {@link CompactedException} will be 
thrown.</li>
+     *         <li>get("bar", 2) - will return a single value with revision 
2.</li>
+     *         <li>get("bar", 3) - will return a single value with revision 
2.</li>
+     *         <li>get("some", 1) - a {@link CompactedException} will be 
thrown.</li>
+     *         <li>get("some", 2) - will return an empty value.</li>
+     *         <li>get("some", 3) - will return an empty value.</li>
+     *     </ul>
+     *     </li>
+     *     <li>Compaction revision is {@code 2}.
+     *     <ul>
+     *         <li>get("foo", 1) - a {@link CompactedException} will be 
thrown.</li>
+     *         <li>get("foo", 2) - will return a single value with revision 
2.</li>
+     *         <li>get("foo", 3) - will return a single value with revision 
2.</li>
+     *         <li>get("bar", 1) - a {@link CompactedException} will be 
thrown.</li>
+     *         <li>get("bar", 2) - a {@link CompactedException} will be 
thrown.</li>
+     *         <li>get("bar", 3) - will return a single value with revision 
2.</li>
+     *         <li>get("some", 1) - a {@link CompactedException} will be 
thrown.</li>
+     *         <li>get("some", 2) - a {@link CompactedException} will be 
thrown.</li>
+     *         <li>get("some", 3) - will return an empty value.</li>
+     *     </ul>
+     *     </li>
+     *     <li>Compaction revision is {@code 3}.
+     *     <ul>
+     *         <li>get("foo", 1) - a {@link CompactedException} will be 
thrown.</li>
+     *         <li>get("foo", 2) - will return a single value with revision 
2.</li>
+     *         <li>get("foo", 3) - will return a single value with revision 
2.</li>
+     *         <li>get("bar", 1) - a {@link CompactedException} will be 
thrown.</li>
+     *         <li>get("bar", 2) - a {@link CompactedException} will be 
thrown.</li>
+     *         <li>get("bar", 3) - a {@link CompactedException} will be 
thrown.</li>
+     *         <li>get("some", 1) - a {@link CompactedException} will be 
thrown.</li>
+     *         <li>get("some", 2) - a {@link CompactedException} will be 
thrown.</li>
+     *         <li>get("some", 3) - a {@link CompactedException} will be 
thrown.</li>
+     *     </ul>
+     *     </li>
+     * </ul>
+     *
      * @param key The key.
      * @param revUpperBound The upper bound of revision.
      * @return Value corresponding to the given key.
+     * @throws IgniteInternalException with cause {@link 
NodeStoppingException} if the node is in the process of stopping.
+     * @throws CompactedException If the requested entry was not found and the 
{@code revUpperBound} is less than or equal to the last
+     *      compacted one.
      */
     Entry getLocally(ByteArray key, long revUpperBound);
 
diff --git 
a/modules/metastorage-api/src/main/java/org/apache/ignite/internal/metastorage/exceptions/CompactedException.java
 
b/modules/metastorage-api/src/main/java/org/apache/ignite/internal/metastorage/exceptions/CompactedException.java
index 3f11f48f65..7c1cbaaba1 100644
--- 
a/modules/metastorage-api/src/main/java/org/apache/ignite/internal/metastorage/exceptions/CompactedException.java
+++ 
b/modules/metastorage-api/src/main/java/org/apache/ignite/internal/metastorage/exceptions/CompactedException.java
@@ -36,10 +36,17 @@ public class CompactedException extends 
MetaStorageException {
     /**
      * Constructs an exception with a given message.
      *
-     * @param revision Requested revision.
+     * @param requestedRevision Requested revision.
+     * @param latestCompactedRevision Latest compacted revision.
      */
-    public CompactedException(long revision) {
-        super(COMPACTED_ERR, "Requested revision has already been compacted: " 
+ revision);
+    public CompactedException(long requestedRevision, long 
latestCompactedRevision) {
+        super(
+                COMPACTED_ERR,
+                String.format(
+                        "Requested revision has already been compacted: 
[requested=%s, lastCompacted=%s]",
+                        requestedRevision, latestCompactedRevision
+                )
+        );
     }
 
     /**
@@ -69,4 +76,11 @@ public class CompactedException extends MetaStorageException 
{
     public CompactedException(Throwable cause) {
         super(COMPACTED_ERR, cause);
     }
+
+    /** Throws {@link CompactedException} if the requested revision is less 
than or equal to the last compacted one. */
+    public static void throwIfRequestedRevisionLessThanOrEqualToCompacted(long 
requestedRevision, long compactedRevision) {
+        if (requestedRevision <= compactedRevision) {
+            throw new CompactedException(requestedRevision, compactedRevision);
+        }
+    }
 }
diff --git 
a/modules/metastorage/src/main/java/org/apache/ignite/internal/metastorage/impl/EntryImpl.java
 
b/modules/metastorage/src/main/java/org/apache/ignite/internal/metastorage/impl/EntryImpl.java
index 8646bdbc60..015217e628 100644
--- 
a/modules/metastorage/src/main/java/org/apache/ignite/internal/metastorage/impl/EntryImpl.java
+++ 
b/modules/metastorage/src/main/java/org/apache/ignite/internal/metastorage/impl/EntryImpl.java
@@ -23,6 +23,7 @@ import java.util.Arrays;
 import java.util.Objects;
 import org.apache.ignite.internal.hlc.HybridTimestamp;
 import org.apache.ignite.internal.metastorage.Entry;
+import org.apache.ignite.internal.metastorage.server.Value;
 import org.jetbrains.annotations.Nullable;
 
 /** Implementation of the {@link Entry}. */
@@ -134,4 +135,13 @@ public final class EntryImpl implements Entry {
                 + ", timestamp=" + timestamp
                 + '}';
     }
+
+    /** Converts to {@link EntryImpl}. */
+    public static Entry toEntry(byte[] key, long revision, Value value) {
+        if (value.tombstone()) {
+            return tombstone(key, revision, value.operationTimestamp());
+        }
+
+        return new EntryImpl(key, value.bytes(), revision, 
value.operationTimestamp());
+    }
 }
diff --git 
a/modules/metastorage/src/main/java/org/apache/ignite/internal/metastorage/impl/MetaStorageManagerImpl.java
 
b/modules/metastorage/src/main/java/org/apache/ignite/internal/metastorage/impl/MetaStorageManagerImpl.java
index b07ce360c7..560f5ece69 100644
--- 
a/modules/metastorage/src/main/java/org/apache/ignite/internal/metastorage/impl/MetaStorageManagerImpl.java
+++ 
b/modules/metastorage/src/main/java/org/apache/ignite/internal/metastorage/impl/MetaStorageManagerImpl.java
@@ -686,28 +686,12 @@ public class MetaStorageManagerImpl implements 
MetaStorageManager, MetastorageGr
 
     @Override
     public CompletableFuture<Entry> get(ByteArray key) {
-        if (!busyLock.enterBusy()) {
-            return failedFuture(new NodeStoppingException());
-        }
-
-        try {
-            return metaStorageSvcFut.thenCompose(svc -> svc.get(key));
-        } finally {
-            busyLock.leaveBusy();
-        }
+        return inBusyLockAsync(busyLock, () -> 
metaStorageSvcFut.thenCompose(svc -> svc.get(key)));
     }
 
     @Override
     public CompletableFuture<Entry> get(ByteArray key, long revUpperBound) {
-        if (!busyLock.enterBusy()) {
-            return failedFuture(new NodeStoppingException());
-        }
-
-        try {
-            return metaStorageSvcFut.thenCompose(svc -> svc.get(key, 
revUpperBound));
-        } finally {
-            busyLock.leaveBusy();
-        }
+        return inBusyLockAsync(busyLock, () -> 
metaStorageSvcFut.thenCompose(svc -> svc.get(key, revUpperBound)));
     }
 
     @Override
@@ -725,15 +709,7 @@ public class MetaStorageManagerImpl implements 
MetaStorageManager, MetastorageGr
 
     @Override
     public Entry getLocally(ByteArray key, long revUpperBound) {
-        if (!busyLock.enterBusy()) {
-            throw new IgniteException(new NodeStoppingException());
-        }
-
-        try {
-            return storage.get(key.bytes(), revUpperBound);
-        } finally {
-            busyLock.leaveBusy();
-        }
+        return inBusyLock(busyLock, () -> storage.get(key.bytes(), 
revUpperBound));
     }
 
     @Override
diff --git 
a/modules/metastorage/src/main/java/org/apache/ignite/internal/metastorage/server/KeyValueStorage.java
 
b/modules/metastorage/src/main/java/org/apache/ignite/internal/metastorage/server/KeyValueStorage.java
index 37481cd00c..ad40c44db3 100644
--- 
a/modules/metastorage/src/main/java/org/apache/ignite/internal/metastorage/server/KeyValueStorage.java
+++ 
b/modules/metastorage/src/main/java/org/apache/ignite/internal/metastorage/server/KeyValueStorage.java
@@ -56,7 +56,9 @@ public interface KeyValueStorage extends ManuallyCloseable {
     long revision();
 
     /**
-     * Returns an entry by the given key.
+     * Returns the latest version of an entry by key.
+     *
+     * <p>Never throws {@link CompactedException}.</p>
      *
      * @param key The key.
      * @return Value corresponding to the given key.
@@ -66,9 +68,54 @@ public interface KeyValueStorage extends ManuallyCloseable {
     /**
      * Returns an entry by the given key and bounded by the given revision.
      *
+     * <p>Let's consider examples of the work of the method and compaction of 
the metastore. Let's assume that we have keys with revisions
+     * "foo" [1, 2] and "bar" [1, 2 (tombstone)], and the key "some" has never 
been in the metastore.</p>
+     * <ul>
+     *     <li>Compaction revision is {@code 1}.
+     *     <ul>
+     *         <li>get("foo", 1) - a {@link CompactedException} will be 
thrown.</li>
+     *         <li>get("foo", 2) - will return a single value with revision 
2.</li>
+     *         <li>get("foo", 3) - will return a single value with revision 
2.</li>
+     *         <li>get("bar", 1) - a {@link CompactedException} will be 
thrown.</li>
+     *         <li>get("bar", 2) - will return a single value with revision 
2.</li>
+     *         <li>get("bar", 3) - will return a single value with revision 
2.</li>
+     *         <li>get("some", 1) - a {@link CompactedException} will be 
thrown.</li>
+     *         <li>get("some", 2) - will return an empty value.</li>
+     *         <li>get("some", 3) - will return an empty value.</li>
+     *     </ul>
+     *     </li>
+     *     <li>Compaction revision is {@code 2}.
+     *     <ul>
+     *         <li>get("foo", 1) - a {@link CompactedException} will be 
thrown.</li>
+     *         <li>get("foo", 2) - will return a single value with revision 
2.</li>
+     *         <li>get("foo", 3) - will return a single value with revision 
2.</li>
+     *         <li>get("bar", 1) - a {@link CompactedException} will be 
thrown.</li>
+     *         <li>get("bar", 2) - a {@link CompactedException} will be 
thrown.</li>
+     *         <li>get("bar", 3) - will return a single value with revision 
2.</li>
+     *         <li>get("some", 1) - a {@link CompactedException} will be 
thrown.</li>
+     *         <li>get("some", 2) - a {@link CompactedException} will be 
thrown.</li>
+     *         <li>get("some", 3) - will return an empty value.</li>
+     *     </ul>
+     *     </li>
+     *     <li>Compaction revision is {@code 3}.
+     *     <ul>
+     *         <li>get("foo", 1) - a {@link CompactedException} will be 
thrown.</li>
+     *         <li>get("foo", 2) - will return a single value with revision 
2.</li>
+     *         <li>get("foo", 3) - will return a single value with revision 
2.</li>
+     *         <li>get("bar", 1) - a {@link CompactedException} will be 
thrown.</li>
+     *         <li>get("bar", 2) - a {@link CompactedException} will be 
thrown.</li>
+     *         <li>get("bar", 3) - a {@link CompactedException} will be 
thrown.</li>
+     *         <li>get("some", 1) - a {@link CompactedException} will be 
thrown.</li>
+     *         <li>get("some", 2) - a {@link CompactedException} will be 
thrown.</li>
+     *         <li>get("some", 3) - a {@link CompactedException} will be 
thrown.</li>
+     *     </ul>
+     *     </li>
+     * </ul>
+     *
      * @param key The key.
      * @param revUpperBound The upper bound of revision.
-     * @return Value corresponding to the given key.
+     * @throws CompactedException If the requested entry was not found and the 
{@code revUpperBound} is less than or equal to the last
+     *      {@link #setCompactionRevision compacted} one.
      */
     Entry get(byte[] key, long revUpperBound);
 
diff --git 
a/modules/metastorage/src/main/java/org/apache/ignite/internal/metastorage/server/KeyValueStorageUtils.java
 
b/modules/metastorage/src/main/java/org/apache/ignite/internal/metastorage/server/KeyValueStorageUtils.java
index e5b97cbb4d..5657008a34 100644
--- 
a/modules/metastorage/src/main/java/org/apache/ignite/internal/metastorage/server/KeyValueStorageUtils.java
+++ 
b/modules/metastorage/src/main/java/org/apache/ignite/internal/metastorage/server/KeyValueStorageUtils.java
@@ -24,11 +24,11 @@ import java.util.function.LongPredicate;
 
 /** Helper class with useful methods and constants for {@link KeyValueStorage} 
implementations. */
 public class KeyValueStorageUtils {
-    /** Special value indicating that there are no key revisions that need to 
be compacted. */
-    public static final int NOTHING_TO_COMPACT_INDEX = -1;
+    /** Constant meaning something could not be found. */
+    public static final int NOT_FOUND = -1;
 
     /**
-     * Calculates the revision index in key revisions up to which compaction 
is needed or {@link #NOTHING_TO_COMPACT_INDEX} if nothing
+     * Calculates the revision index in key revisions up to which compaction 
is needed or {@link #NOT_FOUND} if nothing
      * needs to be compacted.
      *
      * <p>If the returned index points to the last revision and if the last 
revision is <b>not</b> a tombstone, then the returned index is
@@ -43,19 +43,47 @@ public class KeyValueStorageUtils {
 
         if (i < 0) {
             if (i == -1) {
-                return NOTHING_TO_COMPACT_INDEX;
+                return NOT_FOUND;
             }
 
             i = -(i + 2);
         }
 
         if (i == keyRevisions.length - 1 && 
!isTombstone.test(keyRevisions[i])) {
-            i = i == 0 ? NOTHING_TO_COMPACT_INDEX : i - 1;
+            i = i == 0 ? NOT_FOUND : i - 1;
         }
 
         return i;
     }
 
+    /**
+     * Returns index of maximum revision which must be less or equal to {@code 
upperBoundRevision}. If there is no such revision then
+     * {@link #NOT_FOUND} will be returned.
+     *
+     * @param keyRevisions Metastorage key revisions in ascending order.
+     * @param upperBoundRevision Revision upper bound.
+     */
+    public static int maxRevisionIndex(long[] keyRevisions, long 
upperBoundRevision) {
+        int i = binarySearch(keyRevisions, upperBoundRevision);
+
+        if (i < 0) {
+            if (i == -1) {
+                return NOT_FOUND;
+            }
+
+            i = -(i + 2);
+        }
+
+        return i;
+    }
+
+    /** Returns {@link true} if the requested index is the last index of the 
array. */
+    public static boolean isLastIndex(long[] arr, int index) {
+        assert index >= 0 && index < arr.length : "index=" + index + ", 
arr.length=" + arr.length;
+
+        return arr.length - 1 == index;
+    }
+
     /**
      * Converts bytes to UTF-8 string.
      *
diff --git 
a/modules/metastorage/src/main/java/org/apache/ignite/internal/metastorage/server/persistence/RocksDbKeyValueStorage.java
 
b/modules/metastorage/src/main/java/org/apache/ignite/internal/metastorage/server/persistence/RocksDbKeyValueStorage.java
index 773168b72d..a0769a46e3 100644
--- 
a/modules/metastorage/src/main/java/org/apache/ignite/internal/metastorage/server/persistence/RocksDbKeyValueStorage.java
+++ 
b/modules/metastorage/src/main/java/org/apache/ignite/internal/metastorage/server/persistence/RocksDbKeyValueStorage.java
@@ -19,10 +19,11 @@ package 
org.apache.ignite.internal.metastorage.server.persistence;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.apache.ignite.internal.hlc.HybridTimestamp.hybridTimestamp;
-import static 
org.apache.ignite.internal.metastorage.server.KeyValueStorageUtils.NOTHING_TO_COMPACT_INDEX;
+import static 
org.apache.ignite.internal.metastorage.server.KeyValueStorageUtils.NOT_FOUND;
 import static 
org.apache.ignite.internal.metastorage.server.KeyValueStorageUtils.assertCompactionRevisionLessThanCurrent;
 import static 
org.apache.ignite.internal.metastorage.server.KeyValueStorageUtils.assertRequestedRevisionLessThanOrEqualToCurrent;
 import static 
org.apache.ignite.internal.metastorage.server.KeyValueStorageUtils.indexToCompact;
+import static 
org.apache.ignite.internal.metastorage.server.KeyValueStorageUtils.isLastIndex;
 import static 
org.apache.ignite.internal.metastorage.server.KeyValueStorageUtils.toUtf8String;
 import static org.apache.ignite.internal.metastorage.server.Value.TOMBSTONE;
 import static 
org.apache.ignite.internal.metastorage.server.persistence.RocksStorageUtils.appendLong;
@@ -93,6 +94,7 @@ import 
org.apache.ignite.internal.metastorage.impl.MetaStorageManagerImpl;
 import org.apache.ignite.internal.metastorage.server.Condition;
 import org.apache.ignite.internal.metastorage.server.If;
 import org.apache.ignite.internal.metastorage.server.KeyValueStorage;
+import org.apache.ignite.internal.metastorage.server.KeyValueStorageUtils;
 import org.apache.ignite.internal.metastorage.server.OnRevisionAppliedCallback;
 import org.apache.ignite.internal.metastorage.server.Statement;
 import org.apache.ignite.internal.metastorage.server.Value;
@@ -957,7 +959,7 @@ public class RocksDbKeyValueStorage implements 
KeyValueStorage {
 
     @Override
     public void compact(long revision) {
-        assert revision >= 0;
+        assert revision >= 0 : revision;
 
         try {
             compactKeys(revision);
@@ -1018,7 +1020,7 @@ public class RocksDbKeyValueStorage implements 
KeyValueStorage {
         try {
             int indexToCompact = indexToCompact(revs, compactionRevision, 
revision -> isTombstoneForCompaction(key, revision));
 
-            if (NOTHING_TO_COMPACT_INDEX == indexToCompact) {
+            if (NOT_FOUND == indexToCompact) {
                 return;
             }
 
@@ -1065,35 +1067,27 @@ public class RocksDbKeyValueStorage implements 
KeyValueStorage {
         return res;
     }
 
-    /**
-     * Gets the value by key and revision.
-     *
-     * @param key            Target key.
-     * @param revUpperBound  Target upper bound of revision.
-     * @return Value.
-     */
     private Entry doGet(byte[] key, long revUpperBound) {
-        assert revUpperBound >= 0 : "Invalid arguments: [revUpperBound=" + 
revUpperBound + ']';
+        assert revUpperBound >= 0 : revUpperBound;
 
-        long[] revs;
-        try {
-            revs = getRevisions(key);
-        } catch (RocksDBException e) {
-            throw new MetaStorageException(OP_EXECUTION_ERR, e);
-        }
+        long[] keyRevisions = getRevisionsForOperation(key);
+        int maxRevisionIndex = 
KeyValueStorageUtils.maxRevisionIndex(keyRevisions, revUpperBound);
+
+        if (maxRevisionIndex == NOT_FOUND) {
+            
CompactedException.throwIfRequestedRevisionLessThanOrEqualToCompacted(revUpperBound,
 compactionRevision);
 
-        if (revs.length == 0) {
             return EntryImpl.empty(key);
         }
 
-        long lastRev = maxRevision(revs, revUpperBound);
+        long revision = keyRevisions[maxRevisionIndex];
 
-        // lastRev can be -1 if maxRevision return -1.
-        if (lastRev == -1) {
-            return EntryImpl.empty(key);
+        Value value = getValueForOperation(key, revision);
+
+        if (revUpperBound <= compactionRevision && (!isLastIndex(keyRevisions, 
maxRevisionIndex) || value.tombstone())) {
+            throw new CompactedException(revUpperBound, compactionRevision);
         }
 
-        return doGetValue(key, lastRev);
+        return EntryImpl.toEntry(key, revision, value);
     }
 
     /**
@@ -1142,10 +1136,9 @@ public class RocksDbKeyValueStorage implements 
KeyValueStorage {
     }
 
     /**
-     * Get a list of the revisions of the entry corresponding to the key.
+     * Returns array of revisions of the entry corresponding to the key.
      *
      * @param key Key.
-     * @return Array of revisions.
      * @throws RocksDBException If failed to perform {@link 
RocksDB#get(ColumnFamilyHandle, byte[])}.
      */
     private long[] getRevisions(byte[] key) throws RocksDBException {
@@ -1158,6 +1151,20 @@ public class RocksDbKeyValueStorage implements 
KeyValueStorage {
         return getAsLongs(revisions);
     }
 
+    /**
+     * Returns array of revisions of the entry corresponding to the key.
+     *
+     * @param key Key.
+     * @throws MetaStorageException If there was an error while getting the 
revisions for the key.
+     */
+    private long[] getRevisionsForOperation(byte[] key) {
+        try {
+            return getRevisions(key);
+        } catch (RocksDBException e) {
+            throw new MetaStorageException(OP_EXECUTION_ERR, "Failed to get 
revisions for the key: " + toUtf8String(key), e);
+        }
+    }
+
     /**
      * Returns maximum revision which must be less or equal to {@code 
upperBoundRev}. If there is no such revision then {@code -1} will be
      * returned.
@@ -1563,7 +1570,7 @@ public class RocksDbKeyValueStorage implements 
KeyValueStorage {
 
     @Override
     public void saveCompactionRevision(long revision) {
-        assert revision >= 0;
+        assert revision >= 0 : revision;
 
         rwLock.writeLock().lock();
 
@@ -1582,7 +1589,7 @@ public class RocksDbKeyValueStorage implements 
KeyValueStorage {
 
     @Override
     public void setCompactionRevision(long revision) {
-        assert revision >= 0;
+        assert revision >= 0 : revision;
 
         rwLock.writeLock().lock();
 
@@ -1696,4 +1703,20 @@ public class RocksDbKeyValueStorage implements 
KeyValueStorage {
             );
         }
     }
+
+    private Value getValueForOperation(byte[] key, long revision) {
+        try {
+            byte[] valueBytes = data.get(keyToRocksKey(revision, key));
+
+            assert valueBytes != null && valueBytes.length != 0 : "key=" + 
toUtf8String(key) + ", revision=" + revision;
+
+            return bytesToValue(valueBytes);
+        } catch (RocksDBException e) {
+            throw new MetaStorageException(
+                    OP_EXECUTION_ERR,
+                    String.format("Failed to get value: [key=%s, 
revision=%s]", toUtf8String(key), revision),
+                    e
+            );
+        }
+    }
 }
diff --git 
a/modules/metastorage/src/test/java/org/apache/ignite/internal/metastorage/server/AbstractCompactionKeyValueStorageTest.java
 
b/modules/metastorage/src/test/java/org/apache/ignite/internal/metastorage/server/AbstractCompactionKeyValueStorageTest.java
index 097f534eb9..6bee5c9472 100644
--- 
a/modules/metastorage/src/test/java/org/apache/ignite/internal/metastorage/server/AbstractCompactionKeyValueStorageTest.java
+++ 
b/modules/metastorage/src/test/java/org/apache/ignite/internal/metastorage/server/AbstractCompactionKeyValueStorageTest.java
@@ -22,6 +22,7 @@ import static 
org.apache.ignite.internal.metastorage.dsl.Operations.noop;
 import static org.apache.ignite.internal.metastorage.dsl.Operations.ops;
 import static org.apache.ignite.internal.metastorage.dsl.Operations.put;
 import static org.apache.ignite.internal.metastorage.dsl.Operations.remove;
+import static 
org.apache.ignite.internal.metastorage.server.KeyValueStorageUtils.toUtf8String;
 import static 
org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willCompleteSuccessfully;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
@@ -58,6 +59,8 @@ public abstract class AbstractCompactionKeyValueStorageTest 
extends AbstractKeyV
 
     private static final byte[] SOME_VALUE = fromString("someValue");
 
+    private static final byte[] NOT_EXISTS_KEY = fromString("notExistsKey");
+
     @WorkDirectory
     Path workDir;
 
@@ -68,14 +71,19 @@ public abstract class AbstractCompactionKeyValueStorageTest 
extends AbstractKeyV
     void setUp() {
         super.setUp();
 
+        // Revision = 1.
         storage.putAll(List.of(FOO_KEY, BAR_KEY), List.of(SOME_VALUE, 
SOME_VALUE), clock.now());
+        // Revision = 2.
         storage.put(BAR_KEY, SOME_VALUE, clock.now());
+        // Revision = 3.
         storage.put(FOO_KEY, SOME_VALUE, clock.now());
+        // Revision = 4.
         storage.put(SOME_KEY, SOME_VALUE, clock.now());
 
         var fooKey = new ByteArray(FOO_KEY);
         var barKey = new ByteArray(BAR_KEY);
 
+        // Revision = 5.
         var iif = new If(
                 new AndCondition(new ExistenceCondition(Type.EXISTS, FOO_KEY), 
new ExistenceCondition(Type.EXISTS, BAR_KEY)),
                 new Statement(ops(put(fooKey, SOME_VALUE), 
remove(barKey)).yield()),
@@ -84,14 +92,17 @@ public abstract class AbstractCompactionKeyValueStorageTest 
extends AbstractKeyV
 
         storage.invoke(iif, clock.now(), new 
CommandIdGenerator(UUID::randomUUID).newId());
 
+        // Revision = 6.
         storage.remove(SOME_KEY, clock.now());
 
+        // Revision = 7.
         // Special revision update to prevent tests from failing.
         storage.put(fromString("fake"), SOME_VALUE, clock.now());
 
+        assertEquals(7, storage.revision());
         assertEquals(List.of(1, 3, 5), collectRevisions(FOO_KEY));
-        assertEquals(List.of(1, 2, 5), collectRevisions(BAR_KEY));
-        assertEquals(List.of(4, 6), collectRevisions(SOME_KEY));
+        assertEquals(List.of(1, 2, 5/* Tombstone */), 
collectRevisions(BAR_KEY));
+        assertEquals(List.of(4, 6/* Tombstone */), collectRevisions(SOME_KEY));
     }
 
     @Test
@@ -326,6 +337,98 @@ public abstract class 
AbstractCompactionKeyValueStorageTest extends AbstractKeyV
         assertThrows(CompactedException.class, () -> 
storage.revisionByTimestamp(timestamp3.subtractPhysicalTime(1)));
     }
 
+    @Test
+    void testGetSingleEntryLatestAndCompaction() {
+        storage.setCompactionRevision(6);
+
+        assertDoesNotThrow(() -> storage.get(FOO_KEY));
+        assertDoesNotThrow(() -> storage.get(BAR_KEY));
+        assertDoesNotThrow(() -> storage.get(NOT_EXISTS_KEY));
+    }
+
+    @Test
+    void testGetSingleEntryAndCompactionForFooKey() {
+        // FOO_KEY has revisions: [1, 3, 5].
+        storage.setCompactionRevision(1);
+        assertThrowsCompactedExceptionForGetSingleValue(FOO_KEY, 1);
+        assertDoesNotThrowCompactedExceptionForGetSingleValue(FOO_KEY, 2);
+
+        storage.setCompactionRevision(2);
+        assertThrowsCompactedExceptionForGetSingleValue(FOO_KEY, 2);
+        assertDoesNotThrowCompactedExceptionForGetSingleValue(FOO_KEY, 3);
+
+        storage.setCompactionRevision(3);
+        assertThrowsCompactedExceptionForGetSingleValue(FOO_KEY, 3);
+        assertDoesNotThrowCompactedExceptionForGetSingleValue(FOO_KEY, 4);
+
+        storage.setCompactionRevision(4);
+        assertThrowsCompactedExceptionForGetSingleValue(FOO_KEY, 4);
+        assertDoesNotThrowCompactedExceptionForGetSingleValue(FOO_KEY, 5);
+
+        storage.setCompactionRevision(5);
+        assertThrowsCompactedExceptionForGetSingleValue(FOO_KEY, 4);
+        assertDoesNotThrowCompactedExceptionForGetSingleValue(FOO_KEY, 5);
+
+        storage.setCompactionRevision(6);
+        assertThrowsCompactedExceptionForGetSingleValue(FOO_KEY, 4);
+        assertDoesNotThrowCompactedExceptionForGetSingleValue(FOO_KEY, 5);
+    }
+
+    @Test
+    void testGetSingleEntryAndCompactionForBarKey() {
+        // BAR_KEY has revisions: [1, 2, 5 (tombstone)].
+        storage.setCompactionRevision(1);
+        assertThrowsCompactedExceptionForGetSingleValue(BAR_KEY, 1);
+        assertDoesNotThrowCompactedExceptionForGetSingleValue(BAR_KEY, 2);
+
+        storage.setCompactionRevision(2);
+        assertThrowsCompactedExceptionForGetSingleValue(BAR_KEY, 2);
+        assertDoesNotThrowCompactedExceptionForGetSingleValue(BAR_KEY, 3);
+
+        storage.setCompactionRevision(3);
+        assertThrowsCompactedExceptionForGetSingleValue(BAR_KEY, 3);
+        assertDoesNotThrowCompactedExceptionForGetSingleValue(BAR_KEY, 4);
+
+        storage.setCompactionRevision(4);
+        assertThrowsCompactedExceptionForGetSingleValue(BAR_KEY, 4);
+        assertDoesNotThrowCompactedExceptionForGetSingleValue(BAR_KEY, 5);
+
+        storage.setCompactionRevision(5);
+        assertThrowsCompactedExceptionForGetSingleValue(BAR_KEY, 5);
+        assertDoesNotThrowCompactedExceptionForGetSingleValue(BAR_KEY, 6);
+
+        storage.setCompactionRevision(6);
+        assertThrowsCompactedExceptionForGetSingleValue(BAR_KEY, 6);
+        assertDoesNotThrowCompactedExceptionForGetSingleValue(BAR_KEY, 7);
+    }
+
+    @Test
+    void testGetSingleEntryAndCompactionForNotExistsKey() {
+        storage.setCompactionRevision(1);
+        assertThrowsCompactedExceptionForGetSingleValue(NOT_EXISTS_KEY, 1);
+        assertDoesNotThrowCompactedExceptionForGetSingleValue(NOT_EXISTS_KEY, 
2);
+
+        storage.setCompactionRevision(2);
+        assertThrowsCompactedExceptionForGetSingleValue(NOT_EXISTS_KEY, 2);
+        assertDoesNotThrowCompactedExceptionForGetSingleValue(NOT_EXISTS_KEY, 
3);
+
+        storage.setCompactionRevision(3);
+        assertThrowsCompactedExceptionForGetSingleValue(NOT_EXISTS_KEY, 3);
+        assertDoesNotThrowCompactedExceptionForGetSingleValue(NOT_EXISTS_KEY, 
4);
+
+        storage.setCompactionRevision(4);
+        assertThrowsCompactedExceptionForGetSingleValue(NOT_EXISTS_KEY, 4);
+        assertDoesNotThrowCompactedExceptionForGetSingleValue(NOT_EXISTS_KEY, 
5);
+
+        storage.setCompactionRevision(5);
+        assertThrowsCompactedExceptionForGetSingleValue(NOT_EXISTS_KEY, 5);
+        assertDoesNotThrowCompactedExceptionForGetSingleValue(NOT_EXISTS_KEY, 
6);
+
+        storage.setCompactionRevision(6);
+        assertThrowsCompactedExceptionForGetSingleValue(NOT_EXISTS_KEY, 6);
+        assertDoesNotThrowCompactedExceptionForGetSingleValue(NOT_EXISTS_KEY, 
7);
+    }
+
     private List<Integer> collectRevisions(byte[] key) {
         var revisions = new ArrayList<Integer>();
 
@@ -343,4 +446,27 @@ public abstract class 
AbstractCompactionKeyValueStorageTest extends AbstractKeyV
     private static byte[] fromString(String s) {
         return s.getBytes(UTF_8);
     }
+
+    private void assertThrowsCompactedExceptionForGetSingleValue(byte[] key, 
long endRevisionInclusive) {
+        for (long i = 0; i <= endRevisionInclusive; i++) {
+            long revisionUpperBound = i;
+
+            assertThrows(
+                    CompactedException.class,
+                    () -> storage.get(key, revisionUpperBound),
+                    () -> String.format("key=%s, revision=%s", 
toUtf8String(key), revisionUpperBound)
+            );
+        }
+    }
+
+    private void assertDoesNotThrowCompactedExceptionForGetSingleValue(byte[] 
key, long startRevisionInclusive) {
+        for (long i = startRevisionInclusive; i <= storage.revision(); i++) {
+            long revisionUpperBound = i;
+
+            assertDoesNotThrow(
+                    () -> storage.get(key, revisionUpperBound),
+                    () -> String.format("key=%s, revision=%s", 
toUtf8String(key), revisionUpperBound)
+            );
+        }
+    }
 }
diff --git 
a/modules/metastorage/src/test/java/org/apache/ignite/internal/metastorage/server/KeyValueStorageUtilsTest.java
 
b/modules/metastorage/src/test/java/org/apache/ignite/internal/metastorage/server/KeyValueStorageUtilsTest.java
index 241aeb9fbd..4211790552 100644
--- 
a/modules/metastorage/src/test/java/org/apache/ignite/internal/metastorage/server/KeyValueStorageUtilsTest.java
+++ 
b/modules/metastorage/src/test/java/org/apache/ignite/internal/metastorage/server/KeyValueStorageUtilsTest.java
@@ -18,11 +18,15 @@
 package org.apache.ignite.internal.metastorage.server;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
-import static 
org.apache.ignite.internal.metastorage.server.KeyValueStorageUtils.NOTHING_TO_COMPACT_INDEX;
+import static 
org.apache.ignite.internal.metastorage.server.KeyValueStorageUtils.NOT_FOUND;
 import static 
org.apache.ignite.internal.metastorage.server.KeyValueStorageUtils.indexToCompact;
+import static 
org.apache.ignite.internal.metastorage.server.KeyValueStorageUtils.isLastIndex;
+import static 
org.apache.ignite.internal.metastorage.server.KeyValueStorageUtils.maxRevisionIndex;
 import static 
org.apache.ignite.internal.metastorage.server.KeyValueStorageUtils.toUtf8String;
 import static org.apache.ignite.internal.util.ArrayUtils.LONG_EMPTY_ARRAY;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import org.junit.jupiter.api.Test;
 
@@ -30,30 +34,30 @@ import org.junit.jupiter.api.Test;
 public class KeyValueStorageUtilsTest {
     @Test
     void testIndexToCompactNoRevisions() {
-        assertEquals(NOTHING_TO_COMPACT_INDEX, 
indexToCompact(LONG_EMPTY_ARRAY, 0, revision -> false));
-        assertEquals(NOTHING_TO_COMPACT_INDEX, 
indexToCompact(LONG_EMPTY_ARRAY, 0, revision -> true));
+        assertEquals(NOT_FOUND, indexToCompact(LONG_EMPTY_ARRAY, 0, revision 
-> false));
+        assertEquals(NOT_FOUND, indexToCompact(LONG_EMPTY_ARRAY, 0, revision 
-> true));
     }
 
     @Test
     void testIndexToCompactSingleRevision() {
         long[] keyRevisions = {2};
 
-        assertEquals(NOTHING_TO_COMPACT_INDEX, indexToCompact(keyRevisions, 1, 
revision -> false));
-        assertEquals(NOTHING_TO_COMPACT_INDEX, indexToCompact(keyRevisions, 1, 
revision -> true));
+        assertEquals(NOT_FOUND, indexToCompact(keyRevisions, 1, revision -> 
false));
+        assertEquals(NOT_FOUND, indexToCompact(keyRevisions, 1, revision -> 
true));
 
         assertEquals(0, indexToCompact(keyRevisions, 2, revision -> true));
-        assertEquals(NOTHING_TO_COMPACT_INDEX, indexToCompact(keyRevisions, 2, 
revision -> false));
+        assertEquals(NOT_FOUND, indexToCompact(keyRevisions, 2, revision -> 
false));
 
         assertEquals(0, indexToCompact(keyRevisions, 3, revision -> true));
-        assertEquals(NOTHING_TO_COMPACT_INDEX, indexToCompact(keyRevisions, 3, 
revision -> false));
+        assertEquals(NOT_FOUND, indexToCompact(keyRevisions, 3, revision -> 
false));
     }
 
     @Test
     void testIndexToCompactMultipleRevisions() {
         long[] keyRevisions = {2, 4, 5};
 
-        assertEquals(NOTHING_TO_COMPACT_INDEX, indexToCompact(keyRevisions, 1, 
revision -> true));
-        assertEquals(NOTHING_TO_COMPACT_INDEX, indexToCompact(keyRevisions, 1, 
revision -> false));
+        assertEquals(NOT_FOUND, indexToCompact(keyRevisions, 1, revision -> 
true));
+        assertEquals(NOT_FOUND, indexToCompact(keyRevisions, 1, revision -> 
false));
 
         assertEquals(0, indexToCompact(keyRevisions, 2, revision -> true));
         assertEquals(0, indexToCompact(keyRevisions, 2, revision -> false));
@@ -75,4 +79,32 @@ public class KeyValueStorageUtilsTest {
     void testToUtf8String() {
         assertEquals("foo", toUtf8String("foo".getBytes(UTF_8)));
     }
+
+    @Test
+    void testMaxRevisionIndex() {
+        long[] keyRevisions = {3, 5, 7};
+
+        assertEquals(NOT_FOUND, maxRevisionIndex(keyRevisions, 1));
+        assertEquals(NOT_FOUND, maxRevisionIndex(keyRevisions, 2));
+
+        assertEquals(0, maxRevisionIndex(keyRevisions, 3));
+        assertEquals(0, maxRevisionIndex(keyRevisions, 4));
+
+        assertEquals(1, maxRevisionIndex(keyRevisions, 5));
+        assertEquals(1, maxRevisionIndex(keyRevisions, 6));
+
+        assertEquals(2, maxRevisionIndex(keyRevisions, 7));
+        assertEquals(2, maxRevisionIndex(keyRevisions, 8));
+        assertEquals(2, maxRevisionIndex(keyRevisions, 9));
+    }
+
+    @Test
+    void testIsLastIndex() {
+        long[] array = {3, 5, 7};
+
+        assertFalse(isLastIndex(array, 0));
+        assertFalse(isLastIndex(array, 1));
+
+        assertTrue(isLastIndex(array, 2));
+    }
 }
diff --git 
a/modules/metastorage/src/testFixtures/java/org/apache/ignite/internal/metastorage/server/SimpleInMemoryKeyValueStorage.java
 
b/modules/metastorage/src/testFixtures/java/org/apache/ignite/internal/metastorage/server/SimpleInMemoryKeyValueStorage.java
index ec35d7b12c..b92d7a611a 100644
--- 
a/modules/metastorage/src/testFixtures/java/org/apache/ignite/internal/metastorage/server/SimpleInMemoryKeyValueStorage.java
+++ 
b/modules/metastorage/src/testFixtures/java/org/apache/ignite/internal/metastorage/server/SimpleInMemoryKeyValueStorage.java
@@ -22,10 +22,11 @@ import static 
java.util.concurrent.CompletableFuture.failedFuture;
 import static java.util.stream.Collectors.collectingAndThen;
 import static java.util.stream.Collectors.toList;
 import static java.util.stream.Collectors.toMap;
-import static 
org.apache.ignite.internal.metastorage.server.KeyValueStorageUtils.NOTHING_TO_COMPACT_INDEX;
+import static 
org.apache.ignite.internal.metastorage.server.KeyValueStorageUtils.NOT_FOUND;
 import static 
org.apache.ignite.internal.metastorage.server.KeyValueStorageUtils.assertCompactionRevisionLessThanCurrent;
 import static 
org.apache.ignite.internal.metastorage.server.KeyValueStorageUtils.assertRequestedRevisionLessThanOrEqualToCurrent;
 import static 
org.apache.ignite.internal.metastorage.server.KeyValueStorageUtils.indexToCompact;
+import static 
org.apache.ignite.internal.metastorage.server.KeyValueStorageUtils.isLastIndex;
 import static 
org.apache.ignite.internal.metastorage.server.KeyValueStorageUtils.toUtf8String;
 import static org.apache.ignite.internal.metastorage.server.Value.TOMBSTONE;
 import static 
org.apache.ignite.internal.metastorage.server.raft.MetaStorageWriteHandler.IDEMPOTENT_COMMAND_PREFIX;
@@ -417,7 +418,7 @@ public class SimpleInMemoryKeyValueStorage implements 
KeyValueStorage {
 
     @Override
     public HybridTimestamp timestampByRevision(long revision) {
-        assert revision >= 0;
+        assert revision >= 0 : revision;
 
         synchronized (mux) {
             assertRequestedRevisionLessThanOrEqualToCurrent(revision, rev);
@@ -542,7 +543,7 @@ public class SimpleInMemoryKeyValueStorage implements 
KeyValueStorage {
 
     @Override
     public void compact(long revision) {
-        assert revision >= 0;
+        assert revision >= 0 : revision;
 
         for (Map.Entry<byte[], List<Long>> entry : keysIdx.entrySet()) {
             synchronized (mux) {
@@ -681,7 +682,7 @@ public class SimpleInMemoryKeyValueStorage implements 
KeyValueStorage {
     private void compactForKey(byte[] key, long[] revs, long 
compactionRevision) {
         int indexToCompact = indexToCompact(revs, compactionRevision, revision 
-> isTombstoneForCompaction(key, revision));
 
-        if (indexToCompact == NOTHING_TO_COMPACT_INDEX) {
+        if (indexToCompact == NOT_FOUND) {
             return;
         }
 
@@ -725,22 +726,26 @@ public class SimpleInMemoryKeyValueStorage implements 
KeyValueStorage {
     }
 
     private Entry doGet(byte[] key, long revUpperBound) {
-        assert revUpperBound >= 0 : "Invalid arguments: [revUpperBound=" + 
revUpperBound + ']';
+        assert revUpperBound >= 0 : revUpperBound;
 
-        List<Long> revs = keysIdx.get(key);
+        long[] keyRevisions = toLongArray(keysIdx.get(key));
+        int maxRevisionIndex = 
KeyValueStorageUtils.maxRevisionIndex(keyRevisions, revUpperBound);
+
+        if (maxRevisionIndex == NOT_FOUND) {
+            
CompactedException.throwIfRequestedRevisionLessThanOrEqualToCompacted(revUpperBound,
 compactionRevision);
 
-        if (revs == null || revs.isEmpty()) {
             return EntryImpl.empty(key);
         }
 
-        long lastRev = maxRevision(revs, revUpperBound);
+        long revision = keyRevisions[maxRevisionIndex];
 
-        // lastRev can be -1 if maxRevision return -1.
-        if (lastRev == -1) {
-            return EntryImpl.empty(key);
+        Value value = getValue(key, revision);
+
+        if (revUpperBound <= compactionRevision && (!isLastIndex(keyRevisions, 
maxRevisionIndex) || value.tombstone())) {
+            throw new CompactedException(revUpperBound, compactionRevision);
         }
 
-        return doGetValue(key, lastRev);
+        return EntryImpl.toEntry(key, revision, value);
     }
 
     private List<Entry> doGet(byte[] key, long revLowerBound, long 
revUpperBound) {
@@ -943,7 +948,7 @@ public class SimpleInMemoryKeyValueStorage implements 
KeyValueStorage {
 
     @Override
     public void saveCompactionRevision(long revision) {
-        assert revision >= 0;
+        assert revision >= 0 : revision;
 
         synchronized (mux) {
             assertCompactionRevisionLessThanCurrent(revision, rev);
@@ -954,7 +959,7 @@ public class SimpleInMemoryKeyValueStorage implements 
KeyValueStorage {
 
     @Override
     public void setCompactionRevision(long revision) {
-        assert revision >= 0;
+        assert revision >= 0 : revision;
 
         synchronized (mux) {
             assertCompactionRevisionLessThanCurrent(revision, rev);
@@ -970,8 +975,8 @@ public class SimpleInMemoryKeyValueStorage implements 
KeyValueStorage {
         }
     }
 
-    private static long[] toLongArray(List<Long> list) {
-        if (list.isEmpty()) {
+    private static long[] toLongArray(@Nullable List<Long> list) {
+        if (list == null) {
             return LONG_EMPTY_ARRAY;
         }
 
@@ -993,4 +998,16 @@ public class SimpleInMemoryKeyValueStorage implements 
KeyValueStorage {
 
         return value.tombstone();
     }
+
+    private Value getValue(byte[] key, long revision) {
+        NavigableMap<byte[], Value> valueByKey = revsIdx.get(revision);
+
+        assert valueByKey != null : "key=" + toUtf8String(key) + ", revision=" 
+ revision;
+
+        Value value = valueByKey.get(key);
+
+        assert value != null : "key=" + toUtf8String(key) + ", revision=" + 
revision;
+
+        return value;
+    }
 }

Reply via email to