You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by ag...@apache.org on 2021/04/15 18:19:19 UTC

[ignite-3] branch ignite-14389 updated (8ff3571 -> af45385)

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

agura pushed a change to branch ignite-14389
in repository https://gitbox.apache.org/repos/asf/ignite-3.git.


 discard 8ff3571  IGNITE-14389 getAll and tests (WIP)
 discard 35e16e4  IGNITE-14389 Added get and do smth semantic
 discard d6e454c  IGNITE-14398: Meta storage: added update counter
 discard 554b1b4  IGNITE-14389 Meta storage: in-memory implementation WIP
     add 6b20869  IGNITE-14517 Fixed topology change handling on graceful shutdown. - Fixes #92.
     add 28f0927  IGNITE-14501: Ignite 3: Fix toString implementations. (#88)
     add 2b1404d  IGNITE-14476 Explicit specification of Storage class replaced with new ConfigurationType enum (#86)
     add 0a10985  IGNITE-13751: Add maven javadoc plugin. (#93)
     new 5a2434d  IGNITE-14389 Meta storage: in-memory implementation WIP
     new 7ee5728  IGNITE-14398: Meta storage: added update counter
     new a3862e6  IGNITE-14389 Added get and do smth semantic
     new 2033ce5  IGNITE-14389 getAll and tests (WIP)
     new af45385  IGNITE-14389 putAll initial (WIP)

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (8ff3571)
            \
             N -- N -- N   refs/heads/ignite-14389 (af45385)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 5 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../org/apache/ignite/table/InvokeProcessor.java   |    2 +-
 .../java/org/apache/ignite/table/KeyValueView.java |    2 +-
 .../java/org/apache/ignite/table/RecordView.java   |    2 +-
 modules/bytecode/pom.xml                           |   15 +
 .../presto/bytecode/AnnotationDefinition.java      |    4 +-
 modules/cli/pom.xml                                |    6 +
 .../java/org/apache/ignite/cli/IgnitePaths.java    |   45 +-
 .../ignite/cli/builtins/module/ModuleRegistry.java |   25 +-
 .../builtins/module/StandardModuleDefinition.java  |    8 +-
 .../org/apache/ignite/cli/spec/IgniteCliSpec.java  |    2 +-
 .../processor/internal/Processor.java              |   45 +-
 .../configuration/ConfigurationChangerTest.java    |   11 +-
 .../internal/util/ConfigurationUtilTest.java       |    4 +-
 .../internal/validation/ValidationUtilTest.java    |    4 +-
 .../notifications/ConfigurationListenerTest.java   |    9 +-
 .../sample/LocalConfigurationSchema.java           |    4 +-
 .../sample/NetworkConfigurationSchema.java         |    4 +-
 .../storage/TestConfigurationStorage.java          |    5 +
 .../ignite/configuration/ConfigurationChanger.java |   27 +-
 .../configuration/ConfigurationRegistry.java       |    7 +-
 .../ignite/configuration/ConfigurationValue.java   |    2 +-
 .../org/apache/ignite/configuration/RootKey.java   |    4 +-
 .../ignite/configuration/annotation/Config.java    |    4 +-
 .../configuration/annotation/ConfigValue.java      |    4 +-
 .../annotation/ConfigurationRoot.java              |    6 +-
 .../configuration/annotation/NamedConfigValue.java |    4 +-
 .../ignite/configuration/annotation/Value.java     |    6 +-
 .../ignite/configuration/internal/RootKeyImpl.java |    8 +-
 .../internal/util/AnyNodeConfigurationVisitor.java |    2 +-
 .../internal/util/ConfigurationUtil.java           |    5 +-
 .../storage/ConfigurationStorage.java              |    5 +
 .../configuration/storage/ConfigurationType.java}  |   12 +-
 .../ignite/configuration/tree/InnerNode.java       |   20 +-
 .../configuration/validation/ValidationIssue.java  |    8 +-
 .../internal/tostring/CircularStringBuilder.java   |  277 +++
 .../ignite/internal/tostring/ClassDescriptor.java  |   86 +
 .../ignite/internal/tostring/FieldDescriptor.java  |  142 ++
 .../internal/tostring/IgniteToStringBuilder.java   | 2110 ++++++++++++++++++++
 .../internal/tostring/IgniteToStringExclude.java}  |   27 +-
 .../internal/tostring/IgniteToStringInclude.java}  |   36 +-
 .../internal/tostring/IgniteToStringOrder.java}    |   31 +-
 .../org/apache/ignite/internal/tostring/S.java}    |   11 +-
 .../ignite/internal/tostring/SBLimitedLength.java  |  287 +++
 .../tostring/SensitiveDataLoggingPolicy.java}      |   22 +-
 .../ignite/internal/tostring}/package-info.java    |    4 +-
 .../apache/ignite/internal/util/GridUnsafe.java    |    4 +-
 .../apache/ignite/internal/util/IgniteUtils.java   |   56 +-
 .../apache/ignite/lang/IgniteStringBuilder.java    |  504 +++++
 .../apache/ignite/lang/IgniteSystemProperties.java |  307 +++
 .../internal/testframework/IgniteAbstractTest.java |   54 +
 .../testframework/SystemPropertiesExtension.java   |  198 ++
 .../testframework/SystemPropertiesList.java}       |   30 +-
 .../internal/testframework/WithSystemProperty.java |  114 ++
 .../internal/testframework}/package-info.java      |    4 +-
 .../tostring/CircularStringBuilderSelfTest.java    |   72 +
 .../tostring/IgniteToStringBuilderSelfTest.java    |  994 +++++++++
 .../tostring/SensitiveDataToStringTest.java        |  180 ++
 .../apache/ignite/metastorage/common/Entry.java    |    4 +-
 .../metastorage/server/KeyValueStorage.java        |    2 +
 .../server/SimpleInMemoryKeyValueStorage.java      |    7 +
 modules/network/pom.xml                            |   13 +-
 .../org/apache/ignite/network/TestMessage.java     |   17 +-
 .../scalecube/ITScaleCubeNetworkMessagingTest.java |   53 +-
 .../org/apache/ignite/network/ClusterNode.java     |    7 +-
 .../direct/stream/DirectByteBufferStream.java      |    3 +
 .../scalecube/ScaleCubeClusterServiceFactory.java  |   28 +-
 .../scalecube/ScaleCubeTopologyService.java        |   47 +-
 .../java/org/apache/ignite/raft/client/Peer.java   |    6 +-
 .../raft/client/message/AddLearnersRequest.java    |    2 +-
 .../raft/client/message/GetLeaderResponse.java     |    2 +-
 .../client/service/impl/RaftGroupServiceImpl.java  |    1 -
 modules/rest/pom.xml                               |   11 +
 .../InMemoryConfigurationStorage.java              |    6 +
 .../configuration/RestConfigurationSchema.java     |    3 +-
 .../rest/presentation/json/JsonConverterTest.java  |    3 +-
 .../json/TestConfigurationStorage.java             |    6 +
 modules/runner/pom.xml                             |   13 +
 .../extended/LocalConfigurationSchema.java         |    4 +-
 modules/schema/pom.xml                             |    6 -
 .../internal/schema/AbstractSchemaObject.java      |    8 +-
 .../org/apache/ignite/internal/schema/Bitmask.java |    7 +
 .../org/apache/ignite/internal/schema/Column.java  |    4 +-
 .../apache/ignite/internal/schema/ColumnImpl.java  |   10 +-
 .../org/apache/ignite/internal/schema/Columns.java |    6 +
 .../ignite/internal/schema/HashIndexImpl.java      |   11 +-
 .../ignite/internal/schema/IndexColumnImpl.java    |    5 +-
 .../apache/ignite/internal/schema/NativeType.java  |    9 +-
 .../ignite/internal/schema/NativeTypeSpec.java     |    6 +-
 .../ignite/internal/schema/PartialIndexImpl.java   |   14 +-
 .../ignite/internal/schema/PrimaryIndexImpl.java   |   17 +
 .../ignite/internal/schema/SchemaDescriptor.java   |    6 +
 .../ignite/internal/schema/SchemaTableImpl.java    |    9 +-
 .../internal/schema/SortedIndexColumnImpl.java     |    6 +-
 .../ignite/internal/schema/SortedIndexImpl.java    |   15 +-
 parent/pom.xml                                     |  135 +-
 pom.xml                                            |    2 +-
 96 files changed, 6026 insertions(+), 359 deletions(-)
 copy modules/{api/src/main/java/org/apache/ignite/table/InvokeProcessorException.java => configuration/src/main/java/org/apache/ignite/configuration/storage/ConfigurationType.java} (79%)
 create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/tostring/CircularStringBuilder.java
 create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/tostring/ClassDescriptor.java
 create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/tostring/FieldDescriptor.java
 create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/tostring/IgniteToStringBuilder.java
 copy modules/{configuration/src/main/java/org/apache/ignite/configuration/annotation/ConfigValue.java => core/src/main/java/org/apache/ignite/internal/tostring/IgniteToStringExclude.java} (64%)
 copy modules/{configuration/src/main/java/org/apache/ignite/configuration/annotation/ConfigValue.java => core/src/main/java/org/apache/ignite/internal/tostring/IgniteToStringInclude.java} (51%)
 copy modules/{configuration/src/main/java/org/apache/ignite/configuration/annotation/ConfigValue.java => core/src/main/java/org/apache/ignite/internal/tostring/IgniteToStringOrder.java} (60%)
 copy modules/{api/src/main/java/org/apache/ignite/table/mapper/KeyMapper.java => core/src/main/java/org/apache/ignite/internal/tostring/S.java} (64%)
 create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/tostring/SBLimitedLength.java
 copy modules/{table/src/main/java/org/apache/ignite/internal/table/TableSchemaManager.java => core/src/main/java/org/apache/ignite/internal/tostring/SensitiveDataLoggingPolicy.java} (69%)
 copy modules/{table/src/main/java/org/apache/ignite/internal/table => core/src/main/java/org/apache/ignite/internal/tostring}/package-info.java (90%)
 create mode 100644 modules/core/src/main/java/org/apache/ignite/lang/IgniteStringBuilder.java
 create mode 100644 modules/core/src/main/java/org/apache/ignite/lang/IgniteSystemProperties.java
 create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/testframework/IgniteAbstractTest.java
 create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/testframework/SystemPropertiesExtension.java
 copy modules/{configuration/src/main/java/org/apache/ignite/configuration/annotation/ConfigValue.java => core/src/test/java/org/apache/ignite/internal/testframework/SystemPropertiesList.java} (60%)
 create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/testframework/WithSystemProperty.java
 copy modules/{cli/src/main/java/org/apache/ignite/cli/builtins/module => core/src/test/java/org/apache/ignite/internal/testframework}/package-info.java (87%)
 create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/tostring/CircularStringBuilderSelfTest.java
 create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/tostring/IgniteToStringBuilderSelfTest.java
 create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/tostring/SensitiveDataToStringTest.java

[ignite-3] 04/05: IGNITE-14389 getAll and tests (WIP)

Posted by ag...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 2033ce5aa36397ad2a8951d5b572cce8a515a207
Author: Andrey Gura <ag...@apache.org>
AuthorDate: Tue Apr 13 21:34:37 2021 +0300

    IGNITE-14389 getAll and tests (WIP)
---
 .../metastorage/client/MetaStorageService.java     |   4 +-
 modules/metastorage-server/pom.xml                 |   6 +
 .../metastorage/server/KeyValueStorage.java        |   8 +
 .../server/SimpleInMemoryKeyValueStorage.java      |  36 +++-
 .../server/SimpleInMemoryKeyValueStorageTest.java  | 208 +++++++++++++++++++--
 5 files changed, 244 insertions(+), 18 deletions(-)

