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/05/04 22:00:09 UTC

[ignite-3] branch ignite-14389 updated (8833492 -> 4c97c23)

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 8833492  IGNITE-14389 Implemented conditional update (invoke)
     new 4c97c23  IGNITE-14389 Implemented conditional update (invoke). Added ExistenceCondition.

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   (8833492)
            \
             N -- N -- N   refs/heads/ignite-14389 (4c97c23)

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 1 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:
 .../ignite/metastorage/common/Condition.java       |  66 ++++++
 .../ignite/metastorage/common/Conditions.java      |   8 +
 .../metastorage/server/ExistenceCondition.java     |  44 ++++
 .../metastorage/server/RevisionCondition.java      |   1 +
 .../metastorage/server/ExistenceConditionTest.java |  37 ++++
 .../server/SimpleInMemoryKeyValueStorageTest.java  | 224 +++++++++++++++++++++
 .../internal/table/distributed/TableManager.java   |   2 +-
 7 files changed, 381 insertions(+), 1 deletion(-)
 create mode 100644 modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/ExistenceCondition.java
 create mode 100644 modules/metastorage-server/src/test/java/org/apache/ignite/internal/metastorage/server/ExistenceConditionTest.java

[ignite-3] 01/01: IGNITE-14389 Implemented conditional update (invoke). Added ExistenceCondition.

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 4c97c234522e6cd80cd2b4dbca2570b05d05c6fc
Author: Andrey Gura <ag...@apache.org>
AuthorDate: Wed May 5 00:59:37 2021 +0300

    IGNITE-14389 Implemented conditional update (invoke). Added ExistenceCondition.
---
 .../ignite/internal/affinity/AffinityManager.java  |  17 +-
 .../metastorage/client/MetaStorageService.java     |  32 +-
 .../ignite/metastorage/common/Condition.java       | 207 ++++----
 .../ignite/metastorage/common/Conditions.java      |  37 +-
 .../ignite/metastorage/common/Operation.java       |  45 +-
 .../ignite/metastorage/common/Operations.java      |   6 +-
 modules/metastorage-server/pom.xml                 |   6 +-
 .../metastorage/server/AbstractCondition.java      |  13 +
 .../internal/metastorage/server/Condition.java     |   7 +
 .../metastorage/server/ExistenceCondition.java     |  44 ++
 .../metastorage/server/KeyValueStorage.java        |   2 +
 .../internal/metastorage/server/Operation.java     |  31 ++
 .../metastorage/server/RevisionCondition.java      |  76 +++
 .../server/SimpleInMemoryKeyValueStorage.java      |  95 +++-
 .../metastorage/server/ValueCondition.java         |  49 ++
 .../metastorage/server/ExistenceConditionTest.java |  37 ++
 .../metastorage/server/RevisionConditionTest.java  |  65 +++
 .../server/SimpleInMemoryKeyValueStorageTest.java  | 547 +++++++++++++++++++++
 .../metastorage/server/ValueConditionTest.java     |  27 +
 .../internal/metastorage/MetaStorageManager.java   |  23 +-
 .../apache/ignite/internal/app/IgnitionImpl.java   |  56 ++-
 .../internal/table/distributed/TableManager.java   |  19 +-
 22 files changed, 1212 insertions(+), 229 deletions(-)

diff --git a/modules/affinity/src/main/java/org/apache/ignite/internal/affinity/AffinityManager.java b/modules/affinity/src/main/java/org/apache/ignite/internal/affinity/AffinityManager.java
index 37690bf..e658ca1 100644
--- a/modules/affinity/src/main/java/org/apache/ignite/internal/affinity/AffinityManager.java
+++ b/modules/affinity/src/main/java/org/apache/ignite/internal/affinity/AffinityManager.java
@@ -37,6 +37,7 @@ import org.apache.ignite.metastorage.common.Operations;
 import org.apache.ignite.metastorage.common.WatchEvent;
 import org.apache.ignite.metastorage.common.WatchListener;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 /**
  * Affinity manager is responsible for affinity function related logic including calculating affinity assignments.
@@ -143,16 +144,20 @@ public class AffinityManager {
                         UUID tblId = UUID.fromString(placeholderValue);
 
                         try {
-                            String name = new String(vaultManager.get(ByteArray.fromString(INTERNAL_PREFIX + tblId.toString())).get().value(), StandardCharsets.UTF_8);
+                            byte[] tblNameVal = vaultManager.get(ByteArray.fromString(INTERNAL_PREFIX + tblId)).get().value();
 
-                            int partitions = configurationMgr.configurationRegistry().getConfiguration(TablesConfiguration.KEY)
-                                .tables().get(name).partitions().value();
-                            int replicas = configurationMgr.configurationRegistry().getConfiguration(TablesConfiguration.KEY)
-                                .tables().get(name).replicas().value();
+                            String name = new String(tblNameVal, StandardCharsets.UTF_8);
+
+                            int partitions = configurationMgr.configurationRegistry()
+                                    .getConfiguration(TablesConfiguration.KEY).tables().get(name).partitions().value();
+
+                            int replicas = configurationMgr.configurationRegistry()
+                                    .getConfiguration(TablesConfiguration.KEY).tables().get(name).replicas().value();
 
                             var key = evt.newEntry().key();
+
                             metaStorageMgr.invoke(
-                                Conditions.key(key).value().eq(assignmentVal),
+                                Conditions.value(key).eq(assignmentVal),
                                 Operations.put(key, ByteUtils.toBytes(
                                     RendezvousAffinityFunction.assignPartitions(
                                         baselineMgr.nodes(),
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 6c24a59..409f90d 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
@@ -203,9 +203,9 @@ public interface MetaStorageService {
      * <p>Conditional update could be treated as <i>if(condition)-then(success)-else(failure)</i> expression.</p>
      *
      * @param condition The condition.
-     * @param success Batch of updates which will be atomically applied in case of condition evaluation yields {@code true}.
-     * @param failure Batch of updates which will be atomically applied in case of condition evaluation yields {@code false}.
-     * @return Future result {@code true} if {@code success} updates were applied, otherwise {@code false}.
+     * @param success The update which will be applied in case of condition evaluation yields {@code true}.
+     * @param failure The update which will be applied in case of condition evaluation yields {@code false}.
+     * @return Future result {@code true} if {@code success} update was applied, otherwise {@code false}.
      * @throws OperationTimeoutException If the operation is timed out. Will be thrown on getting future result.
      * @see Key
      * @see Entry
@@ -215,14 +215,34 @@ public interface MetaStorageService {
     // TODO: https://issues.apache.org/jira/browse/IGNITE-14269: will be replaced by conditional multi update.
     @NotNull
     CompletableFuture<Boolean> invoke(@NotNull Condition condition,
-        @NotNull Collection<Operation> success, @NotNull Collection<Operation> failure);
+                                      @NotNull Operation success, @NotNull Operation failure);
+
+    /**
+     * Updates an entry for the given key conditionally.
+     *
+     * <p>Conditional update could be treated as <i>if(condition)-then(success)-else(failure)</i> expression.</p>
+     *
+     * @param condition The condition.
+     * @param success The updates which will be applied in case of condition evaluation yields {@code true}.
+     * @param failure The updates which will be applied in case of condition evaluation yields {@code false}.
+     * @return Future result {@code true} if {@code success} update was applied, otherwise {@code false}.
+     * @throws OperationTimeoutException If the operation is timed out. Will be thrown on getting future result.
+     * @see Key
+     * @see Entry
+     * @see Condition
+     * @see Operation
+     */
+    // TODO: https://issues.apache.org/jira/browse/IGNITE-14269: will be replaced by conditional multi update.
+    @NotNull
+    CompletableFuture<Boolean> invoke(@NotNull Condition condition,
+                                      @NotNull Collection<Operation> success, @NotNull Collection<Operation> failure);
+
 
     /**
      * Updates an entry for the given key conditionally.
      *
      * <p>Conditional update could be treated as <i>if(condition)-then(success)-else(failure)</i> expression.</p>
      *
-     * @param key The key. Couldn't be {@code null}.
      * @param condition The condition.
      * @param success The update which will be applied in case of condition evaluation yields {@code true}.
      * @param failure The update which will be applied in case of condition evaluation yields {@code false}.
@@ -235,7 +255,7 @@ public interface MetaStorageService {
      */
     //TODO: https://issues.apache.org/jira/browse/IGNITE-14269: will be replaced by conditional multi update.
     @NotNull
