You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kudu.apache.org by ad...@apache.org on 2019/07/29 18:37:49 UTC

[kudu] branch master updated: [client] Deprecated TabletLocationsPB.ReplicaPB message in client

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

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


The following commit(s) were added to refs/heads/master by this push:
     new d8aeab7  [client] Deprecated TabletLocationsPB.ReplicaPB message in client
d8aeab7 is described below

commit d8aeab7f97a76275e6f7f647099c5b339961c623
Author: oclarms <oc...@gmail.com>
AuthorDate: Wed Jul 24 22:17:24 2019 +0800

    [client] Deprecated TabletLocationsPB.ReplicaPB message in client
    
    In this patch, I removed the dependency on it from the client. It's only
    used for backward compatibility with RPC. In the future we will completely
    deprecated TabletLocationsPB.ReplicaPB message.
    
    Change-Id: I18272274f07d5ae8e9f6b9572f9900aa8df27bef
    Reviewed-on: http://gerrit.cloudera.org:8080/13908
    Tested-by: Kudu Jenkins
    Reviewed-by: Adar Dembo <ad...@cloudera.com>
---
 .../org/apache/kudu/client/AsyncKuduClient.java    | 52 +++++++-------
 .../kudu/client/ConnectToClusterResponse.java      | 14 ++--
 .../java/org/apache/kudu/client/LocatedTablet.java | 43 ++++++------
 .../java/org/apache/kudu/client/RemoteTablet.java  | 15 ++--
 .../apache/kudu/client/TestAsyncKuduClient.java    | 17 +++--
 .../org/apache/kudu/client/TestRemoteTablet.java   |  4 +-
 .../java/org/apache/kudu/test/ProtobufUtils.java   | 29 +++++---
 src/kudu/client/client.cc                          | 24 +++++--
 src/kudu/client/meta_cache.cc                      | 34 +++++----
 src/kudu/integration-tests/cluster_itest_util.cc   | 51 +++++++-------
 src/kudu/integration-tests/cluster_itest_util.h    |  4 +-
 src/kudu/integration-tests/delete_table-itest.cc   |  5 +-
 src/kudu/integration-tests/linked_list-test.cc     |  2 +-
 src/kudu/integration-tests/master_sentry-itest.cc  |  4 +-
 .../integration-tests/raft_config_change-itest.cc  | 23 +++---
 src/kudu/integration-tests/raft_consensus-itest.cc |  2 +-
 .../raft_consensus_nonvoter-itest.cc               | 45 ++++++------
 src/kudu/integration-tests/registration-test.cc    |  7 +-
 .../integration-tests/table_locations-itest.cc     | 46 +++++++++---
 src/kudu/integration-tests/tablet_copy-itest.cc    |  4 +-
 .../integration-tests/tombstoned_voting-itest.cc   |  4 +-
 src/kudu/integration-tests/ts_itest-base.cc        | 12 ++--
 src/kudu/master/catalog_manager.cc                 |  5 +-
 src/kudu/master/master.proto                       | 13 ++--
 src/kudu/master/master_service.cc                  |  8 ++-
 src/kudu/tools/kudu-admin-test.cc                  | 82 +++++++++++-----------
 src/kudu/tools/rebalancer_tool-test.cc             |  4 +-
 27 files changed, 316 insertions(+), 237 deletions(-)

diff --git a/java/kudu-client/src/main/java/org/apache/kudu/client/AsyncKuduClient.java b/java/kudu-client/src/main/java/org/apache/kudu/client/AsyncKuduClient.java
index d908692..4482356 100644
--- a/java/kudu-client/src/main/java/org/apache/kudu/client/AsyncKuduClient.java
+++ b/java/kudu-client/src/main/java/org/apache/kudu/client/AsyncKuduClient.java
@@ -74,6 +74,7 @@ import org.apache.kudu.master.Master;
 import org.apache.kudu.master.Master.GetTableLocationsResponsePB;
 import org.apache.kudu.master.Master.TSInfoPB;
 import org.apache.kudu.master.Master.TableIdentifierPB;
+import org.apache.kudu.master.Master.TabletLocationsPB;
 import org.apache.kudu.security.Token.SignedTokenPB;
 import org.apache.kudu.util.AsyncUtil;
 import org.apache.kudu.util.NetUtil;
