Repository: qdrant/qdrant
File: src/common/metrics.rs (Rust)

Code before:
        let mut total_active_replicas = 0;

        for collection in self.collections.iter().flatten() {
            let collection = match collection {
                CollectionTelemetryEnum::Full(collection_telemetry) => collection_telemetry,
                CollectionTelemetryEnum::Aggregated(_) => {
                    continue;
                }
            };

            total_optimizations_running += collection
                .shards
                .iter()
                .flatten()
                .filter_map(|i| i.local.as_ref())
                .flat_map(|i| i.optimizations.log.iter())
                .flatten()
                .filter(|i| i.status == TrackerStatus::Optimizing)
                .count();

            let min_max_active_replicas = collection
                .shards
                .iter()
                .flatten()
                // Ignore shards in resharding state.
                .filter(|shard| !shard.replicate_states.values().any(|i| i.is_resharding()))
                .map(|shard| {
                    shard
                        .replicate_states
                        .values()
                        .filter(|state| state.is_active())
                        .count()
                })
                .inspect(|active_replicas| total_active_replicas += active_replicas)
                .minmax();

            let min_max_active_replicas = match min_max_active_replicas {
                itertools::MinMaxResult::NoElements => None,
                itertools::MinMaxResult::OneElement(one) => Some((one, one)),
                itertools::MinMaxResult::MinMax(min, max) => Some((min, max)),
            };

            if let Some((min, max)) = min_max_active_replicas {
                total_min_active_replicas = total_min_active_replicas.min(min);
                total_max_active_replicas = total_max_active_replicas.max(max);
            }

            total_expected_replicas += collection.config.params.replication_factor.get() as usize;

            // Sum the optimization triggers over all shards of this collection.
            let optimizer_triggers = collection

Review comment (suggestion):
I don't think this is good enough to filter in case of resharding.

In case of resharding up, this only works during the very first stage of resharding. The new shard will become active before the extra replicas are created. By that time we should still not consider the new shard because it can be safely dropped.

In case of resharding down all replicas in all but the last shard go into resharding state at some point.

I think it'd be better to look at the current resharding state directly (not replica state, but resharding state). Then, based on it's current state we can decide whether to ignore some shard or not.

Code after:
                }
            };

            total_optimizations_running += collection.count_optimizers_running();

            let min_max_active_replicas = collection
                .shards
                .iter()
                .flatten()
                // While resharding up, some (shard) replica sets may still be incomplete during
                // the resharding process. In that case we don't want to consider these replica
                // sets at all in the active replica calculation. This is fine because searches nor
                // updates don't depend on them being available yet.
                //
                // More specifically:
                // - in stage 2 (migrate points) of resharding up we don't rely on the replica
                //   to be available yet. In this stage, these replicas will have the `Resharding`
                //   state.
                // - in stage 3 (replicate) of resharding up we activate the the replica and
                //   replicate to match the configuration replication factor. From this point on we
                //   do rely on the replica to be available. Now one replica will be `Active`, and
                //   the other replicas will be in a transfer state. No replica will have `Resharding`
                //   state.
                //
                // So, during stage 2 of resharding up we don't want to adjust the minimum number
                // of active replicas downwards. During stage 3 we do want it to affect the minimum
                // available replica number. It will be 1 for some time until replication transfers
                // complete.
                //
                // To ignore a (shard) replica set that is in stage 2 of resharding up, we simply
                // check if any of it's replicas is in `Resharding` state.
                .filter(|shard| {
                    !shard
                        .replicate_states
                        .values()
                        .any(|i| matches!(i, ReplicaState::Resharding))
                })
                .map(|shard| {
                    shard
                        .replicate_states
                        .values()
                        // While resharding down, all the replicas that we keep will get the
                        // `ReshardingScaleDown` state for a period of time. We simply consider
                        // these replicas to be active. The `is_active` function already accounts
                        // this.
                        .filter(|state| state.is_active())
                        .count()
                })
                .minmax();

            let min_max_active_replicas = match min_max_active_replicas {