void performReplicas(final PrimaryResponse<Response, ReplicaRequest> response) {
      if (ignoreReplicas() || shardIt.size() == 1 /* no replicas */) {
        postPrimaryOperation(request, response);
        listener.onResponse(response.response());
        return;
      }

      // initialize the counter
      int replicaCounter = shardIt.assignedReplicasIncludingRelocating();

      if (replicaCounter == 0) {
        postPrimaryOperation(request, response);
        listener.onResponse(response.response());
        return;
      }

      if (replicationType == ReplicationType.ASYNC) {
        postPrimaryOperation(request, response);
        // async replication, notify the listener
        listener.onResponse(response.response());
        // now, trick the counter so it won't decrease to 0 and notify the listeners
        replicaCounter = Integer.MIN_VALUE;
      }

      // we add one to the replica count to do the postPrimaryOperation
      replicaCounter++;

      AtomicInteger counter = new AtomicInteger(replicaCounter);
      shardIt.reset(); // reset the iterator
      ShardRouting shard;
      while ((shard = shardIt.nextOrNull()) != null) {
        // if its unassigned, nothing to do here...
        if (shard.unassigned()) {
          continue;
        }

        // if the shard is primary and relocating, add one to the counter since we perform it on the
        // replica as well
        // (and we already did it on the primary)
        boolean doOnlyOnRelocating = false;
        if (shard.primary()) {
          if (shard.relocating()) {
            doOnlyOnRelocating = true;
          } else {
            continue;
          }
        }
        // we index on a replica that is initializing as well since we might not have got the event
        // yet that it was started. We will get an exception IllegalShardState exception if its not
        // started
        // and that's fine, we will ignore it
        if (!doOnlyOnRelocating) {
          performOnReplica(response, counter, shard, shard.currentNodeId());
        }
        if (shard.relocating()) {
          performOnReplica(response, counter, shard, shard.relocatingNodeId());
        }
      }

      // now do the postPrimary operation, and check if the listener needs to be invoked
      postPrimaryOperation(request, response);
      // we also invoke here in case replicas finish before postPrimaryAction does
      if (counter.decrementAndGet() == 0) {
        listener.onResponse(response.response());
      }
    }