You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@accumulo.apache.org by cs...@apache.org on 2023/05/05 12:43:07 UTC

[accumulo] branch 2.1 updated: Reuse already read lastLocation information during location updates (#3331)

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

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


The following commit(s) were added to refs/heads/2.1 by this push:
     new 25959b438c Reuse already read lastLocation information during location updates (#3331)
25959b438c is described below

commit 25959b438c75bc858efcae1b6652a55cc336e777
Author: Christopher L. Shannon <ch...@gmail.com>
AuthorDate: Fri May 5 08:43:01 2023 -0400

    Reuse already read lastLocation information during location updates (#3331)
    
    This change will reuse the previously read lastLocation information when
    ManagerMetadataUtil updates last location to prevent unnecessary
    metadata reads.
    
    This closes #3301
---
 .../core/metadata/schema/TabletMetadata.java       |  10 ++
 .../accumulo/server/manager/state/Assignment.java  |  14 ++-
 .../server/manager/state/MetaDataStateStore.java   |   8 +-
 .../server/manager/state/UnassignedTablet.java     |  84 +++++++++++++++++
 .../server/manager/state/ZooTabletStateStore.java  |   8 +-
 .../accumulo/server/util/ManagerMetadataUtil.java  |  14 +--
 .../manager/state/RootTabletStateStoreTest.java    |   4 +-
 .../server/util/ManagerMetadataUtilTest.java       | 102 +++++++++++++++++++++
 .../java/org/apache/accumulo/manager/Manager.java  |   9 +-
 .../accumulo/manager/TabletGroupWatcher.java       |  51 ++++++-----
 .../apache/accumulo/tserver/AssignmentHandler.java |   3 +-
 .../accumulo/test/functional/SplitRecoveryIT.java  |   2 +-
 .../apache/accumulo/test/manager/MergeStateIT.java |   2 +-
 .../accumulo/test/performance/NullTserver.java     |   2 +-
 14 files changed, 265 insertions(+), 48 deletions(-)

diff --git a/core/src/main/java/org/apache/accumulo/core/metadata/schema/TabletMetadata.java b/core/src/main/java/org/apache/accumulo/core/metadata/schema/TabletMetadata.java
index 2fb16fb092..323f0cb142 100644
--- a/core/src/main/java/org/apache/accumulo/core/metadata/schema/TabletMetadata.java
+++ b/core/src/main/java/org/apache/accumulo/core/metadata/schema/TabletMetadata.java
@@ -192,6 +192,16 @@ public class TabletMetadata {
       return Objects.hash(tServerInstance, lt);
     }
 
+    @Override
+    public String toString() {
+      StringBuilder sb = new StringBuilder(32);
+      sb.append("Location [");
+      sb.append("server=").append(tServerInstance);
+      sb.append(", type=").append(lt);
+      sb.append("]");
+      return sb.toString();
+    }
+
     public static Location last(TServerInstance instance) {
       return new Location(instance, LocationType.LAST);
     }
diff --git a/server/base/src/main/java/org/apache/accumulo/server/manager/state/Assignment.java b/server/base/src/main/java/org/apache/accumulo/server/manager/state/Assignment.java
index a9e52d1741..c59dac90c0 100644
--- a/server/base/src/main/java/org/apache/accumulo/server/manager/state/Assignment.java
+++ b/server/base/src/main/java/org/apache/accumulo/server/manager/state/Assignment.java
@@ -20,13 +20,21 @@ package org.apache.accumulo.server.manager.state;
 
 import org.apache.accumulo.core.dataImpl.KeyExtent;
 import org.apache.accumulo.core.metadata.TServerInstance;
+import org.apache.accumulo.core.metadata.schema.TabletMetadata;
+import org.apache.accumulo.core.metadata.schema.TabletMetadata.Location;
+
+import com.google.common.base.Preconditions;
 
 public class Assignment {
-  public KeyExtent tablet;
-  public TServerInstance server;
+  public final KeyExtent tablet;
+  public final TServerInstance server;
+  public final Location lastLocation;
 
-  public Assignment(KeyExtent tablet, TServerInstance server) {
+  public Assignment(KeyExtent tablet, TServerInstance server, Location lastLocation) {
+    Preconditions.checkArgument(
+        lastLocation == null || lastLocation.getType() == TabletMetadata.LocationType.LAST);
     this.tablet = tablet;
     this.server = server;
+    this.lastLocation = lastLocation;
   }
 }
diff --git a/server/base/src/main/java/org/apache/accumulo/server/manager/state/MetaDataStateStore.java b/server/base/src/main/java/org/apache/accumulo/server/manager/state/MetaDataStateStore.java
index 680f4d1db9..bb27b32cda 100644
--- a/server/base/src/main/java/org/apache/accumulo/server/manager/state/MetaDataStateStore.java
+++ b/server/base/src/main/java/org/apache/accumulo/server/manager/state/MetaDataStateStore.java
@@ -63,8 +63,8 @@ class MetaDataStateStore implements TabletStateStore {
       for (Assignment assignment : assignments) {
         TabletMutator tabletMutator = tabletsMutator.mutateTablet(assignment.tablet);
         tabletMutator.putLocation(Location.current(assignment.server));
-        ManagerMetadataUtil.updateLastForAssignmentMode(context, ample, tabletMutator,
-            assignment.tablet, assignment.server);
+        ManagerMetadataUtil.updateLastForAssignmentMode(context, tabletMutator, assignment.server,
+            assignment.lastLocation);
         tabletMutator.deleteLocation(Location.future(assignment.server));
         tabletMutator.deleteSuspension();
         tabletMutator.mutate();
@@ -107,8 +107,8 @@ class MetaDataStateStore implements TabletStateStore {
       for (TabletLocationState tls : tablets) {
         TabletMutator tabletMutator = tabletsMutator.mutateTablet(tls.extent);
         if (tls.current != null) {
-          ManagerMetadataUtil.updateLastForAssignmentMode(context, ample, tabletMutator, tls.extent,
-              tls.current.getServerInstance());
+          ManagerMetadataUtil.updateLastForAssignmentMode(context, tabletMutator,
+              tls.current.getServerInstance(), tls.last);
           tabletMutator.deleteLocation(tls.current);
           if (logsForDeadServers != null) {
             List<Path> logs = logsForDeadServers.get(tls.current.getServerInstance());
diff --git a/server/base/src/main/java/org/apache/accumulo/server/manager/state/UnassignedTablet.java b/server/base/src/main/java/org/apache/accumulo/server/manager/state/UnassignedTablet.java
new file mode 100644
index 0000000000..6f57d7d563
--- /dev/null
+++ b/server/base/src/main/java/org/apache/accumulo/server/manager/state/UnassignedTablet.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.accumulo.server.manager.state;
+
+import java.util.Objects;
+
+import org.apache.accumulo.core.metadata.TServerInstance;
+import org.apache.accumulo.core.metadata.schema.TabletMetadata;
+import org.apache.accumulo.core.metadata.schema.TabletMetadata.Location;
+
+import com.google.common.base.Preconditions;
+
+public class UnassignedTablet {
+  private final Location location;
+  private final Location lastLocation;
+
+  public UnassignedTablet(Location location, Location lastLocation) {
+    Preconditions.checkArgument(
+        lastLocation == null || lastLocation.getType() == TabletMetadata.LocationType.LAST);
+    this.location = location;
+    this.lastLocation = lastLocation;
+  }
+
+  public Location getLocation() {
+    return location;
+  }
+
+  public Location getLastLocation() {
+    return lastLocation;
+  }
+
+  public TServerInstance getServerInstance() {
+    return location != null ? location.getServerInstance() : null;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    UnassignedTablet that = (UnassignedTablet) o;
+    return Objects.equals(location, that.location)
+        && Objects.equals(lastLocation, that.lastLocation);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(location, lastLocation);
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder(128);
+    sb.append("UnassignedTablet [").append("location:");
+    if (location != null) {
+      sb.append(location.getType()).append("=").append(location.getServerInstance());
+    }
+    sb.append(", lastLocation:");
+    if (lastLocation != null) {
+      sb.append(lastLocation.getType()).append("=").append(lastLocation.getServerInstance());
+    }
+    sb.append("]");
+    return sb.toString();
+  }
+}
diff --git a/server/base/src/main/java/org/apache/accumulo/server/manager/state/ZooTabletStateStore.java b/server/base/src/main/java/org/apache/accumulo/server/manager/state/ZooTabletStateStore.java
index 0930b6c34f..ae7643d32d 100644
--- a/server/base/src/main/java/org/apache/accumulo/server/manager/state/ZooTabletStateStore.java
+++ b/server/base/src/main/java/org/apache/accumulo/server/manager/state/ZooTabletStateStore.java
@@ -138,8 +138,8 @@ class ZooTabletStateStore implements TabletStateStore {
 
     TabletMutator tabletMutator = ample.mutateTablet(assignment.tablet);
     tabletMutator.putLocation(Location.current(assignment.server));
-    ManagerMetadataUtil.updateLastForAssignmentMode(context, ample, tabletMutator,
-        assignment.tablet, assignment.server);
+    ManagerMetadataUtil.updateLastForAssignmentMode(context, tabletMutator, assignment.server,
+        assignment.lastLocation);
     tabletMutator.deleteLocation(Location.future(assignment.server));
 
     tabletMutator.mutate();
@@ -161,8 +161,8 @@ class ZooTabletStateStore implements TabletStateStore {
 
     tabletMutator.deleteLocation(Location.future(futureOrCurrent));
     tabletMutator.deleteLocation(Location.current(futureOrCurrent));
-    ManagerMetadataUtil.updateLastForAssignmentMode(context, ample, tabletMutator, tls.extent,
-        futureOrCurrent);
+    ManagerMetadataUtil.updateLastForAssignmentMode(context, tabletMutator, futureOrCurrent,
+        tls.last);
     if (logsForDeadServers != null) {
       List<Path> logs = logsForDeadServers.get(futureOrCurrent);
       if (logs != null) {
diff --git a/server/base/src/main/java/org/apache/accumulo/server/util/ManagerMetadataUtil.java b/server/base/src/main/java/org/apache/accumulo/server/util/ManagerMetadataUtil.java
index e3d8ff63a6..c042ece5e6 100644
--- a/server/base/src/main/java/org/apache/accumulo/server/util/ManagerMetadataUtil.java
+++ b/server/base/src/main/java/org/apache/accumulo/server/util/ManagerMetadataUtil.java
@@ -64,6 +64,8 @@ import org.apache.zookeeper.KeeperException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.base.Preconditions;
+
 public class ManagerMetadataUtil {
 
   private static final Logger log = LoggerFactory.getLogger(ManagerMetadataUtil.class);
@@ -253,18 +255,18 @@ public class ManagerMetadataUtil {
    * last location if needed and set the new last location
    *
    * @param context The server context
-   * @param ample The metadata persistence layer
    * @param tabletMutator The mutator being built
-   * @param extent The tablet extent
    * @param location The new location
+   * @param lastLocation The previous last location, which may be null
    */
-  public static void updateLastForAssignmentMode(ClientContext context, Ample ample,
-      Ample.TabletMutator tabletMutator, KeyExtent extent, TServerInstance location) {
+  public static void updateLastForAssignmentMode(ClientContext context,
+      Ample.TabletMutator tabletMutator, TServerInstance location, Location lastLocation) {
+    Preconditions.checkArgument(
+        lastLocation == null || lastLocation.getType() == TabletMetadata.LocationType.LAST);
+
     // if the location mode is assignment, then preserve the current location in the last
     // location value
     if ("assignment".equals(context.getConfiguration().get(Property.TSERV_LAST_LOCATION_MODE))) {
-      TabletMetadata lastMetadata = ample.readTablet(extent, TabletMetadata.ColumnType.LAST);
-      Location lastLocation = (lastMetadata == null ? null : lastMetadata.getLast());
       ManagerMetadataUtil.updateLocation(tabletMutator, lastLocation, Location.last(location));
     }
   }
diff --git a/server/base/src/test/java/org/apache/accumulo/server/manager/state/RootTabletStateStoreTest.java b/server/base/src/test/java/org/apache/accumulo/server/manager/state/RootTabletStateStoreTest.java
index e5ba3fec83..a6fdda4a0b 100644
--- a/server/base/src/test/java/org/apache/accumulo/server/manager/state/RootTabletStateStoreTest.java
+++ b/server/base/src/test/java/org/apache/accumulo/server/manager/state/RootTabletStateStoreTest.java
@@ -98,7 +98,7 @@ public class RootTabletStateStoreTest {
     String sessionId = "this is my unique session data";
     TServerInstance server =
         new TServerInstance(HostAndPort.fromParts("127.0.0.1", 10000), sessionId);
-    List<Assignment> assignments = Collections.singletonList(new Assignment(root, server));
+    List<Assignment> assignments = Collections.singletonList(new Assignment(root, server, null));
     tstore.setFutureLocations(assignments);
     int count = 0;
     for (TabletLocationState location : tstore) {
@@ -135,7 +135,7 @@ public class RootTabletStateStoreTest {
     assertEquals(count, 1);
 
     KeyExtent notRoot = new KeyExtent(TableId.of("0"), null, null);
-    final var assignmentList = List.of(new Assignment(notRoot, server));
+    final var assignmentList = List.of(new Assignment(notRoot, server, null));
 
     assertThrows(IllegalArgumentException.class, () -> tstore.setLocations(assignmentList));
     assertThrows(IllegalArgumentException.class, () -> tstore.setFutureLocations(assignmentList));
diff --git a/server/base/src/test/java/org/apache/accumulo/server/util/ManagerMetadataUtilTest.java b/server/base/src/test/java/org/apache/accumulo/server/util/ManagerMetadataUtilTest.java
new file mode 100644
index 0000000000..748dffaa65
--- /dev/null
+++ b/server/base/src/test/java/org/apache/accumulo/server/util/ManagerMetadataUtilTest.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.accumulo.server.util;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.apache.accumulo.core.clientImpl.ClientContext;
+import org.apache.accumulo.core.conf.AccumuloConfiguration;
+import org.apache.accumulo.core.conf.Property;
+import org.apache.accumulo.core.metadata.TServerInstance;
+import org.apache.accumulo.core.metadata.schema.Ample;
+import org.apache.accumulo.core.metadata.schema.TabletMetadata.Location;
+import org.easymock.EasyMock;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class ManagerMetadataUtilTest {
+
+  private AccumuloConfiguration conf;
+  private ClientContext context;
+  private Ample.TabletMutator tabletMutator;
+  private final TServerInstance server1 = new TServerInstance("127.0.0.1:10000", 0);
+  private final Location last1 = Location.last(server1);
+  private final TServerInstance server2 = new TServerInstance("127.0.0.2:10000", 1);
+  private final Location last2 = Location.last(server2);
+
+  @BeforeEach
+  public void before() {
+    conf = EasyMock.createMock(AccumuloConfiguration.class);
+    EasyMock.expect(conf.get(Property.TSERV_LAST_LOCATION_MODE)).andReturn("assignment");
+    context = EasyMock.createMock(ClientContext.class);
+    EasyMock.expect(context.getConfiguration()).andReturn(conf).once();
+    tabletMutator = EasyMock.createMock(Ample.TabletMutator.class);
+  }
+
+  @Test
+  public void testUpdateLastForAssignmentModeNullLastLocation() {
+    // Expect a put of last1 as the previous value
+    EasyMock.expect(tabletMutator.putLocation(last1)).andReturn(tabletMutator).once();
+    EasyMock.replay(conf, context, tabletMutator);
+
+    // Pass in a null last location value. There should be a call to
+    // tabletMutator.putLocation of last 1 but no deletion as lastLocation is null
+    ManagerMetadataUtil.updateLastForAssignmentMode(context, tabletMutator, server1, null);
+    EasyMock.verify(conf, context, tabletMutator);
+  }
+
+  @Test
+  public void testUpdateLastForAssignModeInvalidType() {
+    assertThrows(IllegalArgumentException.class, () -> {
+      // Should throw an IllegalArgumentException as the lastLocation is not LocationType.LAST
+      ManagerMetadataUtil.updateLastForAssignmentMode(context, tabletMutator, server1,
+          Location.current(server1));
+    });
+  }
+
+  @Test
+  public void testUpdateLastForAssignModeLastLocationSame() {
+    EasyMock.replay(conf, context, tabletMutator);
+
+    // Pass in a last location value that matches the new value of server 1
+    // There should be no call to tabletMutator.putLocation or tabletMutator.deleteLocation
+    // as the locations are equal so no expects() are defined and any method calls would
+    // throw an error
+    ManagerMetadataUtil.updateLastForAssignmentMode(context, tabletMutator, server1, last1);
+    EasyMock.verify(conf, context, tabletMutator);
+  }
+
+  @Test
+  public void testUpdateLastForAssignModeLastLocationDifferent() {
+    // Expect a delete of last1 as we are providing that as the previous last location
+    // which is different from server 2 location
+    EasyMock.expect(tabletMutator.deleteLocation(last1)).andReturn(tabletMutator).once();
+    EasyMock.expect(tabletMutator.putLocation(last2)).andReturn(tabletMutator).once();
+
+    EasyMock.replay(conf, context, tabletMutator);
+
+    // Pass in last1 as the last location value.
+    // There should be no read from Ample as we provided a value as an argument
+    // There should be a call to tabletMutator.putLocation and tabletMutator.deleteLocation
+    // as the last location is being updated as last1 does not match server 2
+    ManagerMetadataUtil.updateLastForAssignmentMode(context, tabletMutator, server2, last1);
+    EasyMock.verify(conf, context, tabletMutator);
+  }
+
+}
diff --git a/server/manager/src/main/java/org/apache/accumulo/manager/Manager.java b/server/manager/src/main/java/org/apache/accumulo/manager/Manager.java
index 69fceb3b14..2beca9cd42 100644
--- a/server/manager/src/main/java/org/apache/accumulo/manager/Manager.java
+++ b/server/manager/src/main/java/org/apache/accumulo/manager/Manager.java
@@ -130,6 +130,7 @@ import org.apache.accumulo.server.manager.state.MergeInfo;
 import org.apache.accumulo.server.manager.state.MergeState;
 import org.apache.accumulo.server.manager.state.TabletServerState;
 import org.apache.accumulo.server.manager.state.TabletStateStore;
+import org.apache.accumulo.server.manager.state.UnassignedTablet;
 import org.apache.accumulo.server.rpc.HighlyAvailableServiceWrapper;
 import org.apache.accumulo.server.rpc.ServerAddress;
 import org.apache.accumulo.server.rpc.TServerUtils;
@@ -1805,9 +1806,11 @@ public class Manager extends AbstractServer
   }
 
   void getAssignments(SortedMap<TServerInstance,TabletServerStatus> currentStatus,
-      Map<KeyExtent,TServerInstance> unassigned, Map<KeyExtent,TServerInstance> assignedOut) {
-    AssignmentParamsImpl params =
-        AssignmentParamsImpl.fromThrift(currentStatus, unassigned, assignedOut);
+      Map<KeyExtent,UnassignedTablet> unassigned, Map<KeyExtent,TServerInstance> assignedOut) {
+    AssignmentParamsImpl params = AssignmentParamsImpl.fromThrift(currentStatus,
+        unassigned.entrySet().stream().collect(HashMap::new,
+            (m, e) -> m.put(e.getKey(), e.getValue().getServerInstance()), Map::putAll),
+        assignedOut);
     tabletBalancer.getAssignments(params);
   }
 }
diff --git a/server/manager/src/main/java/org/apache/accumulo/manager/TabletGroupWatcher.java b/server/manager/src/main/java/org/apache/accumulo/manager/TabletGroupWatcher.java
index 593d09438f..69bcfd03c0 100644
--- a/server/manager/src/main/java/org/apache/accumulo/manager/TabletGroupWatcher.java
+++ b/server/manager/src/main/java/org/apache/accumulo/manager/TabletGroupWatcher.java
@@ -74,6 +74,7 @@ import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.Fu
 import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.ServerColumnFamily;
 import org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.TabletColumnFamily;
 import org.apache.accumulo.core.metadata.schema.MetadataTime;
+import org.apache.accumulo.core.metadata.schema.TabletMetadata.Location;
 import org.apache.accumulo.core.security.Authorizations;
 import org.apache.accumulo.core.tabletserver.thrift.NotServingTabletException;
 import org.apache.accumulo.core.util.threads.Threads.AccumuloDaemonThread;
@@ -93,6 +94,7 @@ import org.apache.accumulo.server.manager.state.DistributedStoreException;
 import org.apache.accumulo.server.manager.state.MergeInfo;
 import org.apache.accumulo.server.manager.state.MergeState;
 import org.apache.accumulo.server.manager.state.TabletStateStore;
+import org.apache.accumulo.server.manager.state.UnassignedTablet;
 import org.apache.accumulo.server.tablets.TabletTime;
 import org.apache.accumulo.server.util.MetadataTableUtil;
 import org.apache.hadoop.fs.Path;
@@ -145,7 +147,7 @@ abstract class TabletGroupWatcher extends AccumuloDaemonThread {
     private final List<Assignment> assigned = new ArrayList<>();
     private final List<TabletLocationState> assignedToDeadServers = new ArrayList<>();
     private final List<TabletLocationState> suspendedToGoneServers = new ArrayList<>();
-    private final Map<KeyExtent,TServerInstance> unassigned = new HashMap<>();
+    private final Map<KeyExtent,UnassignedTablet> unassigned = new HashMap<>();
     private final Map<TServerInstance,List<Path>> logsForDeadServers = new TreeMap<>();
     // read only lists of tablet servers
     private final SortedMap<TServerInstance,TabletServerStatus> currentTServers;
@@ -240,7 +242,7 @@ abstract class TabletGroupWatcher extends AccumuloDaemonThread {
             return mStats != null ? mStats : new MergeStats(new MergeInfo());
           });
           TabletGoalState goal = manager.getGoalState(tls, mergeStats.getMergeInfo());
-          TServerInstance location = tls.getServer();
+          Location location = tls.getLocation();
           TabletState state = tls.getState(currentTServers.keySet());
 
           TabletLogger.missassigned(tls.extent, goal.toString(), state.toString(),
@@ -271,7 +273,7 @@ abstract class TabletGroupWatcher extends AccumuloDaemonThread {
             }
             switch (state) {
               case HOSTED:
-                if (location.equals(manager.migrations.get(tls.extent))) {
+                if (location.getServerInstance().equals(manager.migrations.get(tls.extent))) {
                   manager.migrations.remove(tls.extent);
                 }
                 break;
@@ -282,11 +284,11 @@ abstract class TabletGroupWatcher extends AccumuloDaemonThread {
                 hostSuspendedTablet(tLists, tls, location, tableConf);
                 break;
               case UNASSIGNED:
-                hostUnassignedTablet(tLists, tls.extent, location);
+                hostUnassignedTablet(tLists, tls.extent, new UnassignedTablet(location, tls.last));
                 break;
               case ASSIGNED:
                 // Send another reminder
-                tLists.assigned.add(new Assignment(tls.extent, tls.getFutureServer()));
+                tLists.assigned.add(new Assignment(tls.extent, tls.getFutureServer(), tls.last));
                 break;
             }
           } else {
@@ -303,7 +305,8 @@ abstract class TabletGroupWatcher extends AccumuloDaemonThread {
                 unassignDeadTablet(tLists, tls, wals);
                 break;
               case HOSTED:
-                TServerConnection client = manager.tserverSet.getConnection(location);
+                TServerConnection client =
+                    manager.tserverSet.getConnection(location.getServerInstance());
                 if (client != null) {
                   client.unloadTablet(manager.managerLock, tls.extent, goal.howUnload(),
                       manager.getSteadyTime());
@@ -381,25 +384,25 @@ abstract class TabletGroupWatcher extends AccumuloDaemonThread {
   }
 
   private void hostUnassignedTablet(TabletLists tLists, KeyExtent tablet,
-      TServerInstance location) {
+      UnassignedTablet unassignedTablet) {
     // maybe it's a finishing migration
     TServerInstance dest = manager.migrations.get(tablet);
     if (dest != null) {
       // if destination is still good, assign it
       if (tLists.destinations.containsKey(dest)) {
-        tLists.assignments.add(new Assignment(tablet, dest));
+        tLists.assignments.add(new Assignment(tablet, dest, unassignedTablet.getLastLocation()));
       } else {
         // get rid of this migration
         manager.migrations.remove(tablet);
-        tLists.unassigned.put(tablet, location);
+        tLists.unassigned.put(tablet, unassignedTablet);
       }
     } else {
-      tLists.unassigned.put(tablet, location);
+      tLists.unassigned.put(tablet, unassignedTablet);
     }
   }
 
-  private void hostSuspendedTablet(TabletLists tLists, TabletLocationState tls,
-      TServerInstance location, TableConfiguration tableConf) {
+  private void hostSuspendedTablet(TabletLists tLists, TabletLocationState tls, Location location,
+      TableConfiguration tableConf) {
     if (manager.getSteadyTime() - tls.suspend.suspensionTime
         < tableConf.getTimeInMillis(Property.TABLE_SUSPEND_DURATION)) {
       // Tablet is suspended. See if its tablet server is back.
@@ -415,20 +418,20 @@ abstract class TabletGroupWatcher extends AccumuloDaemonThread {
 
       // Old tablet server is back. Return this tablet to its previous owner.
       if (returnInstance != null) {
-        tLists.assignments.add(new Assignment(tls.extent, returnInstance));
+        tLists.assignments.add(new Assignment(tls.extent, returnInstance, tls.last));
       }
       // else - tablet server not back. Don't ask for a new assignment right now.
 
     } else {
       // Treat as unassigned, ask for a new assignment.
-      tLists.unassigned.put(tls.extent, location);
+      tLists.unassigned.put(tls.extent, new UnassignedTablet(location, tls.last));
     }
   }
 
-  private void hostDeadTablet(TabletLists tLists, TabletLocationState tls, TServerInstance location,
+  private void hostDeadTablet(TabletLists tLists, TabletLocationState tls, Location location,
       WalStateManager wals) throws WalMarkerException {
     tLists.assignedToDeadServers.add(tls);
-    if (location.equals(manager.migrations.get(tls.extent))) {
+    if (location.getServerInstance().equals(manager.migrations.get(tls.extent))) {
       manager.migrations.remove(tls.extent);
     }
     TServerInstance tserver = tls.futureOrCurrentServer();
@@ -901,7 +904,7 @@ abstract class TabletGroupWatcher extends AccumuloDaemonThread {
   }
 
   private void getAssignmentsFromBalancer(TabletLists tLists,
-      Map<KeyExtent,TServerInstance> unassigned) {
+      Map<KeyExtent,UnassignedTablet> unassigned) {
     if (!tLists.currentTServers.isEmpty()) {
       Map<KeyExtent,TServerInstance> assignedOut = new HashMap<>();
       manager.getAssignments(tLists.currentTServers, unassigned, assignedOut);
@@ -915,16 +918,19 @@ abstract class TabletGroupWatcher extends AccumuloDaemonThread {
               continue;
             }
 
-            TServerInstance lastLocation = unassigned.get(assignment.getKey());
-            if (lastLocation != null
-                && !assignment.getValue().getHostPort().equals(lastLocation.getHostPort())) {
+            final UnassignedTablet unassignedTablet = unassigned.get(assignment.getKey());
+            final TServerInstance serverInstance =
+                unassignedTablet != null ? unassignedTablet.getServerInstance() : null;
+            if (serverInstance != null
+                && !assignment.getValue().getHostPort().equals(serverInstance.getHostPort())) {
               Manager.log.warn(
                   "balancer assigned {} to {} which is not the suggested location of {}",
                   assignment.getKey(), assignment.getValue().getHostPort(),
-                  lastLocation.getHostPort());
+                  serverInstance.getHostPort());
             }
 
-            tLists.assignments.add(new Assignment(assignment.getKey(), assignment.getValue()));
+            tLists.assignments.add(new Assignment(assignment.getKey(), assignment.getValue(),
+                unassignedTablet != null ? unassignedTablet.getLastLocation() : null));
           }
         } else {
           Manager.log.warn(
@@ -971,4 +977,5 @@ abstract class TabletGroupWatcher extends AccumuloDaemonThread {
       }
     }
   }
+
 }
diff --git a/server/tserver/src/main/java/org/apache/accumulo/tserver/AssignmentHandler.java b/server/tserver/src/main/java/org/apache/accumulo/tserver/AssignmentHandler.java
index 1c629a321a..8b4f117a21 100644
--- a/server/tserver/src/main/java/org/apache/accumulo/tserver/AssignmentHandler.java
+++ b/server/tserver/src/main/java/org/apache/accumulo/tserver/AssignmentHandler.java
@@ -181,7 +181,8 @@ class AssignmentHandler implements Runnable {
           && !tablet.minorCompactNow(MinorCompactionReason.RECOVERY)) {
         throw new RuntimeException("Minor compaction after recovery fails for " + extent);
       }
-      Assignment assignment = new Assignment(extent, server.getTabletSession());
+      Assignment assignment =
+          new Assignment(extent, server.getTabletSession(), tabletMetadata.getLast());
       TabletStateStore.setLocation(server.getContext(), assignment);
 
       synchronized (server.openingTablets) {
diff --git a/test/src/main/java/org/apache/accumulo/test/functional/SplitRecoveryIT.java b/test/src/main/java/org/apache/accumulo/test/functional/SplitRecoveryIT.java
index c27f9fd570..7b5b59accc 100644
--- a/test/src/main/java/org/apache/accumulo/test/functional/SplitRecoveryIT.java
+++ b/test/src/main/java/org/apache/accumulo/test/functional/SplitRecoveryIT.java
@@ -208,7 +208,7 @@ public class SplitRecoveryIT extends ConfigurableMacBase {
 
     MetadataTableUtil.splitTablet(high, extent.prevEndRow(), splitRatio, context, zl, Set.of());
     TServerInstance instance = new TServerInstance(location, zl.getSessionId());
-    Assignment assignment = new Assignment(high, instance);
+    Assignment assignment = new Assignment(high, instance, null);
 
     TabletMutator tabletMutator = context.getAmple().mutateTablet(extent);
     tabletMutator.putLocation(Location.future(assignment.server));
diff --git a/test/src/main/java/org/apache/accumulo/test/manager/MergeStateIT.java b/test/src/main/java/org/apache/accumulo/test/manager/MergeStateIT.java
index 68aa73408b..e36c8b8dd8 100644
--- a/test/src/main/java/org/apache/accumulo/test/manager/MergeStateIT.java
+++ b/test/src/main/java/org/apache/accumulo/test/manager/MergeStateIT.java
@@ -187,7 +187,7 @@ public class MergeStateIT extends ConfigurableMacBase {
       TabletColumnFamily.SPLIT_RATIO_COLUMN.put(m, new Value("0.5"));
       update(accumuloClient, m);
       metaDataStateStore
-          .setLocations(Collections.singletonList(new Assignment(tablet, state.someTServer)));
+          .setLocations(Collections.singletonList(new Assignment(tablet, state.someTServer, null)));
 
       // onos... there's a new tablet online
       stats = scan(state, metaDataStateStore);
diff --git a/test/src/main/java/org/apache/accumulo/test/performance/NullTserver.java b/test/src/main/java/org/apache/accumulo/test/performance/NullTserver.java
index 402c722feb..d11da541d5 100644
--- a/test/src/main/java/org/apache/accumulo/test/performance/NullTserver.java
+++ b/test/src/main/java/org/apache/accumulo/test/performance/NullTserver.java
@@ -355,7 +355,7 @@ public class NullTserver {
 
       while (s.hasNext()) {
         TabletLocationState next = s.next();
-        assignments.add(new Assignment(next.extent, instance));
+        assignments.add(new Assignment(next.extent, instance, next.last));
       }
     }
     // point them to this server