-    CompletableFuture<Entry> getAndInvoke(@NotNull Key key, @NotNull Condition condition,
+    CompletableFuture<Entry> getAndInvoke(@NotNull Condition condition,
                                           @NotNull Operation success, @NotNull Operation failure);
 
     /**
diff --git a/modules/metastorage-common/src/main/java/org/apache/ignite/metastorage/common/Condition.java b/modules/metastorage-common/src/main/java/org/apache/ignite/metastorage/common/Condition.java
index 54049de..abd2edf 100644
--- a/modules/metastorage-common/src/main/java/org/apache/ignite/metastorage/common/Condition.java
+++ b/modules/metastorage-common/src/main/java/org/apache/ignite/metastorage/common/Condition.java
@@ -17,8 +17,6 @@
 
 package org.apache.ignite.metastorage.common;
 
-import java.util.Arrays;
-
 /**
  * Represents a condition for conditional update.
  */
@@ -36,22 +34,11 @@ public final class Condition {
     }
 
     /**
-     * Tests the given entry on satisfaction of the condition.
-     *
-     * @param e Entry.
-     * @return The result of condition test. {@code true} - if the entry satisfies to the condition,
-     * otherwise - {@code false}.
-     */
-    public boolean test(Entry e) {
-        return cond.test(e);
-    }
-
-    /**
      * Represents condition on entry revision. Only one type of condition could be applied to
      * the one instance of condition. Subsequent invocations of any method which produces condition will throw
      * {@link IllegalStateException}.
      */
-    public static final class RevisionCondition implements InnerCondition {
+    public static final class RevisionCondition extends AbstractCondition {
         /**
          * The type of condition.
          *
@@ -62,16 +49,13 @@ public final class Condition {
         /** The revision as the condition argument. */
         private long rev;
 
-        /** Key of entry, which will be tested for condition. */
-        private final Key key;
-
         /**
-         * Creates a new condition for the given {@code key}.
+         * Constructs a condition by a revision for an entry identified by the given key.
          *
-         * @param key Key of entry, to be tested for the condition.
+         * @param key Identifies an entry which condition will be applied to.
          */
-        RevisionCondition(Key key) {
-            this.key = key;
+        RevisionCondition(byte[] key) {
+            super(key);
         }
 
         /**
@@ -176,70 +160,27 @@ public final class Condition {
             return new Condition(this);
         }
 
-        /** {@inheritDoc} */
-        @Override public boolean test(Entry e) {
-            if ((e.key() == key) || (e.key() != null && e.key().equals(key))) {
-                int res = Long.compare(e.revision(), rev);
-
-                return type.test(res);
-            }
-            else
-                return false;
-        }
-
         /**
          * Defines possible condition types which can be applied to the revision.
          */
         enum Type {
             /** Equality condition type. */
-            EQUAL {
-                @Override public boolean test(long res) {
-                    return res == 0;
-                }
-            },
+            EQUAL,
 
             /** Inequality condition type. */
-            NOT_EQUAL {
-                @Override public boolean test(long res) {
-                    return res != 0;
-                }
-            },
+            NOT_EQUAL,
 
             /** Greater than condition type. */
-            GREATER {
-                @Override public boolean test(long res) {
-                    return res > 0;
-                }
-            },
+            GREATER,
 
             /** Less than condition type. */
-            LESS {
-                @Override public boolean test(long res) {
-                    return res < 0;
-                }
-            },
+            LESS,
 
             /** Less than or equal to condition type. */
-            LESS_OR_EQUAL {
-                @Override public boolean test(long res) {
-                    return res <= 0;
-                }
-            },
+            LESS_OR_EQUAL,
 
             /** Greater than or equal to condition type. */
-            GREATER_OR_EQUAL {
-                @Override public boolean test(long res) {
-                    return res >= 0;
-                }
-            };
-
-            /**
-             * Interprets comparison result.
-             *
-             * @param res The result of comparison.
-             * @return The interpretation of the comparison result.
-             */
-            public abstract boolean test(long res);
+            GREATER_OR_EQUAL
         }
     }
 
@@ -248,7 +189,7 @@ public final class Condition {
      * the one instance of condition. Subsequent invocations of any method which produces condition will throw
      * {@link IllegalStateException}.
      */
-    public static final class ValueCondition implements InnerCondition {
+    public static final class ValueCondition extends AbstractCondition {
         /**
          * The type of condition.
          *
@@ -259,16 +200,13 @@ public final class Condition {
         /** The value as the condition argument. */
         private byte[] val;
 
-        /** Key of entry, which will be tested for condition. */
-        private final Key key;
-
         /**
-         * Creates a new condition for the given {@code key}.
+         * Constructs a condition by a value for an entry identified by the given key.
          *
-         * @param key Key of entry, to be tested for the condition.
+         * @param key Identifies an entry which condition will be applied to.
          */
-        ValueCondition(Key key) {
-            this.key = key;
+        ValueCondition(byte[] key) {
+            super(key);
         }
 
         /**
@@ -305,15 +243,69 @@ public final class Condition {
             return new Condition(this);
         }
 
-        /** {@inheritDoc} */
-        @Override public boolean test(Entry e) {
-            if ((e.key() == key) || (e.key() != null && e.key().equals(key))) {
-                int res = Arrays.compare(e.value(), val);
+        /**
+         * Defines possible condition types which can be applied to the value.
+         */
+        enum Type {
+            /** Equality condition type. */
+            EQUAL,
 
-                return type.test(res);
-            }
-            else
-               return false;
+            /** Inequality condition type. */
+            NOT_EQUAL
+        }
+    }
+
+    //TODO: Javadoc
+    /**
+     * Represents condition on entry value. Only one type of condition could be applied to
+     * the one instance of condition. Subsequent invocations of any method which produces condition will throw
+     * {@link IllegalStateException}.
+     */
+    public static final class ExistenceCondition extends AbstractCondition {
+        /**
+         * The type of condition.
+         *
+         * @see Type
+         */
+        private Type type;
+
+        /**
+         * Constructs a condition by a value for an entry identified by the given key.
+         *
+         * @param key Identifies an entry which condition will be applied to.
+         */
+        ExistenceCondition(byte[] key) {
+            super(key);
+        }
+
+        /**
+         * Produces the condition of type {@link Type#EXISTS}. This condition tests the given value on equality with
+         * target entry value.
+         *
+         * @return The condition of type {@link Type#EXISTS}.
+         * @throws IllegalStateException In case when the condition is already defined.
+         */
+        public Condition exists() {
+            validate(type);
+
+            this.type = Type.EXISTS;
+
+            return new Condition(this);
+        }
+
+        /**
+         * Produces the condition of type {@link Type#NOT_EXISTS}. This condition tests the given value on inequality
+         * with target entry value.
+         *
+         * @return The condition of type {@link Type#NOT_EXISTS}.
+         * @throws IllegalStateException In case when the condition is already defined.
+         */
+        public Condition notExists() {
+            validate(type);
+
+            this.type = Type.NOT_EXISTS;
+
+            return new Condition(this);
         }
 
         /**
@@ -321,29 +313,14 @@ public final class Condition {
          */
         enum Type {
             /** Equality condition type. */
-            EQUAL {
-                @Override public boolean test(long res) {
-                    return res == 0;
-                }
-            },
+            EXISTS,
 
             /** Inequality condition type. */
-            NOT_EQUAL {
-                @Override public boolean test(long res) {
-                    return res != 0;
-                }
-            };
-
-            /**
-             * Interprets comparison result.
-             *
-             * @param res The result of comparison.
-             * @return The interpretation of the comparison result.
-             */
-            public abstract boolean test(long res);
+            NOT_EXISTS
         }
     }
 
+
     /**
      * Checks that condition is not defined yet. If the condition is already defined then exception will be thrown.
      *
@@ -357,14 +334,24 @@ public final class Condition {
     /**
      * Defines condition interface.
      */
-    private interface InnerCondition {
+    public interface InnerCondition {
         /**
-         * Tests the given entry on satisfaction of the condition.
+         * Returns key which identifies an entry which condition will be applied to.
          *
-         * @param e Entry.
-         * @return The result of condition test. {@code true} - if the entry satisfies to the condition,
-         * otherwise - {@code false}.
+         * @return Key which identifies an entry which condition will be applied to.
          */
-        boolean test(Entry e);
+        byte[] key();
+    }
+
+    private static abstract class AbstractCondition implements InnerCondition {
+        private final byte[] key;
+
+        public AbstractCondition(byte[] key) {
+            this.key = key;
+        }
+
+        @Override public byte[] key() {
+            return key;
+        }
     }
 }
diff --git a/modules/metastorage-common/src/main/java/org/apache/ignite/metastorage/common/Conditions.java b/modules/metastorage-common/src/main/java/org/apache/ignite/metastorage/common/Conditions.java
index 87fa238..2f4d537 100644
--- a/modules/metastorage-common/src/main/java/org/apache/ignite/metastorage/common/Conditions.java
+++ b/modules/metastorage-common/src/main/java/org/apache/ignite/metastorage/common/Conditions.java
@@ -24,47 +24,34 @@ package org.apache.ignite.metastorage.common;
  * @see Condition
  */
 public final class Conditions {
-
-    /** Key. */
-    private Key key;
-
-    /**
-     * Creates new condition for entry with concrete key.
-     *
-     * @param key Key
-     */
-    private Conditions(Key key) {
-        this.key = key;
-    }
-
     /**
      * Creates condition on entry revision.
      *
+     * @param key Identifies an entry which condition will be applied to.
      * @return Condition on entry revision.
      * @see Condition.RevisionCondition
      */
-    public Condition.RevisionCondition revision() {
-        return new Condition.RevisionCondition(key);
+    public static Condition.RevisionCondition revision(Key key) {
+        return new Condition.RevisionCondition(key.bytes());
     }
 
     /**
      * Creates condition on entry value.
      *
+     * @param key Identifies an entry which condition will be applied to.
      * @return Condition on entry value.
      * @see Condition.ValueCondition
      */
-    public Condition.ValueCondition value() {
-        return new Condition.ValueCondition(key);
+    public static Condition.ValueCondition value(Key key) {
+        return new Condition.ValueCondition(key.bytes());
     }
 
-    /**
-     * Creates key-based condition.
-     *
-     * @param key Key of condition.
-     * @return Key-based condition instance.
-     */
-    public static Conditions key(Key key) {
-        return new Conditions(key);
+    public static Condition exists(Key key) {
+        return new Condition.ExistenceCondition(key.bytes()).exists();
+    }
+
+    public static Condition notExists(Key key) {
+        return new Condition.ExistenceCondition(key.bytes()).notExists();
     }
 
     /**
diff --git a/modules/metastorage-common/src/main/java/org/apache/ignite/metastorage/common/Operation.java b/modules/metastorage-common/src/main/java/org/apache/ignite/metastorage/common/Operation.java
index 9810fe5..fd5428a 100644
--- a/modules/metastorage-common/src/main/java/org/apache/ignite/metastorage/common/Operation.java
+++ b/modules/metastorage-common/src/main/java/org/apache/ignite/metastorage/common/Operation.java
@@ -17,6 +17,8 @@
 
 package org.apache.ignite.metastorage.common;
 
+import org.jetbrains.annotations.Nullable;
+
 /**
  * Defines operation for meta storage conditional update (invoke).
  */
@@ -37,37 +39,33 @@ public final class Operation {
     /**
      * Represents operation of type <i>remove</i>.
      */
-    public static final class RemoveOp implements InnerOp {
-        /** Key. */
-        private final Key key;
-
+    public static final class RemoveOp extends AbstractOp {
         /**
-         * Creates a new remove operation for the given {@code key}.
+         * Default no-op constructor.
          *
-         * @param key Key.
+         * @param key Identifies an entry which operation will be applied to.
          */
-        RemoveOp(Key key) {
-            this.key = key;
+        RemoveOp(byte[] key) {
+            super(key);
         }
     }
 
     /**
      * Represents operation of type <i>put</i>.
      */
-    public static final class PutOp implements InnerOp {
-        /** Key. */
-        private final Key key;
-
+    public static final class PutOp extends AbstractOp {
         /** Value. */
         private final byte[] val;
 
         /**
          * Constructs operation of type <i>put</i>.
          *
+         * @param key Identifies an entry which operation will be applied to.
          * @param val The value to which the entry should be updated.
          */
-        PutOp(Key key, byte[] val) {
-            this.key = key;
+        PutOp(byte[] key, byte[] val) {
+            super(key);
+
             this.val = val;
         }
     }
@@ -75,12 +73,12 @@ public final class Operation {
     /**
      * Represents operation of type <i>no-op</i>.
      */
-    public static final class NoOp implements InnerOp {
+    public static final class NoOp extends AbstractOp {
         /**
          * Default no-op constructor.
          */
         NoOp() {
-            // No-op.
+            super(null);
         }
     }
 
@@ -88,6 +86,19 @@ public final class Operation {
      * Defines operation interface.
      */
     private interface InnerOp {
-        // Marker interface.
+        @Nullable byte[] key();
+    }
+
+    private static class AbstractOp implements InnerOp {
+        @Nullable private final byte[] key;
+
+        public AbstractOp(@Nullable byte[] key) {
+            this.key = key;
+        }
+
+        @Nullable
+        @Override public byte[] key() {
+            return key;
+        }
     }
 }
diff --git a/modules/metastorage-common/src/main/java/org/apache/ignite/metastorage/common/Operations.java b/modules/metastorage-common/src/main/java/org/apache/ignite/metastorage/common/Operations.java
index 31c7449..e51fa9e 100644
--- a/modules/metastorage-common/src/main/java/org/apache/ignite/metastorage/common/Operations.java
+++ b/modules/metastorage-common/src/main/java/org/apache/ignite/metastorage/common/Operations.java
@@ -30,20 +30,22 @@ public final class Operations {
     /**
      * Creates operation of type <i>remove</i>. This type of operation removes entry.
      *
+     * @param key Identifies an entry which operation will be applied to.
      * @return Operation of type <i>remove</i>.
      */
     public static Operation remove(Key key) {
-        return new Operation(new Operation.RemoveOp(key));
+        return new Operation(new Operation.RemoveOp(key.bytes()));
     }
 
     /**
      * Creates operation of type <i>put</i>. This type of operation inserts or updates value of entry.
      *
+     * @param key Identifies an entry which operation will be applied to.
      * @param value Value.
      * @return Operation of type <i>put</i>.
      */
     public static Operation put(Key key, byte[] value) {
-        return new Operation(new Operation.PutOp(key, value));
+        return new Operation(new Operation.PutOp(key.bytes(), value));
     }
 
     /**
diff --git a/modules/metastorage-server/pom.xml b/modules/metastorage-server/pom.xml
index d73d080..5f0f453 100644
--- a/modules/metastorage-server/pom.xml
+++ b/modules/metastorage-server/pom.xml
@@ -29,20 +29,18 @@
         <relativePath>../../parent/pom.xml</relativePath>
     </parent>
 
-    <artifactId>metastorage-server</artifactId>
+    <artifactId>ignite-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.apache.ignite</groupId>
-            <artifactId>metastorage-common</artifactId>
-            <version>${project.version}</version>
+            <artifactId>ignite-metastorage-common</artifactId>
         </dependency>
 
         <dependency>
diff --git a/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/AbstractCondition.java b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/AbstractCondition.java
new file mode 100644
index 0000000..55ec9e6
--- /dev/null
+++ b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/AbstractCondition.java
@@ -0,0 +1,13 @@
+package org.apache.ignite.internal.metastorage.server;
+
+public abstract class AbstractCondition implements Condition {
+    private final byte[] key;
+
+    public AbstractCondition(byte[] key) {
+        this.key = key;
+    }
+
+    @Override public byte[] key() {
+        return key;
+    }
+}
diff --git a/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/Condition.java b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/Condition.java
new file mode 100644
index 0000000..ea4fcc7
--- /dev/null
+++ b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/Condition.java
@@ -0,0 +1,7 @@
+package org.apache.ignite.internal.metastorage.server;
+
+public interface Condition {
+    byte[] key();
+
+    boolean test(Entry e);
+}
diff --git a/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/ExistenceCondition.java b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/ExistenceCondition.java
new file mode 100644
index 0000000..13cd96c
--- /dev/null
+++ b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/ExistenceCondition.java
@@ -0,0 +1,44 @@
+package org.apache.ignite.internal.metastorage.server;
+
+public class ExistenceCondition extends AbstractCondition {
+    private final Type type;
+
+    public ExistenceCondition(Type type, byte[] key) {
+        super(key);
+
+        this.type = type;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean test(Entry e) {
+        boolean res = !(e.empty() || e.tombstone());
+
+        return type.test(res);
+    }
+
+
+    public enum Type {
+        /** Equality condition type. */
+        EXISTS {
+            @Override public boolean test(boolean res) {
+                return res;
+            }
+        },
+
+        /** Inequality condition type. */
+        NOT_EXISTS {
+            @Override public boolean test(boolean res) {
+                return !res;
+            }
+        };
+
+        /**
+         * Interprets comparison result.
+         *
+         * @param res The result of comparison.
+         * @return The interpretation of the comparison result.
+         */
+        public abstract boolean test(boolean res);
+    }
+
+}
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 5d6da44..5659d3e 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
@@ -59,6 +59,8 @@ public interface KeyValueStorage {
     @NotNull
     Collection<Entry> getAndRemoveAll(List<byte[]> keys);
 
+    boolean invoke(Condition condition, Collection<Operation> success, Collection<Operation> failure);
+
     Cursor<Entry> range(byte[] keyFrom, byte[] keyTo);
 
     Cursor<Entry> range(byte[] keyFrom, byte[] keyTo, long revUpperBound);
diff --git a/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/Operation.java b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/Operation.java
new file mode 100644
index 0000000..2d1fbfd
--- /dev/null
+++ b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/Operation.java
@@ -0,0 +1,31 @@
+package org.apache.ignite.internal.metastorage.server;
+
+public class Operation {
+    private final byte[] key;
+    private final byte[] val;
+    private final Type type;
+
+    public Operation(Type type, byte[] key, byte[] val) {
+        this.key = key;
+        this.val = val;
+        this.type = type;
+    }
+
+    byte[] key() {
+        return key;
+    }
+
+    byte[] value() {
+        return val;
+    }
+
+    Type type() {
+        return type;
+    }
+
+    enum Type {
+        PUT,
+        REMOVE,
+        NO_OP
+    }
+}
diff --git a/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/RevisionCondition.java b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/RevisionCondition.java
new file mode 100644
index 0000000..2f3727b
--- /dev/null
+++ b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/RevisionCondition.java
@@ -0,0 +1,76 @@
+package org.apache.ignite.internal.metastorage.server;
+
+public class RevisionCondition extends AbstractCondition {
+    private final Type type;
+
+    private final long rev;
+
+    public RevisionCondition(Type type, byte[] key, long rev) {
+        super(key);
+
+        this.type = type;
+        this.rev = rev;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean test(Entry e) {
+        int res = Long.compare(e.revision(), rev);
+
+        return type.test(res);
+    }
+
+    /**
+     * Defines possible condition types which can be applied to the revision.
+     */
+    public enum Type {
+        /** Equality condition type. */
+        EQUAL {
+            @Override public boolean test(long res) {
+                return res == 0;
+            }
+        },
+
+        /** Inequality condition type. */
+        NOT_EQUAL {
+            @Override public boolean test(long res) {
+                return res != 0;
+            }
+        },
+
+        /** Greater than condition type. */
+        GREATER {
+            @Override public boolean test(long res) {
+                return res > 0;
+            }
+        },
+
+        /** Less than condition type. */
+        LESS {
+            @Override public boolean test(long res) {
+                return res < 0;
+            }
+        },
+
+        /** Less than or equal to condition type. */
+        LESS_OR_EQUAL {
+            @Override public boolean test(long res) {
+                return res <= 0;
+            }
+        },
+
+        /** Greater than or equal to condition type. */
+        GREATER_OR_EQUAL {
+            @Override public boolean test(long res) {
+                return res >= 0;
+            }
+        };
+
+        /**
+         * Interprets comparison result.
+         *
+         * @param res The result of comparison.
+         * @return The interpretation of the comparison result.
+         */
+        public abstract boolean test(long res);
+    }
+}
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 b37c96a..3033f2c 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
@@ -29,6 +29,7 @@ import java.util.NoSuchElementException;
 import java.util.TreeMap;
 import java.util.TreeSet;
 import java.util.function.Predicate;
+
 import org.apache.ignite.metastorage.common.Cursor;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.TestOnly;
@@ -63,14 +64,22 @@ public class SimpleInMemoryKeyValueStorage implements KeyValueStorage {
 
     @Override public void put(byte[] key, byte[] value) {
         synchronized (mux) {
-            doPut(key, value);
+            long curRev = rev + 1;
+
+            doPut(key, value, curRev);
+
+            rev = curRev;
         }
     }
 
     @NotNull
     @Override public Entry getAndPut(byte[] key, byte[] bytes) {
         synchronized (mux) {
-            long lastRev = doPut(key, bytes);
+            long curRev = rev + 1;
+
+            long lastRev = doPut(key, bytes, curRev);
+
+            rev = curRev;
 
             // Return previous value.
             return doGetValue(key, lastRev);
@@ -129,15 +138,24 @@ public class SimpleInMemoryKeyValueStorage implements KeyValueStorage {
     @Override
     public void remove(byte[] key) {
         synchronized (mux) {
-            Entry e = doGet(key, LATEST_REV, false);
-
-            if (e.empty() || e.tombstone())
-                return;
+            long curRev = rev + 1;
 
-            doPut(key, TOMBSTONE);
+            if (doRemove(key, curRev))
+                rev = curRev;
         }
     }
 
+    private boolean doRemove(byte[] key, long curRev) {
+        Entry e = doGet(key, LATEST_REV, false);
+
+        if (e.empty() || e.tombstone())
+            return false;
+
+        doPut(key, TOMBSTONE, curRev);
+
+        return true;
+    }
+
     @NotNull
     @Override public Entry getAndRemove(byte[] key) {
         synchronized (mux) {
@@ -204,6 +222,47 @@ public class SimpleInMemoryKeyValueStorage implements KeyValueStorage {
         return res;
     }
 
+    @Override public boolean invoke(Condition condition, Collection<Operation> success, Collection<Operation> failure) {
+        synchronized (mux) {
+            Entry e = get(condition.key());
+
+            boolean branch = condition.test(e);
+
+            Collection<Operation> ops = branch ? success : failure;
+
+            long curRev = rev + 1;
+
+            boolean modified = false;
+
+            for (Operation op : ops) {
+                switch (op.type()) {
+                    case PUT:
+                        doPut(op.key(), op.value(), curRev);
+
+                        modified = true;
+
+                        break;
+
+                    case REMOVE:
+                        modified |= doRemove(op.key(), curRev);
+
+                        break;
+
+                    case NO_OP:
+                        break;
+
+                    default:
+                        throw new IllegalArgumentException("Unknown operation type: " + op.type());
+                }
+            }
+
+            if (modified)
+                rev = curRev;
+
+            return branch;
+        }
+    }
+
     @Override public Cursor<Entry> range(byte[] keyFrom, byte[] keyTo) {
         return new RangeCursor(keyFrom, keyTo, rev);
     }
@@ -365,9 +424,7 @@ public class SimpleInMemoryKeyValueStorage implements KeyValueStorage {
         return new Entry(key, lastVal.bytes() , lastRev, lastVal.updateCounter());
     }
 
-    private long doPut(byte[] key, byte[] bytes) {
-        long curRev = ++rev;
-
+    private long doPut(byte[] key, byte[] bytes, long curRev) {
         long curUpdCntr = ++updCntr;
 
         // Update keysIdx.
@@ -378,13 +435,25 @@ public class SimpleInMemoryKeyValueStorage implements KeyValueStorage {
         revs.add(curRev);
 
         // Update revsIdx.
-        NavigableMap<byte[], Value> entries = new TreeMap<>(CMP);
+        //NavigableMap<byte[], Value> entries = new TreeMap<>(CMP);
 
         Value val = new Value(bytes, curUpdCntr);
 
-        entries.put(key, val);
+        //entries.put(key, val);
 
-        revsIdx.put(curRev, entries);
+        //revsIdx.put(curRev, entries);
+
+        revsIdx.compute(
+                curRev,
+                (rev, entries) -> {
+                    if (entries == null)
+                        entries = new TreeMap<>(CMP);
+
+                    entries.put(key, val);
+
+                    return entries;
+                }
+        );
 
         return lastRev;
     }
diff --git a/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/ValueCondition.java b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/ValueCondition.java
new file mode 100644
index 0000000..dfd9f8e
--- /dev/null
+++ b/modules/metastorage-server/src/main/java/org/apache/ignite/internal/metastorage/server/ValueCondition.java
@@ -0,0 +1,49 @@
+package org.apache.ignite.internal.metastorage.server;
+
+import java.util.Arrays;
+
+public class ValueCondition extends AbstractCondition {
+    private final Type type;
+
+    private final byte[] val;
+
+    public ValueCondition(Type type, byte[] key, byte[] val) {
+        super(key);
+        this.type = type;
+        this.val = val;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean test(Entry e) {
+        int res = Arrays.compare(e.value(), val);
+
+        return type.test(res);
+    }
+
+    /**
+     * Defines possible condition types which can be applied to the value.
+     */
+    enum Type {
+        /** Equality condition type. */
+        EQUAL {
+            @Override public boolean test(long res) {
+                return res == 0;
+            }
+        },
+
+        /** Inequality condition type. */
+        NOT_EQUAL {
+            @Override public boolean test(long res) {
+                return res != 0;
+            }
+        };
+
+        /**
+         * Interprets comparison result.
+         *
+         * @param res The result of comparison.
+         * @return The interpretation of the comparison result.
+         */
+        public abstract boolean test(long res);
+    }
+}
diff --git a/modules/metastorage-server/src/test/java/org/apache/ignite/internal/metastorage/server/ExistenceConditionTest.java b/modules/metastorage-server/src/test/java/org/apache/ignite/internal/metastorage/server/ExistenceConditionTest.java
new file mode 100644
index 0000000..679b3ad
--- /dev/null
+++ b/modules/metastorage-server/src/test/java/org/apache/ignite/internal/metastorage/server/ExistenceConditionTest.java
@@ -0,0 +1,37 @@
+package org.apache.ignite.internal.metastorage.server;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class ExistenceConditionTest {
+    private static final byte[] KEY = new byte[] {1};
+
+    private static final byte[] VAL = new byte[] {11};
+
+    private static final  Entry ENTRY = new Entry(KEY, VAL, 1, 1);
+
+    private static final Entry EMPTY = Entry.empty(KEY);
+
+    private static final Entry TOMBSTONE = Entry.tombstone(KEY, 1, 1);
+
+    @Test
+    public void exists() {
+        ExistenceCondition cond = new ExistenceCondition(ExistenceCondition.Type.EXISTS, KEY);
+
+        assertTrue(cond.test(ENTRY));
+        assertFalse(cond.test(EMPTY));
+        assertFalse(cond.test(TOMBSTONE));
+    }
+
+    @Test
+    public void notExists() {
+        ExistenceCondition cond = new ExistenceCondition(ExistenceCondition.Type.NOT_EXISTS, KEY);
+
+        assertFalse(cond.test(ENTRY));
+        assertTrue(cond.test(EMPTY));
+        assertTrue(cond.test(TOMBSTONE));
+    }
+
+}
diff --git a/modules/metastorage-server/src/test/java/org/apache/ignite/internal/metastorage/server/RevisionConditionTest.java b/modules/metastorage-server/src/test/java/org/apache/ignite/internal/metastorage/server/RevisionConditionTest.java
new file mode 100644
index 0000000..3a08c13
--- /dev/null
+++ b/modules/metastorage-server/src/test/java/org/apache/ignite/internal/metastorage/server/RevisionConditionTest.java
@@ -0,0 +1,65 @@
+package org.apache.ignite.internal.metastorage.server;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class RevisionConditionTest {
+    private static final byte[] key = new byte[] {1};
+
+    private static final byte[] val = new byte[] {2};
+
+    @Test
+    public void eq() {
+        RevisionCondition cond = new RevisionCondition(RevisionCondition.Type.EQUAL, key, 1);
+
+        // 1 == 1.
+        assertTrue(cond.test(new Entry(key, val, 1, 1)));
+    }
+
+    @Test
+    public void ne() {
+        RevisionCondition cond = new RevisionCondition(RevisionCondition.Type.NOT_EQUAL, key, 1);
+
+        // 2 != 1.
+        assertTrue(cond.test(new Entry(key, val, 2, 1)));
+    }
+
+    @Test
+    public void gt() {
+        RevisionCondition cond = new RevisionCondition(RevisionCondition.Type.GREATER, key, 1);
+
+        // 2 > 1.
+        assertTrue(cond.test(new Entry(key, val, 2, 1)));
+    }
+
+    @Test
+    public void ge() {
+        RevisionCondition cond = new RevisionCondition(RevisionCondition.Type.GREATER_OR_EQUAL, key, 1);
+
+        // 2 >= 1 (2 > 1).
+        assertTrue(cond.test(new Entry(key, val, 2, 1)));
+
+        // 1 >= 1 (1 == 1).
+        assertTrue(cond.test(new Entry(key, val, 1, 1)));
+    }
+
+    @Test
+    public void lt() {
+        RevisionCondition cond = new RevisionCondition(RevisionCondition.Type.LESS, key, 2);
+
+        // 1 < 2
+        assertTrue(cond.test(new Entry(key, val, 1, 1)));
+    }
+
+    @Test
+    public void le() {
+        RevisionCondition cond = new RevisionCondition(RevisionCondition.Type.LESS_OR_EQUAL, key, 2);
+
+        // 1 <= 2 (1 < 2)
+        assertTrue(cond.test(new Entry(key, val, 1, 1)));
+
+        // 1 <= 1 (1 == 1).
+        assertTrue(cond.test(new Entry(key, val, 1, 1)));
+    }
+}
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 27df790..4829bc4 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
@@ -939,6 +939,553 @@ class SimpleInMemoryKeyValueStorageTest {
     }
 
     @Test
+    public void invokeWithRevisionCondition_successBranch() {
+        byte[] key1 = k(1);
+        byte[] val1_1 = kv(1, 11);
+        byte[] val1_2 = kv(1, 12);
+
+        byte[] key2 = k(2);
+        byte[] val2 = kv(2, 2);
+
+        byte[] key3 = k(3);
+        byte[] val3 = kv(3, 3);
+
+        assertEquals(0, storage.revision());
+        assertEquals(0, storage.updateCounter());
+
+        storage.put(key1, val1_1);
+
+        assertEquals(1, storage.revision());
+        assertEquals(1, storage.updateCounter());
+
+        boolean branch = storage.invoke(
+                new RevisionCondition(RevisionCondition.Type.EQUAL, key1, 1),
+                List.of(
+                        new Operation(Operation.Type.PUT, key1, val1_2),
+                        new Operation(Operation.Type.PUT, key2, val2)
+                ),
+                List.of(new Operation(Operation.Type.PUT, key3, val3))
+        );
+
+        // "Success" branch is applied.
+        assertTrue(branch);
+        assertEquals(2, storage.revision());
+        assertEquals(3, storage.updateCounter());
+
+        Entry e1 = storage.get(key1);
+
+        assertFalse(e1.empty());
+        assertFalse(e1.tombstone());
+        assertEquals(2, e1.revision());
+        assertEquals(2, e1.updateCounter());
+        assertArrayEquals(val1_2, e1.value());
+
+        Entry e2 = storage.get(key2);
+
+        assertFalse(e2.empty());
+        assertFalse(e2.tombstone());
+        assertEquals(2, e2.revision());
+        assertEquals(3, e2.updateCounter());
+        assertArrayEquals(val2, e2.value());
+
+        // "Failure" branch isn't applied.
+        Entry e3 = storage.get(key3);
+
+        assertTrue(e3.empty());
+    }
+
+    @Test
+    public void invokeWithRevisionCondition_failureBranch() {
+        byte[] key1 = k(1);
+        byte[] val1_1 = kv(1, 11);
+        byte[] val1_2 = kv(1, 12);
+
+        byte[] key2 = k(2);
+        byte[] val2 = kv(2, 2);
+
+        byte[] key3 = k(3);
+        byte[] val3 = kv(3, 3);
+
+        assertEquals(0, storage.revision());
+        assertEquals(0, storage.updateCounter());
+
+        storage.put(key1, val1_1);
+
+        assertEquals(1, storage.revision());
+        assertEquals(1, storage.updateCounter());
+
+        boolean branch = storage.invoke(
+                new RevisionCondition(RevisionCondition.Type.EQUAL, key1, 2),
+                List.of(new Operation(Operation.Type.PUT, key3, val3)),
+                List.of(
+                        new Operation(Operation.Type.PUT, key1, val1_2),
+                        new Operation(Operation.Type.PUT, key2, val2)
+                )
+        );
+
+        // "Failure" branch is applied.
+        assertFalse(branch);
+        assertEquals(2, storage.revision());
+        assertEquals(3, storage.updateCounter());
+
+        Entry e1 = storage.get(key1);
+
+        assertFalse(e1.empty());
+        assertFalse(e1.tombstone());
+        assertEquals(2, e1.revision());
+        assertEquals(2, e1.updateCounter());
+        assertArrayEquals(val1_2, e1.value());
+
+        Entry e2 = storage.get(key2);
+
+        assertFalse(e2.empty());
+        assertFalse(e2.tombstone());
+        assertEquals(2, e2.revision());
+        assertEquals(3, e2.updateCounter());
+        assertArrayEquals(val2, e2.value());
+
+        // "Success" branch isn't applied.
+        Entry e3 = storage.get(key3);
+
+        assertTrue(e3.empty());
+    }
+
+    @Test
+    public void invokeWithExistsCondition_successBranch() {
+        byte[] key1 = k(1);
+        byte[] val1_1 = kv(1, 11);
+        byte[] val1_2 = kv(1, 12);
+
+        byte[] key2 = k(2);
+        byte[] val2 = kv(2, 2);
+
+        byte[] key3 = k(3);
+        byte[] val3 = kv(3, 3);
+
+        assertEquals(0, storage.revision());
+        assertEquals(0, storage.updateCounter());
+
+        storage.put(key1, val1_1);
+
+        assertEquals(1, storage.revision());
+        assertEquals(1, storage.updateCounter());
+
+        boolean branch = storage.invoke(
+                new ExistenceCondition(ExistenceCondition.Type.EXISTS, key1),
+                List.of(
+                        new Operation(Operation.Type.PUT, key1, val1_2),
+                        new Operation(Operation.Type.PUT, key2, val2)
+                ),
+                List.of(new Operation(Operation.Type.PUT, key3, val3))
+        );
+
+        // "Success" branch is applied.
+        assertTrue(branch);
+        assertEquals(2, storage.revision());
+        assertEquals(3, storage.updateCounter());
+
+        Entry e1 = storage.get(key1);
+
+        assertFalse(e1.empty());
+        assertFalse(e1.tombstone());
+        assertEquals(2, e1.revision());
+        assertEquals(2, e1.updateCounter());
+        assertArrayEquals(val1_2, e1.value());
+
+        Entry e2 = storage.get(key2);
+
+        assertFalse(e2.empty());
+        assertFalse(e2.tombstone());
+        assertEquals(2, e2.revision());
+        assertEquals(3, e2.updateCounter());
+        assertArrayEquals(val2, e2.value());
+
+        // "Failure" branch isn't applied.
+        Entry e3 = storage.get(key3);
+
+        assertTrue(e3.empty());
+    }
+
+    @Test
+    public void invokeWithExistsCondition_failureBranch() {
+        byte[] key1 = k(1);
+        byte[] val1_1 = kv(1, 11);
+        byte[] val1_2 = kv(1, 12);
+
+        byte[] key2 = k(2);
+        byte[] val2 = kv(2, 2);
+
+        byte[] key3 = k(3);
+        byte[] val3 = kv(3, 3);
+
+        assertEquals(0, storage.revision());
+        assertEquals(0, storage.updateCounter());
+
+        storage.put(key1, val1_1);
+
+        assertEquals(1, storage.revision());
+        assertEquals(1, storage.updateCounter());
+
+        boolean branch = storage.invoke(
+                new ExistenceCondition(ExistenceCondition.Type.EXISTS, key3),
+                List.of(new Operation(Operation.Type.PUT, key3, val3)),
+                List.of(
+                        new Operation(Operation.Type.PUT, key1, val1_2),
+                        new Operation(Operation.Type.PUT, key2, val2)
+                )
+        );
+
+        // "Failure" branch is applied.
+        assertFalse(branch);
+        assertEquals(2, storage.revision());
+        assertEquals(3, storage.updateCounter());
+
+        Entry e1 = storage.get(key1);
+
+        assertFalse(e1.empty());
+        assertFalse(e1.tombstone());
+        assertEquals(2, e1.revision());
+        assertEquals(2, e1.updateCounter());
+        assertArrayEquals(val1_2, e1.value());
+
+        Entry e2 = storage.get(key2);
+
+        assertFalse(e2.empty());
+        assertFalse(e2.tombstone());
+        assertEquals(2, e2.revision());
+        assertEquals(3, e2.updateCounter());
+        assertArrayEquals(val2, e2.value());
+
+        // "Success" branch isn't applied.
+        Entry e3 = storage.get(key3);
+
+        assertTrue(e3.empty());
+    }
+
+    @Test
+    public void invokeWithNotExistsCondition_successBranch() {
+        byte[] key1 = k(1);
+        byte[] val1_1 = kv(1, 11);
+        byte[] val1_2 = kv(1, 12);
+
+        byte[] key2 = k(2);
+        byte[] val2 = kv(2, 2);
+
+        byte[] key3 = k(3);
+        byte[] val3 = kv(3, 3);
+
+        assertEquals(0, storage.revision());
+        assertEquals(0, storage.updateCounter());
+
+        storage.put(key1, val1_1);
+
+        assertEquals(1, storage.revision());
+        assertEquals(1, storage.updateCounter());
+
+        boolean branch = storage.invoke(
+                new ExistenceCondition(ExistenceCondition.Type.NOT_EXISTS, key2),
+                List.of(
+                        new Operation(Operation.Type.PUT, key1, val1_2),
+                        new Operation(Operation.Type.PUT, key2, val2)
+                ),
+                List.of(new Operation(Operation.Type.PUT, key3, val3))
+        );
+
+        // "Success" branch is applied.
+        assertTrue(branch);
+        assertEquals(2, storage.revision());
+        assertEquals(3, storage.updateCounter());
+
+        Entry e1 = storage.get(key1);
+
+        assertFalse(e1.empty());
+        assertFalse(e1.tombstone());
+        assertEquals(2, e1.revision());
+        assertEquals(2, e1.updateCounter());
+        assertArrayEquals(val1_2, e1.value());
+
+        Entry e2 = storage.get(key2);
+
+        assertFalse(e2.empty());
+        assertFalse(e2.tombstone());
+        assertEquals(2, e2.revision());
+        assertEquals(3, e2.updateCounter());
+        assertArrayEquals(val2, e2.value());
+
+        // "Failure" branch isn't applied.
+        Entry e3 = storage.get(key3);
+
+        assertTrue(e3.empty());
+    }
+
+    @Test
+    public void invokeWithNotExistsCondition_failureBranch() {
+        byte[] key1 = k(1);
+        byte[] val1_1 = kv(1, 11);
+        byte[] val1_2 = kv(1, 12);
+
+        byte[] key2 = k(2);
+        byte[] val2 = kv(2, 2);
+
+        byte[] key3 = k(3);
+        byte[] val3 = kv(3, 3);
+
+        assertEquals(0, storage.revision());
+        assertEquals(0, storage.updateCounter());
+
+        storage.put(key1, val1_1);
+
+        assertEquals(1, storage.revision());
+        assertEquals(1, storage.updateCounter());
+
+        boolean branch = storage.invoke(
+                new ExistenceCondition(ExistenceCondition.Type.NOT_EXISTS, key1),
+                List.of(new Operation(Operation.Type.PUT, key3, val3)),
+                List.of(
+                        new Operation(Operation.Type.PUT, key1, val1_2),
+                        new Operation(Operation.Type.PUT, key2, val2)
+                )
+        );
+
+        // "Failure" branch is applied.
+        assertFalse(branch);
+        assertEquals(2, storage.revision());
+        assertEquals(3, storage.updateCounter());
+
+        Entry e1 = storage.get(key1);
+
+        assertFalse(e1.empty());
+        assertFalse(e1.tombstone());
+        assertEquals(2, e1.revision());
+        assertEquals(2, e1.updateCounter());
+        assertArrayEquals(val1_2, e1.value());
+
+        Entry e2 = storage.get(key2);
+
+        assertFalse(e2.empty());
+        assertFalse(e2.tombstone());
+        assertEquals(2, e2.revision());
+        assertEquals(3, e2.updateCounter());
+        assertArrayEquals(val2, e2.value());
+
+        // "Success" branch isn't applied.
+        Entry e3 = storage.get(key3);
+
+        assertTrue(e3.empty());
+    }
+
+    @Test
+    public void invokeWithValueCondition_successBranch() {
+        byte[] key1 = k(1);
+        byte[] val1_1 = kv(1, 11);
+        byte[] val1_2 = kv(1, 12);
+
+        byte[] key2 = k(2);
+        byte[] val2 = kv(2, 2);
+
+        byte[] key3 = k(3);
+        byte[] val3 = kv(3, 3);
+
+        assertEquals(0, storage.revision());
+        assertEquals(0, storage.updateCounter());
+
+        storage.put(key1, val1_1);
+
+        assertEquals(1, storage.revision());
+        assertEquals(1, storage.updateCounter());
+
+        boolean branch = storage.invoke(
+                new ValueCondition(ValueCondition.Type.EQUAL, key1, val1_1),
+                List.of(
+                        new Operation(Operation.Type.PUT, key1, val1_2),
+                        new Operation(Operation.Type.PUT, key2, val2)
+                ),
+                List.of(new Operation(Operation.Type.PUT, key3, val3))
+        );
+
+        // "Success" branch is applied.
+        assertTrue(branch);
+        assertEquals(2, storage.revision());
+        assertEquals(3, storage.updateCounter());
+
+        Entry e1 = storage.get(key1);
+
+        assertFalse(e1.empty());
+        assertFalse(e1.tombstone());
+        assertEquals(2, e1.revision());
+        assertEquals(2, e1.updateCounter());
+        assertArrayEquals(val1_2, e1.value());
+
+        Entry e2 = storage.get(key2);
+
+        assertFalse(e2.empty());
+        assertFalse(e2.tombstone());
+        assertEquals(2, e2.revision());
+        assertEquals(3, e2.updateCounter());
+        assertArrayEquals(val2, e2.value());
+
+        // "Failure" branch isn't applied.
+        Entry e3 = storage.get(key3);
+
+        assertTrue(e3.empty());
+    }
+
+    @Test
+    public void invokeWithValueCondition_failureBranch() {
+        byte[] key1 = k(1);
+        byte[] val1_1 = kv(1, 11);
+        byte[] val1_2 = kv(1, 12);
+
+        byte[] key2 = k(2);
+        byte[] val2 = kv(2, 2);
+
+        byte[] key3 = k(3);
+        byte[] val3 = kv(3, 3);
+
+        assertEquals(0, storage.revision());
+        assertEquals(0, storage.updateCounter());
+
+        storage.put(key1, val1_1);
+
+        assertEquals(1, storage.revision());
+        assertEquals(1, storage.updateCounter());
+
+        boolean branch = storage.invoke(
+                new ValueCondition(ValueCondition.Type.EQUAL, key1, val1_2),
+                List.of(new Operation(Operation.Type.PUT, key3, val3)),
+                List.of(
+                        new Operation(Operation.Type.PUT, key1, val1_2),
+                        new Operation(Operation.Type.PUT, key2, val2)
+                )
+        );
+
+        // "Failure" branch is applied.
+        assertFalse(branch);
+        assertEquals(2, storage.revision());
+        assertEquals(3, storage.updateCounter());
+
+        Entry e1 = storage.get(key1);
+
+        assertFalse(e1.empty());
+        assertFalse(e1.tombstone());
+        assertEquals(2, e1.revision());
+        assertEquals(2, e1.updateCounter());
+        assertArrayEquals(val1_2, e1.value());
+
+        Entry e2 = storage.get(key2);
+
+        assertFalse(e2.empty());
+        assertFalse(e2.tombstone());
+        assertEquals(2, e2.revision());
+        assertEquals(3, e2.updateCounter());
+        assertArrayEquals(val2, e2.value());
+
+        // "Success" branch isn't applied.
+        Entry e3 = storage.get(key3);
+
+        assertTrue(e3.empty());
+    }
+
+    @Test
+    public void invokeOperations() {
+        byte[] key1 = k(1);
+        byte[] val1 = kv(1, 1);
+
+        byte[] key2 = k(2);
+        byte[] val2 = kv(2, 2);
+
+        byte[] key3 = k(3);
+        byte[] val3 = kv(3, 3);
+
+        assertEquals(0, storage.revision());
+        assertEquals(0, storage.updateCounter());
+
+        storage.put(key1, val1);
+
+        assertEquals(1, storage.revision());
+        assertEquals(1, storage.updateCounter());
+
+        // No-op.
+        boolean branch = storage.invoke(
+                new ValueCondition(ValueCondition.Type.EQUAL, key1, val1),
+                List.of(new Operation(Operation.Type.NO_OP, null, null)),
+                List.of(new Operation(Operation.Type.NO_OP, null, null))
+        );
+
+        assertTrue(branch);
+
+        // No updates.
+        assertEquals(1, storage.revision());
+        assertEquals(1, storage.updateCounter());
+
+        // Put.
+        branch = storage.invoke(
+                new ValueCondition(ValueCondition.Type.EQUAL, key1, val1),
+                List.of(
+                        new Operation(Operation.Type.PUT, key2, val2),
+                        new Operation(Operation.Type.PUT, key3, val3)
+                ),
+                List.of(new Operation(Operation.Type.NO_OP, null, null))
+        );
+
+        assertTrue(branch);
+
+        // +1 for revision, +2 for update counter.
+        assertEquals(2, storage.revision());
+        assertEquals(3, storage.updateCounter());
+
+        Entry e2 = storage.get(key2);
+
+        assertFalse(e2.empty());
+        assertFalse(e2.tombstone());
+        assertEquals(2, e2.revision());
+        assertEquals(2, e2.updateCounter());
+        assertArrayEquals(key2, e2.key());
+        assertArrayEquals(val2, e2.value());
+
+        Entry e3 = storage.get(key3);
+
+        assertFalse(e3.empty());
+        assertFalse(e3.tombstone());
+        assertEquals(2, e3.revision());
+        assertEquals(3, e3.updateCounter());
+        assertArrayEquals(key3, e3.key());
+        assertArrayEquals(val3, e3.value());
+
+        // Remove.
+        branch = storage.invoke(
+                new ValueCondition(ValueCondition.Type.EQUAL, key1, val1),
+                List.of(
+                        new Operation(Operation.Type.REMOVE, key2, null),
+                        new Operation(Operation.Type.REMOVE, key3, null)
+                ),
+                List.of(new Operation(Operation.Type.NO_OP, null, null))
+        );
+
+        assertTrue(branch);
+
+        // +1 for revision, +2 for update counter.
+        assertEquals(3, storage.revision());
+        assertEquals(5, storage.updateCounter());
+
+        e2 = storage.get(key2);
+
+        assertFalse(e2.empty());
+        assertTrue(e2.tombstone());
+        assertEquals(3, e2.revision());
+        assertEquals(4, e2.updateCounter());
+        assertArrayEquals(key2, e2.key());
+
+        e3 = storage.get(key3);
+
+        assertFalse(e3.empty());
+        assertTrue(e3.tombstone());
+        assertEquals(3, e3.revision());
+        assertEquals(5, e3.updateCounter());
+        assertArrayEquals(key3, e3.key());
+    }
+
+    @Test
     public void compact() {
         assertEquals(0, storage.revision());
         assertEquals(0, storage.updateCounter());
diff --git a/modules/metastorage-server/src/test/java/org/apache/ignite/internal/metastorage/server/ValueConditionTest.java b/modules/metastorage-server/src/test/java/org/apache/ignite/internal/metastorage/server/ValueConditionTest.java
new file mode 100644
index 0000000..717da54
--- /dev/null
+++ b/modules/metastorage-server/src/test/java/org/apache/ignite/internal/metastorage/server/ValueConditionTest.java
@@ -0,0 +1,27 @@
+package org.apache.ignite.internal.metastorage.server;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class ValueConditionTest {
+    private static final byte[] key = new byte[] {1};
+
+    private static final byte[] val1 = new byte[] {11};
+
+    private static final byte[] val2 = new byte[] {22};
+
+    @Test
+    public void eq() {
+        ValueCondition cond = new ValueCondition(ValueCondition.Type.EQUAL, key, val1);
+
+        assertTrue(cond.test(new Entry(key, val1, 1, 1)));
+    }
+
+    @Test
+    public void ne() {
+        ValueCondition cond = new ValueCondition(ValueCondition.Type.NOT_EQUAL, key, val1);
+
+        assertTrue(cond.test(new Entry(key, val2, 1, 1)));
+    }
+}
diff --git a/modules/metastorage/src/main/java/org/apache/ignite/internal/metastorage/MetaStorageManager.java b/modules/metastorage/src/main/java/org/apache/ignite/internal/metastorage/MetaStorageManager.java
index 669b937..fca2125 100644
--- a/modules/metastorage/src/main/java/org/apache/ignite/internal/metastorage/MetaStorageManager.java
+++ b/modules/metastorage/src/main/java/org/apache/ignite/internal/metastorage/MetaStorageManager.java
@@ -18,7 +18,6 @@
 package org.apache.ignite.internal.metastorage;
 
 import java.util.Collection;
-import java.util.Collections;
 import java.util.Map;
 import java.util.Optional;
 import java.util.concurrent.CompletableFuture;
@@ -308,37 +307,36 @@ import org.jetbrains.annotations.Nullable;
     /**
      * Invoke with single success/failure operation.
      *
-     * @see MetaStorageService#invoke(Condition, Collection, Collection)
+     * @see MetaStorageService#invoke(Condition, Operation, Operation)
      */
     public @NotNull CompletableFuture<Boolean> invoke(
         @NotNull Condition cond,
         @NotNull Operation success,
         @NotNull Operation failure
     ) {
-        return metaStorageSvc.invoke(cond, Collections.singletonList(success), Collections.singletonList(failure));
+        return metaStorageSvc.invoke(cond, success, failure);
     }
 
     /**
      * @see MetaStorageService#invoke(Condition, Collection, Collection)
      */
     public @NotNull CompletableFuture<Boolean> invoke(
-        @NotNull Condition cond,
-        @NotNull Collection<Operation> success,
-        @NotNull Collection<Operation> failure
+            @NotNull Condition cond,
+            @NotNull Collection<Operation> success,
+            @NotNull Collection<Operation> failure
     ) {
         return metaStorageSvc.invoke(cond, success, failure);
     }
 
     /**
-     * @see MetaStorageService#getAndInvoke(Key, Condition, Operation, Operation)
+     * @see MetaStorageService#getAndInvoke(Condition, Operation, Operation)
      */
     public @NotNull CompletableFuture<Entry> getAndInvoke(
-        @NotNull Key key,
         @NotNull Condition cond,
         @NotNull Operation success,
         @NotNull Operation failure
     ) {
-        return metaStorageSvc.getAndInvoke(key, cond, success, failure);
+        return metaStorageSvc.getAndInvoke(cond, success, failure);
     }
 
     /**
@@ -429,9 +427,10 @@ import org.jetbrains.annotations.Nullable;
         try {
             return vaultMgr.putAll(entries.stream().collect(
                 Collectors.toMap(
-                    e -> ByteArray.fromString(e.getKey().toString()),
-                    IgniteBiTuple::getValue)),
-                revision);
+                        e -> ByteArray.fromString(e.getKey().toString()),
+                        IgniteBiTuple::getValue)
+                    ),
+                    revision);
         }
         catch (IgniteInternalCheckedException e) {
             throw new IgniteInternalException("Couldn't put entries with considered revision.", e);
diff --git a/modules/runner/src/main/java/org/apache/ignite/internal/app/IgnitionImpl.java b/modules/runner/src/main/java/org/apache/ignite/internal/app/IgnitionImpl.java
index 4e608eb..8fccaba 100644
--- a/modules/runner/src/main/java/org/apache/ignite/internal/app/IgnitionImpl.java
+++ b/modules/runner/src/main/java/org/apache/ignite/internal/app/IgnitionImpl.java
@@ -26,7 +26,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
-import java.util.stream.Collectors;
 import org.apache.ignite.app.Ignite;
 import org.apache.ignite.app.Ignition;
 import org.apache.ignite.configuration.RootKey;
@@ -202,9 +201,7 @@ public class IgnitionImpl implements Ignition {
     private static void ackBanner() {
         String ver = IgniteProperties.get(VER_KEY);
 
-        String banner = Arrays
-            .stream(BANNER)
-            .collect(Collectors.joining("\n"));
+        String banner = String.join("\n", BANNER);
 
         LOG.info(banner + '\n' + " ".repeat(22) + "Apache Ignite ver. " + ver + '\n');
     }
@@ -213,70 +210,79 @@ public class IgnitionImpl implements Ignition {
     private static MetaStorageService metaStorageServiceMock() {
         return new MetaStorageService() {
             @Override public @NotNull CompletableFuture<Entry> get(@NotNull Key key) {
-                throw new UnsupportedOperationException("Metastorage service is not implemented yet");
+                throw new UnsupportedOperationException("Meta storage service is not implemented yet");
             }
 
             @Override public @NotNull CompletableFuture<Entry> get(@NotNull Key key, long revUpperBound) {
-                throw new UnsupportedOperationException("Metastorage service is not implemented yet");
+                throw new UnsupportedOperationException("Meta storage service is not implemented yet");
             }
 
             @Override public @NotNull CompletableFuture<Map<Key, Entry>> getAll(Collection<Key> keys) {
-                throw new UnsupportedOperationException("Metastorage service is not implemented yet");
+                throw new UnsupportedOperationException("Meta storage service is not implemented yet");
             }
 
             @Override public @NotNull CompletableFuture<Map<Key, Entry>> getAll(Collection<Key> keys, long revUpperBound) {
-                throw new UnsupportedOperationException("Metastorage service is not implemented yet");
+                throw new UnsupportedOperationException("Meta storage service is not implemented yet");
             }
 
             @Override public @NotNull CompletableFuture<Void> put(@NotNull Key key, @NotNull byte[] value) {
-                throw new UnsupportedOperationException("Metastorage service is not implemented yet");
+                throw new UnsupportedOperationException("Meta storage service is not implemented yet");
             }
 
             @Override public @NotNull CompletableFuture<Entry> getAndPut(@NotNull Key key, @NotNull byte[] value) {
-                throw new UnsupportedOperationException("Metastorage service is not implemented yet");
+                throw new UnsupportedOperationException("Meta storage service is not implemented yet");
             }
 
             @Override public @NotNull CompletableFuture<Void> putAll(@NotNull Map<Key, byte[]> vals) {
-                throw new UnsupportedOperationException("Metastorage service is not implemented yet");
+                throw new UnsupportedOperationException("Meta storage service is not implemented yet");
             }
 
             @Override public @NotNull CompletableFuture<Map<Key, Entry>> getAndPutAll(@NotNull Map<Key, byte[]> vals) {
-                throw new UnsupportedOperationException("Metastorage service is not implemented yet");
+                throw new UnsupportedOperationException("Meta storage service is not implemented yet");
             }
 
             @Override public @NotNull CompletableFuture<Void> remove(@NotNull Key key) {
-                throw new UnsupportedOperationException("Metastorage service is not implemented yet");
+                throw new UnsupportedOperationException("Meta storage service is not implemented yet");
             }
 
             @Override public @NotNull CompletableFuture<Entry> getAndRemove(@NotNull Key key) {
-                throw new UnsupportedOperationException("Metastorage service is not implemented yet");
+                throw new UnsupportedOperationException("Meta storage service is not implemented yet");
             }
 
             @Override public @NotNull CompletableFuture<Void> removeAll(@NotNull Collection<Key> keys) {
-                throw new UnsupportedOperationException("Metastorage service is not implemented yet");
+                throw new UnsupportedOperationException("Meta storage service is not implemented yet");
             }
 
             @Override
             public @NotNull CompletableFuture<Map<Key, Entry>> getAndRemoveAll(@NotNull Collection<Key> keys) {
-                throw new UnsupportedOperationException("Metastorage service is not implemented yet");
+                throw new UnsupportedOperationException("Meta storage service is not implemented yet");
             }
 
             @Override public @NotNull CompletableFuture<Boolean> invoke(@NotNull Condition condition,
-                @NotNull Collection<Operation> success, @NotNull Collection<Operation> failure) {
-                throw new UnsupportedOperationException("Metastorage service is not implemented yet");
+                @NotNull Operation success, @NotNull Operation failure) {
+                throw new UnsupportedOperationException("Meta storage service is not implemented yet");
             }
 
-            @Override public @NotNull CompletableFuture<Entry> getAndInvoke(@NotNull Key key, @NotNull Condition condition,
-                @NotNull Operation success, @NotNull Operation failure) {
-                throw new UnsupportedOperationException("Metastorage service is not implemented yet");
+            @Override public @NotNull CompletableFuture<Boolean> invoke(@NotNull Condition condition,
+                                                                        @NotNull Collection<Operation> success,
+                                                                        @NotNull Collection<Operation> failure
+            ) {
+                throw new UnsupportedOperationException("Meta storage service is not implemented yet");
+            }
+
+            @Override public @NotNull CompletableFuture<Entry> getAndInvoke(@NotNull Condition condition,
+                                                                            @NotNull Operation success,
+                                                                            @NotNull Operation failure
+            ) {
+                throw new UnsupportedOperationException("Meta storage service is not implemented yet");
             }
 
             @Override public @NotNull Cursor<Entry> range(@NotNull Key keyFrom, @Nullable Key keyTo, long revUpperBound) {
-                throw new UnsupportedOperationException("Metastorage service is not implemented yet");
+                throw new UnsupportedOperationException("Meta storage service is not implemented yet");
             }
 
             @Override public @NotNull Cursor<Entry> range(@NotNull Key keyFrom, @Nullable Key keyTo) {
-                throw new UnsupportedOperationException("Metastorage service is not implemented yet");
+                throw new UnsupportedOperationException("Meta storage service is not implemented yet");
             }
 
             @Override public @NotNull CompletableFuture<IgniteUuid> watch(@Nullable Key keyFrom, @Nullable Key keyTo,
@@ -295,11 +301,11 @@ public class IgnitionImpl implements Ignition {
             }
 
             @Override public @NotNull CompletableFuture<Void> stopWatch(@NotNull IgniteUuid id) {
-                throw new UnsupportedOperationException("Metastorage service is not implemented yet");
+                throw new UnsupportedOperationException("Meta storage service is not implemented yet");
             }
 
             @Override public @NotNull CompletableFuture<Void> compact() {
-                throw new UnsupportedOperationException("Metastorage service is not implemented yet");
+                throw new UnsupportedOperationException("Meta storage service is not implemented yet");
             }
         };
     }
diff --git a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/TableManager.java b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/TableManager.java
index 012d57b..809bc40 100644
--- a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/TableManager.java
+++ b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/TableManager.java
@@ -81,7 +81,7 @@ public class TableManager extends Producer<TableEvent, TableEventParameters> imp
     private CompletableFuture<Long> tableCreationSubscriptionFut;
 
     /** Tables. */
-    private Map<String, TableImpl> tables = new ConcurrentHashMap<>();
+    private final Map<String, TableImpl> tables = new ConcurrentHashMap<>();
 
     /*
      * @param configurationMgr Configuration manager.
@@ -228,7 +228,7 @@ public class TableManager extends Producer<TableEvent, TableEventParameters> imp
         configurationMgr.configurationRegistry().getConfiguration(TablesConfiguration.KEY)
             .tables().listen(ctx -> {
             Set<String> tablesToStart = ctx.newValue().namedListKeys() == null ?
-                Collections.EMPTY_SET : ctx.newValue().namedListKeys();
+                Collections.emptySet() : ctx.newValue().namedListKeys();
 
             tablesToStart.removeAll(ctx.oldValue().namedListKeys());
 
@@ -242,18 +242,19 @@ public class TableManager extends Producer<TableEvent, TableEventParameters> imp
 
                 UUID tblId = new UUID(revision, update);
 
-                var key = new Key(INTERNAL_PREFIX + tblId.toString());
+                var key = new Key(INTERNAL_PREFIX + tblId);
+
                 futs.add(metaStorageMgr.invoke(
-                    Conditions.key(key).value().eq(null),
+                    Conditions.notExists(key),
                     Operations.put(key, tableView.name().getBytes(StandardCharsets.UTF_8)),
                     Operations.noop()).thenCompose(res ->
-                    res ? metaStorageMgr.put(new Key(INTERNAL_PREFIX + "assignment." + tblId.toString()), new byte[0])
+                    res ? metaStorageMgr.put(new Key(INTERNAL_PREFIX + "assignment." + tblId), new byte[0])
                         .thenApply(v -> true)
                         : CompletableFuture.completedFuture(false)));
             }
 
             Set<String> tablesToStop = ctx.oldValue().namedListKeys() == null ?
-                Collections.EMPTY_SET : ctx.oldValue().namedListKeys();
+                Collections.emptySet() : ctx.oldValue().namedListKeys();
 
             tablesToStop.removeAll(ctx.newValue().namedListKeys());
 
@@ -262,12 +263,12 @@ public class TableManager extends Producer<TableEvent, TableEventParameters> imp
 
                 UUID tblId = t.internalTable().tableId();
 
-                var key = new Key(INTERNAL_PREFIX + "assignment." + tblId.toString());
+                var key = new Key(INTERNAL_PREFIX + "assignment." + tblId);
                 futs.add(metaStorageMgr.invoke(
-                    Conditions.key(key).value().ne(null),
+                    Conditions.value(key).ne(null),
                     Operations.remove(key),
                     Operations.noop()).thenCompose(res ->
-                    res ? metaStorageMgr.remove(new Key(INTERNAL_PREFIX + tblId.toString()))
+                    res ? metaStorageMgr.remove(new Key(INTERNAL_PREFIX + tblId))
                         .thenApply(v -> true)
                         : CompletableFuture.completedFuture(false)));
             }