diff --git a/modules/metastorage-client/src/main/java/org/apache/ignite/metastorage/client/MetaStorageService.java b/modules/metastorage-client/src/main/java/org/apache/ignite/metastorage/client/MetaStorageService.java
index dfb4868..d4ded44 100644
--- a/modules/metastorage-client/src/main/java/org/apache/ignite/metastorage/client/MetaStorageService.java
+++ b/modules/metastorage-client/src/main/java/org/apache/ignite/metastorage/client/MetaStorageService.java
@@ -52,7 +52,7 @@ public interface MetaStorageService {
      * Retrieves an entry for the given key and the revision upper bound.
      *
      * @param key The key. Couldn't be {@code null}.
-     * @param revUpperBound  The upper bound for entry revisions. Must be positive.
+     * @param revUpperBound The upper bound for entry revisions. Must be positive.
      * @return An entry for the given key and maximum revision limited by {@code revUpperBound}.
      * Couldn't be {@code null}.
      * @throws OperationTimeoutException If the operation is timed out. Will be thrown on getting future result.
@@ -82,7 +82,7 @@ public interface MetaStorageService {
      *
      * @param keys The collection of keys. Couldn't be {@code null} or empty.
      *             Collection elements couldn't be {@code null}.
-     * @param revUpperBound  The upper bound for entry revisions. Must be positive.
+     * @param revUpperBound The upper bound for entry revisions. Must be positive.
      * @return A map of entries for given keys and maximum revision limited by {@code revUpperBound}.
      * Couldn't be {@code null}.
      * @throws OperationTimeoutException If the operation is timed out. Will be thrown on getting future result.
diff --git a/modules/metastorage-server/pom.xml b/modules/metastorage-server/pom.xml
index 3c51fc5..d73d080 100644
--- a/modules/metastorage-server/pom.xml
+++ b/modules/metastorage-server/pom.xml
@@ -40,6 +40,12 @@
         </dependency>
 
         <dependency>
+            <groupId>org.apache.ignite</groupId>
+            <artifactId>metastorage-common</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
             <groupId>org.jetbrains</groupId>
             <artifactId>annotations</artifactId>
         </dependency>
diff --git a/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/KeyValueStorage.java b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/KeyValueStorage.java
index ead1043..0596c4a 100644
--- a/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/KeyValueStorage.java
+++ b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/KeyValueStorage.java
@@ -2,7 +2,9 @@ package org.apache.ignite.internal.metastorage.server;
 
 import org.jetbrains.annotations.NotNull;
 
+import java.util.Collection;
 import java.util.Iterator;
+import java.util.List;
 
 public interface KeyValueStorage {
 
@@ -16,6 +18,12 @@ public interface KeyValueStorage {
     @NotNull
     Entry get(byte[] key, long rev);
 
+    @NotNull
+    Collection<Entry> getAll(List<byte[]> keys);
+
+    @NotNull
+    Collection<Entry> getAll(List<byte[]> keys, long revUpperBound);
+
     void put(byte[] key, byte[] value);
 
     @NotNull
diff --git a/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/SimpleInMemoryKeyValueStorage.java b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/SimpleInMemoryKeyValueStorage.java
index 3700f4a..8523f51 100644
--- a/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/SimpleInMemoryKeyValueStorage.java
+++ b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/SimpleInMemoryKeyValueStorage.java
@@ -2,6 +2,7 @@ package org.apache.ignite.internal.metastorage.server;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Comparator;
 import java.util.Iterator;
 import java.util.List;
@@ -78,6 +79,16 @@ public class SimpleInMemoryKeyValueStorage implements KeyValueStorage {
     }
 
     @Override
+    public @NotNull Collection<Entry> getAll(List<byte[]> keys) {
+        return doGetAll(keys, LATEST_REV);
+    }
+
+    @Override
+    public @NotNull Collection<Entry> getAll(List<byte[]> keys, long revUpperBound) {
+        return doGetAll(keys, revUpperBound);
+    }
+
+    @Override
     public void remove(byte[] key) {
         synchronized (mux) {
             Entry e = doGet(key, LATEST_REV, false);
@@ -197,17 +208,17 @@ public class SimpleInMemoryKeyValueStorage implements KeyValueStorage {
             NavigableMap<byte[], List<Long>> compactedKeysIdx,
             NavigableMap<Long, NavigableMap<byte[], Value>> compactedRevsIdx
     ) {
-        Long lrev = lastRevision(revs);
+        Long lastRev = lastRevision(revs);
 
-        NavigableMap<byte[], Value> kv = revsIdx.get(lrev);
+        NavigableMap<byte[], Value> kv = revsIdx.get(lastRev);
 
         Value lastVal = kv.get(key);
 
         if (!lastVal.tombstone()) {
-            compactedKeysIdx.put(key, listOf(lrev));
+            compactedKeysIdx.put(key, listOf(lastRev));
 
             NavigableMap<byte[], Value> compactedKv = compactedRevsIdx.computeIfAbsent(
-                    lrev,
+                    lastRev,
                     k -> new TreeMap<>(LEXICOGRAPHIC_COMPARATOR)
             );
 
@@ -215,6 +226,23 @@ public class SimpleInMemoryKeyValueStorage implements KeyValueStorage {
         }
     }
 
+    @NotNull
+    private Collection<Entry> doGetAll(List<byte[]> keys, long rev) {
+        assert keys != null : "keys list can't be null.";
+        assert !keys.isEmpty() : "keys list can't be empty.";
+        assert rev > 0 : "Revision must be positive.";
+
+        Collection<Entry> res = new ArrayList<>(keys.size());
+
+        synchronized (mux) {
+            for (byte[] key : keys) {
+                res.add(doGet(key, rev, false));
+            }
+        }
+
+        return res;
+    }
+
     /**
      * Returns entry for given key.
      *
diff --git a/modules/metastorage-server/src/test/java/org/apache/ignite/internal/metastorage/server/SimpleInMemoryKeyValueStorageTest.java b/modules/metastorage-server/src/test/java/org/apache/ignite/internal/metastorage/server/SimpleInMemoryKeyValueStorageTest.java
index 5b797fc..fa130e6 100644
--- a/modules/metastorage-server/src/test/java/org/apache/ignite/internal/metastorage/server/SimpleInMemoryKeyValueStorageTest.java
+++ b/modules/metastorage-server/src/test/java/org/apache/ignite/internal/metastorage/server/SimpleInMemoryKeyValueStorageTest.java
@@ -1,17 +1,18 @@
 package org.apache.ignite.internal.metastorage.server;
 
-import org.jetbrains.annotations.NotNull;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
+import java.util.Collection;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import org.apache.ignite.metastorage.common.Key;
+import org.jetbrains.annotations.NotNull;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
 
-import static org.junit.jupiter.api.Assertions.assertArrayEquals;
-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 static org.junit.jupiter.api.Assertions.*;
 
 class SimpleInMemoryKeyValueStorageTest {
     private KeyValueStorage storage;
@@ -56,6 +57,193 @@ class SimpleInMemoryKeyValueStorageTest {
     }
 
     @Test
+    void getAll() {
+        byte[] key1 = k(1);
+        byte[] val1 = kv(1, 1);
+
+        byte[] key2 = k(2);
+        byte[] val2_1 = kv(2, 21);
+        byte[] val2_2 = kv(2, 22);
+
+        byte[] key3 = k(3);
+        byte[] val3 = kv(3, 3);
+
+        byte[] key4 = k(4);
+
+        assertEquals(0, storage.revision());
+        assertEquals(0, storage.updateCounter());
+
+        // Regular put.
+        storage.put(key1, val1);
+
+        // Rewrite.
+        storage.put(key2, val2_1);
+        storage.put(key2, val2_2);
+
+        // Remove.
+        storage.put(key3, val3);
+        storage.remove(key3);
+
+        assertEquals(5, storage.revision());
+        assertEquals(5, storage.updateCounter());
+
+        Collection<Entry> entries = storage.getAll(List.of(key1, key2, key3, key4));
+
+        assertEquals(4, entries.size());
+
+        Map<Key, Entry> map =  entries.stream().collect(Collectors.toMap(e -> new Key(e.key()), Function.identity()));
+
+        // Test regular put value.
+        Entry e1 = map.get(new Key(key1));
+
+        assertNotNull(e1);
+        assertEquals(1, e1.revision());
+        assertEquals(1, e1.updateCounter());
+        assertFalse(e1.tombstone());
+        assertFalse(e1.empty());
+        assertArrayEquals(val1, e1.value());
+
+        // Test rewritten value.
+        Entry e2 = map.get(new Key(key2));
+
+        assertNotNull(e2);
+        assertEquals(3, e2.revision());
+        assertEquals(3, e2.updateCounter());
+        assertFalse(e2.tombstone());
+        assertFalse(e2.empty());
+        assertArrayEquals(val2_2, e2.value());
+
+        // Test removed value.
+        Entry e3 = map.get(new Key(key3));
+
+        assertNotNull(e3);
+        assertEquals(5, e3.revision());
+        assertEquals(5, e3.updateCounter());
+        assertTrue(e3.tombstone());
+        assertFalse(e3.empty());
+
+        // Test empty value.
+        Entry e4 = map.get(new Key(key4));
+
+        assertNotNull(e4);
+        assertFalse(e4.tombstone());
+        assertTrue(e4.empty());
+    }
+
+    @Test
+    void getAllWithRevisionBound() {
+        byte[] key1 = k(1);
+        byte[] val1 = kv(1, 1);
+
+        byte[] key2 = k(2);
+        byte[] val2_1 = kv(2, 21);
+        byte[] val2_2 = kv(2, 22);
+
+        byte[] key3 = k(3);
+        byte[] val3 = kv(3, 3);
+
+        byte[] key4 = k(4);
+
+        assertEquals(0, storage.revision());
+        assertEquals(0, storage.updateCounter());
+
+        // Regular put.
+        storage.put(key1, val1);
+
+        // Rewrite.
+        storage.put(key2, val2_1);
+        storage.put(key2, val2_2);
+
+        // Remove.
+        storage.put(key3, val3);
+        storage.remove(key3);
+
+        assertEquals(5, storage.revision());
+        assertEquals(5, storage.updateCounter());
+
+        // Bounded by revision 2.
+        Collection<Entry> entries = storage.getAll(List.of(key1, key2, key3, key4), 2);
+
+        assertEquals(4, entries.size());
+
+        Map<Key, Entry> map =  entries.stream().collect(Collectors.toMap(e -> new Key(e.key()), Function.identity()));
+
+        // Test regular put value.
+        Entry e1 = map.get(new Key(key1));
+
+        assertNotNull(e1);
+        assertEquals(1, e1.revision());
+        assertEquals(1, e1.updateCounter());
+        assertFalse(e1.tombstone());
+        assertFalse(e1.empty());
+        assertArrayEquals(val1, e1.value());
+
+        // Test while not rewritten value.
+        Entry e2 = map.get(new Key(key2));
+
+        assertNotNull(e2);
+        assertEquals(2, e2.revision());
+        assertEquals(2, e2.updateCounter());
+        assertFalse(e2.tombstone());
+        assertFalse(e2.empty());
+        assertArrayEquals(val2_1, e2.value());
+
+        // Values with larger revision don't exist yet.
+        Entry e3 = map.get(new Key(key3));
+
+        assertNotNull(e3);
+        assertTrue(e3.empty());
+
+        Entry e4 = map.get(new Key(key4));
+
+        assertNotNull(e4);
+        assertTrue(e4.empty());
+
+        // Bounded by revision 4.
+        entries = storage.getAll(List.of(key1, key2, key3, key4), 4);
+
+        assertEquals(4, entries.size());
+
+        map =  entries.stream().collect(Collectors.toMap(e -> new Key(e.key()), Function.identity()));
+
+        // Test regular put value.
+        e1 = map.get(new Key(key1));
+
+        assertNotNull(e1);
+        assertEquals(1, e1.revision());
+        assertEquals(1, e1.updateCounter());
+        assertFalse(e1.tombstone());
+        assertFalse(e1.empty());
+        assertArrayEquals(val1, e1.value());
+
+        // Test rewritten value.
+        e2 = map.get(new Key(key2));
+
+        assertNotNull(e2);
+        assertEquals(3, e2.revision());
+        assertEquals(3, e2.updateCounter());
+        assertFalse(e2.tombstone());
+        assertFalse(e2.empty());
+        assertArrayEquals(val2_2, e2.value());
+
+        // Test not removed value.
+        e3 = map.get(new Key(key3));
+
+        assertNotNull(e3);
+        assertEquals(4, e3.revision());
+        assertEquals(4, e3.updateCounter());
+        assertFalse(e3.tombstone());
+        assertFalse(e3.empty());
+        assertArrayEquals(val3, e3.value());
+
+        // Value with larger revision doesn't exist yet.
+        e4 = map.get(new Key(key4));
+
+        assertNotNull(e4);
+        assertTrue(e4.empty());
+    }
+
+    @Test
     public void getAndPut() {
         byte[] key = k(1);
         byte[] val = kv(1, 1);
@@ -208,7 +396,6 @@ class SimpleInMemoryKeyValueStorageTest {
     @Test
     public void getAndPutAfterRemove() {
         byte[] key = k(1);
-
         byte[] val = kv(1, 1);
 
         storage.getAndPut(key, val);
@@ -218,11 +405,8 @@ class SimpleInMemoryKeyValueStorageTest {
         Entry e = storage.getAndPut(key, val);
 
         assertEquals(3, storage.revision());
-
         assertEquals(3, storage.updateCounter());
-
         assertEquals(2, e.revision());
-
         assertTrue(e.tombstone());
     }
 

[ignite-3] 02/05: IGNITE-14398: Meta storage: added update counter

Posted by ag...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 7ee572813d24ca3cb3185f18fda5bb4842723cfc
Author: Andrey Gura <ag...@apache.org>
AuthorDate: Wed Mar 31 18:18:09 2021 +0300

    IGNITE-14398: Meta storage: added update counter
---
 .../ignite/internal/metastorage/server/Entry.java  | 37 +++++++++---
 .../metastorage/server/KeyValueStorage.java        |  2 +
 .../server/SimpleInMemoryKeyValueStorage.java      | 70 +++++++++++++---------
 .../ignite/internal/metastorage/server/Value.java  | 27 +++++++++
 .../server/SimpleInMemoryKeyValueStorageTest.java  | 29 +++++++++
 5 files changed, 130 insertions(+), 35 deletions(-)

diff --git a/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/Entry.java b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/Entry.java
index 442aef9..263a88b 100644
--- a/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/Entry.java
+++ b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/Entry.java
@@ -44,20 +44,31 @@ public class Entry {
     final private long rev;
 
     /**
+     * Update counter corresponds to this particular entry.
+     * <p>
+     *     {@code updCntr == 0} for {@link #empty()} entry,
+     *     {@code updCntr > 0} for regular and {@link #tombstone()} entries.
+     * </p>
+     */
+    final private long updCntr;
+
+    /**
      * Constructor.
      *
      * @param key Key bytes. Couldn't be {@code null}.
      * @param val Value bytes. Couldn't be {@code null}.
      * @param rev Revision.
+     * @param updCntr Update counter.
      */
     // TODO: It seems user will never create Entry, so we can reduce constructor scope to protected or package-private and reuse it from two-place private constructor.
-    public Entry(@NotNull byte[] key, @NotNull byte[] val, long rev) {
+    public Entry(@NotNull byte[] key, @NotNull byte[] val, long rev, long updCntr) {
         assert key != null : "key can't be null";
         assert val != null : "value can't be null";
 
         this.key = key;
         this.val = val;
         this.rev = rev;
+        this.updCntr = updCntr;
     }
 
     /**
@@ -65,13 +76,15 @@ public class Entry {
      *
      * @param key Key bytes. Couldn't be {@code null}.
      * @param rev Revision.
+     * @param updCntr Update counter.
      */
-    private Entry(@NotNull byte[] key, long rev) {
+    private Entry(@NotNull byte[] key, long rev, long updCntr) {
         assert key != null : "key can't be null";
 
         this.key = key;
         this.val = null;
         this.rev = rev;
+        this.updCntr = updCntr;
     }
 
     /**
@@ -82,20 +95,22 @@ public class Entry {
      */
     @NotNull
     public static Entry empty(byte[] key) {
-        return new Entry(key, 0);
+        return new Entry(key, 0, 0);
     }
 
     /**
      * Creates an instance of tombstone entry for a given key and a revision.
      *
      * @param key Key bytes. Couldn't be {@code null}.
+     * @param rev Revision.
+     * @param updCntr Update counter.
      * @return Empty entry.
      */
     @NotNull
-    public static Entry tombstone(byte[] key, long rev) {
+    public static Entry tombstone(byte[] key, long rev, long updCntr) {
         assert rev > 0 : "rev must be positive for tombstone entry.";
 
-        return new Entry(key, rev);
+        return new Entry(key, rev, updCntr);
     }
 
     /**
@@ -127,12 +142,20 @@ public class Entry {
     }
 
     /**
+     * Returns a update counter.
+     * @return Update counter.
+     */
+    public long updateCounter() {
+        return updCntr;
+    }
+
+    /**
      * Returns value which denotes whether entry is tombstone or not.
      *
      * @return {@code True} if entry is tombstone, otherwise - {@code false}.
      */
     public boolean tombstone() {
-        return val == null && rev > 0;
+        return val == null && rev > 0 && updCntr > 0;
     }
 
     /**
@@ -141,6 +164,6 @@ public class Entry {
      * @return {@code True} if entry is empty, otherwise - {@code false}.
      */
     public boolean empty() {
-        return val == null && rev == 0;
+        return val == null && rev == 0 && updCntr == 0;
     }
 }
diff --git a/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/KeyValueStorage.java b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/KeyValueStorage.java
index 1bf6b78..e245e08 100644
--- a/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/KeyValueStorage.java
+++ b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/KeyValueStorage.java
@@ -8,6 +8,8 @@ public interface KeyValueStorage {
 
     long revision();
 
+    long updateCounter();
+
     @NotNull
     Entry put(byte[] key, byte[] value);
 
diff --git a/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/SimpleInMemoryKeyValueStorage.java b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/SimpleInMemoryKeyValueStorage.java
index 9059aec..b764998 100644
--- a/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/SimpleInMemoryKeyValueStorage.java
+++ b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/SimpleInMemoryKeyValueStorage.java
@@ -12,23 +12,25 @@ import java.util.TreeMap;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.TestOnly;
 
+import static org.apache.ignite.internal.metastorage.server.Value.TOMBSTONE;
+
 /**
  * WARNING: Only for test purposes and only for non-distributed (one static instance) storage.
  */
 public class SimpleInMemoryKeyValueStorage implements KeyValueStorage {
     private static final Comparator<byte[]> LEXICOGRAPHIC_COMPARATOR = Arrays::compare;
 
-    private static final byte[] TOMBSTONE = new byte[0];
-
     private static final long LATEST_REV = -1;
 
     private final Watcher watcher;
 
     private NavigableMap<byte[], List<Long>> keysIdx = new TreeMap<>(LEXICOGRAPHIC_COMPARATOR);
 
-    private NavigableMap<Long, NavigableMap<byte[], byte[]>> revsIdx = new TreeMap<>();
+    private NavigableMap<Long, NavigableMap<byte[], Value>> revsIdx = new TreeMap<>();
 
-    private long grev = 0;
+    private long rev;
+
+    private long updCntr;
 
     private final Object mux = new Object();
 
@@ -37,35 +39,45 @@ public class SimpleInMemoryKeyValueStorage implements KeyValueStorage {
     }
 
     @Override public long revision() {
-        return grev;
+        return rev;
+    }
+
+    @Override public long updateCounter() {
+        return updCntr;
     }
 
     @NotNull
-    @Override public Entry put(byte[] key, byte[] val) {
+    @Override public Entry put(byte[] key, byte[] bytes) {
         synchronized (mux) {
-            long crev = ++grev;
+            long curRev = ++rev;
+
+            long curUpdCntr = ++updCntr;
 
             // Update keysIdx.
             List<Long> revs = keysIdx.computeIfAbsent(key, k -> new ArrayList<>());
 
-            long lrev = revs.isEmpty() ? 0 : lastRevision(revs);
+            long lastRev = revs.isEmpty() ? 0 : lastRevision(revs);
 
-            revs.add(crev);
+            revs.add(curRev);
 
             // Update revsIdx.
-            NavigableMap<byte[], byte[]> entries = new TreeMap<>(LEXICOGRAPHIC_COMPARATOR);
+            NavigableMap<byte[], Value> entries = new TreeMap<>(LEXICOGRAPHIC_COMPARATOR);
+
+            Value val = new Value(bytes, curUpdCntr);
 
             entries.put(key, val);
 
-            revsIdx.put(crev, entries);
+            revsIdx.put(curRev, entries);
 
             // Return previous value.
-            if (lrev == 0)
+            if (lastRev == 0)
                 return Entry.empty(key);
 
-            NavigableMap<byte[], byte[]> lastVal = revsIdx.get(lrev);
+            NavigableMap<byte[], Value> lastRevVals = revsIdx.get(lastRev);
+
+            Value lastVal = lastRevVals.get(key);
 
-            Entry res = new Entry(key, lastVal.get(key), lrev);
+            Entry res = new Entry(key, lastVal.bytes(), lastRev, lastVal.updateCounter());
 
             //TODO: notify watchers
 
@@ -158,16 +170,18 @@ public class SimpleInMemoryKeyValueStorage implements KeyValueStorage {
                             //return new AbstractMap.SimpleImmutableEntry<>(key, null);
                         }
 
-                        NavigableMap<byte[], byte[]> vals = revsIdx.get(rev);
+                        NavigableMap<byte[], Value> vals = revsIdx.get(rev);
 
                         if (vals == null || vals.isEmpty()) {
                             throw new IllegalStateException("vals == null || vals.isEmpty()");
                             //return new AbstractMap.SimpleImmutableEntry<>(key, null);
                         }
 
-                        byte[] val = vals.get(key);
+                        Value val = vals.get(key);
 
-                        return val == TOMBSTONE ? Entry.tombstone(key, rev) : new Entry(key, val, rev);
+                        return val.tombstone() ?
+                                Entry.tombstone(key, rev, val.updateCounter()) :
+                                new Entry(key, val.bytes(), rev, val.updateCounter());
                     }
                 }
             };
@@ -178,7 +192,7 @@ public class SimpleInMemoryKeyValueStorage implements KeyValueStorage {
         synchronized (mux) {
             NavigableMap<byte[], List<Long>> compactedKeysIdx = new TreeMap<>(LEXICOGRAPHIC_COMPARATOR);
 
-            NavigableMap<Long, NavigableMap<byte[], byte[]>> compactedRevsIdx = new TreeMap<>();
+            NavigableMap<Long, NavigableMap<byte[], Value>> compactedRevsIdx = new TreeMap<>();
 
             keysIdx.forEach((key, revs) -> compactForKey(key, revs, compactedKeysIdx, compactedRevsIdx));
 
@@ -192,18 +206,18 @@ public class SimpleInMemoryKeyValueStorage implements KeyValueStorage {
             byte[] key,
             List<Long> revs,
             NavigableMap<byte[], List<Long>> compactedKeysIdx,
-            NavigableMap<Long, NavigableMap<byte[], byte[]>> compactedRevsIdx
+            NavigableMap<Long, NavigableMap<byte[], Value>> compactedRevsIdx
     ) {
         Long lrev = lastRevision(revs);
 
-        NavigableMap<byte[], byte[]> kv = revsIdx.get(lrev);
+        NavigableMap<byte[], Value> kv = revsIdx.get(lrev);
 
-        byte[] lastVal = kv.get(key);
+        Value lastVal = kv.get(key);
 
-        if (lastVal != TOMBSTONE) {
+        if (!lastVal.tombstone()) {
             compactedKeysIdx.put(key, listOf(lrev));
 
-            NavigableMap<byte[], byte[]> compactedKv = compactedRevsIdx.computeIfAbsent(
+            NavigableMap<byte[], Value> compactedKv = compactedRevsIdx.computeIfAbsent(
                     lrev,
                     k -> new TreeMap<>(LEXICOGRAPHIC_COMPARATOR)
             );
@@ -227,17 +241,17 @@ public class SimpleInMemoryKeyValueStorage implements KeyValueStorage {
 
         long lrev = rev == LATEST_REV ? lastRevision(revs) : rev;
 
-        NavigableMap<byte[], byte[]> entries = revsIdx.get(lrev);
+        NavigableMap<byte[], Value> entries = revsIdx.get(lrev);
 
         if (entries == null || entries.isEmpty())
             return Entry.empty(key);
 
-        byte[] val = entries.get(key);
+        Value val = entries.get(key);
 
-        if (val == TOMBSTONE)
-            return Entry.tombstone(key, lrev);
+        if (val.tombstone())
+            return Entry.tombstone(key, lrev, val.updateCounter());
 
-        return new Entry(key, val , lrev);
+        return new Entry(key, val.bytes() , lrev, val.updateCounter());
     }
 
     private static boolean isPrefix(byte[] pref, byte[] term) {
diff --git a/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/Value.java b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/Value.java
new file mode 100644
index 0000000..250a5ea
--- /dev/null
+++ b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/Value.java
@@ -0,0 +1,27 @@
+package org.apache.ignite.internal.metastorage.server;
+
+import org.jetbrains.annotations.NotNull;
+
+public class Value {
+    public static final byte[] TOMBSTONE = new byte[0];
+
+    private final byte[] bytes;
+    private final long updCntr;
+
+    public Value(@NotNull byte[] bytes, long updCntr) {
+        this.bytes = bytes;
+        this.updCntr = updCntr;
+    }
+
+    public byte[] bytes() {
+        return bytes;
+    }
+
+    public long updateCounter() {
+        return updCntr;
+    }
+
+    boolean tombstone() {
+        return bytes == TOMBSTONE;
+    }
+}
diff --git a/modules/metastorage-server/src/test/java/org/apache/ignite/internal/metastorage/server/SimpleInMemoryKeyValueStorageTest.java b/modules/metastorage-server/src/test/java/org/apache/ignite/internal/metastorage/server/SimpleInMemoryKeyValueStorageTest.java
index f7fb17e..eae76fd 100644
--- a/modules/metastorage-server/src/test/java/org/apache/ignite/internal/metastorage/server/SimpleInMemoryKeyValueStorageTest.java
+++ b/modules/metastorage-server/src/test/java/org/apache/ignite/internal/metastorage/server/SimpleInMemoryKeyValueStorageTest.java
@@ -31,11 +31,13 @@ class SimpleInMemoryKeyValueStorageTest {
         byte[] val2_2 = kv(2, 2);
 
         assertEquals(0, storage.revision());
+        assertEquals(0, storage.updateCounter());
 
         // Previous entry is empty.
         Entry emptyEntry = storage.put(key1, val1_1);
 
         assertEquals(1, storage.revision());
+        assertEquals(1, storage.updateCounter());
         assertTrue(emptyEntry.empty());
 
         // Entry with rev == 1.
@@ -46,12 +48,15 @@ class SimpleInMemoryKeyValueStorageTest {
         assertArrayEquals(key1, e1_1.key());
         assertArrayEquals(val1_1, e1_1.value());
         assertEquals(1, e1_1.revision());
+        assertEquals(1, e1_1.updateCounter());
         assertEquals(1, storage.revision());
+        assertEquals(1, storage.updateCounter());
 
         // Previous entry is empty.
         emptyEntry = storage.put(key2, val2_2);
 
         assertEquals(2, storage.revision());
+        assertEquals(2, storage.updateCounter());
         assertTrue(emptyEntry.empty());
 
         // Entry with rev == 2.
@@ -62,7 +67,9 @@ class SimpleInMemoryKeyValueStorageTest {
         assertArrayEquals(key2, e2.key());
         assertArrayEquals(val2_2, e2.value());
         assertEquals(2, e2.revision());
+        assertEquals(2, e2.updateCounter());
         assertEquals(2, storage.revision());
+        assertEquals(2, storage.updateCounter());
 
         // Previous entry is not empty.
         e1_1 = storage.put(key1, val1_3);
@@ -72,7 +79,9 @@ class SimpleInMemoryKeyValueStorageTest {
         assertArrayEquals(key1, e1_1.key());
         assertArrayEquals(val1_1, e1_1.value());
         assertEquals(1, e1_1.revision());
+        assertEquals(1, e1_1.updateCounter());
         assertEquals(3, storage.revision());
+        assertEquals(3, storage.updateCounter());
 
         // Entry with rev == 3.
         Entry e1_3 = storage.get(key1);
@@ -82,7 +91,9 @@ class SimpleInMemoryKeyValueStorageTest {
         assertArrayEquals(key1, e1_3.key());
         assertArrayEquals(val1_3, e1_3.value());
         assertEquals(3, e1_3.revision());
+        assertEquals(3, e1_3.updateCounter());
         assertEquals(3, storage.revision());
+        assertEquals(3, storage.updateCounter());
 
         // Remove existing entry.
         Entry e2_2 = storage.remove(key2);
@@ -92,7 +103,9 @@ class SimpleInMemoryKeyValueStorageTest {
         assertArrayEquals(key2, e2_2.key());
         assertArrayEquals(val2_2, e2_2.value());
         assertEquals(2, e2_2.revision());
+        assertEquals(2, e2_2.updateCounter());
         assertEquals(4, storage.revision()); // Storage revision is changed.
+        assertEquals(4, storage.updateCounter());
 
         // Remove already removed entry.
         Entry tombstoneEntry = storage.remove(key2);
@@ -100,11 +113,13 @@ class SimpleInMemoryKeyValueStorageTest {
         assertFalse(tombstoneEntry.empty());
         assertTrue(tombstoneEntry.tombstone());
         assertEquals(4, storage.revision()); // Storage revision is not changed.
+        assertEquals(4, storage.updateCounter());
 
         // Compact and check that tombstones are removed.
         storage.compact();
 
         assertEquals(4, storage.revision());
+        assertEquals(4, storage.updateCounter());
         assertTrue(storage.remove(key2).empty());
         assertTrue(storage.get(key2).empty());
 
@@ -116,7 +131,9 @@ class SimpleInMemoryKeyValueStorageTest {
         assertArrayEquals(key1, e1_3.key());
         assertArrayEquals(val1_3, e1_3.value());
         assertEquals(3, e1_3.revision());
+        assertEquals(3, e1_3.updateCounter());
         assertEquals(5, storage.revision()); // Storage revision is changed.
+        assertEquals(5, storage.updateCounter());
 
         // Remove already removed entry.
         tombstoneEntry = storage.remove(key1);
@@ -124,11 +141,13 @@ class SimpleInMemoryKeyValueStorageTest {
         assertFalse(tombstoneEntry.empty());
         assertTrue(tombstoneEntry.tombstone());
         assertEquals(5, storage.revision()); // // Storage revision is not changed.
+        assertEquals(5, storage.updateCounter());
 
         // Compact and check that tombstones are removed.
         storage.compact();
 
         assertEquals(5, storage.revision());
+        assertEquals(5, storage.updateCounter());
         assertTrue(storage.remove(key1).empty());
         assertTrue(storage.get(key1).empty());
     }
@@ -136,33 +155,40 @@ class SimpleInMemoryKeyValueStorageTest {
     @Test
     void compact() {
         assertEquals(0, storage.revision());
+        assertEquals(0, storage.updateCounter());
 
         // Compact empty.
         storage.compact();
 
         assertEquals(0, storage.revision());
+        assertEquals(0, storage.updateCounter());
 
         // Compact non-empty.
         fill(storage, 1, 1);
 
         assertEquals(1, storage.revision());
+        assertEquals(1, storage.updateCounter());
 
         fill(storage, 2, 2);
 
         assertEquals(3, storage.revision());
+        assertEquals(3, storage.updateCounter());
 
         fill(storage, 3, 3);
 
         assertEquals(6, storage.revision());
+        assertEquals(6, storage.updateCounter());
 
         storage.remove(k(3));
 
         assertEquals(7, storage.revision());
+        assertEquals(7, storage.updateCounter());
         assertTrue(storage.get(k(3)).tombstone());
 
         storage.compact();
 
         assertEquals(7, storage.revision());
+        assertEquals(7, storage.updateCounter());
 
         Entry e1 = storage.get(k(1));
 
@@ -171,6 +197,7 @@ class SimpleInMemoryKeyValueStorageTest {
         assertArrayEquals(k(1), e1.key());
         assertArrayEquals(kv(1,1), e1.value());
         assertEquals(1, e1.revision());
+        assertEquals(1, e1.updateCounter());
 
         Entry e2 = storage.get(k(2));
 
@@ -180,6 +207,7 @@ class SimpleInMemoryKeyValueStorageTest {
         assertArrayEquals(kv(2,2), e2.value());
         assertTrue(storage.get(k(2), 2).empty());
         assertEquals(3, e2.revision());
+        assertEquals(3, e2.updateCounter());
 
         Entry e3 = storage.get(k(3));
 
@@ -200,6 +228,7 @@ class SimpleInMemoryKeyValueStorageTest {
         fill("zoo", storage, expZooMap);
 
         assertEquals(300, storage.revision());
+        assertEquals(300, storage.updateCounter());
 
         assertIterate("key", storage, expKeyMap);
         assertIterate("zoo", storage, expZooMap);

[ignite-3] 01/05: IGNITE-14389 Meta storage: in-memory implementation WIP

Posted by ag...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 5a2434dcfddd99d2cb1ac3c15ded9f51a641e2d7
Author: Andrey Gura <ag...@apache.org>
AuthorDate: Tue Mar 30 21:21:07 2021 +0300

    IGNITE-14389 Meta storage: in-memory implementation WIP
---
 modules/metastorage-server/pom.xml                 |  60 +++++
 .../ignite/internal/metastorage/server/Entry.java  | 146 +++++++++++
 .../metastorage/server/KeyValueStorage.java        |  28 +++
 .../server/SimpleInMemoryKeyValueStorage.java      | 267 ++++++++++++++++++++
 .../ignite/internal/metastorage/server/Watch.java  |  45 ++++
 .../internal/metastorage/server/Watcher.java       |  13 +
 .../internal/metastorage/server/WatcherImpl.java   |  58 +++++
 .../server/SimpleInMemoryKeyValueStorageTest.java  | 274 +++++++++++++++++++++
 pom.xml                                            |   1 +
 9 files changed, 892 insertions(+)

diff --git a/modules/metastorage-server/pom.xml b/modules/metastorage-server/pom.xml
new file mode 100644
index 0000000..3c51fc5
--- /dev/null
+++ b/modules/metastorage-server/pom.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ 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.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.ignite</groupId>
+        <artifactId>ignite-parent</artifactId>
+        <version>1</version>
+        <relativePath>../../parent/pom.xml</relativePath>
+    </parent>
+
+    <artifactId>metastorage-server</artifactId>
+    <version>3.0.0-SNAPSHOT</version>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.ignite</groupId>
+            <artifactId>ignite-core</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.jetbrains</groupId>
+            <artifactId>annotations</artifactId>
+        </dependency>
+
+        <!-- Test dependencies. -->
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-api</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-engine</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/Entry.java b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/Entry.java
new file mode 100644
index 0000000..442aef9
--- /dev/null
+++ b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/Entry.java
@@ -0,0 +1,146 @@
+package org.apache.ignite.internal.metastorage.server;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Represents a storage unit as entry with key, value and revision, where
+ * <ul>
+ *     <li>key - an unique entry's key represented by an array of bytes. Keys are comparable in lexicographic manner.</li>
+ *     <ul>value - a data which is associated with a key and represented as an array of bytes.</ul>
+ *     <ul>revision - a number which denotes a version of whole meta storage. Each change increments the revision.</ul>
+ * </ul>
+ *
+ * Instance of {@link #Entry} could represents:
+ * <ul>
+ *     <li>A regular entry which stores a particular key, a value and a revision number.</li>
+ *     <li>An empty entry which denotes absence a regular entry in the meta storage for a given key.
+ *     A revision is 0 for such kind of entry.</li>
+ *     <li>A tombstone entry which denotes that a regular entry for a given key was removed from storage on some revision.</li>
+ * </ul>
+ */
+//TODO: Separate client and server entries. Empty and tombstone for client is the same.
+public class Entry {
+    /** Entry key. Couldn't be {@code null}. */
+    @NotNull
+    final private byte[] key;
+
+    /**
+     * Entry value.
+     * <p>
+     *     {@code val == null} only for {@link #empty()} and {@link #tombstone()} entries.
+     * </p>
+     */
+    @Nullable
+    final private byte[] val;
+
+    /**
+     * Revision number corresponding to this particular entry.
+     * <p>
+     *     {@code rev == 0} for {@link #empty()} entry,
+     *     {@code rev > 0} for regular and {@link #tombstone()} entries.
+     * </p>
+     */
+    final private long rev;
+
+    /**
+     * Constructor.
+     *
+     * @param key Key bytes. Couldn't be {@code null}.
+     * @param val Value bytes. Couldn't be {@code null}.
+     * @param rev Revision.
+     */
+    // TODO: It seems user will never create Entry, so we can reduce constructor scope to protected or package-private and reuse it from two-place private constructor.
+    public Entry(@NotNull byte[] key, @NotNull byte[] val, long rev) {
+        assert key != null : "key can't be null";
+        assert val != null : "value can't be null";
+
+        this.key = key;
+        this.val = val;
+        this.rev = rev;
+    }
+
+    /**
+     * Constructor for empty and tombstone entries.
+     *
+     * @param key Key bytes. Couldn't be {@code null}.
+     * @param rev Revision.
+     */
+    private Entry(@NotNull byte[] key, long rev) {
+        assert key != null : "key can't be null";
+
+        this.key = key;
+        this.val = null;
+        this.rev = rev;
+    }
+
+    /**
+     * Creates an instance of empty entry for a given key.
+     *
+     * @param key Key bytes. Couldn't be {@code null}.
+     * @return Empty entry.
+     */
+    @NotNull
+    public static Entry empty(byte[] key) {
+        return new Entry(key, 0);
+    }
+
+    /**
+     * Creates an instance of tombstone entry for a given key and a revision.
+     *
+     * @param key Key bytes. Couldn't be {@code null}.
+     * @return Empty entry.
+     */
+    @NotNull
+    public static Entry tombstone(byte[] key, long rev) {
+        assert rev > 0 : "rev must be positive for tombstone entry.";
+
+        return new Entry(key, rev);
+    }
+
+    /**
+     * Returns a key.
+     *
+     * @return Key.
+     */
+    @NotNull
+    public byte[] key() {
+        return key;
+    }
+
+    /**
+     * Returns a value.
+     *
+     * @return Value.
+     */
+    @Nullable
+    public byte[] value() {
+        return val;
+    }
+
+    /**
+     * Returns a revision.
+     * @return Revision.
+     */
+    public long revision() {
+        return rev;
+    }
+
+    /**
+     * Returns value which denotes whether entry is tombstone or not.
+     *
+     * @return {@code True} if entry is tombstone, otherwise - {@code false}.
+     */
+    public boolean tombstone() {
+        return val == null && rev > 0;
+    }
+
+    /**
+     * Returns value which denotes whether entry is empty or not.
+     *
+     * @return {@code True} if entry is empty, otherwise - {@code false}.
+     */
+    public boolean empty() {
+        return val == null && rev == 0;
+    }
+}
diff --git a/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/KeyValueStorage.java b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/KeyValueStorage.java
new file mode 100644
index 0000000..1bf6b78
--- /dev/null
+++ b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/KeyValueStorage.java
@@ -0,0 +1,28 @@
+package org.apache.ignite.internal.metastorage.server;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Iterator;
+
+public interface KeyValueStorage {
+
+    long revision();
+
+    @NotNull
+    Entry put(byte[] key, byte[] value);
+
+    @NotNull
+    Entry get(byte[] key);
+
+    @NotNull
+    Entry get(byte[] key, long rev);
+
+    @NotNull
+    Entry remove(byte[] key);
+
+    Iterator<Entry> iterate(byte[] key);
+
+    //Iterator<Entry> iterate(long rev);
+
+    void compact();
+}
diff --git a/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/SimpleInMemoryKeyValueStorage.java b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/SimpleInMemoryKeyValueStorage.java
new file mode 100644
index 0000000..9059aec
--- /dev/null
+++ b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/SimpleInMemoryKeyValueStorage.java
@@ -0,0 +1,267 @@
+package org.apache.ignite.internal.metastorage.server;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.NoSuchElementException;
+import java.util.TreeMap;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.TestOnly;
+
+/**
+ * WARNING: Only for test purposes and only for non-distributed (one static instance) storage.
+ */
+public class SimpleInMemoryKeyValueStorage implements KeyValueStorage {
+    private static final Comparator<byte[]> LEXICOGRAPHIC_COMPARATOR = Arrays::compare;
+
+    private static final byte[] TOMBSTONE = new byte[0];
+
+    private static final long LATEST_REV = -1;
+
+    private final Watcher watcher;
+
+    private NavigableMap<byte[], List<Long>> keysIdx = new TreeMap<>(LEXICOGRAPHIC_COMPARATOR);
+
+    private NavigableMap<Long, NavigableMap<byte[], byte[]>> revsIdx = new TreeMap<>();
+
+    private long grev = 0;
+
+    private final Object mux = new Object();
+
+    public SimpleInMemoryKeyValueStorage(Watcher watcher) {
+        this.watcher = watcher;
+    }
+
+    @Override public long revision() {
+        return grev;
+    }
+
+    @NotNull
+    @Override public Entry put(byte[] key, byte[] val) {
+        synchronized (mux) {
+            long crev = ++grev;
+
+            // Update keysIdx.
+            List<Long> revs = keysIdx.computeIfAbsent(key, k -> new ArrayList<>());
+
+            long lrev = revs.isEmpty() ? 0 : lastRevision(revs);
+
+            revs.add(crev);
+
+            // Update revsIdx.
+            NavigableMap<byte[], byte[]> entries = new TreeMap<>(LEXICOGRAPHIC_COMPARATOR);
+
+            entries.put(key, val);
+
+            revsIdx.put(crev, entries);
+
+            // Return previous value.
+            if (lrev == 0)
+                return Entry.empty(key);
+
+            NavigableMap<byte[], byte[]> lastVal = revsIdx.get(lrev);
+
+            Entry res = new Entry(key, lastVal.get(key), lrev);
+
+            //TODO: notify watchers
+
+            return res;
+        }
+    }
+
+    @NotNull
+    @Override public Entry get(byte[] key) {
+        synchronized (mux) {
+            return doGet(key, LATEST_REV);
+        }
+    }
+
+    @NotNull
+    @TestOnly
+    @Override public Entry get(byte[] key, long rev) {
+        synchronized (mux) {
+            return doGet(key, rev);
+        }
+    }
+
+    @NotNull
+    @Override public Entry remove(byte[] key) {
+        synchronized (mux) {
+            Entry e = doGet(key, LATEST_REV);
+
+            if (e.value() == null)
+                return e;
+
+            return put(key, TOMBSTONE);
+        }
+    }
+
+    @Override public Iterator<Entry> iterate(byte[] keyFrom) {
+        synchronized (mux) {
+            NavigableMap<byte[], List<Long>> tailMap = keysIdx.tailMap(keyFrom, true);
+
+            final Iterator<Map.Entry<byte[], List<Long>>> it = tailMap.entrySet().iterator();
+
+            return new Iterator<>() {
+                private Map.Entry<byte[], List<Long>> curr;
+                private boolean hasNext;
+
+                private void advance() {
+                    if (it.hasNext()) {
+                        Map.Entry<byte[], List<Long>> e = it.next();
+
+                        byte[] key = e.getKey();
+
+                        if (!isPrefix(keyFrom, key))
+                            hasNext = false;
+                        else {
+                            curr = e;
+
+                            hasNext = true;
+                        }
+                    } else
+                        hasNext = false;
+                }
+
+                @Override
+                public boolean hasNext() {
+                    synchronized (mux) {
+                        if (curr == null)
+                            advance();
+
+                        return hasNext;
+                    }
+                }
+
+                @Override
+                public Entry next() {
+                    synchronized (mux) {
+                        if (!hasNext())
+                            throw new NoSuchElementException();
+
+                        Map.Entry<byte[], List<Long>> e = curr;
+
+                        curr = null;
+
+                        byte[] key = e.getKey();
+
+                        List<Long> revs = e.getValue();
+
+                        long rev = revs == null || revs.isEmpty() ? 0 : lastRevision(revs);
+
+                        if (rev == 0) {
+                            throw new IllegalStateException("rev == 0");
+                            //return new AbstractMap.SimpleImmutableEntry<>(key, null);
+                        }
+
+                        NavigableMap<byte[], byte[]> vals = revsIdx.get(rev);
+
+                        if (vals == null || vals.isEmpty()) {
+                            throw new IllegalStateException("vals == null || vals.isEmpty()");
+                            //return new AbstractMap.SimpleImmutableEntry<>(key, null);
+                        }
+
+                        byte[] val = vals.get(key);
+
+                        return val == TOMBSTONE ? Entry.tombstone(key, rev) : new Entry(key, val, rev);
+                    }
+                }
+            };
+        }
+    }
+
+    @Override public void compact() {
+        synchronized (mux) {
+            NavigableMap<byte[], List<Long>> compactedKeysIdx = new TreeMap<>(LEXICOGRAPHIC_COMPARATOR);
+
+            NavigableMap<Long, NavigableMap<byte[], byte[]>> compactedRevsIdx = new TreeMap<>();
+
+            keysIdx.forEach((key, revs) -> compactForKey(key, revs, compactedKeysIdx, compactedRevsIdx));
+
+            keysIdx = compactedKeysIdx;
+
+            revsIdx = compactedRevsIdx;
+        }
+    }
+
+    private void compactForKey(
+            byte[] key,
+            List<Long> revs,
+            NavigableMap<byte[], List<Long>> compactedKeysIdx,
+            NavigableMap<Long, NavigableMap<byte[], byte[]>> compactedRevsIdx
+    ) {
+        Long lrev = lastRevision(revs);
+
+        NavigableMap<byte[], byte[]> kv = revsIdx.get(lrev);
+
+        byte[] lastVal = kv.get(key);
+
+        if (lastVal != TOMBSTONE) {
+            compactedKeysIdx.put(key, listOf(lrev));
+
+            NavigableMap<byte[], byte[]> compactedKv = compactedRevsIdx.computeIfAbsent(
+                    lrev,
+                    k -> new TreeMap<>(LEXICOGRAPHIC_COMPARATOR)
+            );
+
+            compactedKv.put(key, lastVal);
+        }
+    }
+
+    /**
+     * Returns entry for given key.
+     *
+     * @param key Key.
+     * @param rev Revision.
+     * @return Entry for given key.
+     */
+    @NotNull private Entry doGet(byte[] key, long rev) {
+        List<Long> revs = keysIdx.get(key);
+
+        if (revs == null || revs.isEmpty())
+            return Entry.empty(key);
+
+        long lrev = rev == LATEST_REV ? lastRevision(revs) : rev;
+
+        NavigableMap<byte[], byte[]> entries = revsIdx.get(lrev);
+
+        if (entries == null || entries.isEmpty())
+            return Entry.empty(key);
+
+        byte[] val = entries.get(key);
+
+        if (val == TOMBSTONE)
+            return Entry.tombstone(key, lrev);
+
+        return new Entry(key, val , lrev);
+    }
+
+    private static boolean isPrefix(byte[] pref, byte[] term) {
+        if (pref.length > term.length)
+            return false;
+
+        for (int i = 0; i < pref.length - 1; i++) {
+            if (pref[i] != term[i])
+                return false;
+        }
+
+        return true;
+    }
+
+    private static long lastRevision(List<Long> revs) {
+        return revs.get(revs.size() - 1);
+    }
+
+    private static  List<Long> listOf(long val) {
+        List<Long> res = new ArrayList<>();
+
+        res.add(val);
+
+        return res;
+    }
+
+}
diff --git a/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/Watch.java b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/Watch.java
new file mode 100644
index 0000000..26cfa5c
--- /dev/null
+++ b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/Watch.java
@@ -0,0 +1,45 @@
+package org.apache.ignite.internal.metastorage.server;
+
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+public class Watch {
+    private static final Comparator<byte[]> CMP = Arrays::compare;
+
+    private static final long ANY_REVISION = -1;
+
+    @Nullable
+    private byte[] startKey;
+
+    @Nullable
+    private byte[] endKey;
+
+    long rev = ANY_REVISION;
+
+    public void startKey(byte[] startKey) {
+        this.startKey = startKey;
+    }
+
+    public void endKey(byte[] endKey) {
+        this.endKey = endKey;
+    }
+
+    public void revision(long rev) {
+        this.rev = rev;
+    }
+
+    public void notify(Entry e) {
+        if (startKey != null && CMP.compare(e.key(), startKey) < 0)
+            return;
+
+        if (endKey != null && CMP.compare(e.key(), endKey) > 0)
+            return;
+
+        if (rev != ANY_REVISION && e.revision() <= rev)
+            return;
+
+        System.out.println("Entry: key=" + new String(e.key()) + ", value=" + new String(e.value()) + ", rev=" + e.revision());
+    }
+}
diff --git a/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/Watcher.java b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/Watcher.java
new file mode 100644
index 0000000..5516d06
--- /dev/null
+++ b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/Watcher.java
@@ -0,0 +1,13 @@
+package org.apache.ignite.internal.metastorage.server;
+
+import org.jetbrains.annotations.NotNull;
+
+public interface Watcher {
+    void register(@NotNull Watch watch);
+
+    void notify(@NotNull Entry e);
+
+    //TODO: implement
+    void cancel(@NotNull Watch watch);
+}
+
diff --git a/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/WatcherImpl.java b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/WatcherImpl.java
new file mode 100644
index 0000000..dc126a0
--- /dev/null
+++ b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/WatcherImpl.java
@@ -0,0 +1,58 @@
+package org.apache.ignite.internal.metastorage.server;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class WatcherImpl implements Watcher {
+    private final BlockingQueue<Entry> queue = new LinkedBlockingQueue<>();
+
+    private final List<Watch> watches = new ArrayList<>();
+
+    private volatile boolean stop;
+
+    private final Object mux = new Object();
+
+    @Override public void register(@NotNull Watch watch) {
+        synchronized (mux) {
+            watches.add(watch);
+        }
+    }
+
+    @Override public void notify(@NotNull Entry e) {
+        queue.offer(e);
+    }
+
+    @Override
+    public void cancel(@NotNull Watch watch) {
+        throw new UnsupportedOperationException("Not implemented yet.");
+    }
+
+    public void shutdown() {
+        stop = true;
+    }
+
+    private class WatcherWorker implements Runnable {
+        @Override public void run() {
+            while (!stop) {
+                try {
+                    Entry e = queue.poll(100, TimeUnit.MILLISECONDS);
+
+                    if (e != null) {
+                        synchronized (mux) {
+                            watches.forEach(w -> w.notify(e));
+                        }
+                    }
+                }
+                catch (InterruptedException interruptedException) {
+                    // No-op.
+                }
+            }
+        }
+    }
+}
+
diff --git a/modules/metastorage-server/src/test/java/org/apache/ignite/internal/metastorage/server/SimpleInMemoryKeyValueStorageTest.java b/modules/metastorage-server/src/test/java/org/apache/ignite/internal/metastorage/server/SimpleInMemoryKeyValueStorageTest.java
new file mode 100644
index 0000000..f7fb17e
--- /dev/null
+++ b/modules/metastorage-server/src/test/java/org/apache/ignite/internal/metastorage/server/SimpleInMemoryKeyValueStorageTest.java
@@ -0,0 +1,274 @@
+package org.apache.ignite.internal.metastorage.server;
+
+import org.jetbrains.annotations.NotNull;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.Iterator;
+import java.util.Map;
+import java.util.TreeMap;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class SimpleInMemoryKeyValueStorageTest {
+    private KeyValueStorage storage;
+
+    @BeforeEach
+    public void setUp() {
+        storage = new SimpleInMemoryKeyValueStorage(new NoOpWatcher());
+    }
+
+    @Test
+    void putGetRemoveCompact() {
+        byte[] key1 = k(1);
+        byte[] val1_1 = kv(1, 1);
+        byte[] val1_3 = kv(1, 3);
+
+        byte[] key2 = k(2);
+        byte[] val2_2 = kv(2, 2);
+
+        assertEquals(0, storage.revision());
+
+        // Previous entry is empty.
+        Entry emptyEntry = storage.put(key1, val1_1);
+
+        assertEquals(1, storage.revision());
+        assertTrue(emptyEntry.empty());
+
+        // Entry with rev == 1.
+        Entry e1_1 = storage.get(key1);
+
+        assertFalse(e1_1.empty());
+        assertFalse(e1_1.tombstone());
+        assertArrayEquals(key1, e1_1.key());
+        assertArrayEquals(val1_1, e1_1.value());
+        assertEquals(1, e1_1.revision());
+        assertEquals(1, storage.revision());
+
+        // Previous entry is empty.
+        emptyEntry = storage.put(key2, val2_2);
+
+        assertEquals(2, storage.revision());
+        assertTrue(emptyEntry.empty());
+
+        // Entry with rev == 2.
+        Entry e2 = storage.get(key2);
+
+        assertFalse(e2.empty());
+        assertFalse(e2.tombstone());
+        assertArrayEquals(key2, e2.key());
+        assertArrayEquals(val2_2, e2.value());
+        assertEquals(2, e2.revision());
+        assertEquals(2, storage.revision());
+
+        // Previous entry is not empty.
+        e1_1 = storage.put(key1, val1_3);
+
+        assertFalse(e1_1.empty());
+        assertFalse(e1_1.tombstone());
+        assertArrayEquals(key1, e1_1.key());
+        assertArrayEquals(val1_1, e1_1.value());
+        assertEquals(1, e1_1.revision());
+        assertEquals(3, storage.revision());
+
+        // Entry with rev == 3.
+        Entry e1_3 = storage.get(key1);
+
+        assertFalse(e1_3.empty());
+        assertFalse(e1_3.tombstone());
+        assertArrayEquals(key1, e1_3.key());
+        assertArrayEquals(val1_3, e1_3.value());
+        assertEquals(3, e1_3.revision());
+        assertEquals(3, storage.revision());
+
+        // Remove existing entry.
+        Entry e2_2 = storage.remove(key2);
+
+        assertFalse(e2_2.empty());
+        assertFalse(e2_2.tombstone());
+        assertArrayEquals(key2, e2_2.key());
+        assertArrayEquals(val2_2, e2_2.value());
+        assertEquals(2, e2_2.revision());
+        assertEquals(4, storage.revision()); // Storage revision is changed.
+
+        // Remove already removed entry.
+        Entry tombstoneEntry = storage.remove(key2);
+
+        assertFalse(tombstoneEntry.empty());
+        assertTrue(tombstoneEntry.tombstone());
+        assertEquals(4, storage.revision()); // Storage revision is not changed.
+
+        // Compact and check that tombstones are removed.
+        storage.compact();
+
+        assertEquals(4, storage.revision());
+        assertTrue(storage.remove(key2).empty());
+        assertTrue(storage.get(key2).empty());
+
+        // Remove existing entry.
+        e1_3 = storage.remove(key1);
+
+        assertFalse(e1_3.empty());
+        assertFalse(e1_3.tombstone());
+        assertArrayEquals(key1, e1_3.key());
+        assertArrayEquals(val1_3, e1_3.value());
+        assertEquals(3, e1_3.revision());
+        assertEquals(5, storage.revision()); // Storage revision is changed.
+
+        // Remove already removed entry.
+        tombstoneEntry = storage.remove(key1);
+
+        assertFalse(tombstoneEntry.empty());
+        assertTrue(tombstoneEntry.tombstone());
+        assertEquals(5, storage.revision()); // // Storage revision is not changed.
+
+        // Compact and check that tombstones are removed.
+        storage.compact();
+
+        assertEquals(5, storage.revision());
+        assertTrue(storage.remove(key1).empty());
+        assertTrue(storage.get(key1).empty());
+    }
+
+    @Test
+    void compact() {
+        assertEquals(0, storage.revision());
+
+        // Compact empty.
+        storage.compact();
+
+        assertEquals(0, storage.revision());
+
+        // Compact non-empty.
+        fill(storage, 1, 1);
+
+        assertEquals(1, storage.revision());
+
+        fill(storage, 2, 2);
+
+        assertEquals(3, storage.revision());
+
+        fill(storage, 3, 3);
+
+        assertEquals(6, storage.revision());
+
+        storage.remove(k(3));
+
+        assertEquals(7, storage.revision());
+        assertTrue(storage.get(k(3)).tombstone());
+
+        storage.compact();
+
+        assertEquals(7, storage.revision());
+
+        Entry e1 = storage.get(k(1));
+
+        assertFalse(e1.empty());
+        assertFalse(e1.tombstone());
+        assertArrayEquals(k(1), e1.key());
+        assertArrayEquals(kv(1,1), e1.value());
+        assertEquals(1, e1.revision());
+
+        Entry e2 = storage.get(k(2));
+
+        assertFalse(e2.empty());
+        assertFalse(e2.tombstone());
+        assertArrayEquals(k(2), e2.key());
+        assertArrayEquals(kv(2,2), e2.value());
+        assertTrue(storage.get(k(2), 2).empty());
+        assertEquals(3, e2.revision());
+
+        Entry e3 = storage.get(k(3));
+
+        assertTrue(e3.empty());
+        assertTrue(storage.get(k(3), 5).empty());
+        assertTrue(storage.get(k(3), 6).empty());
+        assertTrue(storage.get(k(3), 7).empty());
+    }
+
+    @Test
+    void iterate() {
+        TreeMap<String, String> expFooMap = new TreeMap<>();
+        TreeMap<String, String> expKeyMap = new TreeMap<>();
+        TreeMap<String, String> expZooMap = new TreeMap<>();
+
+        fill("foo", storage, expFooMap);
+        fill("key", storage, expKeyMap);
+        fill("zoo", storage, expZooMap);
+
+        assertEquals(300, storage.revision());
+
+        assertIterate("key", storage, expKeyMap);
+        assertIterate("zoo", storage, expZooMap);
+        assertIterate("foo", storage, expFooMap);
+    }
+
+    private void assertIterate(String pref,  KeyValueStorage storage, TreeMap<String, String> expMap) {
+        Iterator<Entry> it = storage.iterate((pref + "_").getBytes());
+        Iterator<Map.Entry<String, String>> expIt = expMap.entrySet().iterator();
+
+        // Order.
+        while (it.hasNext()) {
+            Entry entry = it.next();
+            Map.Entry<String, String> expEntry = expIt.next();
+
+            assertEquals(expEntry.getKey(), new String(entry.key()));
+            assertEquals(expEntry.getValue(), new String(entry.value()));
+        }
+
+        // Range boundaries.
+        it = storage.iterate((pref + '_').getBytes());
+
+        while (it.hasNext()) {
+            Entry entry = it.next();
+
+            assertTrue(expMap.containsKey(new String(entry.key())));
+        }
+    }
+
+    private static void fill(String pref, KeyValueStorage storage, TreeMap<String, String> expMap) {
+        for (int i = 0; i < 100; i++) {
+            String keyStr = pref + '_' + i;
+
+            String valStr = "val_" + i;
+
+            expMap.put(keyStr, valStr);
+
+            byte[] key = keyStr.getBytes();
+
+            byte[] val = valStr.getBytes();
+
+            storage.put(key, val);
+        }
+    }
+
+    private static void fill(KeyValueStorage storage, int keySuffix, int num) {
+        for (int i = 0; i < num; i++)
+            storage.put(k(keySuffix), kv(keySuffix, i + 1));
+    }
+
+    private static byte[] k(int k) {
+        return ("key" + k).getBytes();
+    }
+
+    private static byte[] kv(int k, int v) {
+        return ("key" + k + '_' + "val" + v).getBytes();
+    }
+
+    private static class NoOpWatcher implements Watcher {
+        @Override public void register(@NotNull Watch watch) {
+            // No-op.
+        }
+
+        @Override public void notify(@NotNull Entry e) {
+            // No-op.
+        }
+
+        @Override public void cancel(@NotNull Watch watch) {
+            // No-op.
+        }
+    }
+}
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 9612a10..cf25d57 100644
--- a/pom.xml
+++ b/pom.xml
@@ -43,6 +43,7 @@
         <module>modules/core</module>
         <module>modules/metastorage-client</module>
         <module>modules/metastorage-common</module>
+        <module>modules/metastorage-server</module>
         <module>modules/network</module>
         <module>modules/raft</module>
         <module>modules/raft-client</module>

[ignite-3] 05/05: IGNITE-14389 putAll initial (WIP)

Posted by ag...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit af453853ed3f2d6663bef6a3f5a1f8b104cc9cd5
Author: Andrey Gura <ag...@apache.org>
AuthorDate: Thu Apr 15 21:18:56 2021 +0300

    IGNITE-14389 putAll initial (WIP)
---
 .../apache/ignite/internal/metastorage/server/KeyValueStorage.java | 2 ++
 .../internal/metastorage/server/SimpleInMemoryKeyValueStorage.java | 7 +++++++
 2 files changed, 9 insertions(+)

diff --git a/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/KeyValueStorage.java b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/KeyValueStorage.java
index 0596c4a..0f18ece 100644
--- a/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/KeyValueStorage.java
+++ b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/KeyValueStorage.java
@@ -29,6 +29,8 @@ public interface KeyValueStorage {
     @NotNull
     Entry getAndPut(byte[] key, byte[] value);
 
+    void putAll(List<byte[]> keys, List<byte[]> values);
+
     void remove(byte[] key);
 
     @NotNull
diff --git a/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/SimpleInMemoryKeyValueStorage.java b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/SimpleInMemoryKeyValueStorage.java
index 8523f51..f532005 100644
--- a/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/SimpleInMemoryKeyValueStorage.java
+++ b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/SimpleInMemoryKeyValueStorage.java
@@ -63,6 +63,13 @@ public class SimpleInMemoryKeyValueStorage implements KeyValueStorage {
         }
     }
 
+    @Override
+    public void putAll(List<byte[]> keys, List<byte[]> values) {
+        synchronized (mux) {
+
+        }
+    }
+
     @NotNull
     @Override public Entry get(byte[] key) {
         synchronized (mux) {

[ignite-3] 03/05: IGNITE-14389 Added get and do smth semantic

Posted by ag...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit a3862e6435d6b8e91f9043efab9bba6cf7d38ef3
Author: Andrey Gura <ag...@apache.org>
AuthorDate: Tue Apr 6 22:25:03 2021 +0300

    IGNITE-14389 Added get and do smth semantic
---
 .../metastorage/server/KeyValueStorage.java        |  12 +-
 .../server/SimpleInMemoryKeyValueStorage.java      | 147 +++++++++----
 .../server/SimpleInMemoryKeyValueStorageTest.java  | 235 +++++++++++++++++++--
 3 files changed, 329 insertions(+), 65 deletions(-)

diff --git a/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/KeyValueStorage.java b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/KeyValueStorage.java
index e245e08..ead1043 100644
--- a/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/KeyValueStorage.java
+++ b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/KeyValueStorage.java
@@ -11,16 +11,20 @@ public interface KeyValueStorage {
     long updateCounter();
 
     @NotNull
-    Entry put(byte[] key, byte[] value);
-
-    @NotNull
     Entry get(byte[] key);
 
     @NotNull
     Entry get(byte[] key, long rev);
 
+    void put(byte[] key, byte[] value);
+
+    @NotNull
+    Entry getAndPut(byte[] key, byte[] value);
+
+    void remove(byte[] key);
+
     @NotNull
-    Entry remove(byte[] key);
+    Entry getAndRemove(byte[] key);
 
     Iterator<Entry> iterate(byte[] key);
 
diff --git a/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/SimpleInMemoryKeyValueStorage.java b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/SimpleInMemoryKeyValueStorage.java
index b764998..3700f4a 100644
--- a/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/SimpleInMemoryKeyValueStorage.java
+++ b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/SimpleInMemoryKeyValueStorage.java
@@ -46,49 +46,26 @@ public class SimpleInMemoryKeyValueStorage implements KeyValueStorage {
         return updCntr;
     }
 
-    @NotNull
-    @Override public Entry put(byte[] key, byte[] bytes) {
+    @Override public void put(byte[] key, byte[] value) {
         synchronized (mux) {
-            long curRev = ++rev;
-
-            long curUpdCntr = ++updCntr;
-
-            // Update keysIdx.
-            List<Long> revs = keysIdx.computeIfAbsent(key, k -> new ArrayList<>());
-
-            long lastRev = revs.isEmpty() ? 0 : lastRevision(revs);
-
-            revs.add(curRev);
-
-            // Update revsIdx.
-            NavigableMap<byte[], Value> entries = new TreeMap<>(LEXICOGRAPHIC_COMPARATOR);
-
-            Value val = new Value(bytes, curUpdCntr);
-
-            entries.put(key, val);
+            doPut(key, value);
+        }
+    }
 
-            revsIdx.put(curRev, entries);
+    @NotNull
+    @Override public Entry getAndPut(byte[] key, byte[] bytes) {
+        synchronized (mux) {
+            long lastRev = doPut(key, bytes);
 
             // Return previous value.
-            if (lastRev == 0)
-                return Entry.empty(key);
-
-            NavigableMap<byte[], Value> lastRevVals = revsIdx.get(lastRev);
-
-            Value lastVal = lastRevVals.get(key);
-
-            Entry res = new Entry(key, lastVal.bytes(), lastRev, lastVal.updateCounter());
-
-            //TODO: notify watchers
-
-            return res;
+            return doGetValue(key, lastRev);
         }
     }
 
     @NotNull
     @Override public Entry get(byte[] key) {
         synchronized (mux) {
-            return doGet(key, LATEST_REV);
+            return doGet(key, LATEST_REV, false);
         }
     }
 
@@ -96,19 +73,31 @@ public class SimpleInMemoryKeyValueStorage implements KeyValueStorage {
     @TestOnly
     @Override public Entry get(byte[] key, long rev) {
         synchronized (mux) {
-            return doGet(key, rev);
+            return doGet(key, rev, true);
+        }
+    }
+
+    @Override
+    public void remove(byte[] key) {
+        synchronized (mux) {
+            Entry e = doGet(key, LATEST_REV, false);
+
+            if (e.empty() || e.tombstone())
+                return;
+
+            doPut(key, TOMBSTONE);
         }
     }
 
     @NotNull
-    @Override public Entry remove(byte[] key) {
+    @Override public Entry getAndRemove(byte[] key) {
         synchronized (mux) {
-            Entry e = doGet(key, LATEST_REV);
+            Entry e = doGet(key, LATEST_REV, false);
 
-            if (e.value() == null)
+            if (e.empty() || e.tombstone())
                 return e;
 
-            return put(key, TOMBSTONE);
+            return getAndPut(key, TOMBSTONE);
         }
     }
 
@@ -233,25 +222,91 @@ public class SimpleInMemoryKeyValueStorage implements KeyValueStorage {
      * @param rev Revision.
      * @return Entry for given key.
      */
-    @NotNull private Entry doGet(byte[] key, long rev) {
+    @NotNull
+    private Entry doGet(byte[] key, long rev, boolean exactRev) {
+        assert rev == LATEST_REV && !exactRev || rev > LATEST_REV :
+                "Invalid arguments: [rev=" + rev + ", exactRev=" + exactRev + ']';
+
         List<Long> revs = keysIdx.get(key);
 
         if (revs == null || revs.isEmpty())
             return Entry.empty(key);
 
-        long lrev = rev == LATEST_REV ? lastRevision(revs) : rev;
+        long lastRev;
 
-        NavigableMap<byte[], Value> entries = revsIdx.get(lrev);
+        if (rev == LATEST_REV)
+            lastRev = lastRevision(revs);
+        else
+            lastRev = exactRev ? rev : maxRevision(revs, rev);
 
-        if (entries == null || entries.isEmpty())
+        // lastRev can be -1 if maxRevision return -1.
+        if (lastRev == -1)
             return Entry.empty(key);
 
-        Value val = entries.get(key);
+        return doGetValue(key, lastRev);
+    }
+
+    /**
+     * Returns maximum revision which must be less or equal to {@code upperBoundRev}. If there is no such revision then
+     * {@code -1} will be returned.
+     *
+     * @param revs Revisions list.
+     * @param upperBoundRev Revision upper bound.
+     * @return Appropriate revision or {@code -1} if there is no such revision.
+     */
+    private static long maxRevision(List<Long> revs, long upperBoundRev) {
+        int i = revs.size() - 1;
+
+        for (; i >= 0 ; i--) {
+            long rev = revs.get(i);
+
+            if (rev <= upperBoundRev)
+                return rev;
+        }
+
+        return -1;
+    }
+
+    @NotNull
+    private Entry doGetValue(byte[] key, long lastRev) {
+        if (lastRev == 0)
+            return Entry.empty(key);
+
+        NavigableMap<byte[], Value> lastRevVals = revsIdx.get(lastRev);
+
+        if (lastRevVals == null || lastRevVals.isEmpty())
+            return Entry.empty(key);
+
+        Value lastVal = lastRevVals.get(key);
+
+        if (lastVal.tombstone())
+            return Entry.tombstone(key, lastRev, lastVal.updateCounter());
+
+        return new Entry(key, lastVal.bytes() , lastRev, lastVal.updateCounter());
+    }
+
+    private long doPut(byte[] key, byte[] bytes) {
+        long curRev = ++rev;
+
+        long curUpdCntr = ++updCntr;
+
+        // Update keysIdx.
+        List<Long> revs = keysIdx.computeIfAbsent(key, k -> new ArrayList<>());
+
+        long lastRev = revs.isEmpty() ? 0 : lastRevision(revs);
+
+        revs.add(curRev);
+
+        // Update revsIdx.
+        NavigableMap<byte[], Value> entries = new TreeMap<>(LEXICOGRAPHIC_COMPARATOR);
+
+        Value val = new Value(bytes, curUpdCntr);
+
+        entries.put(key, val);
 
-        if (val.tombstone())
-            return Entry.tombstone(key, lrev, val.updateCounter());
+        revsIdx.put(curRev, entries);
 
-        return new Entry(key, val.bytes() , lrev, val.updateCounter());
+        return lastRev;
     }
 
     private static boolean isPrefix(byte[] pref, byte[] term) {
diff --git a/modules/metastorage-server/src/test/java/org/apache/ignite/internal/metastorage/server/SimpleInMemoryKeyValueStorageTest.java b/modules/metastorage-server/src/test/java/org/apache/ignite/internal/metastorage/server/SimpleInMemoryKeyValueStorageTest.java
index eae76fd..5b797fc 100644
--- a/modules/metastorage-server/src/test/java/org/apache/ignite/internal/metastorage/server/SimpleInMemoryKeyValueStorageTest.java
+++ b/modules/metastorage-server/src/test/java/org/apache/ignite/internal/metastorage/server/SimpleInMemoryKeyValueStorageTest.java
@@ -22,7 +22,212 @@ class SimpleInMemoryKeyValueStorageTest {
     }
 
     @Test
-    void putGetRemoveCompact() {
+    public void put() {
+        byte[] key = k(1);
+        byte[] val = kv(1, 1);
+
+        assertEquals(0, storage.revision());
+        assertEquals(0, storage.updateCounter());
+        assertTrue(storage.get(key).empty());
+
+        storage.put(key, val);
+
+        assertEquals(1, storage.revision());
+        assertEquals(1, storage.updateCounter());
+
+        Entry e = storage.get(key);
+
+        assertFalse(e.empty());
+        assertFalse(e.tombstone());
+        assertEquals(1, e.revision());
+        assertEquals(1, e.updateCounter());
+
+        storage.put(key, val);
+
+        assertEquals(2, storage.revision());
+        assertEquals(2, storage.updateCounter());
+
+        e = storage.get(key);
+
+        assertFalse(e.empty());
+        assertFalse(e.tombstone());
+        assertEquals(2, e.revision());
+        assertEquals(2, e.updateCounter());
+    }
+
+    @Test
+    public void getAndPut() {
+        byte[] key = k(1);
+        byte[] val = kv(1, 1);
+
+        assertEquals(0, storage.revision());
+        assertEquals(0, storage.updateCounter());
+        assertTrue(storage.get(key).empty());
+
+        Entry e = storage.getAndPut(key, val);
+
+        assertEquals(1, storage.revision());
+        assertEquals(1, storage.updateCounter());
+        assertTrue(e.empty());
+        assertFalse(e.tombstone());
+        assertEquals(0, e.revision());
+        assertEquals(0, e.updateCounter());
+
+        e = storage.getAndPut(key, val);
+
+        assertEquals(2, storage.revision());
+        assertEquals(2, storage.updateCounter());
+        assertFalse(e.empty());
+        assertFalse(e.tombstone());
+        assertEquals(1, e.revision());
+        assertEquals(1, e.updateCounter());
+    }
+
+    @Test
+    public void remove() {
+        byte[] key = k(1);
+        byte[] val = kv(1, 1);
+
+        assertEquals(0, storage.revision());
+        assertEquals(0, storage.updateCounter());
+        assertTrue(storage.get(key).empty());
+
+        // Remove non-existent entry.
+        storage.remove(key);
+
+        assertEquals(0, storage.revision());
+        assertEquals(0, storage.updateCounter());
+        assertTrue(storage.get(key).empty());
+
+        storage.put(key, val);
+
+        assertEquals(1, storage.revision());
+        assertEquals(1, storage.updateCounter());
+
+        // Remove existent entry.
+        storage.remove(key);
+
+        assertEquals(2, storage.revision());
+        assertEquals(2, storage.updateCounter());
+
+        Entry e = storage.get(key);
+
+        assertFalse(e.empty());
+        assertTrue(e.tombstone());
+        assertEquals(2, e.revision());
+        assertEquals(2, e.updateCounter());
+
+        // Remove already removed entry (tombstone can't be removed).
+        storage.remove(key);
+
+        assertEquals(2, storage.revision());
+        assertEquals(2, storage.updateCounter());
+
+        e = storage.get(key);
+
+        assertFalse(e.empty());
+        assertTrue(e.tombstone());
+        assertEquals(2, e.revision());
+        assertEquals(2, e.updateCounter());
+    }
+
+    @Test
+    public void getAndRemove() {
+        byte[] key = k(1);
+        byte[] val = kv(1, 1);
+
+        assertEquals(0, storage.revision());
+        assertEquals(0, storage.updateCounter());
+        assertTrue(storage.get(key).empty());
+
+        // Remove non-existent entry.
+        Entry e = storage.getAndRemove(key);
+
+        assertTrue(e.empty());
+        assertEquals(0, storage.revision());
+        assertEquals(0, storage.updateCounter());
+        assertTrue(storage.get(key).empty());
+
+        storage.put(key, val);
+
+        assertEquals(1, storage.revision());
+        assertEquals(1, storage.updateCounter());
+
+        // Remove existent entry.
+        e = storage.getAndRemove(key);
+
+        assertFalse(e.empty());
+        assertFalse(e.tombstone());
+        assertEquals(1, e.revision());
+        assertEquals(1, e.updateCounter());
+        assertEquals(2, storage.revision());
+        assertEquals(2, storage.updateCounter());
+
+        e = storage.get(key);
+
+        assertFalse(e.empty());
+        assertTrue(e.tombstone());
+        assertEquals(2, e.revision());
+        assertEquals(2, e.updateCounter());
+
+        // Remove already removed entry (tombstone can't be removed).
+        e = storage.getAndRemove(key);
+
+        assertFalse(e.empty());
+        assertTrue(e.tombstone());
+        assertEquals(2, e.revision());
+        assertEquals(2, e.updateCounter());
+        assertEquals(2, storage.revision());
+        assertEquals(2, storage.updateCounter());
+
+        e = storage.get(key);
+
+        assertFalse(e.empty());
+        assertTrue(e.tombstone());
+        assertEquals(2, e.revision());
+        assertEquals(2, e.updateCounter());
+    }
+
+    @Test
+    public void getAfterRemove() {
+        byte[] key = k(1);
+        byte[] val = kv(1, 1);
+
+        storage.getAndPut(key, val);
+
+        storage.getAndRemove(key);
+
+        Entry e = storage.get(key);
+
+        assertEquals(2, storage.revision());
+        assertEquals(2, storage.updateCounter());
+        assertEquals(2, e.revision());
+        assertTrue(e.tombstone());
+    }
+
+    @Test
+    public void getAndPutAfterRemove() {
+        byte[] key = k(1);
+
+        byte[] val = kv(1, 1);
+
+        storage.getAndPut(key, val);
+
+        storage.getAndRemove(key);
+
+        Entry e = storage.getAndPut(key, val);
+
+        assertEquals(3, storage.revision());
+
+        assertEquals(3, storage.updateCounter());
+
+        assertEquals(2, e.revision());
+
+        assertTrue(e.tombstone());
+    }
+
+    @Test
+    public void putGetRemoveCompact() {
         byte[] key1 = k(1);
         byte[] val1_1 = kv(1, 1);
         byte[] val1_3 = kv(1, 3);
@@ -34,7 +239,7 @@ class SimpleInMemoryKeyValueStorageTest {
         assertEquals(0, storage.updateCounter());
 
         // Previous entry is empty.
-        Entry emptyEntry = storage.put(key1, val1_1);
+        Entry emptyEntry = storage.getAndPut(key1, val1_1);
 
         assertEquals(1, storage.revision());
         assertEquals(1, storage.updateCounter());
@@ -53,7 +258,7 @@ class SimpleInMemoryKeyValueStorageTest {
         assertEquals(1, storage.updateCounter());
 
         // Previous entry is empty.
-        emptyEntry = storage.put(key2, val2_2);
+        emptyEntry = storage.getAndPut(key2, val2_2);
 
         assertEquals(2, storage.revision());
         assertEquals(2, storage.updateCounter());
@@ -72,7 +277,7 @@ class SimpleInMemoryKeyValueStorageTest {
         assertEquals(2, storage.updateCounter());
 
         // Previous entry is not empty.
-        e1_1 = storage.put(key1, val1_3);
+        e1_1 = storage.getAndPut(key1, val1_3);
 
         assertFalse(e1_1.empty());
         assertFalse(e1_1.tombstone());
@@ -96,7 +301,7 @@ class SimpleInMemoryKeyValueStorageTest {
         assertEquals(3, storage.updateCounter());
 
         // Remove existing entry.
-        Entry e2_2 = storage.remove(key2);
+        Entry e2_2 = storage.getAndRemove(key2);
 
         assertFalse(e2_2.empty());
         assertFalse(e2_2.tombstone());
@@ -108,7 +313,7 @@ class SimpleInMemoryKeyValueStorageTest {
         assertEquals(4, storage.updateCounter());
 
         // Remove already removed entry.
-        Entry tombstoneEntry = storage.remove(key2);
+        Entry tombstoneEntry = storage.getAndRemove(key2);
 
         assertFalse(tombstoneEntry.empty());
         assertTrue(tombstoneEntry.tombstone());
@@ -120,11 +325,11 @@ class SimpleInMemoryKeyValueStorageTest {
 
         assertEquals(4, storage.revision());
         assertEquals(4, storage.updateCounter());
-        assertTrue(storage.remove(key2).empty());
+        assertTrue(storage.getAndRemove(key2).empty());
         assertTrue(storage.get(key2).empty());
 
         // Remove existing entry.
-        e1_3 = storage.remove(key1);
+        e1_3 = storage.getAndRemove(key1);
 
         assertFalse(e1_3.empty());
         assertFalse(e1_3.tombstone());
@@ -136,7 +341,7 @@ class SimpleInMemoryKeyValueStorageTest {
         assertEquals(5, storage.updateCounter());
 
         // Remove already removed entry.
-        tombstoneEntry = storage.remove(key1);
+        tombstoneEntry = storage.getAndRemove(key1);
 
         assertFalse(tombstoneEntry.empty());
         assertTrue(tombstoneEntry.tombstone());
@@ -148,12 +353,12 @@ class SimpleInMemoryKeyValueStorageTest {
 
         assertEquals(5, storage.revision());
         assertEquals(5, storage.updateCounter());
-        assertTrue(storage.remove(key1).empty());
+        assertTrue(storage.getAndRemove(key1).empty());
         assertTrue(storage.get(key1).empty());
     }
 
     @Test
-    void compact() {
+    public void compact() {
         assertEquals(0, storage.revision());
         assertEquals(0, storage.updateCounter());
 
@@ -179,7 +384,7 @@ class SimpleInMemoryKeyValueStorageTest {
         assertEquals(6, storage.revision());
         assertEquals(6, storage.updateCounter());
 
-        storage.remove(k(3));
+        storage.getAndRemove(k(3));
 
         assertEquals(7, storage.revision());
         assertEquals(7, storage.updateCounter());
@@ -218,7 +423,7 @@ class SimpleInMemoryKeyValueStorageTest {
     }
 
     @Test
-    void iterate() {
+    public void iterate() {
         TreeMap<String, String> expFooMap = new TreeMap<>();
         TreeMap<String, String> expKeyMap = new TreeMap<>();
         TreeMap<String, String> expZooMap = new TreeMap<>();
@@ -270,13 +475,13 @@ class SimpleInMemoryKeyValueStorageTest {
 
             byte[] val = valStr.getBytes();
 
-            storage.put(key, val);
+            storage.getAndPut(key, val);
         }
     }
 
     private static void fill(KeyValueStorage storage, int keySuffix, int num) {
         for (int i = 0; i < num; i++)
-            storage.put(k(keySuffix), kv(keySuffix, i + 1));
+            storage.getAndPut(k(keySuffix), kv(keySuffix, i + 1));
     }
 
     private static byte[] k(int k) {