Skip to content

RedisMetrics and AsyncRedisProvider both ship, but nothing joins them: every consumer writes the same dishka provider #36

Description

@AlexeyShalaev

redis-client-kit ships AsyncRedisProvider and it ships RedisMetrics, but nothing that
joins them, so every consumer writes the same provider. sqlalchemy-foundation-kit ships
exactly that join for Postgres — contrib.di.metrics.PrometheusPostgresMetricsProvider
and the two kits are otherwise the same shape.

What we carry

class PrometheusRedisMetricsProvider(BaseMetricsProvider):
    @provide
    def get_metrics(
        self,
        metrics: PrometheusMetricsSettingsProtocol,
        default_prefix: str | None,
        redis: RedisMetricsSettingsProtocol | None = None,
    ) -> RedisMetrics | None:
        if redis is None or not redis.metrics_enabled:
            return None
        return RedisMetrics(prefix=_infra_metrics_prefix(default_prefix))

    @provide
    def get_metrics_protocol(self, metrics: RedisMetrics | None) -> RedisMetricsProtocol | None:
        return metrics

Everything it reads is yours: BaseRedisSettings.metrics_enabled (default False) is the
gate, RedisMetrics is the collector, RedisMetricsProtocol is the key
AsyncRedisProvider consumes. The only thing we contribute is the wiring, and it is the
same wiring for every consumer.

Registered next to yours, it reads:

make_async_container(
    AsyncRedisProvider(provide_default_metrics=False),
    PrometheusRedisMetricsProvider(),   # ours, and it should be yours
    ...
)

The provide_default_metrics=False is the tell: AsyncRedisProvider already has an opinion
about who provides the metrics seam, and the alternative to its None is a provider the kit
does not ship.

The second half: the collector cannot be built twice

>>> RedisMetrics(prefix=None); RedisMetrics(prefix=None)
ValueError: Duplicated timeseries in CollectorRegistry: {'redis_pool_size', ...}

A container rebuilt per test — which is what a test suite does — asks for the collector
again and gets that. grpc-client-kit solved this with metrics.get_grpc_client_metrics():
one instance per prefix on the default registry, documented as "so a container rebuilt per
test never asks Prometheus to register the same series twice". A
get_redis_metrics(prefix=None) of the same shape would make the provider safe to register
in a fixture, and is worth having on its own even for consumers who never use dishka.

What we would use

redis_client_kit.providers.PrometheusRedisMetricsProvider, gated on
BaseRedisSettings.metrics_enabled, sourcing the collector from a cached getter — the
sqlalchemy-foundation-kit layout, since that is the one consumers already know. Then our
provider goes and dishka-providers loses another quarter of what is left of it.

Context: ai-ops, 11 services registering this provider. We have just deleted our postgres
and gRPC copies because those kits shipped theirs; Redis, inbox/outbox and idempotency are
the three left, and they are the same issue three times
(bedrock-python/omni-box, bedrock-python/idempotency-kit).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions