You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@accumulo.apache.org by ct...@apache.org on 2020/11/03 00:53:24 UTC

[accumulo] branch main updated: Minor cleanup of ZooReaderWriter mutate (#1758)

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

ctubbsii pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/accumulo.git


The following commit(s) were added to refs/heads/main by this push:
     new e929e41  Minor cleanup of ZooReaderWriter mutate (#1758)
e929e41 is described below

commit e929e41d031ecf61cb5d24ddd2527fdd2d822ee5
Author: Christopher Tubbs <ct...@apache.org>
AuthorDate: Mon Nov 2 19:53:16 2020 -0500

    Minor cleanup of ZooReaderWriter mutate (#1758)
    
    * Minor cleanup of ZooReaderWriter mutate
    
    Separate out the two distinct use cases for calling
    `ZooReaderWriter.mutate()` into two separate methods, with fewer
    required parameters and more clear naming. This also allows for stricter
    null checking in the parameters that are passed.
    
    * Cleanup from code review
    
    * Add javadocs
    * Shorten mutateOrCreate method name
    * Reuse putPersistentData method in mutateOrCreate method and
      recursiveCopy method
    * Eliminate a private method that was now only used once
    * Remove unused Stat object in getACLs method
    * Clean up test (use static imports for EasyMock for readability)
---
 .../apache/accumulo/fate/zookeeper/ZooReader.java  |   4 +-
 .../accumulo/fate/zookeeper/ZooReaderWriter.java   | 158 +++++++++++++--------
 .../fate/zookeeper/ZooReaderWriterTest.java        | 108 +++++++-------
 .../server/metadata/RootTabletMutatorImpl.java     |  12 +-
 .../accumulo/server/metadata/ServerAmpleImpl.java  |  10 +-
 .../ZooAuthenticationKeyDistributor.java           |   3 +-
 .../accumulo/server/tables/TableManager.java       |   3 +-
 .../server/tablets/UniqueNameAllocator.java        |  15 +-
 .../server/problems/ProblemReportTest.java         |   5 +-
 .../ZooAuthenticationKeyDistributorTest.java       |  28 ++--
 .../master/MasterClientServiceHandler.java         |   5 +-
 .../org/apache/accumulo/master/tableOps/Utils.java |   3 +-
 .../master/tableOps/compact/CompactRange.java      |   7 +-
 .../tableOps/compact/cancel/CancelCompactions.java |   2 +-
 .../tableOps/namespace/rename/RenameNamespace.java |   8 +-
 .../master/tableOps/rename/RenameTable.java        |   2 +-
 .../accumulo/master/upgrade/Upgrader9to10.java     |  13 +-
 17 files changed, 197 insertions(+), 189 deletions(-)

diff --git a/core/src/main/java/org/apache/accumulo/fate/zookeeper/ZooReader.java b/core/src/main/java/org/apache/accumulo/fate/zookeeper/ZooReader.java
index 79f1b44..f4d6b3c 100644
--- a/core/src/main/java/org/apache/accumulo/fate/zookeeper/ZooReader.java
+++ b/core/src/main/java/org/apache/accumulo/fate/zookeeper/ZooReader.java
@@ -150,8 +150,8 @@ public class ZooReader {
 
   /**
    * This method is a special case of {@link #retryLoop(ZKFunction, Predicate)}, intended to handle
-   * {@link ZooReaderWriter#mutate(String, byte[], List, ZooReaderWriter.Mutator)}'s additional
-   * thrown exception type. Other callers should use {@link #retryLoop(ZKFunction)} or
+   * {@link ZooReaderWriter#mutateExisting(String, ZooReaderWriter.Mutator)}'s additional thrown
+   * exception type. Other callers should use {@link #retryLoop(ZKFunction)} or
    * {@link #retryLoop(ZKFunction, Predicate)} instead.
    */
   protected <R> R retryLoopMutator(ZKFunctionMutator<R> zkf,
diff --git a/core/src/main/java/org/apache/accumulo/fate/zookeeper/ZooReaderWriter.java b/core/src/main/java/org/apache/accumulo/fate/zookeeper/ZooReaderWriter.java
index f372d69..e0fe6ec 100644
--- a/core/src/main/java/org/apache/accumulo/fate/zookeeper/ZooReaderWriter.java
+++ b/core/src/main/java/org/apache/accumulo/fate/zookeeper/ZooReaderWriter.java
@@ -19,9 +19,9 @@
 package org.apache.accumulo.fate.zookeeper;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.Objects.requireNonNull;
 
 import java.util.List;
-import java.util.Objects;
 
 import org.apache.accumulo.core.clientImpl.AcceptableThriftTableOperationException;
 import org.apache.accumulo.core.conf.AccumuloConfiguration;
@@ -58,44 +58,105 @@ public class ZooReaderWriter extends ZooReader {
     return ZooSession.getAuthenticatedSession(keepers, timeout, "digest", auth);
   }
 
-  public List<ACL> getACL(String zPath, Stat stat) throws KeeperException, InterruptedException {
-    return retryLoop(zk -> zk.getACL(zPath, stat));
+  /**
+   * Retrieve the ACL list that was on the node
+   */
+  public List<ACL> getACL(String zPath) throws KeeperException, InterruptedException {
+    return retryLoop(zk -> zk.getACL(zPath, null));
   }
 
   /**
    * Create a persistent node with the default ACL
+   *
+   * @return true if the data was set on a new node or overwritten, and false if an existing node
+   *         was skipped
    */
-  public void putPersistentData(String zPath, byte[] data, NodeExistsPolicy policy)
+  public boolean putPersistentData(String zPath, byte[] data, NodeExistsPolicy policy)
       throws KeeperException, InterruptedException {
-    putPersistentData(zPath, data, policy, ZooUtil.PUBLIC);
+    return putPersistentData(zPath, data, policy, ZooUtil.PUBLIC);
   }
 
-  public void putPrivatePersistentData(String zPath, byte[] data, NodeExistsPolicy policy)
+  /**
+   * Create a persistent node with the private ACL
+   *
+   * @return true if the data was set on a new node or overwritten, and false if an existing node
+   *         was skipped
+   */
+  public boolean putPrivatePersistentData(String zPath, byte[] data, NodeExistsPolicy policy)
       throws KeeperException, InterruptedException {
-    putPersistentData(zPath, data, policy, ZooUtil.PRIVATE);
+    return putPersistentData(zPath, data, policy, ZooUtil.PRIVATE);
   }
 
-  public void putPersistentData(String zPath, byte[] data, NodeExistsPolicy policy, List<ACL> acls)
-      throws KeeperException, InterruptedException {
-    putData(zPath, data, CreateMode.PERSISTENT, policy, acls);
+  /**
+   * Create a persistent node with the provided ACLs
+   *
+   * @return true if the data was set on a new node or overwritten, and false if an existing node
+   *         was skipped
+   */
+  public boolean putPersistentData(String zPath, byte[] data, NodeExistsPolicy policy,
+      List<ACL> acls) throws KeeperException, InterruptedException {
+    // zk allows null ACLs, but it's probably a bug in Accumulo if we see it used in our code
+    requireNonNull(acls);
+    requireNonNull(policy);
+    return retryLoop(zk -> {
+      try {
+        zk.create(zPath, data, acls, CreateMode.PERSISTENT);
+        return true;
+      } catch (KeeperException e) {
+        if (e.code() == Code.NODEEXISTS) {
+          switch (policy) {
+            case SKIP:
+              return false;
+            case OVERWRITE:
+              zk.setData(zPath, data, -1);
+              return true;
+            case FAIL:
+            default:
+              // re-throw below
+          }
+        }
+        throw e;
+      }
+    },
+        // if OVERWRITE policy is used, create() can fail with NODEEXISTS;
+        // then, the node can be deleted, causing setData() to fail with NONODE;
+        // if that happens, the following code ensures we retry
+        e -> e.code() == Code.NONODE && policy == NodeExistsPolicy.OVERWRITE);
   }
 
+  /**
+   * Create a persistent sequential node with the default ACL
+   *
+   * @return the actual path of the created node
+   */
   public String putPersistentSequential(String zPath, byte[] data)
       throws KeeperException, InterruptedException {
     return retryLoop(
         zk -> zk.create(zPath, data, ZooUtil.PUBLIC, CreateMode.PERSISTENT_SEQUENTIAL));
   }
 
-  public String putEphemeralData(String zPath, byte[] data)
+  /**
+   * Create an ephemeral node with the default ACL
+   */
+  public void putEphemeralData(String zPath, byte[] data)
       throws KeeperException, InterruptedException {
-    return retryLoop(zk -> zk.create(zPath, data, ZooUtil.PUBLIC, CreateMode.EPHEMERAL));
+    retryLoop(zk -> zk.create(zPath, data, ZooUtil.PUBLIC, CreateMode.EPHEMERAL));
   }
 
+  /**
+   * Create an ephemeral sequential node with the default ACL
+   *
+   * @return the actual path of the created node
+   */
   public String putEphemeralSequential(String zPath, byte[] data)
       throws KeeperException, InterruptedException {
     return retryLoop(zk -> zk.create(zPath, data, ZooUtil.PUBLIC, CreateMode.EPHEMERAL_SEQUENTIAL));
   }
 
+  /**
+   * Recursively copy any persistent data from the source to the destination, using the default ACL
+   * to create any missing nodes and skipping over any ephemeral data.
+   */
   public void recursiveCopyPersistentOverwrite(String source, String destination)
       throws KeeperException, InterruptedException {
     var stat = new Stat();
@@ -104,7 +165,7 @@ public class ZooReaderWriter extends ZooReader {
     if (stat.getEphemeralOwner() != 0) {
       return;
     }
-    putData(destination, data, CreateMode.PERSISTENT, NodeExistsPolicy.OVERWRITE, ZooUtil.PUBLIC);
+    putPersistentData(destination, data, NodeExistsPolicy.OVERWRITE);
     if (stat.getNumChildren() > 0) {
       for (String child : getChildren(source)) {
         recursiveCopyPersistentOverwrite(source + "/" + child, destination + "/" + child);
@@ -112,20 +173,15 @@ public class ZooReaderWriter extends ZooReader {
     }
   }
 
-  public byte[] mutate(String zPath, byte[] createValue, List<ACL> acl, Mutator mutator)
+  /**
+   * Update an existing ZK node using the provided mutator function. If it's possible the node
+   * doesn't exist yet, use {@link #mutateOrCreate(String, byte[], Mutator)} instead.
+   *
+   * @return the value set on the node
+   */
+  public byte[] mutateExisting(String zPath, Mutator mutator)
       throws KeeperException, InterruptedException, AcceptableThriftTableOperationException {
-    if (createValue != null) {
-      try {
-        retryLoop(zk -> zk.create(zPath, createValue, acl, CreateMode.PERSISTENT));
-        // create node was successful; return current (new) value
-        return createValue;
-      } catch (KeeperException e) {
-        // if value already exists, use mutator instead
-        if (e.code() != Code.NODEEXISTS) {
-          throw e;
-        }
-      }
-    }
+    requireNonNull(mutator);
     return retryLoopMutator(zk -> {
       var stat = new Stat();
       byte[] data = zk.getData(zPath, null, stat);
@@ -138,6 +194,24 @@ public class ZooReaderWriter extends ZooReader {
     }, e -> e.code() == Code.BADVERSION); // always retry if bad version
   }
 
+  /**
+   * Create a new {@link CreateMode#PERSISTENT} ZK node with the default ACL if it does not exist.
+   * If it does already exist, then update it with the provided mutator function. If it is known to
+   * exist already, use {@link #mutateExisting(String, Mutator)} instead.
+   *
+   * @return the value set on the node
+   */
+  public byte[] mutateOrCreate(String zPath, byte[] createValue, Mutator mutator)
+      throws KeeperException, InterruptedException, AcceptableThriftTableOperationException {
+    requireNonNull(mutator);
+    return putPersistentData(zPath, createValue, NodeExistsPolicy.SKIP) ? createValue
+        : mutateExisting(zPath, mutator);
+  }
+
+  /**
+   * Ensure the provided path exists, using persistent nodes, empty data, and the default ACL for
+   * any missing path elements.
+   */
   public void mkdirs(String path) throws KeeperException, InterruptedException {
     if (path.equals("")) {
       // terminal condition for recursion
@@ -172,10 +246,7 @@ public class ZooReaderWriter extends ZooReader {
   }
 
   /**
-   * This method will delete a node and all its children from zookeeper
-   *
-   * @param zPath
-   *          the path to delete
+   * This method will delete a node and all its children.
    */
   public void recursiveDelete(String zPath, NodeMissingPolicy policy)
       throws KeeperException, InterruptedException {
@@ -204,31 +275,4 @@ public class ZooReaderWriter extends ZooReader {
       throw e;
     }
   }
-
-  private void putData(String zPath, byte[] data, CreateMode mode, NodeExistsPolicy policy,
-      List<ACL> acls) throws KeeperException, InterruptedException {
-    Objects.requireNonNull(policy);
-    retryLoop(zk -> {
-      try {
-        zk.create(zPath, data, acls, mode);
-        return null;
-      } catch (KeeperException e) {
-        if (e.code() == Code.NODEEXISTS) {
-          switch (policy) {
-            case SKIP:
-              return null;
-            case OVERWRITE:
-              zk.setData(zPath, data, -1);
-              return null;
-            default:
-          }
-        }
-        throw e;
-      }
-    },
-        // if OVERWRITE policy is used, create() can fail with NODEEXISTS;
-        // then, the node can be deleted, causing setData() to fail with NONODE;
-        // if that happens, the following code ensures we retry
-        e -> e.code() == Code.NONODE && policy == NodeExistsPolicy.OVERWRITE);
-  }
 }
diff --git a/core/src/test/java/org/apache/accumulo/fate/zookeeper/ZooReaderWriterTest.java b/core/src/test/java/org/apache/accumulo/fate/zookeeper/ZooReaderWriterTest.java
index e778179..37d7395 100644
--- a/core/src/test/java/org/apache/accumulo/fate/zookeeper/ZooReaderWriterTest.java
+++ b/core/src/test/java/org/apache/accumulo/fate/zookeeper/ZooReaderWriterTest.java
@@ -18,11 +18,14 @@
  */
 package org.apache.accumulo.fate.zookeeper;
 
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.createMockBuilder;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
 import static org.junit.Assert.assertArrayEquals;
 
-import java.util.Collections;
-import java.util.List;
-
 import org.apache.accumulo.fate.util.Retry;
 import org.apache.accumulo.fate.util.Retry.RetryFactory;
 import org.apache.accumulo.fate.zookeeper.ZooReaderWriter.Mutator;
@@ -34,9 +37,7 @@ import org.apache.zookeeper.KeeperException.ConnectionLossException;
 import org.apache.zookeeper.KeeperException.NodeExistsException;
 import org.apache.zookeeper.KeeperException.SessionExpiredException;
 import org.apache.zookeeper.ZooKeeper;
-import org.apache.zookeeper.data.ACL;
 import org.apache.zookeeper.data.Stat;
-import org.easymock.EasyMock;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -49,15 +50,15 @@ public class ZooReaderWriterTest {
 
   @Before
   public void setup() {
-    zk = EasyMock.createMock(ZooKeeper.class);
-    zrw = EasyMock.createMockBuilder(ZooReaderWriter.class)
+    zk = createMock(ZooKeeper.class);
+    zrw = createMockBuilder(ZooReaderWriter.class)
         .addMockedMethods("getRetryFactory", "getZooKeeper").createMock();
-    retryFactory = EasyMock.createMock(RetryFactory.class);
-    retry = EasyMock.createMock(Retry.class);
+    retryFactory = createMock(RetryFactory.class);
+    retry = createMock(Retry.class);
 
-    EasyMock.expect(zrw.getZooKeeper()).andReturn(zk).anyTimes();
-    EasyMock.expect(zrw.getRetryFactory()).andReturn(retryFactory).anyTimes();
-    EasyMock.expect(retryFactory.createRetry()).andReturn(retry).anyTimes();
+    expect(zrw.getZooKeeper()).andReturn(zk).anyTimes();
+    expect(zrw.getRetryFactory()).andReturn(retryFactory).anyTimes();
+    expect(retryFactory.createRetry()).andReturn(retry).anyTimes();
   }
 
   @Test
@@ -65,9 +66,9 @@ public class ZooReaderWriterTest {
     final String path = "/foo";
 
     zk.delete(path, -1);
-    EasyMock.expectLastCall().andThrow(KeeperException.create(Code.NONODE));
+    expectLastCall().andThrow(KeeperException.create(Code.NONODE));
 
-    EasyMock.replay(zk, zrw, retryFactory, retry);
+    replay(zk, zrw, retryFactory, retry);
 
     zrw.delete(path);
   }
@@ -77,102 +78,99 @@ public class ZooReaderWriterTest {
     final String path = "/foo";
 
     zk.delete(path, -1);
-    EasyMock.expectLastCall().andThrow(KeeperException.create(Code.CONNECTIONLOSS));
-    EasyMock.expect(retry.canRetry()).andReturn(true);
+    expectLastCall().andThrow(KeeperException.create(Code.CONNECTIONLOSS));
+    expect(retry.canRetry()).andReturn(true);
     retry.useRetry();
-    EasyMock.expectLastCall().once();
+    expectLastCall().once();
     retry.waitForNextAttempt();
-    EasyMock.expectLastCall().once();
+    expectLastCall().once();
     zk.delete(path, -1);
-    EasyMock.expectLastCall().andThrow(KeeperException.create(Code.NONODE));
+    expectLastCall().andThrow(KeeperException.create(Code.NONODE));
 
-    EasyMock.replay(zk, zrw, retryFactory, retry);
+    replay(zk, zrw, retryFactory, retry);
 
     zrw.delete(path);
 
-    EasyMock.verify(zk, zrw, retryFactory, retry);
+    verify(zk, zrw, retryFactory, retry);
   }
 
   @Test(expected = SessionExpiredException.class)
   public void testMutateNodeCreationFails() throws Exception {
     final String path = "/foo";
     final byte[] value = {0};
-    final List<ACL> acls = Collections.emptyList();
     Mutator mutator = currentValue -> new byte[] {1};
 
-    zk.create(path, value, acls, CreateMode.PERSISTENT);
-    EasyMock.expectLastCall().andThrow(new SessionExpiredException()).once();
-    EasyMock.expect(retry.canRetry()).andReturn(false);
-    EasyMock.expect(retry.retriesCompleted()).andReturn(1L).once();
+    zk.create(path, value, ZooUtil.PUBLIC, CreateMode.PERSISTENT);
+    expectLastCall().andThrow(new SessionExpiredException()).once();
+    expect(retry.canRetry()).andReturn(false);
+    expect(retry.retriesCompleted()).andReturn(1L).once();
 
-    EasyMock.replay(zk, zrw, retryFactory, retry);
+    replay(zk, zrw, retryFactory, retry);
 
-    zrw.mutate(path, value, acls, mutator);
+    zrw.mutateOrCreate(path, value, mutator);
   }
 
   @Test
   public void testMutateWithBadVersion() throws Exception {
     final String path = "/foo";
     final byte[] value = {0};
-    final List<ACL> acls = Collections.emptyList();
     final byte[] mutatedBytes = {1};
     Mutator mutator = currentValue -> mutatedBytes;
 
-    zrw = EasyMock.createMockBuilder(ZooReaderWriter.class)
+    zrw = createMockBuilder(ZooReaderWriter.class)
         .addMockedMethods("getRetryFactory", "getZooKeeper").createMock();
-    EasyMock.expect(zrw.getRetryFactory()).andReturn(retryFactory).anyTimes();
-    EasyMock.expect(zrw.getZooKeeper()).andReturn(zk).anyTimes();
+    expect(zrw.getRetryFactory()).andReturn(retryFactory).anyTimes();
+    expect(zrw.getZooKeeper()).andReturn(zk).anyTimes();
 
     Stat stat = new Stat();
 
-    zk.create(path, value, acls, CreateMode.PERSISTENT);
-    EasyMock.expectLastCall().andThrow(new NodeExistsException()).once();
-    EasyMock.expect(zk.getData(path, null, stat)).andReturn(new byte[] {3}).times(2);
+    zk.create(path, value, ZooUtil.PUBLIC, CreateMode.PERSISTENT);
+    expectLastCall().andThrow(new NodeExistsException()).once();
+    expect(zk.getData(path, null, stat)).andReturn(new byte[] {3}).times(2);
     // BadVersionException should retry
-    EasyMock.expect(zk.setData(path, mutatedBytes, 0)).andThrow(new BadVersionException());
+    expect(zk.setData(path, mutatedBytes, 0)).andThrow(new BadVersionException());
     // Let 2nd setData succeed
-    EasyMock.expect(zk.setData(path, mutatedBytes, 0)).andReturn(null);
+    expect(zk.setData(path, mutatedBytes, 0)).andReturn(null);
 
-    EasyMock.replay(zk, zrw, retryFactory, retry);
+    replay(zk, zrw, retryFactory, retry);
 
-    assertArrayEquals(new byte[] {1}, zrw.mutate(path, value, acls, mutator));
+    assertArrayEquals(new byte[] {1}, zrw.mutateOrCreate(path, value, mutator));
 
-    EasyMock.verify(zk, zrw, retryFactory, retry);
+    verify(zk, zrw, retryFactory, retry);
   }
 
   @Test
   public void testMutateWithRetryOnSetData() throws Exception {
     final String path = "/foo";
     final byte[] value = {0};
-    final List<ACL> acls = Collections.emptyList();
     final byte[] mutatedBytes = {1};
     Mutator mutator = currentValue -> mutatedBytes;
 
-    zrw = EasyMock.createMockBuilder(ZooReaderWriter.class)
+    zrw = createMockBuilder(ZooReaderWriter.class)
         .addMockedMethods("getRetryFactory", "getZooKeeper").createMock();
-    EasyMock.expect(zrw.getRetryFactory()).andReturn(retryFactory).anyTimes();
-    EasyMock.expect(zrw.getZooKeeper()).andReturn(zk).anyTimes();
+    expect(zrw.getRetryFactory()).andReturn(retryFactory).anyTimes();
+    expect(zrw.getZooKeeper()).andReturn(zk).anyTimes();
 
     Stat stat = new Stat();
 
-    zk.create(path, value, acls, CreateMode.PERSISTENT);
-    EasyMock.expectLastCall().andThrow(new NodeExistsException()).once();
-    EasyMock.expect(zk.getData(path, null, stat)).andReturn(new byte[] {3}).times(2);
+    zk.create(path, value, ZooUtil.PUBLIC, CreateMode.PERSISTENT);
+    expectLastCall().andThrow(new NodeExistsException()).once();
+    expect(zk.getData(path, null, stat)).andReturn(new byte[] {3}).times(2);
     // BadVersionException should retry
-    EasyMock.expect(zk.setData(path, mutatedBytes, 0)).andThrow(new ConnectionLossException());
+    expect(zk.setData(path, mutatedBytes, 0)).andThrow(new ConnectionLossException());
 
-    EasyMock.expect(retry.canRetry()).andReturn(true);
+    expect(retry.canRetry()).andReturn(true);
     retry.useRetry();
-    EasyMock.expectLastCall();
+    expectLastCall();
     retry.waitForNextAttempt();
-    EasyMock.expectLastCall();
+    expectLastCall();
     // Let 2nd setData succeed
-    EasyMock.expect(zk.setData(path, mutatedBytes, 0)).andReturn(null);
+    expect(zk.setData(path, mutatedBytes, 0)).andReturn(null);
 
-    EasyMock.replay(zk, zrw, retryFactory, retry);
+    replay(zk, zrw, retryFactory, retry);
 
-    assertArrayEquals(new byte[] {1}, zrw.mutate(path, value, acls, mutator));
+    assertArrayEquals(new byte[] {1}, zrw.mutateOrCreate(path, value, mutator));
 
-    EasyMock.verify(zk, zrw, retryFactory, retry);
+    verify(zk, zrw, retryFactory, retry);
   }
 }
diff --git a/server/base/src/main/java/org/apache/accumulo/server/metadata/RootTabletMutatorImpl.java b/server/base/src/main/java/org/apache/accumulo/server/metadata/RootTabletMutatorImpl.java
index 6b0fd50..a28bda6 100644
--- a/server/base/src/main/java/org/apache/accumulo/server/metadata/RootTabletMutatorImpl.java
+++ b/server/base/src/main/java/org/apache/accumulo/server/metadata/RootTabletMutatorImpl.java
@@ -28,7 +28,6 @@ import org.apache.accumulo.core.metadata.RootTable;
 import org.apache.accumulo.core.metadata.schema.Ample;
 import org.apache.accumulo.core.metadata.schema.RootTabletMetadata;
 import org.apache.accumulo.core.security.AuthorizationContainer;
-import org.apache.accumulo.fate.zookeeper.ZooUtil;
 import org.apache.accumulo.server.ServerContext;
 import org.apache.accumulo.server.constraints.MetadataConstraints;
 import org.apache.accumulo.server.constraints.SystemEnvironment;
@@ -93,20 +92,13 @@ public class RootTabletMutatorImpl extends TabletMutatorBase implements Ample.Ta
       context.getZooCache().clear(zpath);
 
       // TODO examine implementation of getZooReaderWriter().mutate()
-      context.getZooReaderWriter().mutate(zpath, new byte[0], ZooUtil.PUBLIC, currVal -> {
-
+      context.getZooReaderWriter().mutateOrCreate(zpath, new byte[0], currVal -> {
         String currJson = new String(currVal, UTF_8);
-
         log.debug("Before mutating : {}, ", currJson);
-
-        RootTabletMetadata rtm = RootTabletMetadata.fromJson(currJson);
-
+        var rtm = RootTabletMetadata.fromJson(currJson);
         rtm.update(mutation);
-
         String newJson = rtm.toJson();
-
         log.debug("After mutating : {} ", newJson);
-
         return newJson.getBytes(UTF_8);
       });
 
diff --git a/server/base/src/main/java/org/apache/accumulo/server/metadata/ServerAmpleImpl.java b/server/base/src/main/java/org/apache/accumulo/server/metadata/ServerAmpleImpl.java
index b1d89dd..a11f317 100644
--- a/server/base/src/main/java/org/apache/accumulo/server/metadata/ServerAmpleImpl.java
+++ b/server/base/src/main/java/org/apache/accumulo/server/metadata/ServerAmpleImpl.java
@@ -47,7 +47,6 @@ import org.apache.accumulo.core.metadata.schema.AmpleImpl;
 import org.apache.accumulo.core.metadata.schema.MetadataSchema.DeletesSection;
 import org.apache.accumulo.core.metadata.schema.MetadataSchema.DeletesSection.SkewedKeyValue;
 import org.apache.accumulo.core.security.Authorizations;
-import org.apache.accumulo.fate.zookeeper.ZooUtil;
 import org.apache.accumulo.server.ServerContext;
 import org.apache.hadoop.io.Text;
 import org.slf4j.Logger;
@@ -82,26 +81,19 @@ public class ServerAmpleImpl extends AmpleImpl implements Ample {
   private void mutateRootGcCandidates(Consumer<RootGcCandidates> mutator) {
     String zpath = context.getZooKeeperRoot() + ZROOT_TABLET_GC_CANDIDATES;
     try {
-      context.getZooReaderWriter().mutate(zpath, new byte[0], ZooUtil.PUBLIC, currVal -> {
+      context.getZooReaderWriter().mutateOrCreate(zpath, new byte[0], currVal -> {
         String currJson = new String(currVal, UTF_8);
-
         RootGcCandidates rgcc = RootGcCandidates.fromJson(currJson);
-
         log.debug("Root GC candidates before change : {}", currJson);
-
         mutator.accept(rgcc);
-
         String newJson = rgcc.toJson();
-
         log.debug("Root GC candidates after change  : {}", newJson);
-
         if (newJson.length() > 262_144) {
           log.warn(
               "Root tablet deletion candidates stored in ZK at {} are getting large ({} bytes), is"
                   + " Accumulo GC process running?  Large nodes may cause problems for Zookeeper!",
               zpath, newJson.length());
         }
-
         return newJson.getBytes(UTF_8);
       });
     } catch (Exception e) {
diff --git a/server/base/src/main/java/org/apache/accumulo/server/security/delegation/ZooAuthenticationKeyDistributor.java b/server/base/src/main/java/org/apache/accumulo/server/security/delegation/ZooAuthenticationKeyDistributor.java
index 387ba1d..ca2ebe2 100644
--- a/server/base/src/main/java/org/apache/accumulo/server/security/delegation/ZooAuthenticationKeyDistributor.java
+++ b/server/base/src/main/java/org/apache/accumulo/server/security/delegation/ZooAuthenticationKeyDistributor.java
@@ -37,7 +37,6 @@ import org.apache.accumulo.fate.zookeeper.ZooUtil.NodeExistsPolicy;
 import org.apache.zookeeper.KeeperException;
 import org.apache.zookeeper.data.ACL;
 import org.apache.zookeeper.data.Id;
-import org.apache.zookeeper.data.Stat;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -69,7 +68,7 @@ public class ZooAuthenticationKeyDistributor {
     }
 
     if (zk.exists(baseNode)) {
-      List<ACL> acls = zk.getACL(baseNode, new Stat());
+      List<ACL> acls = zk.getACL(baseNode);
       if (acls.size() == 1) {
         ACL actualAcl = acls.get(0), expectedAcl = ZooUtil.PRIVATE.get(0);
         Id actualId = actualAcl.getId();
diff --git a/server/base/src/main/java/org/apache/accumulo/server/tables/TableManager.java b/server/base/src/main/java/org/apache/accumulo/server/tables/TableManager.java
index 8a830c0..304223a 100644
--- a/server/base/src/main/java/org/apache/accumulo/server/tables/TableManager.java
+++ b/server/base/src/main/java/org/apache/accumulo/server/tables/TableManager.java
@@ -36,7 +36,6 @@ import org.apache.accumulo.core.master.state.tables.TableState;
 import org.apache.accumulo.core.util.Pair;
 import org.apache.accumulo.fate.zookeeper.ZooCache;
 import org.apache.accumulo.fate.zookeeper.ZooReaderWriter;
-import org.apache.accumulo.fate.zookeeper.ZooUtil;
 import org.apache.accumulo.fate.zookeeper.ZooUtil.NodeExistsPolicy;
 import org.apache.accumulo.fate.zookeeper.ZooUtil.NodeMissingPolicy;
 import org.apache.accumulo.server.ServerContext;
@@ -116,7 +115,7 @@ public class TableManager {
     String statePath = zkRoot + Constants.ZTABLES + "/" + tableId + Constants.ZTABLE_STATE;
 
     try {
-      zoo.mutate(statePath, newState.name().getBytes(UTF_8), ZooUtil.PUBLIC, oldData -> {
+      zoo.mutateOrCreate(statePath, newState.name().getBytes(UTF_8), oldData -> {
         TableState oldState = TableState.UNKNOWN;
         if (oldData != null)
           oldState = TableState.valueOf(new String(oldData, UTF_8));
diff --git a/server/base/src/main/java/org/apache/accumulo/server/tablets/UniqueNameAllocator.java b/server/base/src/main/java/org/apache/accumulo/server/tablets/UniqueNameAllocator.java
index 5a36881..e431d42 100644
--- a/server/base/src/main/java/org/apache/accumulo/server/tablets/UniqueNameAllocator.java
+++ b/server/base/src/main/java/org/apache/accumulo/server/tablets/UniqueNameAllocator.java
@@ -25,8 +25,6 @@ import java.util.Random;
 
 import org.apache.accumulo.core.Constants;
 import org.apache.accumulo.core.util.FastFormat;
-import org.apache.accumulo.fate.zookeeper.ZooReaderWriter;
-import org.apache.accumulo.fate.zookeeper.ZooUtil;
 import org.apache.accumulo.server.ServerContext;
 
 /**
@@ -55,15 +53,10 @@ public class UniqueNameAllocator {
       final int allocate = 100 + rand.nextInt(100);
 
       try {
-        byte[] max = context.getZooReaderWriter().mutate(nextNamePath, null, ZooUtil.PRIVATE,
-            new ZooReaderWriter.Mutator() {
-              @Override
-              public byte[] mutate(byte[] currentValue) {
-                long l = Long.parseLong(new String(currentValue, UTF_8), Character.MAX_RADIX);
-                l += allocate;
-                return Long.toString(l, Character.MAX_RADIX).getBytes(UTF_8);
-              }
-            });
+        byte[] max = context.getZooReaderWriter().mutateExisting(nextNamePath, currentValue -> {
+          long l = Long.parseLong(new String(currentValue, UTF_8), Character.MAX_RADIX);
+          return Long.toString(l + allocate, Character.MAX_RADIX).getBytes(UTF_8);
+        });
 
         maxAllocated = Long.parseLong(new String(max, UTF_8), Character.MAX_RADIX);
         next = maxAllocated - allocate;
diff --git a/server/base/src/test/java/org/apache/accumulo/server/problems/ProblemReportTest.java b/server/base/src/test/java/org/apache/accumulo/server/problems/ProblemReportTest.java
index b0ea0b9..43a4ad3 100644
--- a/server/base/src/test/java/org/apache/accumulo/server/problems/ProblemReportTest.java
+++ b/server/base/src/test/java/org/apache/accumulo/server/problems/ProblemReportTest.java
@@ -22,7 +22,6 @@ import static org.easymock.EasyMock.aryEq;
 import static org.easymock.EasyMock.createMock;
 import static org.easymock.EasyMock.eq;
 import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.expectLastCall;
 import static org.easymock.EasyMock.replay;
 import static org.easymock.EasyMock.verify;
 import static org.junit.Assert.assertEquals;
@@ -175,8 +174,8 @@ public class ProblemReportTest {
     String path = ZooUtil.getRoot("instance") + Constants.ZPROBLEMS + "/"
         + Encoding.encodeAsBase64FileName(new Text(zpathFileName));
     byte[] encoded = encodeReportData(now, SERVER, null);
-    zoorw.putPersistentData(eq(path), aryEq(encoded), eq(NodeExistsPolicy.OVERWRITE));
-    expectLastCall();
+    expect(zoorw.putPersistentData(eq(path), aryEq(encoded), eq(NodeExistsPolicy.OVERWRITE)))
+        .andReturn(true);
     replay(zoorw);
 
     r.saveToZooKeeper(context);
diff --git a/server/base/src/test/java/org/apache/accumulo/server/security/delegation/ZooAuthenticationKeyDistributorTest.java b/server/base/src/test/java/org/apache/accumulo/server/security/delegation/ZooAuthenticationKeyDistributorTest.java
index 4d8f4ab..1258b74 100644
--- a/server/base/src/test/java/org/apache/accumulo/server/security/delegation/ZooAuthenticationKeyDistributorTest.java
+++ b/server/base/src/test/java/org/apache/accumulo/server/security/delegation/ZooAuthenticationKeyDistributorTest.java
@@ -43,7 +43,6 @@ import org.apache.accumulo.fate.zookeeper.ZooUtil.NodeExistsPolicy;
 import org.apache.zookeeper.KeeperException.AuthFailedException;
 import org.apache.zookeeper.data.ACL;
 import org.apache.zookeeper.data.Id;
-import org.apache.zookeeper.data.Stat;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -77,8 +76,9 @@ public class ZooAuthenticationKeyDistributorTest {
 
     // Attempt to create the directory and fail
     expect(zrw.exists(baseNode)).andReturn(false);
-    zrw.putPrivatePersistentData(eq(baseNode), aryEq(new byte[0]), eq(NodeExistsPolicy.FAIL));
-    expectLastCall().andThrow(new AuthFailedException());
+    expect(
+        zrw.putPrivatePersistentData(eq(baseNode), aryEq(new byte[0]), eq(NodeExistsPolicy.FAIL)))
+            .andThrow(new AuthFailedException());
 
     replay(zrw);
 
@@ -94,8 +94,8 @@ public class ZooAuthenticationKeyDistributorTest {
 
     // Attempt to create the directory and fail
     expect(zrw.exists(baseNode)).andReturn(false);
-    zrw.putPrivatePersistentData(eq(baseNode), anyObject(), eq(NodeExistsPolicy.FAIL));
-    expectLastCall();
+    expect(zrw.putPrivatePersistentData(eq(baseNode), anyObject(), eq(NodeExistsPolicy.FAIL)))
+        .andReturn(true);
 
     replay(zrw);
 
@@ -132,7 +132,7 @@ public class ZooAuthenticationKeyDistributorTest {
 
     // Attempt to create the directory and fail
     expect(zrw.exists(baseNode)).andReturn(true);
-    expect(zrw.getACL(eq(baseNode), anyObject(Stat.class))).andReturn(Collections.emptyList());
+    expect(zrw.getACL(eq(baseNode))).andReturn(Collections.emptyList());
 
     replay(zrw);
 
@@ -150,7 +150,7 @@ public class ZooAuthenticationKeyDistributorTest {
 
     // Attempt to create the directory and fail
     expect(zrw.exists(baseNode)).andReturn(true);
-    expect(zrw.getACL(eq(baseNode), anyObject(Stat.class))).andReturn(Collections.singletonList(
+    expect(zrw.getACL(eq(baseNode))).andReturn(Collections.singletonList(
         new ACL(ZooUtil.PRIVATE.get(0).getPerms(), new Id("digest", "somethingweird"))));
 
     replay(zrw);
@@ -174,11 +174,11 @@ public class ZooAuthenticationKeyDistributorTest {
 
     // Attempt to create the directory and fail
     expect(zrw.exists(baseNode)).andReturn(true);
-    expect(zrw.getACL(eq(baseNode), anyObject(Stat.class))).andReturn(Collections.singletonList(
+    expect(zrw.getACL(eq(baseNode))).andReturn(Collections.singletonList(
         new ACL(ZooUtil.PRIVATE.get(0).getPerms(), new Id("digest", "accumulo:DEFAULT"))));
     expect(zrw.exists(path)).andReturn(false);
-    zrw.putPrivatePersistentData(eq(path), aryEq(serialized), eq(NodeExistsPolicy.FAIL));
-    expectLastCall();
+    expect(zrw.putPrivatePersistentData(eq(path), aryEq(serialized), eq(NodeExistsPolicy.FAIL)))
+        .andReturn(true);
 
     replay(zrw);
 
@@ -197,7 +197,7 @@ public class ZooAuthenticationKeyDistributorTest {
 
     // Attempt to create the directory and fail
     expect(zrw.exists(baseNode)).andReturn(true);
-    expect(zrw.getACL(eq(baseNode), anyObject(Stat.class))).andReturn(Collections.singletonList(
+    expect(zrw.getACL(eq(baseNode))).andReturn(Collections.singletonList(
         new ACL(ZooUtil.PRIVATE.get(0).getPerms(), new Id("digest", "accumulo:DEFAULT"))));
     expect(zrw.exists(path)).andReturn(true);
 
@@ -218,7 +218,7 @@ public class ZooAuthenticationKeyDistributorTest {
 
     // Attempt to create the directory and fail
     expect(zrw.exists(baseNode)).andReturn(true);
-    expect(zrw.getACL(eq(baseNode), anyObject(Stat.class))).andReturn(Collections.singletonList(
+    expect(zrw.getACL(eq(baseNode))).andReturn(Collections.singletonList(
         new ACL(ZooUtil.PRIVATE.get(0).getPerms(), new Id("digest", "accumulo:DEFAULT"))));
     expect(zrw.exists(path)).andReturn(true);
     zrw.delete(path);
@@ -241,7 +241,7 @@ public class ZooAuthenticationKeyDistributorTest {
 
     // Attempt to create the directory and fail
     expect(zrw.exists(baseNode)).andReturn(true);
-    expect(zrw.getACL(eq(baseNode), anyObject(Stat.class))).andReturn(Collections.singletonList(
+    expect(zrw.getACL(eq(baseNode))).andReturn(Collections.singletonList(
         new ACL(ZooUtil.PRIVATE.get(0).getPerms(), new Id("digest", "accumulo:DEFAULT"))));
     expect(zrw.exists(path)).andReturn(false);
 
@@ -270,7 +270,7 @@ public class ZooAuthenticationKeyDistributorTest {
     }
 
     expect(zrw.exists(baseNode)).andReturn(true);
-    expect(zrw.getACL(eq(baseNode), anyObject(Stat.class))).andReturn(Collections.singletonList(
+    expect(zrw.getACL(eq(baseNode))).andReturn(Collections.singletonList(
         new ACL(ZooUtil.PRIVATE.get(0).getPerms(), new Id("digest", "accumulo:DEFAULT"))));
     expect(zrw.getChildren(baseNode)).andReturn(children);
     for (int i = 1; i < 6; i++) {
diff --git a/server/manager/src/main/java/org/apache/accumulo/master/MasterClientServiceHandler.java b/server/manager/src/main/java/org/apache/accumulo/master/MasterClientServiceHandler.java
index 62d2981..9178aeb 100644
--- a/server/manager/src/main/java/org/apache/accumulo/master/MasterClientServiceHandler.java
+++ b/server/manager/src/main/java/org/apache/accumulo/master/MasterClientServiceHandler.java
@@ -126,10 +126,9 @@ public class MasterClientServiceHandler extends FateServiceHandler
     ZooReaderWriter zoo = master.getContext().getZooReaderWriter();
     byte[] fid;
     try {
-      fid = zoo.mutate(zTablePath, null, null, currentValue -> {
+      fid = zoo.mutateExisting(zTablePath, currentValue -> {
         long flushID = Long.parseLong(new String(currentValue, UTF_8));
-        flushID++;
-        return ("" + flushID).getBytes(UTF_8);
+        return Long.toString(flushID + 1).getBytes(UTF_8);
       });
     } catch (NoNodeException nne) {
       throw new ThriftTableOperationException(tableId.canonical(), null, TableOperation.FLUSH,
diff --git a/server/manager/src/main/java/org/apache/accumulo/master/tableOps/Utils.java b/server/manager/src/main/java/org/apache/accumulo/master/tableOps/Utils.java
index 227f837..aa89da9 100644
--- a/server/manager/src/main/java/org/apache/accumulo/master/tableOps/Utils.java
+++ b/server/manager/src/main/java/org/apache/accumulo/master/tableOps/Utils.java
@@ -42,7 +42,6 @@ import org.apache.accumulo.fate.zookeeper.DistributedReadWriteLock;
 import org.apache.accumulo.fate.zookeeper.ZooQueueLock;
 import org.apache.accumulo.fate.zookeeper.ZooReaderWriter;
 import org.apache.accumulo.fate.zookeeper.ZooReservation;
-import org.apache.accumulo.fate.zookeeper.ZooUtil;
 import org.apache.accumulo.master.Master;
 import org.apache.accumulo.server.ServerContext;
 import org.apache.hadoop.fs.FileSystem;
@@ -71,7 +70,7 @@ public class Utils {
     try {
       ZooReaderWriter zoo = context.getZooReaderWriter();
       final String ntp = context.getZooKeeperRoot() + Constants.ZTABLES;
-      byte[] nid = zoo.mutate(ntp, ZERO_BYTE, ZooUtil.PUBLIC, currentValue -> {
+      byte[] nid = zoo.mutateOrCreate(ntp, ZERO_BYTE, currentValue -> {
         BigInteger nextId = new BigInteger(new String(currentValue, UTF_8), Character.MAX_RADIX);
         nextId = nextId.add(BigInteger.ONE);
         return nextId.toString(Character.MAX_RADIX).getBytes(UTF_8);
diff --git a/server/manager/src/main/java/org/apache/accumulo/master/tableOps/compact/CompactRange.java b/server/manager/src/main/java/org/apache/accumulo/master/tableOps/compact/CompactRange.java
index 6c51e9a..706fcb7 100644
--- a/server/manager/src/main/java/org/apache/accumulo/master/tableOps/compact/CompactRange.java
+++ b/server/manager/src/main/java/org/apache/accumulo/master/tableOps/compact/CompactRange.java
@@ -101,11 +101,10 @@ public class CompactRange extends MasterRepo {
     ZooReaderWriter zoo = env.getContext().getZooReaderWriter();
     byte[] cid;
     try {
-      cid = zoo.mutate(zTablePath, null, null, currentValue -> {
+      cid = zoo.mutateExisting(zTablePath, currentValue -> {
         String cvs = new String(currentValue, UTF_8);
         String[] tokens = cvs.split(",");
-        long flushID = Long.parseLong(tokens[0]);
-        flushID++;
+        long flushID = Long.parseLong(tokens[0]) + 1;
 
         String txidString = String.format("%016x", tid);
 
@@ -150,7 +149,7 @@ public class CompactRange extends MasterRepo {
 
     ZooReaderWriter zoo = environment.getContext().getZooReaderWriter();
 
-    zoo.mutate(zTablePath, null, null, currentValue -> {
+    zoo.mutateExisting(zTablePath, currentValue -> {
       String cvs = new String(currentValue, UTF_8);
       String[] tokens = cvs.split(",");
       long flushID = Long.parseLong(tokens[0]);
diff --git a/server/manager/src/main/java/org/apache/accumulo/master/tableOps/compact/cancel/CancelCompactions.java b/server/manager/src/main/java/org/apache/accumulo/master/tableOps/compact/cancel/CancelCompactions.java
index 394c6c8..e4371a2 100644
--- a/server/manager/src/main/java/org/apache/accumulo/master/tableOps/compact/cancel/CancelCompactions.java
+++ b/server/manager/src/main/java/org/apache/accumulo/master/tableOps/compact/cancel/CancelCompactions.java
@@ -67,7 +67,7 @@ public class CancelCompactions extends MasterRepo {
     String[] tokens = cvs.split(",");
     final long flushID = Long.parseLong(tokens[0]);
 
-    zoo.mutate(zCancelID, null, null, currentValue2 -> {
+    zoo.mutateExisting(zCancelID, currentValue2 -> {
       long cid = Long.parseLong(new String(currentValue2, UTF_8));
 
       if (cid < flushID) {
diff --git a/server/manager/src/main/java/org/apache/accumulo/master/tableOps/namespace/rename/RenameNamespace.java b/server/manager/src/main/java/org/apache/accumulo/master/tableOps/namespace/rename/RenameNamespace.java
index db48e20..a2830e5 100644
--- a/server/manager/src/main/java/org/apache/accumulo/master/tableOps/namespace/rename/RenameNamespace.java
+++ b/server/manager/src/main/java/org/apache/accumulo/master/tableOps/namespace/rename/RenameNamespace.java
@@ -18,6 +18,8 @@
  */
 package org.apache.accumulo.master.tableOps.namespace.rename;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import org.apache.accumulo.core.Constants;
 import org.apache.accumulo.core.clientImpl.AcceptableThriftTableOperationException;
 import org.apache.accumulo.core.clientImpl.Tables;
@@ -62,15 +64,15 @@ public class RenameNamespace extends MasterRepo {
       final String tap = master.getZooKeeperRoot() + Constants.ZNAMESPACES + "/" + namespaceId
           + Constants.ZNAMESPACE_NAME;
 
-      zoo.mutate(tap, null, null, current -> {
-        final String currentName = new String(current);
+      zoo.mutateExisting(tap, current -> {
+        final String currentName = new String(current, UTF_8);
         if (currentName.equals(newName))
           return null; // assume in this case the operation is running again, so we are done
         if (!currentName.equals(oldName)) {
           throw new AcceptableThriftTableOperationException(null, oldName, TableOperation.RENAME,
               TableOperationExceptionType.NAMESPACE_NOTFOUND, "Name changed while processing");
         }
-        return newName.getBytes();
+        return newName.getBytes(UTF_8);
       });
       Tables.clearCache(master.getContext());
     } finally {
diff --git a/server/manager/src/main/java/org/apache/accumulo/master/tableOps/rename/RenameTable.java b/server/manager/src/main/java/org/apache/accumulo/master/tableOps/rename/RenameTable.java
index 2df5c47..9e2cae7 100644
--- a/server/manager/src/main/java/org/apache/accumulo/master/tableOps/rename/RenameTable.java
+++ b/server/manager/src/main/java/org/apache/accumulo/master/tableOps/rename/RenameTable.java
@@ -84,7 +84,7 @@ public class RenameTable extends MasterRepo {
       final String tap =
           master.getZooKeeperRoot() + Constants.ZTABLES + "/" + tableId + Constants.ZTABLE_NAME;
 
-      zoo.mutate(tap, null, null, current -> {
+      zoo.mutateExisting(tap, current -> {
         final String currentName = new String(current, UTF_8);
         if (currentName.equals(newName))
           return null; // assume in this case the operation is running again, so we are done
diff --git a/server/manager/src/main/java/org/apache/accumulo/master/upgrade/Upgrader9to10.java b/server/manager/src/main/java/org/apache/accumulo/master/upgrade/Upgrader9to10.java
index f7c6d1a..aaae335 100644
--- a/server/manager/src/main/java/org/apache/accumulo/master/upgrade/Upgrader9to10.java
+++ b/server/manager/src/main/java/org/apache/accumulo/master/upgrade/Upgrader9to10.java
@@ -70,7 +70,6 @@ import org.apache.accumulo.core.spi.compaction.SimpleCompactionDispatcher;
 import org.apache.accumulo.core.tabletserver.log.LogEntry;
 import org.apache.accumulo.core.util.HostAndPort;
 import org.apache.accumulo.fate.zookeeper.ZooReaderWriter;
-import org.apache.accumulo.fate.zookeeper.ZooUtil;
 import org.apache.accumulo.fate.zookeeper.ZooUtil.NodeExistsPolicy;
 import org.apache.accumulo.fate.zookeeper.ZooUtil.NodeMissingPolicy;
 import org.apache.accumulo.server.ServerContext;
@@ -216,9 +215,8 @@ public class Upgrader9to10 implements Upgrader {
       Mutation mutation = getMutation();
 
       try {
-        context.getZooReaderWriter().mutate(context.getZooKeeperRoot() + RootTable.ZROOT_TABLET,
-            new byte[0], ZooUtil.PUBLIC, currVal -> {
-
+        context.getZooReaderWriter().mutateOrCreate(
+            context.getZooKeeperRoot() + RootTable.ZROOT_TABLET, new byte[0], currVal -> {
               // Earlier, it was checked that root tablet metadata did not exists. However the
               // earlier check does handle race conditions. Race conditions are unexpected. This is
               // a sanity check when making the update in ZK using compare and set. If this fails
@@ -226,15 +224,10 @@ public class Upgrader9to10 implements Upgrader {
               // concurrently running upgrade could cause this to fail.
               Preconditions.checkState(currVal.length == 0,
                   "Expected root tablet metadata to be empty!");
-
-              RootTabletMetadata rtm = new RootTabletMetadata();
-
+              var rtm = new RootTabletMetadata();
               rtm.update(mutation);
-
               String json = rtm.toJson();
-
               log.info("Upgrading root tablet metadata, writing following to ZK : \n {}", json);
-
               return json.getBytes(UTF_8);
             });
       } catch (Exception e) {