public HelixAggregatedViewClusterInfo initialize() throws Exception {
    // We will have Helix Aggregated view cluster in each data center. Connect to helix aggregated view cluster in local
    // DC.
    String localDcName = clusterMapConfig.clusterMapDatacenterName;
    String aggregatedViewClusterName = clusterMapConfig.clusterMapAggregatedViewClusterName;
    DcZkInfo localDcZkInfo = dataCenterToZkAddress.get(localDcName);

    // The first ZK endpoint (if there are more than one endpoints) will be adopted by default for initialization since
    // Ambry doesn't support multiple spectators and uses only one source of truth.
    String localZkConnectStr = localDcZkInfo.getZkConnectStrs().get(0);
    HelixManager helixManager =
        helixFactory.getZkHelixManagerAndConnect(aggregatedViewClusterName, selfInstanceName, InstanceType.SPECTATOR,
            localZkConnectStr, clusterMapConfig);
    logger.info("Helix cluster name {}", helixManager.getClusterName());

    clusterChangeHandler =
        helixClusterManager.new HelixClusterChangeHandler(localDcName, aggregatedViewClusterName, ex -> exception = ex,
            true);

    // Ambry needs below information from Helix:
    // 1. Data node configuration: This gives the host configs such as disks on each host, replicas on each disk, etc.
    //    This is stored in helix "PROPERTYSTORE/DataNodeConfigs" ZNodes.
    // 2. Ideal states: This tells the assigned mapping of partition to replicas.
    // 2. External view: This tells the current mapping of partition to replicas along with their states.
    // 3. Live instances: This tells the list of live hosts in cluster so that we can mark the hosts as down.

    // Currently, Helix only provides aggregated view of External view and Live instances. So, we will have to
    // fetch data in Helix Property store from all colos ourselves.

    // 1. Fetch Data node configs from each Data center and wait until the initialization is complete before registering
    // for changes from Aggregated helix view.
    List<Thread> dcInitThreads = new ArrayList<>();
    for (String dcName : dataCenterToZkAddress.keySet()) {
      DcZkInfo dcZkInfo = dataCenterToZkAddress.get(dcName);
      if (dcZkInfo.getReplicaType() != ReplicaType.DISK_BACKED) {
        // There could be old configs which have CLOUD_BACKED replica information. Keep this until CLOUD_BACKED replicas
        // are completely deprecated.
        continue;
      }
      Thread thread = Utils.newThread("helix-aggregated-view-init-dc-" + dcName, () -> {
        try {
          // The order to register these three listeners can't be changed. We have to the data node config first, since
          // it will construct instance to data node, instance to partition and replicas mapping.
          // We also have to register ideal state change after instance, since we check if the data node is in full auto
          // by looking at the tag from instance config and ideal state but we only call full auto change method in ideal
          // state change listener, by that time, we have to have instance config in memory.
          registerDataNodeConfigChangeListener(dcName);
          if (dcName.equals(localDcName)) {
            registerInstanceConfigChangeListener(dcName);
          }
          registerIdealStateChangeListener(dcName);
        } catch (Exception e) {
          exception = e;
        }
      }, false);
      thread.start();
      dcInitThreads.add(thread);
    }
    for (Thread thread : dcInitThreads) {
      thread.join();
    }

    // 2. Fetch External view. Helix provides a helper class RoutingTableProvider to get information in external view in
    // a convenient way.
    logger.info("Creating routing table provider for cluster {} via Helix manager at {}", aggregatedViewClusterName,
        localZkConnectStr);
    // There are two ways to instantiate a RoutingTable. 1. EXTERNAL_VIEW based, 2. CURRENT_STATES based. In the former
    // one, helix controller generates the external view and this is read by the helix spectator to create the Routing
    // table. In the latter one, CURRENT STATES are read from helix participant to participant at the helix spectator to
    // create the Routing table. According to helix team, the former one usually takes longer time since it is dependent
    // on helix controller to generate up-to-date view but has less read traffic since we have to read constructed view.
    // The latter one takes lesser time since we don't have to wait for controller to calculate the view but has more
    // read traffic.
    // In case of Aggregated view, CURRENT_STATES based RoutingTable doesn't work since CURRENT_STATES are
    // not aggregated by helix. So, we only have one option, i.e EXTERNAL_VIEW based and helix team asked to use it.
    RoutingTableProvider routingTableProvider = new RoutingTableProvider(helixManager, PropertyType.EXTERNALVIEW);
    logger.info("Routing table provider is created for cluster {} via Helix manager at {}", aggregatedViewClusterName,
        localZkConnectStr);
    routingTableProvider.addRoutingTableChangeListener(clusterChangeHandler, null);
    logger.info("Registered routing table change listeners for cluster {}", aggregatedViewClusterName);
    // Since it is possible that initial event occurs before adding routing table listener, we explicitly set snapshot in
    // HelixClusterManager. The reason is, if listener missed initial event, snapshot inside routing table
    // provider should be already populated.
    clusterChangeHandler.setRoutingTableSnapshot(routingTableProvider.getRoutingTableSnapshot());
    // The initial routing table change should populate the instanceConfigs. If it's empty that means initial
    // change didn't come and thread should wait on the init latch to ensure routing table snapshot is non-empty
    if (clusterChangeHandler.getRoutingTableSnapshot(null).getInstanceConfigs().isEmpty()) {
      // Periodic refresh in routing table provider is enabled by default. In worst case, routerUpdater should
      // trigger routing table change within 5 minutes
      logger.info("Routing table snapshot for cluster is currently empty. Waiting for initial notification.");
      clusterChangeHandler.waitForInitNotification();
    }

    // 3. Fetch live instance changes in entire cluster
    // When adding a listener, current live instances are fetched from Helix using blocking call and notified to the
    // listener (Please follow below method to
    // https://github.com/apache/helix/blob/fb773839117ee0689451fab7c8b949115f555100/helix-core/src/main/java/org/apache/helix/manager/zk/CallbackHandler.java#L393).
    // I.e. by the time we return from below method, initial notification of live instances states would have been
    // arrived. So, we don't need to separate latch to wait for notification of live instances.
    helixManager.addLiveInstanceChangeListener(clusterChangeHandler);
    logger.info("Registered live instance change listeners for cluster {} via Helix manager at {}",
        aggregatedViewClusterName, localZkConnectStr);

    return new HelixAggregatedViewClusterInfo(helixManager, clusterChangeHandler, dataNodeConfigSources,
        routingTableProvider);
  }
Perform initialization steps related to registering with Helix Aggregated view cluster.
@return the {@link HelixAggregatedViewClusterInfo} for the cluster.
@throws Exception if something went wrong during startup
ambry-clustermap/src/main/java/com/github/ambry/clustermap/HelixAggregatedViewClusterInitializer.java