@@ -2241,8 +2242,8 @@ public class AsyncKuduClient implements AutoCloseable {
   void discoverTablets(KuduTable table,
                        byte[] requestPartitionKey,
                        int requestedBatchSize,
-                       List<Master.TabletLocationsPB> locations,
-                       List<Master.TSInfoPB> tsInfosList,
+                       List<TabletLocationsPB> locations,
+                       List<TSInfoPB> tsInfosList,
                        long ttl) throws KuduException {
     String tableId = table.getTableId();
     String tableName = table.getName();
@@ -2264,13 +2265,13 @@ public class AsyncKuduClient implements AutoCloseable {
     // already discovered the tablet, its locations are refreshed.
     int numTsInfos = tsInfosList.size();
     List<RemoteTablet> tablets = new ArrayList<>(locations.size());
-    for (Master.TabletLocationsPB tabletPb : locations) {
+    for (TabletLocationsPB tabletPb : locations) {
 
-      List<Exception> lookupExceptions = new ArrayList<>(tabletPb.getReplicasCount());
-      List<ServerInfo> servers = new ArrayList<>(tabletPb.getReplicasCount());
+      List<Exception> lookupExceptions = new ArrayList<>(tabletPb.getInternedReplicasCount());
+      List<ServerInfo> servers = new ArrayList<>(tabletPb.getInternedReplicasCount());
 
       // Lambda that does the common handling of a ts info.
-      Consumer<Master.TSInfoPB> updateServersAndCollectExceptions = tsInfo -> {
+      Consumer<TSInfoPB> updateServersAndCollectExceptions = tsInfo -> {
         try {
           ServerInfo serverInfo = resolveTS(tsInfo);
           if (serverInfo != null) {
@@ -2281,16 +2282,20 @@ public class AsyncKuduClient implements AutoCloseable {
         }
       };
 
-      // Handle "old-style" non-interned replicas.
-      for (Master.TabletLocationsPB.ReplicaPB replica : tabletPb.getReplicasList()) {
-        updateServersAndCollectExceptions.accept(replica.getTsInfo());
+      List<LocatedTablet.Replica> replicas = new ArrayList<>();
+      // Handle "old-style" non-interned replicas. It's used for backward compatibility.
+      for (TabletLocationsPB.DEPRECATED_ReplicaPB replica : tabletPb.getDEPRECATEDReplicasList()) {
+        TSInfoPB tsInfo = replica.getTsInfo();
+        updateServersAndCollectExceptions.accept(tsInfo);
+        String tsHost = tsInfo.getRpcAddressesList().isEmpty() ?
+            null : tsInfo.getRpcAddressesList().get(0).getHost();
+        Integer tsPort = tsInfo.getRpcAddressesList().isEmpty() ?
+            null : tsInfo.getRpcAddressesList().get(0).getPort();
+        String dimensionLabel = replica.hasDimensionLabel() ? replica.getDimensionLabel() : null;
+        replicas.add(new LocatedTablet.Replica(tsHost, tsPort, replica.getRole(), dimensionLabel));
       }
-
-      // Handle interned replicas. As a shim, we also need to create a list of "old-style" ReplicaPBs
-      // to be stored inside the RemoteTablet.
-      // TODO(wdberkeley): Change this so ReplicaPBs aren't used by the client at all anymore.
-      List<Master.TabletLocationsPB.ReplicaPB> replicas = new ArrayList<>();
-      for (Master.TabletLocationsPB.InternedReplicaPB replica : tabletPb.getInternedReplicasList()) {
+      // Handle interned replicas.
+      for (TabletLocationsPB.InternedReplicaPB replica : tabletPb.getInternedReplicasList()) {
         int tsInfoIdx = replica.getTsInfoIdx();
         if (tsInfoIdx >= numTsInfos) {
           lookupExceptions.add(new NonRecoverableException(Status.Corruption(
@@ -2300,17 +2305,16 @@ public class AsyncKuduClient implements AutoCloseable {
         }
         TSInfoPB tsInfo = tsInfosList.get(tsInfoIdx);
         updateServersAndCollectExceptions.accept(tsInfo);
-        Master.TabletLocationsPB.ReplicaPB.Builder builder = Master.TabletLocationsPB.ReplicaPB.newBuilder();
-        builder.setRole(replica.getRole());
-        builder.setTsInfo(tsInfo);
-        if (replica.hasDimensionLabel()) {
-          builder.setDimensionLabel(replica.getDimensionLabel());
-        }
-        replicas.add(builder.build());
+        String tsHost = tsInfo.getRpcAddressesList().isEmpty() ?
+            null : tsInfo.getRpcAddressesList().get(0).getHost();
+        Integer tsPort = tsInfo.getRpcAddressesList().isEmpty() ?
+            null : tsInfo.getRpcAddressesList().get(0).getPort();
+        String dimensionLabel = replica.hasDimensionLabel() ? replica.getDimensionLabel() : null;
+        replicas.add(new LocatedTablet.Replica(tsHost, tsPort, replica.getRole(), dimensionLabel));
       }
 
       if (!lookupExceptions.isEmpty() &&
-          lookupExceptions.size() == tabletPb.getReplicasCount()) {
+          lookupExceptions.size() == tabletPb.getInternedReplicasCount()) {
         Status statusIOE = Status.IOError("Couldn't find any valid locations, exceptions: " +
             lookupExceptions);
         throw new NonRecoverableException(statusIOE);
@@ -2319,7 +2323,7 @@ public class AsyncKuduClient implements AutoCloseable {
       RemoteTablet rt = new RemoteTablet(tableId,
                                          tabletPb.getTabletId().toStringUtf8(),
                                          ProtobufHelper.pbToPartition(tabletPb.getPartition()),
-                                         replicas.isEmpty() ? tabletPb.getReplicasList() : replicas,
+                                         replicas,
                                          servers);
 
       LOG.debug("Learned about tablet {} for table '{}' with partition {}",
diff --git a/java/kudu-client/src/main/java/org/apache/kudu/client/ConnectToClusterResponse.java b/java/kudu-client/src/main/java/org/apache/kudu/client/ConnectToClusterResponse.java
index 97dd14c..18421a6 100644
--- a/java/kudu-client/src/main/java/org/apache/kudu/client/ConnectToClusterResponse.java
+++ b/java/kudu-client/src/main/java/org/apache/kudu/client/ConnectToClusterResponse.java
@@ -24,7 +24,7 @@ import org.apache.kudu.master.Master.ConnectToMasterResponsePB;
 import org.apache.kudu.master.Master.GetTableLocationsResponsePB;
 import org.apache.kudu.master.Master.TSInfoPB;
 import org.apache.kudu.master.Master.TabletLocationsPB;
-import org.apache.kudu.master.Master.TabletLocationsPB.ReplicaPB;
+import org.apache.kudu.master.Master.TabletLocationsPB.InternedReplicaPB;
 
 /**
  * The aggregated response after connecting to a cluster. This stores the
@@ -67,10 +67,12 @@ class ConnectToClusterResponse {
                 .setPartitionKeyStart(ByteString.EMPTY)
                 .setPartitionKeyEnd(ByteString.EMPTY))
             .setTabletId(FAKE_TABLET_ID)
-            .addReplicas(ReplicaPB.newBuilder()
-                .setTsInfo(TSInfoPB.newBuilder()
-                    .addRpcAddresses(ProtobufHelper.hostAndPortToPB(leaderHostAndPort))
-                    .setPermanentUuid(ByteString.copyFromUtf8(fakeUuid)))
-                .setRole(connectResponse.getRole()))).build();
+            .addInternedReplicas(InternedReplicaPB.newBuilder()
+                .setTsInfoIdx(0)
+                .setRole(connectResponse.getRole())))
+        .addTsInfos(TSInfoPB.newBuilder()
+            .addRpcAddresses(ProtobufHelper.hostAndPortToPB(leaderHostAndPort))
+            .setPermanentUuid(ByteString.copyFromUtf8(fakeUuid)))
+        .build();
   }
 }
diff --git a/java/kudu-client/src/main/java/org/apache/kudu/client/LocatedTablet.java b/java/kudu-client/src/main/java/org/apache/kudu/client/LocatedTablet.java
index 837156e..63c2a15 100644
--- a/java/kudu-client/src/main/java/org/apache/kudu/client/LocatedTablet.java
+++ b/java/kudu-client/src/main/java/org/apache/kudu/client/LocatedTablet.java
@@ -26,7 +26,6 @@ import org.apache.yetus.audience.InterfaceAudience;
 import org.apache.yetus.audience.InterfaceStability;
 
 import org.apache.kudu.consensus.Metadata.RaftPeerPB.Role;
-import org.apache.kudu.master.Master.TabletLocationsPB.ReplicaPB;
 
 /**
  * Information about the locations of tablets in a Kudu table.
@@ -107,43 +106,47 @@ public class LocatedTablet {
   @InterfaceAudience.Public
   @InterfaceStability.Evolving
   public static class Replica {
-    // TODO(wdberkeley): The ReplicaPB is deprecated server-side, so we ought to redo how this
-    // class stores its information.
-    private final ReplicaPB pb;
-
-    Replica(ReplicaPB pb) {
-      this.pb = pb;
+    private final String host;
+    private final Integer port;
+    private final Role role;
+    private final String dimensionLabel;
+
+    Replica(String host, Integer port, Role role, String dimensionLabel) {
+      this.host = host;
+      this.port = port;
+      this.role = role;
+      this.dimensionLabel = dimensionLabel;
     }
 
     public String getRpcHost() {
-      if (pb.getTsInfo().getRpcAddressesList().isEmpty()) {
-        return null;
-      }
-      return pb.getTsInfo().getRpcAddressesList().get(0).getHost();
+      return host;
     }
 
     public Integer getRpcPort() {
-      if (pb.getTsInfo().getRpcAddressesList().isEmpty()) {
-        return null;
-      }
-      return pb.getTsInfo().getRpcAddressesList().get(0).getPort();
+      return port;
     }
 
-    private Role getRoleAsEnum() {
-      return pb.getRole();
+    Role getRoleAsEnum() {
+      return role;
     }
 
     public String getRole() {
-      return pb.getRole().toString();
+      return role.toString();
     }
 
     public String getDimensionLabel() {
-      return pb.hasDimensionLabel() ? pb.getDimensionLabel() : null;
+      return dimensionLabel;
     }
 
     @Override
     public String toString() {
-      return pb.toString();
+      final StringBuilder buf = new StringBuilder();
+      buf.append("Replica(host=").append(host == null ? "null" : host);
+      buf.append(", port=").append(port == null ? "null" : port.toString());
+      buf.append(", role=").append(getRole());
+      buf.append(", dimensionLabel=").append(dimensionLabel == null ? "null" : dimensionLabel);
+      buf.append(')');
+      return buf.toString();
     }
   }
 }
diff --git a/java/kudu-client/src/main/java/org/apache/kudu/client/RemoteTablet.java b/java/kudu-client/src/main/java/org/apache/kudu/client/RemoteTablet.java
index 2241f58..bcfa488 100644
--- a/java/kudu-client/src/main/java/org/apache/kudu/client/RemoteTablet.java
+++ b/java/kudu-client/src/main/java/org/apache/kudu/client/RemoteTablet.java
@@ -30,6 +30,7 @@ import javax.annotation.concurrent.GuardedBy;
 
 import com.google.common.base.Joiner;
 import com.google.common.base.Objects;
+import com.google.common.base.Preconditions;
 import com.google.common.collect.ComparisonChain;
 import com.google.common.collect.ImmutableList;
 import org.apache.yetus.audience.InterfaceAudience;
@@ -38,7 +39,6 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import org.apache.kudu.consensus.Metadata;
-import org.apache.kudu.master.Master;
 
 /**
  * This class encapsulates the information regarding a tablet and its locations.
@@ -71,8 +71,10 @@ public class RemoteTablet implements Comparable<RemoteTablet> {
   RemoteTablet(String tableId,
                String tabletId,
                Partition partition,
-               List<Master.TabletLocationsPB.ReplicaPB> replicas,
+               List<LocatedTablet.Replica> replicas,
                List<ServerInfo> serverInfos) {
+    Preconditions.checkArgument(replicas.size() == serverInfos.size(),
+                                "the number of replicas does not equal the number of servers");
     this.tabletId = tabletId;
     this.tableId = tableId;
     this.partition = partition;
@@ -83,11 +85,10 @@ public class RemoteTablet implements Comparable<RemoteTablet> {
     }
 
     ImmutableList.Builder<LocatedTablet.Replica> replicasBuilder = new ImmutableList.Builder<>();
-    for (Master.TabletLocationsPB.ReplicaPB replica : replicas) {
-      String uuid = replica.getTsInfo().getPermanentUuid().toStringUtf8();
-      replicasBuilder.add(new LocatedTablet.Replica(replica));
-      if (replica.getRole().equals(Metadata.RaftPeerPB.Role.LEADER)) {
-        this.leaderUuid = uuid;
+    for (int i = 0; i < replicas.size(); ++i) {
+      replicasBuilder.add(replicas.get(i));
+      if (replicas.get(i).getRoleAsEnum().equals(Metadata.RaftPeerPB.Role.LEADER)) {
+        this.leaderUuid = serverInfos.get(i).getUuid();
       }
     }
 
diff --git a/java/kudu-client/src/test/java/org/apache/kudu/client/TestAsyncKuduClient.java b/java/kudu-client/src/test/java/org/apache/kudu/client/TestAsyncKuduClient.java
index dd323e7..59517d4 100644
--- a/java/kudu-client/src/test/java/org/apache/kudu/client/TestAsyncKuduClient.java
+++ b/java/kudu-client/src/test/java/org/apache/kudu/client/TestAsyncKuduClient.java
@@ -155,6 +155,7 @@ public class TestAsyncKuduClient {
     }
 
     List<Master.TabletLocationsPB> tabletLocations = new ArrayList<>();
+    List<Master.TSInfoPB> tsInfos = new ArrayList<>();
 
     // Builder three bad locations.
     Master.TabletLocationsPB.Builder tabletPb = Master.TabletLocationsPB.newBuilder();
@@ -164,9 +165,10 @@ public class TestAsyncKuduClient {
       partition.setPartitionKeyEnd(ByteString.copyFrom("b" + i, UTF_8.name()));
       tabletPb.setPartition(partition);
       tabletPb.setTabletId(ByteString.copyFromUtf8("some id " + i));
-      tabletPb.addReplicas(ProtobufUtils.getFakeTabletReplicaPB(
-          "uuid", badHostname + i, i, Metadata.RaftPeerPB.Role.FOLLOWER));
+      tabletPb.addInternedReplicas(ProtobufUtils.getFakeTabletInternedReplicaPB(
+          i, Metadata.RaftPeerPB.Role.FOLLOWER));
       tabletLocations.add(tabletPb.build());
+      tsInfos.add(ProtobufUtils.getFakeTSInfoPB("uuid",badHostname + i, i).build());
     }
 
     // Test that a tablet full of unreachable replicas won't make us retry.
@@ -174,7 +176,7 @@ public class TestAsyncKuduClient {
       KuduTable badTable = new KuduTable(asyncClient, "Invalid table name",
           "Invalid table ID", null, null, 3, null);
       asyncClient.discoverTablets(badTable, null, requestBatchSize,
-                                  tabletLocations, new ArrayList<>(), 1000);
+                                  tabletLocations, tsInfos, 1000);
       fail("This should have failed quickly");
     } catch (NonRecoverableException ex) {
       assertTrue(ex.getMessage().contains(badHostname));
@@ -199,15 +201,18 @@ public class TestAsyncKuduClient {
 
     // Fake a master lookup that only returns one follower for the tablet.
     List<Master.TabletLocationsPB> tabletLocations = new ArrayList<>();
+    List<Master.TSInfoPB> tsInfos = new ArrayList<>();
     Master.TabletLocationsPB.Builder tabletPb = Master.TabletLocationsPB.newBuilder();
     tabletPb.setPartition(ProtobufUtils.getFakePartitionPB());
     tabletPb.setTabletId(ByteString.copyFrom(tablet.getTabletId()));
-    tabletPb.addReplicas(ProtobufUtils.getFakeTabletReplicaPB(
-        "master", leader.getRpcHost(), leader.getRpcPort(), Metadata.RaftPeerPB.Role.FOLLOWER));
+    tabletPb.addInternedReplicas(ProtobufUtils.getFakeTabletInternedReplicaPB(
+        0, Metadata.RaftPeerPB.Role.FOLLOWER));
     tabletLocations.add(tabletPb.build());
+    tsInfos.add(ProtobufUtils.getFakeTSInfoPB(
+        "master", leader.getRpcHost(), leader.getRpcPort()).build());
     try {
       asyncClient.discoverTablets(table, new byte[0], requestBatchSize,
-                                  tabletLocations, new ArrayList<>(), 1000);
+                                  tabletLocations, tsInfos, 1000);
       fail("discoverTablets should throw an exception if there's no leader");
     } catch (NoLeaderFoundException ex) {
       // Expected.
diff --git a/java/kudu-client/src/test/java/org/apache/kudu/client/TestRemoteTablet.java b/java/kudu-client/src/test/java/org/apache/kudu/client/TestRemoteTablet.java
index 3c58df0..c241d1d 100644
--- a/java/kudu-client/src/test/java/org/apache/kudu/client/TestRemoteTablet.java
+++ b/java/kudu-client/src/test/java/org/apache/kudu/client/TestRemoteTablet.java
@@ -223,7 +223,7 @@ public class TestRemoteTablet {
                                 int localReplicaIndex,
                                 int sameLocationReplicaIndex) {
     Partition partition = ProtobufHelper.pbToPartition(ProtobufUtils.getFakePartitionPB().build());
-    List<Master.TabletLocationsPB.ReplicaPB> replicas = new ArrayList<>();
+    List<LocatedTablet.Replica> replicas = new ArrayList<>();
     List<ServerInfo> servers = new ArrayList<>();
     for (int i = 0; i < 3; i++) {
       InetAddress addr;
@@ -245,7 +245,7 @@ public class TestRemoteTablet {
                                  location));
       Metadata.RaftPeerPB.Role role = leaderIndex == i ? Metadata.RaftPeerPB.Role.LEADER :
                                                          Metadata.RaftPeerPB.Role.FOLLOWER;
-      replicas.add(ProtobufUtils.getFakeTabletReplicaPB(uuid, "host", i, role).build());
+      replicas.add(new LocatedTablet.Replica("host", i, role, null));
     }
 
     return new RemoteTablet("fake table", "fake tablet", partition, replicas, servers);
diff --git a/java/kudu-test-utils/src/main/java/org/apache/kudu/test/ProtobufUtils.java b/java/kudu-test-utils/src/main/java/org/apache/kudu/test/ProtobufUtils.java
index 5be17c2..ee3cb0d 100644
--- a/java/kudu-test-utils/src/main/java/org/apache/kudu/test/ProtobufUtils.java
+++ b/java/kudu-test-utils/src/main/java/org/apache/kudu/test/ProtobufUtils.java
@@ -40,25 +40,34 @@ public class ProtobufUtils {
   }
 
   /**
-   * Create a ReplicaPB based on the passed information.
+   * Create a InternedReplicaPB based on the passed information.
+   * @param tsInfoIndex server's index in the TSInfoPB list
+   * @param role server's role in the configuration
+   * @return a fake InternedReplicaPB
+   */
+  public static Master.TabletLocationsPB.InternedReplicaPB.Builder getFakeTabletInternedReplicaPB(
+      int tsInfoIndex,  Metadata.RaftPeerPB.Role role) {
+    Master.TabletLocationsPB.InternedReplicaPB.Builder internedReplicaBuilder =
+        Master.TabletLocationsPB.InternedReplicaPB.newBuilder();
+    internedReplicaBuilder.setTsInfoIdx(tsInfoIndex);
+    internedReplicaBuilder.setRole(role);
+    return internedReplicaBuilder;
+  }
+
+  /**
+   * Create a TSInfoPB based on the passed information.
    * @param uuid server's identifier
    * @param host server's hostname
    * @param port server's port
-   * @param role server's role in the configuration
-   * @return a fake ReplicaPB
+   * @return a fake TSInfoPB
    */
-  public static Master.TabletLocationsPB.ReplicaPB.Builder getFakeTabletReplicaPB(
-      String uuid, String host, int port, Metadata.RaftPeerPB.Role role) {
+  public static Master.TSInfoPB.Builder getFakeTSInfoPB(String uuid, String host, int port) {
     Master.TSInfoPB.Builder tsInfoBuilder = Master.TSInfoPB.newBuilder();
     Common.HostPortPB.Builder hostBuilder = Common.HostPortPB.newBuilder();
     hostBuilder.setHost(host);
     hostBuilder.setPort(port);
     tsInfoBuilder.addRpcAddresses(hostBuilder);
     tsInfoBuilder.setPermanentUuid(ByteString.copyFromUtf8(uuid));
-    Master.TabletLocationsPB.ReplicaPB.Builder replicaBuilder =
-        Master.TabletLocationsPB.ReplicaPB.newBuilder();
-    replicaBuilder.setTsInfo(tsInfoBuilder);
-    replicaBuilder.setRole(role);
-    return replicaBuilder;
+    return tsInfoBuilder;
   }
 }
diff --git a/src/kudu/client/client.cc b/src/kudu/client/client.cc
index 62f8ef6..fcdb78e 100644
--- a/src/kudu/client/client.cc
+++ b/src/kudu/client/client.cc
@@ -100,6 +100,7 @@
 #include "kudu/util/scoped_cleanup.h"
 #include "kudu/util/version_info.h"
 
+using kudu::consensus::RaftPeerPB;
 using kudu::master::AlterTableRequestPB;
 using kudu::master::AlterTableResponsePB;
 using kudu::master::CreateTableRequestPB;
@@ -524,6 +525,7 @@ Status KuduClient::GetTablet(const string& tablet_id, KuduTablet** tablet) {
   GetTabletLocationsResponsePB resp;
 
   req.add_tablet_ids(tablet_id);
+  req.set_intern_ts_infos_in_response(true);
   MonoTime deadline = MonoTime::Now() + default_admin_operation_timeout();
   Synchronizer sync;
   AsyncLeaderMasterRpc<GetTabletLocationsRequestPB, GetTabletLocationsResponsePB> rpc(
@@ -547,8 +549,10 @@ Status KuduClient::GetTablet(const string& tablet_id, KuduTablet** tablet) {
 
   vector<const KuduReplica*> replicas;
   ElementDeleter deleter(&replicas);
-  for (const auto& r : t.replicas()) {
-    const TSInfoPB& ts_info = r.ts_info();
+
+  auto add_replica_func = [](const TSInfoPB& ts_info,
+                             const RaftPeerPB::Role role,
+                             vector<const KuduReplica*>* replicas) -> Status {
     if (ts_info.rpc_addresses_size() == 0) {
       return Status::IllegalState(Substitute(
           "No RPC addresses found for tserver $0",
@@ -560,12 +564,22 @@ Status KuduClient::GetTablet(const string& tablet_id, KuduTablet** tablet) {
     ts->data_ = new KuduTabletServer::Data(ts_info.permanent_uuid(), hp, ts_info.location());
 
     // TODO(aserbin): try to use member_type instead of role for metacache.
-    bool is_leader = r.role() == consensus::RaftPeerPB::LEADER;
-    bool is_voter = is_leader || r.role() == consensus::RaftPeerPB::FOLLOWER;
+    bool is_leader = role == RaftPeerPB::LEADER;
+    bool is_voter = is_leader || role == RaftPeerPB::FOLLOWER;
     unique_ptr<KuduReplica> replica(new KuduReplica);
     replica->data_ = new KuduReplica::Data(is_leader, is_voter, std::move(ts));
 
-    replicas.push_back(replica.release());
+    replicas->push_back(replica.release());
+    return Status::OK();
+  };
+
+  // Handle "old-style" non-interned replicas. It's used for backward compatibility.
+  for (const auto& r : t.deprecated_replicas()) {
+    RETURN_NOT_OK(add_replica_func(r.ts_info(), r.role(), &replicas));
+  }
+  // Handle interned replicas.
+  for (const auto& r : t.interned_replicas()) {
+    RETURN_NOT_OK(add_replica_func(resp.ts_infos(r.ts_info_idx()), r.role(), &replicas));
   }
 
   unique_ptr<KuduTablet> client_tablet(new KuduTablet);
diff --git a/src/kudu/client/meta_cache.cc b/src/kudu/client/meta_cache.cc
index eee0d31..a00ff05 100644
--- a/src/kudu/client/meta_cache.cc
+++ b/src/kudu/client/meta_cache.cc
@@ -35,6 +35,7 @@
 #include "kudu/client/schema.h"
 #include "kudu/common/common.pb.h"
 #include "kudu/common/wire_protocol.h"
+#include "kudu/consensus/metadata.pb.h"
 #include "kudu/gutil/basictypes.h"
 #include "kudu/gutil/bind.h"
 #include "kudu/gutil/bind_helpers.h"
@@ -71,8 +72,6 @@ using master::GetTableLocationsRequestPB;
 using master::GetTableLocationsResponsePB;
 using master::MasterServiceProxy;
 using master::TabletLocationsPB;
-using master::TabletLocationsPB_InternedReplicaPB;
-using master::TabletLocationsPB_ReplicaPB;
 using master::TSInfoPB;
 using rpc::BackoffType;
 using rpc::CredentialsPolicy;
@@ -190,32 +189,30 @@ Status RemoteTablet::Refresh(
     const TabletLocationsPB& locs_pb,
     const google::protobuf::RepeatedPtrField<TSInfoPB>& ts_info_dict) {
 
-  vector<std::pair<string, consensus::RaftPeerPB::Role>> uuid_and_role;
+  vector<RemoteReplica> replicas;
 
-  for (const TabletLocationsPB_ReplicaPB& r : locs_pb.replicas()) {
-    uuid_and_role.emplace_back(r.ts_info().permanent_uuid(), r.role());
+  // Handle "old-style" non-interned replicas. It's used for backward compatibility.
+  for (const auto& r : locs_pb.deprecated_replicas()) {
+    RemoteReplica replica = { FindOrDie(tservers, r.ts_info().permanent_uuid()),
+                              r.role(), /*failed=*/false };
+    replicas.push_back(replica);
   }
-  for (const TabletLocationsPB_InternedReplicaPB& r : locs_pb.interned_replicas()) {
+  // Handle interned replicas.
+  for (const auto& r : locs_pb.interned_replicas()) {
     if (r.ts_info_idx() >= ts_info_dict.size()) {
       return Status::Corruption(Substitute(
           "invalid response from master: referenced tablet idx $0 but only $1 present",
           r.ts_info_idx(), ts_info_dict.size()));
     }
     const TSInfoPB& ts_info = ts_info_dict.Get(r.ts_info_idx());
-    uuid_and_role.emplace_back(ts_info.permanent_uuid(), r.role());
+    RemoteReplica replica = { FindOrDie(tservers, ts_info.permanent_uuid()),
+                              r.role(), /*failed=*/false };
+    replicas.push_back(replica);
   }
 
   // Adopt the data from the successful response.
   std::lock_guard<simple_spinlock> l(lock_);
-  replicas_.clear();
-
-  for (const auto& p : uuid_and_role) {
-    RemoteReplica rep;
-    rep.ts = FindOrDie(tservers, p.first);
-    rep.role = p.second;
-    rep.failed = false;
-    replicas_.emplace_back(rep);
-  }
+  replicas_ = std::move(replicas);
 
   stale_ = false;
   return Status::OK();
@@ -801,12 +798,13 @@ Status MetaCache::ProcessLookupResponse(const LookupRpc& rpc,
     InsertOrDie(&tablets_by_key, "", entry);
   } else {
     // First, update the tserver cache, needed for the Refresh calls below.
+    // It's used for backward compatibility.
     for (const TabletLocationsPB& tablet : tablet_locations) {
-      for (const TabletLocationsPB_ReplicaPB& replicas : tablet.replicas()) {
+      for (const auto& replicas : tablet.deprecated_replicas()) {
         UpdateTabletServer(replicas.ts_info());
       }
     }
-    // In the case of "interned" replicas, the above 'replicas' lists will be empty
+    // In the case of "interned" replicas, the above 'deprecated_replicas' lists will be empty
     // and instead we'll need to update from the top-level list of tservers.
     const auto& ts_infos = rpc.resp().ts_infos();
     for (const TSInfoPB& ts_info : ts_infos) {
diff --git a/src/kudu/integration-tests/cluster_itest_util.cc b/src/kudu/integration-tests/cluster_itest_util.cc
index 6c33326..92f6cfc 100644
--- a/src/kudu/integration-tests/cluster_itest_util.cc
+++ b/src/kudu/integration-tests/cluster_itest_util.cc
@@ -97,7 +97,6 @@ using kudu::consensus::VoteResponsePB;
 using kudu::consensus::kInvalidOpIdIndex;
 using kudu::master::ListTabletServersResponsePB_Entry;
 using kudu::master::MasterServiceProxy;
-using kudu::master::TabletLocationsPB;
 using kudu::pb_util::SecureDebugString;
 using kudu::pb_util::SecureShortDebugString;
 using kudu::rpc::Messenger;
@@ -502,15 +501,13 @@ Status WaitForReplicasReportedToMaster(
     WaitForLeader wait_for_leader,
     master::ReplicaTypeFilter filter,
     bool* has_leader,
-    master::TabletLocationsPB* tablet_locations) {
+    master::GetTabletLocationsResponsePB* tablet_locations) {
   MonoTime deadline(MonoTime::Now() + timeout);
   while (true) {
-    RETURN_NOT_OK(GetTabletLocations(
-        master_proxy, tablet_id, timeout, filter, tablet_locations));
+    RETURN_NOT_OK(GetTabletLocations(master_proxy, tablet_id, timeout, filter, tablet_locations));
     *has_leader = false;
-    if (tablet_locations->replicas_size() == num_replicas) {
-      for (const master::TabletLocationsPB_ReplicaPB& replica :
-                    tablet_locations->replicas()) {
+    if (tablet_locations->tablet_locations(0).interned_replicas_size() == num_replicas) {
+      for (const auto& replica : tablet_locations->tablet_locations(0).interned_replicas()) {
         if (replica.role() == RaftPeerPB::LEADER) {
           *has_leader = true;
         }
@@ -527,17 +524,18 @@ Status WaitForReplicasReportedToMaster(
     }
     SleepFor(MonoDelta::FromMilliseconds(20));
   }
-  if (num_replicas != tablet_locations->replicas_size()) {
+  if (num_replicas != tablet_locations->tablet_locations(0).interned_replicas_size()) {
       return Status::NotFound(Substitute("Number of replicas for tablet $0 "
           "reported to master $1:$2",
-          tablet_id, tablet_locations->replicas_size(),
+          tablet_id, tablet_locations->tablet_locations(0).interned_replicas_size(),
           SecureDebugString(*tablet_locations)));
   }
   if (wait_for_leader == WAIT_FOR_LEADER && !(*has_leader)) {
-    return Status::NotFound(Substitute("Leader for tablet $0 not found on master, "
-                                       "number of replicas $1:$2",
-                                       tablet_id, tablet_locations->replicas_size(),
-                                       SecureDebugString(*tablet_locations)));
+    return Status::NotFound(Substitute(
+        "Leader for tablet $0 not found on master, number of replicas $1:$2",
+        tablet_id,
+        tablet_locations->tablet_locations(0).interned_replicas_size(),
+        SecureDebugString(*tablet_locations)));
   }
   return Status::OK();
 }
@@ -907,24 +905,24 @@ Status GetTabletLocations(const shared_ptr<MasterServiceProxy>& master_proxy,
                           const string& tablet_id,
                           const MonoDelta& timeout,
                           master::ReplicaTypeFilter filter,
-                          master::TabletLocationsPB* tablet_locations) {
+                          master::GetTabletLocationsResponsePB* tablet_locations) {
   master::GetTabletLocationsRequestPB req;
   *req.add_tablet_ids() = tablet_id;
   req.set_replica_type_filter(filter);
-
-  master::GetTabletLocationsResponsePB resp;
+  req.set_intern_ts_infos_in_response(true);
   rpc::RpcController rpc;
   rpc.set_timeout(timeout);
-  RETURN_NOT_OK(master_proxy->GetTabletLocations(req, &resp, &rpc));
-  if (resp.has_error()) {
-    return StatusFromPB(resp.error().status());
+  RETURN_NOT_OK(master_proxy->GetTabletLocations(req, tablet_locations, &rpc));
+  if (tablet_locations->has_error()) {
+    return StatusFromPB(tablet_locations->error().status());
   }
-  if (resp.errors_size() > 0) {
-    CHECK_EQ(1, resp.errors_size()) << SecureShortDebugString(resp);
-    return StatusFromPB(resp.errors(0).status());
+  if (tablet_locations->errors_size() > 0) {
+    CHECK_EQ(1, tablet_locations->errors_size())
+        << SecureShortDebugString(*tablet_locations);
+    return StatusFromPB(tablet_locations->errors(0).status());
   }
-  CHECK_EQ(1, resp.tablet_locations_size()) << SecureShortDebugString(resp);
-  *tablet_locations = resp.tablet_locations(0);
+  CHECK_EQ(1, tablet_locations->tablet_locations_size())
+      << SecureShortDebugString(*tablet_locations);
   return Status::OK();
 }
 
@@ -941,6 +939,7 @@ Status GetTableLocations(const shared_ptr<MasterServiceProxy>& master_proxy,
   }
   req.set_replica_type_filter(filter);
   req.set_max_returned_locations(1000);
+  req.set_intern_ts_infos_in_response(true);
   rpc::RpcController rpc;
   rpc.set_timeout(timeout);
   RETURN_NOT_OK(master_proxy->GetTableLocations(req, table_locations, &rpc));
@@ -958,13 +957,13 @@ Status WaitForNumVotersInConfigOnMaster(const shared_ptr<MasterServiceProxy>& ma
   MonoTime deadline = MonoTime::Now() + timeout;
   int num_voters_found = 0;
   while (true) {
-    TabletLocationsPB tablet_locations;
+    master::GetTabletLocationsResponsePB tablet_locations;
     MonoDelta time_remaining = deadline - MonoTime::Now();
     s = GetTabletLocations(master_proxy, tablet_id, time_remaining,
                            master::VOTER_REPLICA, &tablet_locations);
     if (s.ok()) {
       num_voters_found = 0;
-      for (const TabletLocationsPB::ReplicaPB& r : tablet_locations.replicas()) {
+      for (const auto& r : tablet_locations.tablet_locations(0).interned_replicas()) {
         if (r.role() == RaftPeerPB::LEADER || r.role() == RaftPeerPB::FOLLOWER) num_voters_found++;
       }
       if (num_voters_found == num_voters) break;
diff --git a/src/kudu/integration-tests/cluster_itest_util.h b/src/kudu/integration-tests/cluster_itest_util.h
index 286b23d..682857e 100644
--- a/src/kudu/integration-tests/cluster_itest_util.h
+++ b/src/kudu/integration-tests/cluster_itest_util.h
@@ -221,7 +221,7 @@ Status WaitForReplicasReportedToMaster(
     WaitForLeader wait_for_leader,
     master::ReplicaTypeFilter filter,
     bool* has_leader,
-    master::TabletLocationsPB* tablet_locations);
+    master::GetTabletLocationsResponsePB* tablet_locations);
 
 // Wait until the last committed OpId has index exactly 'opid_index'.
 Status WaitUntilCommittedOpIdIndexIs(int64_t opid_index,
@@ -351,7 +351,7 @@ Status GetTabletLocations(const std::shared_ptr<master::MasterServiceProxy>& mas
                           const std::string& tablet_id,
                           const MonoDelta& timeout,
                           master::ReplicaTypeFilter filter,
-                          master::TabletLocationsPB* tablet_locations);
+                          master::GetTabletLocationsResponsePB* tablet_locations);
 
 // Get the list of tablet locations for all tablets in the specified table via the given
 // table name (and table ID if provided) from the Master.
diff --git a/src/kudu/integration-tests/delete_table-itest.cc b/src/kudu/integration-tests/delete_table-itest.cc
index ea48ff9..ce0fdd9 100644
--- a/src/kudu/integration-tests/delete_table-itest.cc
+++ b/src/kudu/integration-tests/delete_table-itest.cc
@@ -311,6 +311,7 @@ TEST_F(DeleteTableITest, TestDeleteEmptyTable) {
     master::GetTabletLocationsResponsePB resp;
     rpc.set_timeout(MonoDelta::FromSeconds(10));
     req.add_tablet_ids()->assign(tablet_id);
+    req.set_intern_ts_infos_in_response(true);
     ASSERT_OK(cluster_->master_proxy()->GetTabletLocations(req, &resp, &rpc));
     SCOPED_TRACE(SecureDebugString(resp));
     ASSERT_EQ(1, resp.errors_size());
@@ -1183,8 +1184,8 @@ TEST_F(DeleteTableITest, TestNoDeleteTombstonedTablets) {
   std::set<string> replicas;
   for (const auto& t : table_locations.tablet_locations()) {
     tablet_id = t.tablet_id();
-    for (const auto& r : t.replicas()) {
-      replicas.insert(r.ts_info().permanent_uuid());
+    for (const auto& r : t.interned_replicas()) {
+      replicas.insert(table_locations.ts_infos(r.ts_info_idx()).permanent_uuid());
     }
   }
 
diff --git a/src/kudu/integration-tests/linked_list-test.cc b/src/kudu/integration-tests/linked_list-test.cc
index 9415bdb..52cfc5b 100644
--- a/src/kudu/integration-tests/linked_list-test.cc
+++ b/src/kudu/integration-tests/linked_list-test.cc
@@ -320,7 +320,7 @@ TEST_F(LinkedListTest, TestLoadWhileOneServerDownAndVerify) {
   // index, let's first make sure that the replica at the restarted tserver
   // is a voter.
   bool has_leader;
-  master::TabletLocationsPB tablet_locations;
+  master::GetTabletLocationsResponsePB tablet_locations;
   ASSERT_OK(WaitForReplicasReportedToMaster(
       cluster_->master_proxy(), FLAGS_num_tablet_servers, tablet_id, wait_time,
       WAIT_FOR_LEADER, VOTER_REPLICA, &has_leader, &tablet_locations));
diff --git a/src/kudu/integration-tests/master_sentry-itest.cc b/src/kudu/integration-tests/master_sentry-itest.cc
index 8f7f849..b9f0696 100644
--- a/src/kudu/integration-tests/master_sentry-itest.cc
+++ b/src/kudu/integration-tests/master_sentry-itest.cc
@@ -78,11 +78,11 @@ using kudu::master::AlterRoleGrantPrivilege;
 using kudu::master::CreateRoleAndAddToGroups;
 using kudu::master::GetDatabasePrivilege;
 using kudu::master::GetTableLocationsResponsePB;
+using kudu::master::GetTabletLocationsResponsePB;
 using kudu::master::GetTablePrivilege;
 using kudu::master::MasterServiceProxy;
 using kudu::master::ResetAuthzCacheRequestPB;
 using kudu::master::ResetAuthzCacheResponsePB;
-using kudu::master::TabletLocationsPB;
 using kudu::master::VOTER_REPLICA;
 using kudu::rpc::RpcController;
 using kudu::rpc::UserCredentials;
@@ -260,7 +260,7 @@ class SentryITestBase : public HmsITestBase {
     user_credentials.set_real_user(kTestUser);
     proxy->set_user_credentials(user_credentials);
 
-    TabletLocationsPB tablet_locations;
+    GetTabletLocationsResponsePB tablet_locations;
     return itest::GetTabletLocations(proxy, tablet_id, kTimeout,
                                      VOTER_REPLICA, &tablet_locations);
   }
diff --git a/src/kudu/integration-tests/raft_config_change-itest.cc b/src/kudu/integration-tests/raft_config_change-itest.cc
index 94af01d..9dfb4a1 100644
--- a/src/kudu/integration-tests/raft_config_change-itest.cc
+++ b/src/kudu/integration-tests/raft_config_change-itest.cc
@@ -127,8 +127,8 @@ TEST_F(RaftConfigChangeITest, TestKudu2147) {
   ASSERT_OK(GetTableLocations(cluster_->master_proxy(), TestWorkload::kDefaultTableName,
                               kTimeout, VOTER_REPLICA, /*table_id=*/none, &table_locations));
   ASSERT_EQ(1, table_locations.tablet_locations_size()); // Only 1 tablet.
-  ASSERT_EQ(3, table_locations.tablet_locations().begin()->replicas_size()); // 3 replicas.
-  string tablet_id = table_locations.tablet_locations().begin()->tablet_id();
+  ASSERT_EQ(3, table_locations.tablet_locations(0).interned_replicas_size()); // 3 replicas.
+  string tablet_id = table_locations.tablet_locations(0).tablet_id();
 
   // Wait for all 3 replicas to converge before we start the test.
   ASSERT_OK(WaitForServersToAgree(kTimeout, ts_map_, tablet_id, 1));
@@ -192,13 +192,14 @@ TEST_F(RaftConfigChangeITest, TestNonVoterPromotion) {
   ASSERT_OK(GetTableLocations(cluster_->master_proxy(), TestWorkload::kDefaultTableName,
                               kTimeout, VOTER_REPLICA, /*table_id=*/none, &table_locations));
   ASSERT_EQ(1, table_locations.tablet_locations_size()); // Only 1 tablet.
-  ASSERT_EQ(3, table_locations.tablet_locations().begin()->replicas_size()); // 3 replicas.
-  string tablet_id = table_locations.tablet_locations().begin()->tablet_id();
+  ASSERT_EQ(3, table_locations.tablet_locations(0).interned_replicas_size()); // 3 replicas.
+  string tablet_id = table_locations.tablet_locations(0).tablet_id();
 
   // Find the TS that does not have a replica.
   unordered_set<string> initial_replicas;
-  for (const auto& replica : table_locations.tablet_locations().begin()->replicas()) {
-    initial_replicas.insert(replica.ts_info().permanent_uuid());
+  for (const auto& replica : table_locations.tablet_locations(0).interned_replicas()) {
+    const auto& uuid = table_locations.ts_infos(replica.ts_info_idx()).permanent_uuid();
+    initial_replicas.insert(uuid);
   }
   TServerDetails* new_replica = nullptr;
   for (const auto& entry : ts_map_) {
@@ -250,12 +251,12 @@ TEST_F(RaftConfigChangeITest, TestBulkChangeConfig) {
   ASSERT_OK(GetTableLocations(cluster_->master_proxy(), TestWorkload::kDefaultTableName,
                               kTimeout, VOTER_REPLICA, /*table_id=*/none, &table_locations));
   ASSERT_EQ(1, table_locations.tablet_locations_size()); // Only 1 tablet.
-  ASSERT_EQ(kNumInitialReplicas, table_locations.tablet_locations().begin()->replicas_size());
-  string tablet_id = table_locations.tablet_locations().begin()->tablet_id();
+  ASSERT_EQ(kNumInitialReplicas, table_locations.tablet_locations(0).interned_replicas_size());
+  string tablet_id = table_locations.tablet_locations(0).tablet_id();
   unordered_set<int> replica_indexes;
-  for (int i = 0; i < table_locations.tablet_locations().begin()->replicas_size(); i++) {
-    const auto& replica = table_locations.tablet_locations().begin()->replicas(i);
-    int idx = cluster_->tablet_server_index_by_uuid(replica.ts_info().permanent_uuid());
+  for (const auto& replica : table_locations.tablet_locations(0).interned_replicas()) {
+    const auto& uuid = table_locations.ts_infos(replica.ts_info_idx()).permanent_uuid();
+    int idx = cluster_->tablet_server_index_by_uuid(uuid);
     replica_indexes.emplace(idx);
     ASSERT_OK(itest::WaitUntilTabletRunning(ts_map_[cluster_->tablet_server(idx)->uuid()],
                                             tablet_id, kTimeout));
diff --git a/src/kudu/integration-tests/raft_consensus-itest.cc b/src/kudu/integration-tests/raft_consensus-itest.cc
index 5068e9c..4e77708 100644
--- a/src/kudu/integration-tests/raft_consensus-itest.cc
+++ b/src/kudu/integration-tests/raft_consensus-itest.cc
@@ -1768,7 +1768,7 @@ TEST_F(RaftConsensusITest, TestMasterNotifiedOnConfigChange) {
 
   // Get a baseline config reported to the master.
   LOG(INFO) << "Waiting for Master to see the current replicas...";
-  master::TabletLocationsPB tablet_locations;
+  master::GetTabletLocationsResponsePB tablet_locations;
   bool has_leader;
   ASSERT_OK(WaitForReplicasReportedToMaster(cluster_->master_proxy(),
                                             2, tablet_id, timeout,
diff --git a/src/kudu/integration-tests/raft_consensus_nonvoter-itest.cc b/src/kudu/integration-tests/raft_consensus_nonvoter-itest.cc
index 48081d2..5f508d9 100644
--- a/src/kudu/integration-tests/raft_consensus_nonvoter-itest.cc
+++ b/src/kudu/integration-tests/raft_consensus_nonvoter-itest.cc
@@ -97,6 +97,7 @@ using kudu::itest::WaitForNumTabletsOnTS;
 using kudu::itest::WaitForReplicasReportedToMaster;
 using kudu::master::ANY_REPLICA;
 using kudu::master::GetTableLocationsResponsePB;
+using kudu::master::GetTabletLocationsResponsePB;
 using kudu::master::TabletLocationsPB;
 using kudu::master::VOTER_REPLICA;
 using kudu::tablet::TABLET_DATA_COPYING;
@@ -265,7 +266,7 @@ TEST_F(RaftConsensusNonVoterITest, GetTableAndTabletLocations) {
     *num_leaders = 0;
     *num_followers = 0;
     *num_learners = 0;
-    for (const auto& r : tablet_locations.replicas()) {
+    for (const auto& r : tablet_locations.interned_replicas()) {
       *num_leaders += (r.role() == RaftPeerPB::LEADER) ? 1 : 0;
       *num_followers += (r.role() == RaftPeerPB::FOLLOWER) ? 1 : 0;
       *num_learners += (r.role() == RaftPeerPB::LEARNER) ? 1 : 0;
@@ -282,7 +283,7 @@ TEST_F(RaftConsensusNonVoterITest, GetTableAndTabletLocations) {
     ASSERT_EQ(1, table_locations.tablet_locations().size());
     const TabletLocationsPB& locations = table_locations.tablet_locations(0);
     ASSERT_EQ(tablet_id_, locations.tablet_id());
-    ASSERT_EQ(kOriginalReplicasNum, locations.replicas_size());
+    ASSERT_EQ(kOriginalReplicasNum, locations.interned_replicas_size());
     int num_leaders = 0, num_followers = 0, num_learners = 0;
     count_roles(locations, &num_leaders, &num_followers, &num_learners);
     ASSERT_EQ(kOriginalReplicasNum, num_leaders + num_followers);
@@ -296,7 +297,7 @@ TEST_F(RaftConsensusNonVoterITest, GetTableAndTabletLocations) {
     ASSERT_EQ(1, table_locations.tablet_locations().size());
     const TabletLocationsPB& locations = table_locations.tablet_locations(0);
     ASSERT_EQ(tablet_id_, locations.tablet_id());
-    ASSERT_EQ(kOriginalReplicasNum + 1, locations.replicas_size());
+    ASSERT_EQ(kOriginalReplicasNum + 1, locations.interned_replicas_size());
     int num_leaders = 0, num_followers = 0, num_learners = 0;
     count_roles(locations, &num_leaders, &num_followers, &num_learners);
     ASSERT_EQ(kOriginalReplicasNum, num_leaders + num_followers);
@@ -306,22 +307,24 @@ TEST_F(RaftConsensusNonVoterITest, GetTableAndTabletLocations) {
   // Verify that replica type filter yields appropriate results for
   // GetTabletLocations() RPC.
   {
-    TabletLocationsPB tablet_locations;
+    GetTabletLocationsResponsePB tablet_locations;
     ASSERT_OK(GetTabletLocations(cluster_->master_proxy(), tablet_id_,
                                  kTimeout, VOTER_REPLICA, &tablet_locations));
-    ASSERT_EQ(kOriginalReplicasNum, tablet_locations.replicas_size());
+    ASSERT_EQ(kOriginalReplicasNum,
+              tablet_locations.tablet_locations(0).interned_replicas_size());
     int num_leaders = 0, num_followers = 0, num_learners = 0;
-    count_roles(tablet_locations, &num_leaders, &num_followers, &num_learners);
+    count_roles(tablet_locations.tablet_locations(0), &num_leaders, &num_followers, &num_learners);
     ASSERT_EQ(kOriginalReplicasNum, num_leaders + num_followers);
     ASSERT_EQ(0, num_learners);
   }
   {
-    TabletLocationsPB tablet_locations;
+    GetTabletLocationsResponsePB tablet_locations;
     ASSERT_OK(GetTabletLocations(cluster_->master_proxy(), tablet_id_,
                                  kTimeout, ANY_REPLICA, &tablet_locations));
-    ASSERT_EQ(kOriginalReplicasNum + 1, tablet_locations.replicas_size());
+    ASSERT_EQ(kOriginalReplicasNum + 1,
+              tablet_locations.tablet_locations(0).interned_replicas_size());
     int num_leaders = 0, num_followers = 0, num_learners = 0;
-    count_roles(tablet_locations, &num_leaders, &num_followers, &num_learners);
+    count_roles(tablet_locations.tablet_locations(0), &num_leaders, &num_followers, &num_learners);
     ASSERT_EQ(kOriginalReplicasNum, num_leaders + num_followers);
     ASSERT_EQ(1, num_learners);
   }
@@ -527,7 +530,7 @@ TEST_F(RaftConsensusNonVoterITest, AddNonVoterReplica) {
   // The master should report about the newly added NON_VOTER tablet replica
   // to the established leader.
   bool has_leader;
-  TabletLocationsPB tablet_locations;
+  GetTabletLocationsResponsePB tablet_locations;
   ASSERT_OK(WaitForReplicasReportedToMaster(
       cluster_->master_proxy(), kOriginalReplicasNum + 1, tablet_id, kTimeout,
       WAIT_FOR_LEADER, ANY_REPLICA, &has_leader, &tablet_locations));
@@ -660,7 +663,7 @@ TEST_F(RaftConsensusNonVoterITest, AddThenRemoveNonVoterReplica) {
   // should report appropriate replica count at this point. The tablet leader
   // should be established.
   bool has_leader;
-  TabletLocationsPB tablet_locations;
+  GetTabletLocationsResponsePB tablet_locations;
   ASSERT_OK(WaitForReplicasReportedToMaster(
       cluster_->master_proxy(), kOriginalReplicasNum, tablet_id, kTimeout,
       WAIT_FOR_LEADER, ANY_REPLICA, &has_leader, &tablet_locations));
@@ -987,7 +990,7 @@ TEST_F(RaftConsensusNonVoterITest, PromoteAndDemote) {
     // The removed tablet replica should be gone, and the master should report
     // appropriate replica count at this point.
     bool has_leader;
-    TabletLocationsPB tablet_locations;
+    GetTabletLocationsResponsePB tablet_locations;
     ASSERT_OK(WaitForReplicasReportedToMaster(
         cluster_->master_proxy(), kInitialReplicasNum, tablet_id, kTimeout,
         WAIT_FOR_LEADER, ANY_REPLICA, &has_leader, &tablet_locations));
@@ -1192,7 +1195,7 @@ TEST_F(RaftConsensusNonVoterITest, CatalogManagerEvictsExcessNonVoter) {
   ASSERT_OK(AddReplica(tablet_id_, new_replica, RaftPeerPB::NON_VOTER, kTimeout));
 
   bool has_leader = false;
-  TabletLocationsPB tablet_locations;
+  GetTabletLocationsResponsePB tablet_locations;
   // Make sure the extra replica is seen by the master.
   ASSERT_OK(WaitForReplicasReportedToMaster(cluster_->master_proxy(),
                                             kReplicasNum + 1,
@@ -1281,7 +1284,7 @@ TEST_F(RaftConsensusNonVoterITest, CatalogManagerAddsNonVoter) {
   // Wait for a new non-voter replica added by the catalog manager to
   // replace the failed one.
   bool has_leader = false;
-  TabletLocationsPB tablet_locations;
+  GetTabletLocationsResponsePB tablet_locations;
   ASSERT_OK(WaitForReplicasReportedToMaster(cluster_->master_proxy(),
                                             kReplicasNum + 1,
                                             tablet_id_,
@@ -1362,7 +1365,7 @@ TEST_F(RaftConsensusNonVoterITest, TabletServerIsGoneAndBack) {
   // catalog manager should spot that and add a new non-voter replica as a
   // replacement.
   bool has_leader = false;
-  TabletLocationsPB tablet_locations;
+  GetTabletLocationsResponsePB tablet_locations;
   ASSERT_OK(WaitForReplicasReportedToMaster(cluster_->master_proxy(),
                                             kReplicasNum + 1,
                                             tablet_id_,
@@ -1491,7 +1494,7 @@ TEST_F(RaftConsensusNonVoterITest, FailedTabletCopy) {
   // manager evicts the failed replica right away since it failed in an
   // unrecoverable way.
   bool has_leader = false;
-  TabletLocationsPB tablet_locations;
+  GetTabletLocationsResponsePB tablet_locations;
   ASSERT_OK(WaitForReplicasReportedToMaster(cluster_->master_proxy(),
                                             kReplicasNum - 1,
                                             tablet_id_,
@@ -1548,7 +1551,7 @@ TEST_F(RaftConsensusNonVoterITest, FailedTabletCopy) {
   // state.
   ASSERT_EVENTUALLY([&] {
     bool has_leader = false;
-    TabletLocationsPB tablet_locations;
+    GetTabletLocationsResponsePB tablet_locations;
     ASSERT_OK(WaitForReplicasReportedToMaster(cluster_->master_proxy(),
                                               kReplicasNum,
                                               tablet_id_,
@@ -1653,7 +1656,7 @@ TEST_F(RaftConsensusNonVoterITest, RestartClusterWithNonVoter) {
   ts_with_replica->Shutdown();
 
   bool has_leader = false;
-  TabletLocationsPB tablet_locations;
+  GetTabletLocationsResponsePB tablet_locations;
   ASSERT_OK(WaitForReplicasReportedToMaster(cluster_->master_proxy(),
                                             kReplicasNum + 1,
                                             tablet_id_,
@@ -1664,11 +1667,11 @@ TEST_F(RaftConsensusNonVoterITest, RestartClusterWithNonVoter) {
                                             &tablet_locations));
   // Find the location of the new non-voter replica.
   string new_replica_uuid;
-  for (const auto& r : tablet_locations.replicas()) {
+  for (const auto& r : tablet_locations.tablet_locations(0).interned_replicas()) {
     if (r.role() != RaftPeerPB::LEARNER) {
       continue;
     }
-    new_replica_uuid = r.ts_info().permanent_uuid();
+    new_replica_uuid = tablet_locations.ts_infos(r.ts_info_idx()).permanent_uuid();
     break;
   }
   ASSERT_FALSE(new_replica_uuid.empty());
@@ -2011,7 +2014,7 @@ TEST_P(ReplicaBehindWalGcThresholdITest, ReplicaReplacement) {
   // The catalog manager should evict the replicas which fell behing the WAL
   // segment GC threshold right away.
   bool has_leader = false;
-  TabletLocationsPB tablet_locations;
+  GetTabletLocationsResponsePB tablet_locations;
   const auto num_replicas = (tserver_behavior == BehindWalGcBehavior::SHUTDOWN)
       ? kReplicasNum : kReplicasNum - 1;
   ASSERT_OK(WaitForReplicasReportedToMaster(cluster_->master_proxy(),
diff --git a/src/kudu/integration-tests/registration-test.cc b/src/kudu/integration-tests/registration-test.cc
index 6e6eb36..def3b9e 100644
--- a/src/kudu/integration-tests/registration-test.cc
+++ b/src/kudu/integration-tests/registration-test.cc
@@ -189,6 +189,7 @@ class RegistrationTest : public KuduTest {
           cluster_->mini_master()->master()->catalog_manager();
       Status s;
       TabletLocationsPB loc;
+      CatalogManager::TSInfosDict infos_dict;
       do {
         master::CatalogManager::ScopedLeaderSharedLock l(catalog);
         const Status& ls = l.first_failed_status();
@@ -203,10 +204,10 @@ class RegistrationTest : public KuduTest {
         s = catalog->GetTabletLocations(tablet_id,
                                         master::VOTER_REPLICA,
                                         &loc,
-                                        /*ts_infos_dict=*/nullptr,
+                                        &infos_dict,
                                         /*user=*/none);
       } while (false);
-      if (s.ok() && loc.replicas_size() == expected_count) {
+      if (s.ok() && loc.interned_replicas_size() == expected_count) {
         if (locations) {
           *locations = std::move(loc);
         }
@@ -302,7 +303,7 @@ TEST_F(RegistrationTest, TestTabletReports) {
       cluster_->mini_master(), "tablet-reports-1", schema_, &tablet_id_1));
   TabletLocationsPB locs_1;
   ASSERT_OK(WaitForReplicaCount(tablet_id_1, 1, &locs_1));
-  ASSERT_EQ(1, locs_1.replicas_size());
+  ASSERT_EQ(1, locs_1.interned_replicas_size());
 
   // Check that we inserted the right number of rows for the new single-tablet table
   // (one for the table, one for the tablet).
diff --git a/src/kudu/integration-tests/table_locations-itest.cc b/src/kudu/integration-tests/table_locations-itest.cc
index efa5169..e3d2cef 100644
--- a/src/kudu/integration-tests/table_locations-itest.cc
+++ b/src/kudu/integration-tests/table_locations-itest.cc
@@ -247,11 +247,35 @@ TEST_F(TableLocationsTest, TestGetTableLocations) {
     EXPECT_EQ("aa", resp.tablet_locations(1).partition().partition_key_start());
     EXPECT_EQ("ab", resp.tablet_locations(2).partition().partition_key_start());
 
+    auto get_tablet_location = [](MasterServiceProxy* proxy, const string& tablet_id) {
+      rpc::RpcController rpc_old;
+      GetTabletLocationsRequestPB req_old;
+      GetTabletLocationsResponsePB resp_old;
+      *req_old.add_tablet_ids() = tablet_id;
+      ASSERT_OK(proxy->GetTabletLocations(req_old, &resp_old, &rpc_old));
+      const auto& loc_old = resp_old.tablet_locations(0);
+      ASSERT_GT(loc_old.deprecated_replicas_size(), 0);
+      ASSERT_EQ(0, loc_old.interned_replicas_size());
+
+      rpc::RpcController rpc_new;
+      GetTabletLocationsRequestPB req_new;
+      GetTabletLocationsResponsePB resp_new;
+      *req_new.add_tablet_ids() = tablet_id;
+      req_new.set_intern_ts_infos_in_response(true);
+      ASSERT_OK(proxy->GetTabletLocations(req_new, &resp_new, &rpc_new));
+      const auto& loc_new = resp_new.tablet_locations(0);
+      ASSERT_GT(loc_new.interned_replicas_size(), 0);
+      ASSERT_EQ(0, loc_new.deprecated_replicas_size());
+
+      ASSERT_EQ(loc_old.tablet_id(), loc_new.tablet_id());
+    };
+
     // Check that a UUID was returned for every replica
     for (const auto& loc : resp.tablet_locations()) {
-      ASSERT_GT(loc.replicas_size(), 0);
+      NO_FATALS(get_tablet_location(proxy_.get(), loc.tablet_id()));
+      ASSERT_GT(loc.deprecated_replicas_size(), 0);
       ASSERT_EQ(0, loc.interned_replicas_size());
-      for (const auto& replica : loc.replicas()) {
+      for (const auto& replica : loc.deprecated_replicas()) {
         ASSERT_NE("", replica.ts_info().permanent_uuid());
       }
     }
@@ -275,7 +299,7 @@ TEST_F(TableLocationsTest, TestGetTableLocations) {
     EXPECT_EQ("ab", resp.tablet_locations(2).partition().partition_key_start());
     // Check that a UUID was returned for every replica
     for (const auto& loc : resp.tablet_locations()) {
-      ASSERT_EQ(loc.replicas_size(), 0);
+      ASSERT_EQ(loc.deprecated_replicas_size(), 0);
       ASSERT_GT(loc.interned_replicas_size(), 0);
       for (const auto& replica : loc.interned_replicas()) {
         int idx = replica.ts_info_idx();
@@ -324,14 +348,16 @@ TEST_F(TableLocationsTest, TestGetTableLocations) {
     RpcController controller;
     req.mutable_table()->set_table_name(table_name);
     req.set_max_returned_locations(1);
+    req.set_intern_ts_infos_in_response(true);
     ASSERT_OK(proxy_->GetTableLocations(req, &resp, &controller));
     SCOPED_TRACE(SecureDebugString(resp));
 
     ASSERT_TRUE(!resp.has_error());
     ASSERT_EQ(1, resp.tablet_locations().size());
-    ASSERT_EQ(3, resp.tablet_locations(0).replicas_size());
-    for (int i = 0; i < 3; i++) {
-      EXPECT_EQ("", resp.tablet_locations(0).replicas(i).ts_info().location());
+    ASSERT_EQ(3, resp.tablet_locations(0).interned_replicas_size());
+    const auto& loc = resp.tablet_locations(0);
+    for (const auto& replica : loc.interned_replicas()) {
+      EXPECT_EQ("", resp.ts_infos(replica.ts_info_idx()).location());
     }
   }
 }
@@ -348,14 +374,16 @@ TEST_F(TableLocationsWithTSLocationTest, TestGetTSLocation) {
     GetTableLocationsResponsePB resp;
     RpcController controller;
     req.mutable_table()->set_table_name(table_name);
+    req.set_intern_ts_infos_in_response(true);
     ASSERT_OK(proxy_->GetTableLocations(req, &resp, &controller));
     SCOPED_TRACE(SecureDebugString(resp));
 
     ASSERT_TRUE(!resp.has_error());
     ASSERT_EQ(1, resp.tablet_locations().size());
-    ASSERT_EQ(3, resp.tablet_locations(0).replicas_size());
-    for (int i = 0; i < 3; i++) {
-      ASSERT_EQ("/foo", resp.tablet_locations(0).replicas(i).ts_info().location());
+    ASSERT_EQ(3, resp.tablet_locations(0).interned_replicas_size());
+    const auto& loc = resp.tablet_locations(0);
+    for (const auto& replica : loc.interned_replicas()) {
+      ASSERT_EQ("/foo", resp.ts_infos(replica.ts_info_idx()).location());
     }
   }
 }
diff --git a/src/kudu/integration-tests/tablet_copy-itest.cc b/src/kudu/integration-tests/tablet_copy-itest.cc
index 2a5fbbf..53ec383 100644
--- a/src/kudu/integration-tests/tablet_copy-itest.cc
+++ b/src/kudu/integration-tests/tablet_copy-itest.cc
@@ -1256,8 +1256,8 @@ TEST_P(TabletCopyFailureITest, TestTabletCopyNewReplicaFailureCanVote) {
   ASSERT_EQ(1, table_locations.tablet_locations_size());
   string tablet_id = table_locations.tablet_locations(0).tablet_id();
   set<string> replica_uuids;
-  for (const auto& replica : table_locations.tablet_locations(0).replicas()) {
-    replica_uuids.insert(replica.ts_info().permanent_uuid());
+  for (const auto& replica : table_locations.tablet_locations(0).interned_replicas()) {
+    replica_uuids.insert(table_locations.ts_infos(replica.ts_info_idx()).permanent_uuid());
   }
 
   string new_replica_uuid;
diff --git a/src/kudu/integration-tests/tombstoned_voting-itest.cc b/src/kudu/integration-tests/tombstoned_voting-itest.cc
index 484b575..6e958ef 100644
--- a/src/kudu/integration-tests/tombstoned_voting-itest.cc
+++ b/src/kudu/integration-tests/tombstoned_voting-itest.cc
@@ -97,8 +97,8 @@ TEST_F(TombstonedVotingITest, TestTombstonedReplicaWithoutCMetaCanVote) {
   ASSERT_EQ(1, table_locations.tablet_locations_size());
   string tablet_id = table_locations.tablet_locations(0).tablet_id();
   set<string> replica_uuids;
-  for (const auto& replica : table_locations.tablet_locations(0).replicas()) {
-    replica_uuids.insert(replica.ts_info().permanent_uuid());
+  for (const auto& replica : table_locations.tablet_locations(0).interned_replicas()) {
+    replica_uuids.insert(table_locations.ts_infos(replica.ts_info_idx()).permanent_uuid());
   }
 
   // Figure out which tablet server didn't get a replica. We will use it for
diff --git a/src/kudu/integration-tests/ts_itest-base.cc b/src/kudu/integration-tests/ts_itest-base.cc
index cdbc4a1..ae5d60d 100644
--- a/src/kudu/integration-tests/ts_itest-base.cc
+++ b/src/kudu/integration-tests/ts_itest-base.cc
@@ -168,6 +168,7 @@ void TabletServerIntegrationTestBase::WaitForReplicasAndUpdateLocations(
     rpc::RpcController controller;
     req.mutable_table()->set_table_name(table_id);
     req.set_replica_type_filter(master::ANY_REPLICA);
+    req.set_intern_ts_infos_in_response(true);
     controller.set_timeout(MonoDelta::FromSeconds(1));
     CHECK_OK(cluster_->master_proxy()->GetTableLocations(req, &resp, &controller));
     CHECK_OK(controller.status());
@@ -191,10 +192,10 @@ void TabletServerIntegrationTestBase::WaitForReplicasAndUpdateLocations(
       continue;
     }
 
-    for (const master::TabletLocationsPB& location : resp.tablet_locations()) {
-      for (const master::TabletLocationsPB_ReplicaPB& replica : location.replicas()) {
+    for (const auto& location : resp.tablet_locations()) {
+      for (const auto& replica : location.interned_replicas()) {
         TServerDetails* server =
-            FindOrDie(tablet_servers_, replica.ts_info().permanent_uuid());
+            FindOrDie(tablet_servers_, resp.ts_infos(replica.ts_info_idx()).permanent_uuid());
         tablet_replicas.insert(pair<string, TServerDetails*>(
             location.tablet_id(), server));
       }
@@ -345,13 +346,14 @@ Status TabletServerIntegrationTestBase::GetTabletLeaderUUIDFromMaster(
   rpc::RpcController controller;
   controller.set_timeout(MonoDelta::FromMilliseconds(100));
   req.mutable_table()->set_table_name(kTableId);
+  req.set_intern_ts_infos_in_response(true);
 
   RETURN_NOT_OK(cluster_->master_proxy()->GetTableLocations(req, &resp, &controller));
   for (const master::TabletLocationsPB& loc : resp.tablet_locations()) {
     if (loc.tablet_id() == tablet_id) {
-      for (const master::TabletLocationsPB::ReplicaPB& replica : loc.replicas()) {
+      for (const auto& replica : loc.interned_replicas()) {
         if (replica.role() == consensus::RaftPeerPB::LEADER) {
-          *leader_uuid = replica.ts_info().permanent_uuid();
+          *leader_uuid = resp.ts_infos(replica.ts_info_idx()).permanent_uuid();
           return Status::OK();
         }
       }
diff --git a/src/kudu/master/catalog_manager.cc b/src/kudu/master/catalog_manager.cc
index 46ebf25..e37b1eb 100644
--- a/src/kudu/master/catalog_manager.cc
+++ b/src/kudu/master/catalog_manager.cc
@@ -4784,7 +4784,7 @@ Status CatalogManager::BuildLocationsForTablet(
         interned_replica_pb->set_dimension_label(*dimension);
       }
     } else {
-      TabletLocationsPB_ReplicaPB* replica_pb = locs_pb->add_replicas();
+      TabletLocationsPB_DEPRECATED_ReplicaPB* replica_pb = locs_pb->add_deprecated_replicas();
       replica_pb->set_allocated_ts_info(make_tsinfo_pb().release());
       replica_pb->set_role(role);
     }
@@ -4807,7 +4807,8 @@ Status CatalogManager::GetTabletLocations(const string& tablet_id,
   leader_lock_.AssertAcquiredForReading();
   RETURN_NOT_OK(CheckOnline());
 
-  locs_pb->mutable_replicas()->Clear();
+  locs_pb->mutable_deprecated_replicas()->Clear();
+  locs_pb->mutable_interned_replicas()->Clear();
   scoped_refptr<TabletInfo> tablet_info;
   {
     shared_lock<LockType> l(lock_);
diff --git a/src/kudu/master/master.proto b/src/kudu/master/master.proto
index 448e074..6df4836 100644
--- a/src/kudu/master/master.proto
+++ b/src/kudu/master/master.proto
@@ -371,7 +371,7 @@ message TabletLocationsPB {
   // DEPRECATED.
   // TODO: new clients should prefer the 'Interned' type below.
   // Remove 'ReplicaPB' when we stop using it internally.
-  message ReplicaPB {
+  message DEPRECATED_ReplicaPB {
     required TSInfoPB ts_info = 1;
     required consensus.RaftPeerPB.Role role = 2;
     optional string dimension_label = 3;
@@ -392,8 +392,9 @@ message TabletLocationsPB {
 
   optional PartitionPB partition = 6;
 
+  // DEPRECATED.
   // Used only if interned replicas are not supported by client.
-  repeated ReplicaPB replicas = 4;
+  repeated DEPRECATED_ReplicaPB DEPRECATED_replicas = 4;
 
   // More efficient representation of replicas: instead of duplicating the TSInfoPB
   // in each tablet location, instead we just encode indexes into a list of TSInfoPB
@@ -435,8 +436,8 @@ message GetTabletLocationsRequestPB {
   // What type of tablet replicas to include in the response.
   optional ReplicaTypeFilter replica_type_filter = 2 [ default = VOTER_REPLICA ];
 
-  // NOTE: we don't support the "interned" TSInfoPB response in this request
-  // since this RPC is currently called with a small number of tablets.
+  // Whether the response should use the 'interned_replicas' field.
+  optional bool intern_ts_infos_in_response = 3 [ default = false ];
 }
 
 message GetTabletLocationsResponsePB {
@@ -444,6 +445,10 @@ message GetTabletLocationsResponsePB {
 
   repeated TabletLocationsPB tablet_locations = 2;
 
+  // Used if 'intern_ts_infos_in_response' was requested.
+  // See InternedReplicaPB above.
+  repeated TSInfoPB ts_infos = 4;
+
   message Error {
     required bytes tablet_id = 1;
     required AppStatusPB status = 2;
diff --git a/src/kudu/master/master_service.cc b/src/kudu/master/master_service.cc
index d1647ce..8a03f45 100644
--- a/src/kudu/master/master_service.cc
+++ b/src/kudu/master/master_service.cc
@@ -307,15 +307,14 @@ void MasterServiceImpl::GetTabletLocations(const GetTabletLocationsRequestPB* re
     SleepFor(MonoDelta::FromMilliseconds(FLAGS_master_inject_latency_on_tablet_lookups_ms));
   }
 
-  ServerRegistrationPB reg;
-  vector<TSDescriptor*> locs;
+  CatalogManager::TSInfosDict infos_dict;
   for (const string& tablet_id : req->tablet_ids()) {
     // TODO(todd): once we have catalog data. ACL checks would also go here, probably.
     TabletLocationsPB* locs_pb = resp->add_tablet_locations();
     Status s = server_->catalog_manager()->GetTabletLocations(
         tablet_id, req->replica_type_filter(),
         locs_pb,
-        /*ts_infos_dict=*/nullptr,
+        req->intern_ts_infos_in_response() ? &infos_dict : nullptr,
         make_optional<const string&>(rpc->remote_user().username()));
     if (!s.ok()) {
       resp->mutable_tablet_locations()->RemoveLast();
@@ -325,6 +324,9 @@ void MasterServiceImpl::GetTabletLocations(const GetTabletLocationsRequestPB* re
       StatusToPB(s, err->mutable_status());
     }
   }
+  for (auto& pb : infos_dict.ts_info_pbs) {
+    resp->mutable_ts_infos()->AddAllocated(pb.release());
+  }
 
   rpc->RespondSuccess();
 }
diff --git a/src/kudu/tools/kudu-admin-test.cc b/src/kudu/tools/kudu-admin-test.cc
index a9a0cc0..1110ec3 100644
--- a/src/kudu/tools/kudu-admin-test.cc
+++ b/src/kudu/tools/kudu-admin-test.cc
@@ -450,7 +450,7 @@ TEST_F(AdminCliTest, TestUnsafeChangeConfigOnSingleFollower) {
 
   // Get a baseline config reported to the master.
   LOG(INFO) << "Waiting for Master to see the current replicas...";
-  master::TabletLocationsPB tablet_locations;
+  master::GetTabletLocationsResponsePB tablet_locations;
   bool has_leader;
   ASSERT_OK(WaitForReplicasReportedToMaster(cluster_->master_proxy(),
                                             3, tablet_id, kTimeout,
@@ -497,18 +497,18 @@ TEST_F(AdminCliTest, TestUnsafeChangeConfigOnSingleFollower) {
 
   active_tablet_servers.clear();
   std::unordered_set<string> replica_uuids;
-  for (const auto& loc : tablet_locations.replicas()) {
-    const string& uuid = loc.ts_info().permanent_uuid();
+  for (const auto& loc : tablet_locations.tablet_locations(0).interned_replicas()) {
+    const auto& uuid = tablet_locations.ts_infos(loc.ts_info_idx()).permanent_uuid();
     InsertOrDie(&active_tablet_servers, uuid, tablet_servers_[uuid]);
   }
   ASSERT_OK(WaitForServersToAgree(kTimeout, active_tablet_servers, tablet_id, opid.index()));
 
   // Verify that two new servers are part of new config and old
   // servers are gone.
-  for (const master::TabletLocationsPB_ReplicaPB& replica :
-      tablet_locations.replicas()) {
-    ASSERT_NE(replica.ts_info().permanent_uuid(), followers[1]->uuid());
-    ASSERT_NE(replica.ts_info().permanent_uuid(), leader_ts->uuid());
+  for (const auto& replica : tablet_locations.tablet_locations(0).interned_replicas()) {
+    const auto& uuid = tablet_locations.ts_infos(replica.ts_info_idx()).permanent_uuid();
+    ASSERT_NE(uuid, followers[1]->uuid());
+    ASSERT_NE(uuid, leader_ts->uuid());
   }
 
   // Also verify that when we bring back followers[1] and leader,
@@ -540,7 +540,7 @@ TEST_F(AdminCliTest, TestUnsafeChangeConfigOnSingleLeader) {
 
   // Get a baseline config reported to the master.
   LOG(INFO) << "Waiting for Master to see the current replicas...";
-  master::TabletLocationsPB tablet_locations;
+  master::GetTabletLocationsResponsePB tablet_locations;
   bool has_leader;
   ASSERT_OK(WaitForReplicasReportedToMaster(cluster_->master_proxy(),
                                             3, tablet_id_, kTimeout,
@@ -595,10 +595,10 @@ TEST_F(AdminCliTest, TestUnsafeChangeConfigOnSingleLeader) {
                                             WAIT_FOR_LEADER, VOTER_REPLICA,
                                             &has_leader, &tablet_locations));
   LOG(INFO) << "Tablet locations:\n" << SecureDebugString(tablet_locations);
-  for (const master::TabletLocationsPB_ReplicaPB& replica :
-      tablet_locations.replicas()) {
-    ASSERT_NE(replica.ts_info().permanent_uuid(), followers[0]->uuid());
-    ASSERT_NE(replica.ts_info().permanent_uuid(), followers[1]->uuid());
+  for (const auto& replica : tablet_locations.tablet_locations(0).interned_replicas()) {
+    const auto& uuid = tablet_locations.ts_infos(replica.ts_info_idx()).permanent_uuid();
+    ASSERT_NE(uuid, followers[0]->uuid());
+    ASSERT_NE(uuid, followers[1]->uuid());
   }
 }
 
@@ -623,7 +623,7 @@ TEST_F(AdminCliTest, TestUnsafeChangeConfigForConfigWithTwoNodes) {
 
   // Get a baseline config reported to the master.
   LOG(INFO) << "Waiting for Master to see the current replicas...";
-  master::TabletLocationsPB tablet_locations;
+  master::GetTabletLocationsResponsePB tablet_locations;
   bool has_leader;
   ASSERT_OK(WaitForReplicasReportedToMaster(cluster_->master_proxy(),
                                             3, tablet_id_, kTimeout,
@@ -677,9 +677,9 @@ TEST_F(AdminCliTest, TestUnsafeChangeConfigForConfigWithTwoNodes) {
                                             WAIT_FOR_LEADER, VOTER_REPLICA,
                                             &has_leader, &tablet_locations));
   LOG(INFO) << "Tablet locations:\n" << SecureDebugString(tablet_locations);
-  for (const master::TabletLocationsPB_ReplicaPB& replica :
-      tablet_locations.replicas()) {
-    ASSERT_NE(replica.ts_info().permanent_uuid(), leader_ts->uuid());
+  for (const auto& replica : tablet_locations.tablet_locations(0).interned_replicas()) {
+    const auto& uuid = tablet_locations.ts_infos(replica.ts_info_idx()).permanent_uuid();
+    ASSERT_NE(uuid, leader_ts->uuid());
   }
 }
 
@@ -714,7 +714,7 @@ TEST_F(AdminCliTest, TestUnsafeChangeConfigWithFiveReplicaConfig) {
 
   // Get a baseline config reported to the master.
   LOG(INFO) << "Waiting for Master to see the current replicas...";
-  master::TabletLocationsPB tablet_locations;
+  master::GetTabletLocationsResponsePB tablet_locations;
   bool has_leader;
   ASSERT_OK(WaitForReplicasReportedToMaster(cluster_->master_proxy(),
                                             5, tablet_id_, kTimeout,
@@ -769,11 +769,11 @@ TEST_F(AdminCliTest, TestUnsafeChangeConfigWithFiveReplicaConfig) {
                                             WAIT_FOR_LEADER, VOTER_REPLICA,
                                             &has_leader, &tablet_locations));
   LOG(INFO) << "Tablet locations:\n" << SecureDebugString(tablet_locations);
-  for (const master::TabletLocationsPB_ReplicaPB& replica :
-      tablet_locations.replicas()) {
-    ASSERT_NE(replica.ts_info().permanent_uuid(), leader_ts->uuid());
-    ASSERT_NE(replica.ts_info().permanent_uuid(), followers[2]->uuid());
-    ASSERT_NE(replica.ts_info().permanent_uuid(), followers[3]->uuid());
+  for (const auto& replica : tablet_locations.tablet_locations(0).interned_replicas()) {
+    const auto& uuid = tablet_locations.ts_infos(replica.ts_info_idx()).permanent_uuid();
+    ASSERT_NE(uuid, leader_ts->uuid());
+    ASSERT_NE(uuid, followers[2]->uuid());
+    ASSERT_NE(uuid, followers[3]->uuid());
   }
 }
 
@@ -800,7 +800,7 @@ TEST_F(AdminCliTest, TestUnsafeChangeConfigLeaderWithPendingConfig) {
 
   // Get a baseline config reported to the master.
   LOG(INFO) << "Waiting for Master to see the current replicas...";
-  master::TabletLocationsPB tablet_locations;
+  master::GetTabletLocationsResponsePB tablet_locations;
   bool has_leader;
   ASSERT_OK(WaitForReplicasReportedToMaster(cluster_->master_proxy(),
                                             3, tablet_id_, kTimeout,
@@ -864,10 +864,10 @@ TEST_F(AdminCliTest, TestUnsafeChangeConfigLeaderWithPendingConfig) {
                                             WAIT_FOR_LEADER, VOTER_REPLICA,
                                             &has_leader, &tablet_locations));
   LOG(INFO) << "Tablet locations:\n" << SecureDebugString(tablet_locations);
-  for (const master::TabletLocationsPB_ReplicaPB& replica :
-      tablet_locations.replicas()) {
-    ASSERT_NE(replica.ts_info().permanent_uuid(), followers[0]->uuid());
-    ASSERT_NE(replica.ts_info().permanent_uuid(), followers[1]->uuid());
+  for (const auto& replica : tablet_locations.tablet_locations(0).interned_replicas()) {
+    const auto& uuid = tablet_locations.ts_infos(replica.ts_info_idx()).permanent_uuid();
+    ASSERT_NE(uuid, followers[0]->uuid());
+    ASSERT_NE(uuid, followers[1]->uuid());
   }
 }
 
@@ -895,7 +895,7 @@ TEST_F(AdminCliTest, TestUnsafeChangeConfigFollowerWithPendingConfig) {
 
   // Get a baseline config reported to the master.
   LOG(INFO) << "Waiting for Master to see the current replicas...";
-  master::TabletLocationsPB tablet_locations;
+  master::GetTabletLocationsResponsePB tablet_locations;
   bool has_leader;
   ASSERT_OK(WaitForReplicasReportedToMaster(cluster_->master_proxy(),
                                             3, tablet_id_, kTimeout,
@@ -970,10 +970,10 @@ TEST_F(AdminCliTest, TestUnsafeChangeConfigFollowerWithPendingConfig) {
                                             WAIT_FOR_LEADER, VOTER_REPLICA,
                                             &has_leader, &tablet_locations));
   LOG(INFO) << "Tablet locations:\n" << SecureDebugString(tablet_locations);
-  for (const master::TabletLocationsPB_ReplicaPB& replica :
-      tablet_locations.replicas()) {
-    ASSERT_NE(replica.ts_info().permanent_uuid(), followers[1]->uuid());
-    ASSERT_NE(replica.ts_info().permanent_uuid(), followers[0]->uuid());
+  for (const auto& replica : tablet_locations.tablet_locations(0).interned_replicas()) {
+    const auto& uuid = tablet_locations.ts_infos(replica.ts_info_idx()).permanent_uuid();
+    ASSERT_NE(uuid, followers[1]->uuid());
+    ASSERT_NE(uuid, followers[0]->uuid());
   }
 }
 
@@ -1001,7 +1001,7 @@ TEST_F(AdminCliTest, TestUnsafeChangeConfigWithPendingConfigsOnWAL) {
 
   // Get a baseline config reported to the master.
   LOG(INFO) << "Waiting for Master to see the current replicas...";
-  master::TabletLocationsPB tablet_locations;
+  master::GetTabletLocationsResponsePB tablet_locations;
   bool has_leader;
   ASSERT_OK(WaitForReplicasReportedToMaster(cluster_->master_proxy(),
                                             3, tablet_id_, kTimeout,
@@ -1074,10 +1074,10 @@ TEST_F(AdminCliTest, TestUnsafeChangeConfigWithPendingConfigsOnWAL) {
                                             WAIT_FOR_LEADER, VOTER_REPLICA,
                                             &has_leader, &tablet_locations));
   LOG(INFO) << "Tablet locations:\n" << SecureDebugString(tablet_locations);
-  for (const master::TabletLocationsPB_ReplicaPB& replica :
-      tablet_locations.replicas()) {
-    ASSERT_NE(replica.ts_info().permanent_uuid(), followers[0]->uuid());
-    ASSERT_NE(replica.ts_info().permanent_uuid(), followers[1]->uuid());
+  for (const auto& replica : tablet_locations.tablet_locations(0).interned_replicas()) {
+    const auto& uuid = tablet_locations.ts_infos(replica.ts_info_idx()).permanent_uuid();
+    ASSERT_NE(uuid, followers[0]->uuid());
+    ASSERT_NE(uuid, followers[1]->uuid());
   }
 }
 
@@ -1112,7 +1112,7 @@ TEST_F(AdminCliTest, TestUnsafeChangeConfigWithMultiplePendingConfigs) {
 
   // Get a baseline config reported to the master.
   LOG(INFO) << "Waiting for Master to see the current replicas...";
-  master::TabletLocationsPB tablet_locations;
+  master::GetTabletLocationsResponsePB tablet_locations;
   bool has_leader;
   ASSERT_OK(WaitForReplicasReportedToMaster(cluster_->master_proxy(),
                                             5, tablet_id_, kTimeout,
@@ -1182,11 +1182,11 @@ TEST_F(AdminCliTest, TestUnsafeChangeConfigWithMultiplePendingConfigs) {
                                             WAIT_FOR_LEADER, VOTER_REPLICA,
                                             &has_leader, &tablet_locations));
   LOG(INFO) << "Tablet locations:\n" << SecureDebugString(tablet_locations);
-  for (const master::TabletLocationsPB_ReplicaPB& replica :
-      tablet_locations.replicas()) {
+  for (const auto& replica : tablet_locations.tablet_locations(0).interned_replicas()) {
     // Verify that old followers aren't part of new config.
     for (const auto& old_follower : followers) {
-      ASSERT_NE(replica.ts_info().permanent_uuid(), old_follower->uuid());
+      const auto& uuid = tablet_locations.ts_infos(replica.ts_info_idx()).permanent_uuid();
+      ASSERT_NE(uuid, old_follower->uuid());
     }
   }
 }
diff --git a/src/kudu/tools/rebalancer_tool-test.cc b/src/kudu/tools/rebalancer_tool-test.cc
index 3641204..4d2ce31 100644
--- a/src/kudu/tools/rebalancer_tool-test.cc
+++ b/src/kudu/tools/rebalancer_tool-test.cc
@@ -1321,8 +1321,8 @@ TEST_F(LocationAwareRebalancingBasicTest, Basic) {
       const auto& location = table_locations.tablet_locations(i);
       const auto& tablet_id = location.tablet_id();
       unordered_map<string, int> count_per_location;
-      for (const auto& replica : location.replicas()) {
-        const auto& ts_id = replica.ts_info().permanent_uuid();
+      for (const auto& replica : location.interned_replicas()) {
+        const auto& ts_id = table_locations.ts_infos(replica.ts_info_idx()).permanent_uuid();
         const auto& location = FindOrDie(location_by_ts_id, ts_id);
         ++LookupOrEmplace(&count_per_location, location, 0);
         ++LookupOrEmplace(&total_count_per_location, location, 0);