Controlling Spring WebClient metrics cardinality

Nick Jarzembowski
2 min readMay 15, 2020

--

Spring 5 introduced WebClient which provides a “a reactive, non-blocking WebClient for HTTP requests.”

WebClient can be configured to work with metrics by adding a filter:

var metricsFilter = new MetricsWebClientFilterFunction(
meterRegistry,
new DefaultWebClientExchangeTagsProvider(),
"downstream-service",
AutoTimer.ENABLED
);
WebClient webClient = WebClient.builder()
.baseUrl(baseUrl)
.filter(metricsFilter)
.build();

DefaultWebClientExchangeTagsProvider auto-configures tags applied to each request metric including the HTTP method, client name and URI.

An example request:

webClient.get()
.uri(uriBuilder -> uriBuilder.path("/v1/users{userId}").build(1)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(new ParameterizedTypeReference<List<User>>(){});

The metric exposed by Prometheus looks like:

name_seconds_count{app="downstream-service", clientName="downstream-service.envoy.tw.ee",method="GET",outcome="SUCCESS",status="200",uri="/v1/users/1/",} 2.0

However the cardinality of the URI tag can explode the number of metrics recorded if the request includes many variations of path variables or queries over time — as a new metric will be created for each unique URI.

For example, a request for User 2 will create the metric:

metric_name_seconds_count{app="downstream-service", clientName="downstream-service.envoy.tw.ee",method="GET",outcome="SUCCESS",status="200",uri="/v1/users/2/",} 2.0

This can be particularly bad for bucketed metrics such as histograms.

You can prevent this by passing the request URI to the WebClient in the following way:

String path = "/v1/users/{userId}";
webClient
.get()
.uri(path, uriBuilder -> uriBuilder.queryParam("k","v").build(1);

Alternatively:

String path = "/v1/users/{userId}?k={v}";
webClient
.get()
.uri(path, 1, "v");

The resulting request URI is/v1/users/1?k=v

metric_name_seconds_count{app="downstream-service",clientName="downstream-service.envoy.tw.ee",method="GET",outcome="SUCCESS",status="200",uri="/v1/users/{userId}",} 2.0

Passing the URI in this way prevents the explosion of request metrics because the underlying WebClient.uri method adds the URI template to the request as an attribute with the key:

WebClient.class.getName() + ".uriTemplate"

This key’s value (the URI template) is used by WebClientExchangeTags.uri(ClientRequest request) when creating the tag for the metric (in the above example this is done by DefaultWebClientExchangeTagsProvider) instead of the request URI.

--

--