You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iceberg.apache.org by pv...@apache.org on 2023/04/25 07:13:26 UTC

[iceberg] branch master updated: Hive: Use EnvironmentContext instead of Hive Locks to provide transactional commits after HIVE-26882 (#6570)

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

pvary pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iceberg.git


The following commit(s) were added to refs/heads/master by this push:
     new c3232b6647 Hive: Use EnvironmentContext instead of Hive Locks to provide transactional commits after HIVE-26882 (#6570)
c3232b6647 is described below

commit c3232b664745ebf761b6a74f4c5b55cc48bfd209
Author: pvary <pe...@gmail.com>
AuthorDate: Tue Apr 25 09:13:20 2023 +0200

    Hive: Use EnvironmentContext instead of Hive Locks to provide transactional commits after HIVE-26882 (#6570)
---
 .../java/org/apache/iceberg/TableProperties.java   |  3 +
 .../apache/iceberg/hadoop/ConfigProperties.java    |  1 +
 docs/configuration.md                              | 13 +++++
 .../apache/iceberg/hive/HiveTableOperations.java   | 66 +++++++++++++++++++--
 .../org/apache/iceberg/hive/MetastoreUtil.java     | 28 +++++++--
 .../main/java/org/apache/iceberg/hive/NoLock.java  | 27 +++++++--
 .../apache/iceberg/hive/TestHiveCommitLocks.java   | 41 +++++++++++++
 .../org/apache/iceberg/hive/TestHiveCommits.java   | 67 ++++++++++++++++++----
 8 files changed, 217 insertions(+), 29 deletions(-)

diff --git a/core/src/main/java/org/apache/iceberg/TableProperties.java b/core/src/main/java/org/apache/iceberg/TableProperties.java
index 4a1b6678b8..b14354def6 100644
--- a/core/src/main/java/org/apache/iceberg/TableProperties.java
+++ b/core/src/main/java/org/apache/iceberg/TableProperties.java
@@ -303,6 +303,9 @@ public class TableProperties {
   public static final String ENGINE_HIVE_ENABLED = "engine.hive.enabled";
   public static final boolean ENGINE_HIVE_ENABLED_DEFAULT = false;
 
+  public static final String HIVE_LOCK_ENABLED = "engine.hive.lock-enabled";
+  public static final boolean HIVE_LOCK_ENABLED_DEFAULT = true;
+
   public static final String WRITE_DISTRIBUTION_MODE = "write.distribution-mode";
   public static final String WRITE_DISTRIBUTION_MODE_NONE = "none";
   public static final String WRITE_DISTRIBUTION_MODE_HASH = "hash";
diff --git a/core/src/main/java/org/apache/iceberg/hadoop/ConfigProperties.java b/core/src/main/java/org/apache/iceberg/hadoop/ConfigProperties.java
index a8bc21af06..f87b23b3d5 100644
--- a/core/src/main/java/org/apache/iceberg/hadoop/ConfigProperties.java
+++ b/core/src/main/java/org/apache/iceberg/hadoop/ConfigProperties.java
@@ -23,5 +23,6 @@ public class ConfigProperties {
   private ConfigProperties() {}
 
   public static final String ENGINE_HIVE_ENABLED = "iceberg.engine.hive.enabled";
+  public static final String LOCK_HIVE_ENABLED = "iceberg.engine.hive.lock-enabled";
   public static final String KEEP_HIVE_STATS = "iceberg.hive.keep.stats";
 }
diff --git a/docs/configuration.md b/docs/configuration.md
index 8efd8fc12a..b2c190c134 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -178,8 +178,21 @@ The HMS table locking is a 2-step process:
 | iceberg.hive.lock-heartbeat-interval-ms   | 240000 (4 min)  | The heartbeat interval for the HMS locks.                                    |
 | iceberg.hive.metadata-refresh-max-retries | 2               | Maximum number of retries when the metadata file is missing                  |
 | iceberg.hive.table-level-lock-evict-ms    | 600000 (10 min) | The timeout for the JVM table lock is                                        |
+| iceberg.engine.hive.lock-enabled          | true            | Use HMS locks to ensure atomicity of commits                                 |
 
 Note: `iceberg.hive.lock-check-max-wait-ms` and `iceberg.hive.lock-heartbeat-interval-ms` should be less than the [transaction timeout](https://cwiki.apache.org/confluence/display/Hive/Configuration+Properties#ConfigurationProperties-hive.txn.timeout) 
 of the Hive Metastore (`hive.txn.timeout` or `metastore.txn.timeout` in the newer versions). Otherwise, the heartbeats on the lock (which happens during the lock checks) would end up expiring in the 
 Hive Metastore before the lock is retried from Iceberg.
 
+Warn: Setting `iceberg.engine.hive.lock-enabled`=`false` will cause HiveCatalog to commit to tables without using Hive locks.
+This should only be set to `false` if all following conditions are met:
+ - [HIVE-26882](https://issues.apache.org/jira/browse/HIVE-26882)
+is available on the Hive Metastore server
+ - All other HiveCatalogs committing to tables that this HiveCatalog commits to are also on Iceberg 1.3 or later
+ - All other HiveCatalogs committing to tables that this HiveCatalog commits to have also disabled Hive locks on commit.
+
+**Failing to ensure these conditions risks corrupting the table.**
+
+Even with `iceberg.engine.hive.lock-enabled` set to `false`, a HiveCatalog can still use locks for individual tables by setting the table property `engine.hive.lock-enabled`=`true`.
+This is useful in the case where other HiveCatalogs cannot be upgraded and set to commit without using Hive locks.
+
diff --git a/hive-metastore/src/main/java/org/apache/iceberg/hive/HiveTableOperations.java b/hive-metastore/src/main/java/org/apache/iceberg/hive/HiveTableOperations.java
index 5c51af2f61..2723353741 100644
--- a/hive-metastore/src/main/java/org/apache/iceberg/hive/HiveTableOperations.java
+++ b/hive-metastore/src/main/java/org/apache/iceberg/hive/HiveTableOperations.java
@@ -80,6 +80,8 @@ public class HiveTableOperations extends BaseMetastoreTableOperations {
   // characters, see https://issues.apache.org/jira/browse/HIVE-12274
   // set to 0 to not expose Iceberg metadata in HMS Table properties.
   private static final String HIVE_TABLE_PROPERTY_MAX_SIZE = "iceberg.hive.table-property-max-size";
+  private static final String NO_LOCK_EXPECTED_KEY = "expected_parameter_key";
+  private static final String NO_LOCK_EXPECTED_VALUE = "expected_parameter_value";
   private static final long HIVE_TABLE_PROPERTY_MAX_SIZE_DEFAULT = 32672;
   private static final int HIVE_ICEBERG_METADATA_REFRESH_MAX_RETRIES_DEFAULT = 2;
   private static final BiMap<String, String> ICEBERG_TO_HMS_TRANSLATION =
@@ -187,7 +189,7 @@ public class HiveTableOperations extends BaseMetastoreTableOperations {
     CommitStatus commitStatus = CommitStatus.FAILURE;
     boolean updateHiveTable = false;
 
-    HiveLock lock = lockObject();
+    HiveLock lock = lockObject(metadata);
     try {
       lock.lock();
 
@@ -242,7 +244,8 @@ public class HiveTableOperations extends BaseMetastoreTableOperations {
       lock.ensureActive();
 
       try {
-        persistTable(tbl, updateHiveTable);
+        persistTable(
+            tbl, updateHiveTable, hiveLockEnabled(metadata, conf) ? null : baseMetadataLocation);
         lock.ensureActive();
 
         commitStatus = CommitStatus.SUCCESS;
@@ -263,6 +266,15 @@ public class HiveTableOperations extends BaseMetastoreTableOperations {
         throw e;
 
       } catch (Throwable e) {
+        if (e.getMessage()
+            .contains(
+                "The table has been modified. The parameter value for key '"
+                    + HiveTableOperations.METADATA_LOCATION_PROP
+                    + "' is")) {
+          throw new CommitFailedException(
+              e, "The table %s.%s has been modified concurrently", database, tableName);
+        }
+
         if (e.getMessage() != null
             && e.getMessage().contains("Table/View 'HIVE_LOCKS' does not exist")) {
           throw new RuntimeException(
@@ -307,12 +319,23 @@ public class HiveTableOperations extends BaseMetastoreTableOperations {
   }
 
   @VisibleForTesting
-  void persistTable(Table hmsTable, boolean updateHiveTable)
+  void persistTable(Table hmsTable, boolean updateHiveTable, String expectedMetadataLocation)
       throws TException, InterruptedException {
     if (updateHiveTable) {
       metaClients.run(
           client -> {
-            MetastoreUtil.alterTable(client, database, tableName, hmsTable);
+            MetastoreUtil.alterTable(
+                client,
+                database,
+                tableName,
+                hmsTable,
+                expectedMetadataLocation != null
+                    ? ImmutableMap.of(
+                        NO_LOCK_EXPECTED_KEY,
+                        METADATA_LOCATION_PROP,
+                        NO_LOCK_EXPECTED_VALUE,
+                        expectedMetadataLocation)
+                    : ImmutableMap.of());
             return null;
           });
     } else {
@@ -572,8 +595,39 @@ public class HiveTableOperations extends BaseMetastoreTableOperations {
         ConfigProperties.ENGINE_HIVE_ENABLED, TableProperties.ENGINE_HIVE_ENABLED_DEFAULT);
   }
 
+  /**
+   * Returns if the hive locking should be enabled on the table, or not.
+   *
+   * <p>The decision is made like this:
+   *
+   * <ol>
+   *   <li>Table property value {@link TableProperties#HIVE_LOCK_ENABLED}
+   *   <li>If the table property is not set then check the hive-site.xml property value {@link
+   *       ConfigProperties#LOCK_HIVE_ENABLED}
+   *   <li>If none of the above is enabled then use the default value {@link
+   *       TableProperties#HIVE_LOCK_ENABLED_DEFAULT}
+   * </ol>
+   *
+   * @param metadata Table metadata to use
+   * @param conf The hive configuration to use
+   * @return if the hive engine related values should be enabled or not
+   */
+  private static boolean hiveLockEnabled(TableMetadata metadata, Configuration conf) {
+    if (metadata.properties().get(TableProperties.HIVE_LOCK_ENABLED) != null) {
+      // We know that the property is set, so default value will not be used,
+      return metadata.propertyAsBoolean(TableProperties.HIVE_LOCK_ENABLED, false);
+    }
+
+    return conf.getBoolean(
+        ConfigProperties.LOCK_HIVE_ENABLED, TableProperties.HIVE_LOCK_ENABLED_DEFAULT);
+  }
+
   @VisibleForTesting
-  HiveLock lockObject() {
-    return new MetastoreLock(conf, metaClients, catalogName, database, tableName);
+  HiveLock lockObject(TableMetadata metadata) {
+    if (hiveLockEnabled(metadata, conf)) {
+      return new MetastoreLock(conf, metaClients, catalogName, database, tableName);
+    } else {
+      return new NoLock();
+    }
   }
 }
diff --git a/hive-metastore/src/main/java/org/apache/iceberg/hive/MetastoreUtil.java b/hive-metastore/src/main/java/org/apache/iceberg/hive/MetastoreUtil.java
index 7d9dd32e4c..83ff2b60d0 100644
--- a/hive-metastore/src/main/java/org/apache/iceberg/hive/MetastoreUtil.java
+++ b/hive-metastore/src/main/java/org/apache/iceberg/hive/MetastoreUtil.java
@@ -18,12 +18,14 @@
  */
 package org.apache.iceberg.hive;
 
+import java.util.Map;
 import org.apache.hadoop.hive.common.StatsSetupConst;
 import org.apache.hadoop.hive.metastore.IMetaStoreClient;
 import org.apache.hadoop.hive.metastore.api.EnvironmentContext;
 import org.apache.hadoop.hive.metastore.api.Table;
 import org.apache.iceberg.common.DynMethods;
 import org.apache.iceberg.relocated.com.google.common.collect.ImmutableMap;
+import org.apache.iceberg.relocated.com.google.common.collect.Maps;
 
 public class MetastoreUtil {
   private static final DynMethods.UnboundMethod ALTER_TABLE =
@@ -48,14 +50,28 @@ public class MetastoreUtil {
   private MetastoreUtil() {}
 
   /**
-   * Calls alter_table method using the metastore client. If possible, an environmental context will
-   * be used that turns off stats updates to avoid recursive listing.
+   * Calls alter_table method using the metastore client. If the HMS supports it, environmental
+   * context will be set in a way that turns off stats updates to avoid recursive file listing.
    */
   public static void alterTable(
       IMetaStoreClient client, String databaseName, String tblName, Table table) {
-    EnvironmentContext envContext =
-        new EnvironmentContext(
-            ImmutableMap.of(StatsSetupConst.DO_NOT_UPDATE_STATS, StatsSetupConst.TRUE));
-    ALTER_TABLE.invoke(client, databaseName, tblName, table, envContext);
+    alterTable(client, databaseName, tblName, table, ImmutableMap.of());
+  }
+
+  /**
+   * Calls alter_table method using the metastore client. If the HMS supports it, environmental
+   * context will be set in a way that turns off stats updates to avoid recursive file listing.
+   */
+  public static void alterTable(
+      IMetaStoreClient client,
+      String databaseName,
+      String tblName,
+      Table table,
+      Map<String, String> extraEnv) {
+    Map<String, String> env = Maps.newHashMapWithExpectedSize(extraEnv.size() + 1);
+    env.putAll(extraEnv);
+    env.put(StatsSetupConst.DO_NOT_UPDATE_STATS, StatsSetupConst.TRUE);
+
+    ALTER_TABLE.invoke(client, databaseName, tblName, table, new EnvironmentContext(env));
   }
 }
diff --git a/core/src/main/java/org/apache/iceberg/hadoop/ConfigProperties.java b/hive-metastore/src/main/java/org/apache/iceberg/hive/NoLock.java
similarity index 59%
copy from core/src/main/java/org/apache/iceberg/hadoop/ConfigProperties.java
copy to hive-metastore/src/main/java/org/apache/iceberg/hive/NoLock.java
index a8bc21af06..8085d11c82 100644
--- a/core/src/main/java/org/apache/iceberg/hadoop/ConfigProperties.java
+++ b/hive-metastore/src/main/java/org/apache/iceberg/hive/NoLock.java
@@ -16,12 +16,29 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.iceberg.hadoop;
+package org.apache.iceberg.hive;
 
-public class ConfigProperties {
+import org.apache.iceberg.relocated.com.google.common.base.Preconditions;
 
-  private ConfigProperties() {}
+public class NoLock implements HiveLock {
+  public NoLock() {
+    Preconditions.checkArgument(
+        HiveVersion.min(HiveVersion.HIVE_2),
+        "Minimally Hive 2 HMS client is needed to use HIVE-26882 based locking");
+  }
 
-  public static final String ENGINE_HIVE_ENABLED = "iceberg.engine.hive.enabled";
-  public static final String KEEP_HIVE_STATS = "iceberg.hive.keep.stats";
+  @Override
+  public void lock() throws LockException {
+    // no-op
+  }
+
+  @Override
+  public void ensureActive() throws LockException {
+    // no-op
+  }
+
+  @Override
+  public void unlock() {
+    // no-op
+  }
 }
diff --git a/hive-metastore/src/test/java/org/apache/iceberg/hive/TestHiveCommitLocks.java b/hive-metastore/src/test/java/org/apache/iceberg/hive/TestHiveCommitLocks.java
index d64653301e..c687ae36a6 100644
--- a/hive-metastore/src/test/java/org/apache/iceberg/hive/TestHiveCommitLocks.java
+++ b/hive-metastore/src/test/java/org/apache/iceberg/hive/TestHiveCommitLocks.java
@@ -33,6 +33,7 @@ import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import java.util.Collections;
+import java.util.Map;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
@@ -42,6 +43,7 @@ import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.hive.conf.HiveConf;
 import org.apache.hadoop.hive.metastore.HiveMetaStoreClient;
 import org.apache.hadoop.hive.metastore.IMetaStoreClient;
+import org.apache.hadoop.hive.metastore.api.EnvironmentContext;
 import org.apache.hadoop.hive.metastore.api.LockRequest;
 import org.apache.hadoop.hive.metastore.api.LockResponse;
 import org.apache.hadoop.hive.metastore.api.LockState;
@@ -52,6 +54,7 @@ import org.apache.iceberg.HasTableOperations;
 import org.apache.iceberg.Table;
 import org.apache.iceberg.TableMetadata;
 import org.apache.iceberg.exceptions.CommitFailedException;
+import org.apache.iceberg.hadoop.ConfigProperties;
 import org.apache.iceberg.relocated.com.google.common.collect.ImmutableMap;
 import org.apache.iceberg.relocated.com.google.common.collect.Lists;
 import org.apache.iceberg.types.Types;
@@ -494,4 +497,42 @@ public class TestHiveCommitLocks extends HiveTableBaseTest {
         "Failed to heartbeat for hive lock. Failed to heart beat.",
         () -> spyOps.doCommit(metadataV2, metadataV1));
   }
+
+  @Test
+  public void testNoLockCallsWithNoLock() throws TException {
+    Configuration confWithLock = new Configuration(overriddenHiveConf);
+    confWithLock.setBoolean(ConfigProperties.LOCK_HIVE_ENABLED, false);
+
+    HiveTableOperations noLockSpyOps =
+        spy(
+            new HiveTableOperations(
+                confWithLock,
+                spyCachedClientPool,
+                ops.io(),
+                catalog.name(),
+                TABLE_IDENTIFIER.namespace().level(0),
+                TABLE_IDENTIFIER.name()));
+
+    ArgumentCaptor<EnvironmentContext> contextCaptor =
+        ArgumentCaptor.forClass(EnvironmentContext.class);
+
+    doNothing()
+        .when(spyClient)
+        .alter_table_with_environmentContext(any(), any(), any(), contextCaptor.capture());
+
+    noLockSpyOps.doCommit(metadataV2, metadataV1);
+
+    // Make sure that the locking is not used
+    verify(spyClient, never()).lock(any(LockRequest.class));
+    verify(spyClient, never()).checkLock(any(Long.class));
+    verify(spyClient, never()).heartbeat(any(Long.class), any(Long.class));
+    verify(spyClient, never()).unlock(any(Long.class));
+
+    // Make sure that the expected parameter context values are set
+    Map<String, String> context = contextCaptor.getValue().getProperties();
+    Assert.assertEquals(3, context.size());
+    Assert.assertEquals(
+        context.get("expected_parameter_key"), HiveTableOperations.METADATA_LOCATION_PROP);
+    Assert.assertEquals(context.get("expected_parameter_value"), metadataV2.metadataFileLocation());
+  }
 }
diff --git a/hive-metastore/src/test/java/org/apache/iceberg/hive/TestHiveCommits.java b/hive-metastore/src/test/java/org/apache/iceberg/hive/TestHiveCommits.java
index b9799cefe6..96c39ce30d 100644
--- a/hive-metastore/src/test/java/org/apache/iceberg/hive/TestHiveCommits.java
+++ b/hive-metastore/src/test/java/org/apache/iceberg/hive/TestHiveCommits.java
@@ -21,12 +21,12 @@ package org.apache.iceberg.hive;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyBoolean;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
 import java.io.File;
-import java.net.UnknownHostException;
 import java.util.concurrent.atomic.AtomicReference;
 import org.apache.iceberg.AssertHelpers;
 import org.apache.iceberg.HasTableOperations;
@@ -35,6 +35,7 @@ import org.apache.iceberg.Table;
 import org.apache.iceberg.TableMetadata;
 import org.apache.iceberg.catalog.TableIdentifier;
 import org.apache.iceberg.exceptions.AlreadyExistsException;
+import org.apache.iceberg.exceptions.CommitFailedException;
 import org.apache.iceberg.exceptions.CommitStateUnknownException;
 import org.apache.iceberg.exceptions.ValidationException;
 import org.apache.iceberg.types.Types;
@@ -45,7 +46,7 @@ import org.junit.Test;
 public class TestHiveCommits extends HiveTableBaseTest {
 
   @Test
-  public void testSuppressUnlockExceptions() throws TException, InterruptedException {
+  public void testSuppressUnlockExceptions() {
     Table table = catalog.loadTable(TABLE_IDENTIFIER);
     HiveTableOperations ops = (HiveTableOperations) ((HasTableOperations) table).operations();
 
@@ -63,7 +64,7 @@ public class TestHiveCommits extends HiveTableBaseTest {
 
     AtomicReference<HiveLock> lockRef = new AtomicReference<>();
 
-    when(spyOps.lockObject())
+    when(spyOps.lockObject(metadataV1))
         .thenAnswer(
             i -> {
               HiveLock lock = (HiveLock) i.callRealMethod();
@@ -259,8 +260,7 @@ public class TestHiveCommits extends HiveTableBaseTest {
    * because it isn't the current one during the recheck phase.
    */
   @Test
-  public void testThriftExceptionConcurrentCommit()
-      throws TException, InterruptedException, UnknownHostException {
+  public void testThriftExceptionConcurrentCommit() throws TException, InterruptedException {
     Table table = catalog.loadTable(TABLE_IDENTIFIER);
     HiveTableOperations ops = (HiveTableOperations) ((HasTableOperations) table).operations();
 
@@ -279,11 +279,11 @@ public class TestHiveCommits extends HiveTableBaseTest {
     AtomicReference<HiveLock> lock = new AtomicReference<>();
     doAnswer(
             l -> {
-              lock.set(ops.lockObject());
+              lock.set(ops.lockObject(metadataV1));
               return lock.get();
             })
         .when(spyOps)
-        .lockObject();
+        .lockObject(metadataV1);
 
     concurrentCommitAndThrowException(ops, spyOps, table, lock);
 
@@ -320,6 +320,47 @@ public class TestHiveCommits extends HiveTableBaseTest {
         () -> catalog.createTable(TABLE_IDENTIFIER, schema, PartitionSpec.unpartitioned()));
   }
 
+  /** Uses NoLock and pretends we throw an error because of a concurrent commit */
+  @Test
+  public void testNoLockThriftExceptionConcurrentCommit() throws TException, InterruptedException {
+    Table table = catalog.loadTable(TABLE_IDENTIFIER);
+    HiveTableOperations ops = (HiveTableOperations) ((HasTableOperations) table).operations();
+
+    TableMetadata metadataV1 = ops.current();
+
+    table.updateSchema().addColumn("n", Types.IntegerType.get()).commit();
+
+    ops.refresh();
+
+    TableMetadata metadataV2 = ops.current();
+
+    Assert.assertEquals(2, ops.current().schema().columns().size());
+
+    HiveTableOperations spyOps = spy(ops);
+
+    // Sets NoLock
+    doReturn(new NoLock()).when(spyOps).lockObject(any());
+
+    // Simulate a concurrent table modification error
+    doThrow(
+            new RuntimeException(
+                "MetaException(message:The table has been modified. The parameter value for key 'metadata_location' is"))
+        .when(spyOps)
+        .persistTable(any(), anyBoolean(), any());
+
+    // Should throw a CommitFailedException so the commit could be retried
+    AssertHelpers.assertThrows(
+        "Should throw CommitFailedException since the table has been modified concurrently",
+        CommitFailedException.class,
+        "has been modified concurrently",
+        () -> spyOps.commit(metadataV2, metadataV1));
+
+    ops.refresh();
+    Assert.assertEquals("Current metadata should not have changed", metadataV2, ops.current());
+    Assert.assertTrue("Current metadata should still exist", metadataFileExists(metadataV2));
+    Assert.assertEquals("New metadata files should not exist", 2, metadataFileCount(ops.current()));
+  }
+
   private void commitAndThrowException(
       HiveTableOperations realOperations, HiveTableOperations spyOperations)
       throws TException, InterruptedException {
@@ -328,11 +369,12 @@ public class TestHiveCommits extends HiveTableBaseTest {
             i -> {
               org.apache.hadoop.hive.metastore.api.Table tbl =
                   i.getArgument(0, org.apache.hadoop.hive.metastore.api.Table.class);
-              realOperations.persistTable(tbl, true);
+              String location = i.getArgument(2, String.class);
+              realOperations.persistTable(tbl, true, location);
               throw new TException("Datacenter on fire");
             })
         .when(spyOperations)
-        .persistTable(any(), anyBoolean());
+        .persistTable(any(), anyBoolean(), any());
   }
 
   private void concurrentCommitAndThrowException(
@@ -346,7 +388,8 @@ public class TestHiveCommits extends HiveTableBaseTest {
             i -> {
               org.apache.hadoop.hive.metastore.api.Table tbl =
                   i.getArgument(0, org.apache.hadoop.hive.metastore.api.Table.class);
-              realOperations.persistTable(tbl, true);
+              String location = i.getArgument(2, String.class);
+              realOperations.persistTable(tbl, true, location);
               // Simulate lock expiration or removal
               lock.get().unlock();
               table.refresh();
@@ -354,14 +397,14 @@ public class TestHiveCommits extends HiveTableBaseTest {
               throw new TException("Datacenter on fire");
             })
         .when(spyOperations)
-        .persistTable(any(), anyBoolean());
+        .persistTable(any(), anyBoolean(), any());
   }
 
   private void failCommitAndThrowException(HiveTableOperations spyOperations)
       throws TException, InterruptedException {
     doThrow(new TException("Datacenter on fire"))
         .when(spyOperations)
-        .persistTable(any(), anyBoolean());
+        .persistTable(any(), anyBoolean(), any());
   }
 
   private void breakFallbackCatalogCommitCheck(HiveTableOperations spyOperations) {