This is an automated email from the ASF dual-hosted git repository.

emaynard pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/polaris.git


The following commit(s) were added to refs/heads/main by this push:
     new 97d16d3d6 feat(metrics): Mitigate potential performance issues with 
realm_id tag (#1662)
97d16d3d6 is described below

commit 97d16d3d6883fb9d97960d8ba0331bc219c74487
Author: Alexandre Dutra <[email protected]>
AuthorDate: Wed May 28 17:35:59 2025 +0200

    feat(metrics): Mitigate potential performance issues with realm_id tag 
(#1662)
    
    As discussed in the ML, this PR introduces two flags to enable the 
inclusion of realm ID tags in API and HTTP metrics.
    
    They are both disabled by default.
    
    There is also a new safeguard: if the cardinality of realm IDs in HTTP 
metrics goes above a configurable threshold (100 by default), a warning is 
printed and no more HTTP metrics will be recorded. (Quarkus has a similar 
safeguard for URI tags in HTTP metrics.)
---
 .../metrics/QuarkusMeterFilterProducer.java        | 27 ++++++-
 .../metrics/QuarkusMetricsConfiguration.java       | 39 ++++++++++
 .../metrics/QuarkusValueExpressionResolver.java    | 10 ++-
 .../quarkus/metrics/RealmIdTagContributor.java     |  8 +-
 .../MetricsTestBase.java}                          | 86 ++++++++++++++++------
 .../metrics/RealmIdTagDisabledMetricsTest.java}    | 19 +++--
 .../metrics/RealmIdTagEnabledMetricsTest.java}     | 26 +++++--
 .../quarkus/ratelimiter/RateLimiterFilterTest.java | 35 ++++-----
 site/content/in-dev/unreleased/configuration.md    | 78 ++++++++++----------
 site/content/in-dev/unreleased/telemetry.md        | 19 +++++
 10 files changed, 248 insertions(+), 99 deletions(-)

diff --git 
a/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/metrics/QuarkusMeterFilterProducer.java
 
b/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/metrics/QuarkusMeterFilterProducer.java
index 28f77e913..85a3f9837 100644
--- 
a/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/metrics/QuarkusMeterFilterProducer.java
+++ 
b/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/metrics/QuarkusMeterFilterProducer.java
@@ -20,6 +20,8 @@ package org.apache.polaris.service.quarkus.metrics;
 
 import io.micrometer.core.instrument.Tag;
 import io.micrometer.core.instrument.config.MeterFilter;
+import io.micrometer.core.instrument.internal.OnlyOnceLoggingDenyMeterFilter;
+import io.quarkus.micrometer.runtime.binder.HttpBinderConfiguration;
 import jakarta.enterprise.inject.Produces;
 import jakarta.inject.Inject;
 import jakarta.inject.Singleton;
@@ -27,14 +29,33 @@ import java.util.stream.Collectors;
 
 public class QuarkusMeterFilterProducer {
 
-  @Inject QuarkusMetricsConfiguration configuration;
+  @Inject QuarkusMetricsConfiguration metricsConfiguration;
+  @Inject HttpBinderConfiguration binderConfiguration;
 
   @Produces
   @Singleton
-  public MeterFilter produceGlobalMeterFilter() {
+  public MeterFilter commonTagsFilter() {
     return MeterFilter.commonTags(
-        this.configuration.tags().entrySet().stream()
+        this.metricsConfiguration.tags().entrySet().stream()
             .map(e -> Tag.of(e.getKey(), e.getValue()))
             .collect(Collectors.toSet()));
   }
+
+  @Produces
+  @Singleton
+  public MeterFilter maxRealmIdTagsInHttpMetricsFilter() {
+    MeterFilter denyFilter =
+        new OnlyOnceLoggingDenyMeterFilter(
+            () ->
+                String.format(
+                    "Reached the maximum number (%s) of '%s' tags for '%s'",
+                    
metricsConfiguration.realmIdTag().httpMetricsMaxCardinality(),
+                    RealmIdTagContributor.TAG_REALM,
+                    binderConfiguration.getHttpServerRequestsName()));
+    return MeterFilter.maximumAllowableTags(
+        binderConfiguration.getHttpServerRequestsName(),
+        RealmIdTagContributor.TAG_REALM,
+        metricsConfiguration.realmIdTag().httpMetricsMaxCardinality(),
+        denyFilter);
+  }
 }
diff --git 
a/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/metrics/QuarkusMetricsConfiguration.java
 
b/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/metrics/QuarkusMetricsConfiguration.java
index 756de6adb..34ae813a3 100644
--- 
a/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/metrics/QuarkusMetricsConfiguration.java
+++ 
b/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/metrics/QuarkusMetricsConfiguration.java
@@ -19,6 +19,8 @@
 package org.apache.polaris.service.quarkus.metrics;
 
 import io.smallrye.config.ConfigMapping;
+import io.smallrye.config.WithDefault;
+import jakarta.validation.constraints.Min;
 import java.util.Map;
 
 @ConfigMapping(prefix = "polaris.metrics")
@@ -26,4 +28,41 @@ public interface QuarkusMetricsConfiguration {
 
   /** Additional tags to include in the metrics. */
   Map<String, String> tags();
+
+  /** Configuration for the Realm ID metric tag. */
+  RealmIdTag realmIdTag();
+
+  interface RealmIdTag {
+
+    /**
+     * Whether to include the Realm ID tag in the API request metrics.
+     *
+     * <p>Beware that if the cardinality of this tag is too high, it can cause 
performance issues or
+     * even crash the server.
+     */
+    @WithDefault("false")
+    boolean enableInApiMetrics();
+
+    /**
+     * Whether to include the Realm ID tag in the HTTP server request metrics.
+     *
+     * <p>Beware that if the cardinality of this tag is too high, it can cause 
performance issues or
+     * even crash the server.
+     */
+    @WithDefault("false")
+    boolean enableInHttpMetrics();
+
+    /**
+     * The maximum number of Realm ID tag values allowed for the HTTP server 
request metrics.
+     *
+     * <p>This is used to prevent the number of tags from growing indefinitely 
and causing
+     * performance issues or crashing the server.
+     *
+     * <p>If the number of tags exceeds this value, a warning will be logged 
and no more HTTP server
+     * request metrics will be recorded.
+     */
+    @WithDefault("100")
+    @Min(1)
+    int httpMetricsMaxCardinality();
+  }
 }
diff --git 
a/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/metrics/QuarkusValueExpressionResolver.java
 
b/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/metrics/QuarkusValueExpressionResolver.java
index 98783de60..2c2af9336 100644
--- 
a/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/metrics/QuarkusValueExpressionResolver.java
+++ 
b/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/metrics/QuarkusValueExpressionResolver.java
@@ -22,17 +22,23 @@ import 
io.micrometer.common.annotation.ValueExpressionResolver;
 import io.micrometer.common.lang.Nullable;
 import jakarta.annotation.Nonnull;
 import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
 import org.apache.polaris.core.context.RealmContext;
 
 @ApplicationScoped
 public class QuarkusValueExpressionResolver implements ValueExpressionResolver 
{
 
+  @Inject QuarkusMetricsConfiguration metricsConfiguration;
+
   @Override
   public String resolve(@Nonnull String expression, @Nullable Object 
parameter) {
     // TODO maybe replace with CEL of some expression engine and make this 
more generic
-    if (parameter instanceof RealmContext realmContext && 
expression.equals("realmIdentifier")) {
+    if (metricsConfiguration.realmIdTag().enableInApiMetrics()
+        && parameter instanceof RealmContext realmContext
+        && expression.equals("realmIdentifier")) {
       return realmContext.getRealmIdentifier();
     }
-    return null;
+    // FIXME cannot return null here, see 
https://github.com/quarkusio/quarkus/issues/47891
+    return "";
   }
 }
diff --git 
a/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/metrics/RealmIdTagContributor.java
 
b/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/metrics/RealmIdTagContributor.java
index 1d13c97b7..232d12668 100644
--- 
a/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/metrics/RealmIdTagContributor.java
+++ 
b/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/metrics/RealmIdTagContributor.java
@@ -33,12 +33,16 @@ public class RealmIdTagContributor implements 
HttpServerMetricsTagsContributor {
 
   private static final Tags UNFINISHED_RESOLUTION_TAGS = Tags.of(TAG_REALM, 
"???");
 
+  @Inject QuarkusMetricsConfiguration metricsConfiguration;
   @Inject RealmContextResolver realmContextResolver;
 
   @Override
   public Tags contribute(Context context) {
-    // FIXME retrieve the realm context from context.requestContextLocalData() 
when this PR is in:
-    // https://github.com/quarkusio/quarkus/pull/47887
+    // FIXME retrieve the realm context from context.requestContextLocalData()
+    // after upgrading to Quarkus 3.24
+    if (!metricsConfiguration.realmIdTag().enableInHttpMetrics()) {
+      return Tags.empty();
+    }
     HttpServerRequest request = context.request();
     try {
       return realmContextResolver
diff --git 
a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/TimedApplicationEventListenerTest.java
 
b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/metrics/MetricsTestBase.java
similarity index 63%
rename from 
quarkus/service/src/test/java/org/apache/polaris/service/quarkus/TimedApplicationEventListenerTest.java
rename to 
quarkus/service/src/test/java/org/apache/polaris/service/quarkus/metrics/MetricsTestBase.java
index 66a8aa388..590712403 100644
--- 
a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/TimedApplicationEventListenerTest.java
+++ 
b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/metrics/MetricsTestBase.java
@@ -16,20 +16,16 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.polaris.service.quarkus;
+package org.apache.polaris.service.quarkus.metrics;
 
 import static 
org.apache.polaris.service.context.TestRealmContextResolver.REALM_PROPERTY_KEY;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.InstanceOfAssertFactories.type;
 
 import io.micrometer.core.instrument.MeterRegistry;
-import io.quarkus.test.junit.QuarkusTest;
-import io.quarkus.test.junit.QuarkusTestProfile;
-import io.quarkus.test.junit.TestProfile;
 import jakarta.inject.Inject;
 import jakarta.ws.rs.core.Response;
 import java.util.Map;
-import 
org.apache.polaris.service.quarkus.TimedApplicationEventListenerTest.Profile;
 import org.apache.polaris.service.quarkus.test.PolarisIntegrationTestFixture;
 import org.apache.polaris.service.quarkus.test.PolarisIntegrationTestHelper;
 import org.apache.polaris.service.quarkus.test.TestEnvironment;
@@ -46,27 +42,18 @@ import org.junit.jupiter.api.extension.ExtendWith;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.ValueSource;
 
-@QuarkusTest
 @TestInstance(TestInstance.Lifecycle.PER_CLASS)
 @ExtendWith(TestEnvironmentExtension.class)
-@TestProfile(Profile.class)
-public class TimedApplicationEventListenerTest {
-
-  public static class Profile implements QuarkusTestProfile {
-
-    @Override
-    public Map<String, String> getConfigOverrides() {
-      return Map.of(
-          "polaris.metrics.tags.environment", "prod", 
"polaris.realm-context.type", "test");
-    }
-  }
+public abstract class MetricsTestBase {
 
   private static final int ERROR_CODE = 
Response.Status.NOT_FOUND.getStatusCode();
   private static final String ENDPOINT = "api/management/v1/principals";
-  private static final String METRIC_NAME = 
"polaris_principals_getPrincipal_seconds";
+  private static final String API_METRIC_NAME = 
"polaris_principals_getPrincipal_seconds";
+  private static final String HTTP_METRIC_NAME = 
"http_server_requests_seconds";
 
   @Inject PolarisIntegrationTestHelper helper;
   @Inject MeterRegistry registry;
+  @Inject QuarkusMetricsConfiguration metricsConfiguration;
 
   private TestEnvironment testEnv;
   private PolarisIntegrationTestFixture fixture;
@@ -88,15 +75,19 @@ public class TimedApplicationEventListenerTest {
     sendSuccessfulRequest();
     Map<String, MetricFamily> allMetrics =
         TestMetricsUtil.fetchMetrics(fixture.client, 
testEnv.baseManagementUri(), endpoint);
-    assertThat(allMetrics).containsKey(METRIC_NAME);
-    assertThat(allMetrics.get(METRIC_NAME).getMetrics())
+    assertThat(allMetrics).containsKey(API_METRIC_NAME);
+    assertThat(allMetrics.get(API_METRIC_NAME).getMetrics())
         .satisfiesOnlyOnce(
             metric -> {
               assertThat(metric.getLabels())
                   .contains(
                       Map.entry("application", "Polaris"),
                       Map.entry("environment", "prod"),
-                      Map.entry("realm_id", fixture.realm),
+                      Map.entry(
+                          "realm_id",
+                          
metricsConfiguration.realmIdTag().enableInApiMetrics()
+                              ? fixture.realm
+                              : ""),
                       Map.entry(
                           "class", 
"org.apache.polaris.service.admin.api.PolarisPrincipalsApi"),
                       Map.entry("exception", "none"),
@@ -106,6 +97,28 @@ public class TimedApplicationEventListenerTest {
                   .extracting(Summary::getSampleCount)
                   .isEqualTo(1L);
             });
+    assertThat(allMetrics).containsKey(HTTP_METRIC_NAME);
+    assertThat(allMetrics.get(HTTP_METRIC_NAME).getMetrics())
+        .satisfiesOnlyOnce(
+            metric -> {
+              assertThat(metric.getLabels())
+                  .contains(
+                      Map.entry("application", "Polaris"),
+                      Map.entry("environment", "prod"),
+                      Map.entry("method", "GET"),
+                      Map.entry("outcome", "SUCCESS"),
+                      Map.entry("status", "200"),
+                      Map.entry("uri", 
"/api/management/v1/principals/{principalName}"));
+              if (metricsConfiguration.realmIdTag().enableInHttpMetrics()) {
+                assertThat(metric.getLabels()).containsEntry("realm_id", 
fixture.realm);
+              } else {
+                assertThat(metric.getLabels()).doesNotContainKey("realm_id");
+              }
+              assertThat(metric)
+                  .asInstanceOf(type(Summary.class))
+                  .extracting(Summary::getSampleCount)
+                  .isEqualTo(1L);
+            });
   }
 
   @ParameterizedTest
@@ -114,15 +127,19 @@ public class TimedApplicationEventListenerTest {
     sendFailingRequest();
     Map<String, MetricFamily> allMetrics =
         TestMetricsUtil.fetchMetrics(fixture.client, 
testEnv.baseManagementUri(), endpoint);
-    assertThat(allMetrics).containsKey(METRIC_NAME);
-    assertThat(allMetrics.get(METRIC_NAME).getMetrics())
+    assertThat(allMetrics).containsKey(API_METRIC_NAME);
+    assertThat(allMetrics.get(API_METRIC_NAME).getMetrics())
         .satisfiesOnlyOnce(
             metric -> {
               assertThat(metric.getLabels())
                   .contains(
                       Map.entry("application", "Polaris"),
                       Map.entry("environment", "prod"),
-                      Map.entry("realm_id", fixture.realm),
+                      Map.entry(
+                          "realm_id",
+                          
metricsConfiguration.realmIdTag().enableInApiMetrics()
+                              ? fixture.realm
+                              : ""),
                       Map.entry(
                           "class", 
"org.apache.polaris.service.admin.api.PolarisPrincipalsApi"),
                       Map.entry("exception", "NotFoundException"),
@@ -132,6 +149,27 @@ public class TimedApplicationEventListenerTest {
                   .extracting(Summary::getSampleCount)
                   .isEqualTo(1L);
             });
+    assertThat(allMetrics.get(HTTP_METRIC_NAME).getMetrics())
+        .satisfiesOnlyOnce(
+            metric -> {
+              assertThat(metric.getLabels())
+                  .contains(
+                      Map.entry("application", "Polaris"),
+                      Map.entry("environment", "prod"),
+                      Map.entry("method", "GET"),
+                      Map.entry("outcome", "CLIENT_ERROR"),
+                      Map.entry("status", "404"),
+                      Map.entry("uri", 
"/api/management/v1/principals/{principalName}"));
+              if (metricsConfiguration.realmIdTag().enableInHttpMetrics()) {
+                assertThat(metric.getLabels()).containsEntry("realm_id", 
fixture.realm);
+              } else {
+                assertThat(metric.getLabels()).doesNotContainKey("realm_id");
+              }
+              assertThat(metric)
+                  .asInstanceOf(type(Summary.class))
+                  .extracting(Summary::getSampleCount)
+                  .isEqualTo(1L);
+            });
   }
 
   private int sendRequest(String principalName) {
diff --git 
a/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/metrics/QuarkusMetricsConfiguration.java
 
b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/metrics/RealmIdTagDisabledMetricsTest.java
similarity index 62%
copy from 
quarkus/service/src/main/java/org/apache/polaris/service/quarkus/metrics/QuarkusMetricsConfiguration.java
copy to 
quarkus/service/src/test/java/org/apache/polaris/service/quarkus/metrics/RealmIdTagDisabledMetricsTest.java
index 756de6adb..87af35775 100644
--- 
a/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/metrics/QuarkusMetricsConfiguration.java
+++ 
b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/metrics/RealmIdTagDisabledMetricsTest.java
@@ -18,12 +18,21 @@
  */
 package org.apache.polaris.service.quarkus.metrics;
 
-import io.smallrye.config.ConfigMapping;
+import io.quarkus.test.junit.QuarkusTest;
+import io.quarkus.test.junit.QuarkusTestProfile;
+import io.quarkus.test.junit.TestProfile;
 import java.util.Map;
 
-@ConfigMapping(prefix = "polaris.metrics")
-public interface QuarkusMetricsConfiguration {
+@QuarkusTest
+@TestProfile(RealmIdTagDisabledMetricsTest.Profile.class)
+public class RealmIdTagDisabledMetricsTest extends MetricsTestBase {
 
-  /** Additional tags to include in the metrics. */
-  Map<String, String> tags();
+  public static class Profile implements QuarkusTestProfile {
+
+    @Override
+    public Map<String, String> getConfigOverrides() {
+      return Map.of(
+          "polaris.metrics.tags.environment", "prod", 
"polaris.realm-context.type", "test");
+    }
+  }
 }
diff --git 
a/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/metrics/QuarkusMetricsConfiguration.java
 
b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/metrics/RealmIdTagEnabledMetricsTest.java
similarity index 55%
copy from 
quarkus/service/src/main/java/org/apache/polaris/service/quarkus/metrics/QuarkusMetricsConfiguration.java
copy to 
quarkus/service/src/test/java/org/apache/polaris/service/quarkus/metrics/RealmIdTagEnabledMetricsTest.java
index 756de6adb..6d261dc65 100644
--- 
a/quarkus/service/src/main/java/org/apache/polaris/service/quarkus/metrics/QuarkusMetricsConfiguration.java
+++ 
b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/metrics/RealmIdTagEnabledMetricsTest.java
@@ -18,12 +18,28 @@
  */
 package org.apache.polaris.service.quarkus.metrics;
 
-import io.smallrye.config.ConfigMapping;
+import io.quarkus.test.junit.QuarkusTest;
+import io.quarkus.test.junit.QuarkusTestProfile;
+import io.quarkus.test.junit.TestProfile;
 import java.util.Map;
 
-@ConfigMapping(prefix = "polaris.metrics")
-public interface QuarkusMetricsConfiguration {
+@QuarkusTest
+@TestProfile(RealmIdTagEnabledMetricsTest.Profile.class)
+public class RealmIdTagEnabledMetricsTest extends MetricsTestBase {
 
-  /** Additional tags to include in the metrics. */
-  Map<String, String> tags();
+  public static class Profile implements QuarkusTestProfile {
+
+    @Override
+    public Map<String, String> getConfigOverrides() {
+      return Map.of(
+          "polaris.metrics.tags.environment",
+          "prod",
+          "polaris.realm-context.type",
+          "test",
+          "polaris.metrics.realm-id-tag.enable-in-api-metrics",
+          "true",
+          "polaris.metrics.realm-id-tag.enable-in-http-metrics",
+          "true");
+    }
+  }
 }
diff --git 
a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/ratelimiter/RateLimiterFilterTest.java
 
b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/ratelimiter/RateLimiterFilterTest.java
index 48aa45001..f81ffe774 100644
--- 
a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/ratelimiter/RateLimiterFilterTest.java
+++ 
b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/ratelimiter/RateLimiterFilterTest.java
@@ -21,6 +21,7 @@ package org.apache.polaris.service.quarkus.ratelimiter;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.InstanceOfAssertFactories.type;
 
+import com.google.common.collect.ImmutableMap;
 import io.micrometer.core.instrument.MeterRegistry;
 import io.quarkus.test.junit.QuarkusTest;
 import io.quarkus.test.junit.QuarkusTestProfile;
@@ -68,25 +69,21 @@ public class RateLimiterFilterTest {
 
     @Override
     public Map<String, String> getConfigOverrides() {
-      return Map.of(
-          "polaris.rate-limiter.filter.type",
-          "default",
-          "polaris.rate-limiter.token-bucket.type",
-          "default",
-          "polaris.rate-limiter.token-bucket.requests-per-second",
-          String.valueOf(REQUESTS_PER_SECOND),
-          "polaris.rate-limiter.token-bucket.window",
-          WINDOW.toString(),
-          "polaris.metrics.tags.environment",
-          "prod",
-          "polaris.realm-context.type",
-          "test",
-          "polaris.authentication.token-broker.type",
-          "symmetric-key",
-          "polaris.authentication.token-broker.symmetric-key.secret",
-          "secret",
-          "polaris.event-listener.type",
-          "test");
+      return ImmutableMap.<String, String>builder()
+          .put("polaris.rate-limiter.filter.type", "default")
+          .put("polaris.rate-limiter.token-bucket.type", "default")
+          .put(
+              "polaris.rate-limiter.token-bucket.requests-per-second",
+              String.valueOf(REQUESTS_PER_SECOND))
+          .put("polaris.rate-limiter.token-bucket.window", WINDOW.toString())
+          .put("polaris.metrics.tags.environment", "prod")
+          .put("polaris.metrics.realm-id-tag.enable-in-api-metrics", "true")
+          .put("polaris.metrics.realm-id-tag.enable-in-http-metrics", "true")
+          .put("polaris.realm-context.type", "test")
+          .put("polaris.authentication.token-broker.type", "symmetric-key")
+          .put("polaris.authentication.token-broker.symmetric-key.secret", 
"secret")
+          .put("polaris.event-listener.type", "test")
+          .build();
     }
   }
 
diff --git a/site/content/in-dev/unreleased/configuration.md 
b/site/content/in-dev/unreleased/configuration.md
index b195d05a5..9da84c7d5 100644
--- a/site/content/in-dev/unreleased/configuration.md
+++ b/site/content/in-dev/unreleased/configuration.md
@@ -77,45 +77,45 @@ read-only mode, as Polaris only reads the configuration 
file once, at startup.
 
 ## Polaris Configuration Options Reference
 
-| Configuration Property                                                       
              | Default Value       | Description                               
                                                                                
                                                                                
         |
-|--------------------------------------------------------------------------------------------|---------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| `polaris.persistence.type`                                                   
              | `relational-jdbc`   | Define the persistence backend used by 
Polaris (`in-memory`, `relational-jdbc`, `eclipse-link` (deprecated)). See 
[Configuring Apache Polaris for Production)[{{% ref 
"configuring-polaris-for-production.md" %}}) | 
-| `polaris.persistence.relational.jdbc.max-retries`                            
              | `1`                 | Total number of retries JDBC persistence 
will attempt on connection resets or serialization failures before giving up.   
                                                                                
          |
-| `polaris.persistence.relational.jdbc.max_duaration_in_ms`                    
              | `5000 ms`           | Max time interval (ms) since the start of 
a transaction when retries can be attempted.                                    
                                                                                
         |
-| `polaris.persistence.relational.jdbc.initial_delay_in_ms`                    
              | `100 ms`            | Initial delay before retrying. The delay 
is doubled after each retry.                                                    
                                                                                
          |
-| `polaris.persistence.eclipselink.configurationFile`                          
              |                     | Define the location of the 
`persistence.xml`. By default, it's the built-in `persistence.xml` in use.      
                                                                                
                        |
-| `polaris.persistence.eclipselink.persistenceUnit`                            
              | `polaris`           | Define the name of the persistence unit 
to use, as defined in the `persistence.xml`.                                    
                                                                                
           |
-| `polaris.realm-context.type`                                                 
              | `default`           | Define the type of the Polaris realm to 
use.                                                                            
                                                                                
           |
-| `polaris.realm-context.realms`                                               
              | `POLARIS`           | Define the list of realms to use.         
                                                                                
                                                                                
         |
-| `polaris.realm-context.header-name`                                          
              | `Polaris-Realm`     | Define the header name defining the realm 
context.                                                                        
                                                                                
         |
-| `polaris.features."ENFORCE_PRINCIPAL_CREDENTIAL_ROTATION_REQUIRED_CHECKING"` 
              | `false`             | Flag to enforce check if credential 
rotation.                                                                       
                                                                                
               |
-| `polaris.features."SUPPORTED_CATALOG_STORAGE_TYPES"`                         
              | `FILE`              | Define the catalog supported storage. 
Supported values are `S3`, `GCS`, `AZURE`, `FILE`.                              
                                                                                
             |
-| 
`polaris.features.realm-overrides."my-realm"."SKIP_CREDENTIAL_SUBSCOPING_INDIRECTION"`
     | `true`              | "Override" realm features, here the skip 
credential subscoping indirection flag.                                         
                                                                                
          |
-| `polaris.authentication.authenticator.type`                                  
              | `default`           | Define the Polaris authenticator type.    
                                                                                
                                                                                
         |
-| `polaris.authentication.token-service.type`                                  
              | `default`           | Define the Polaris token service type.    
                                                                                
                                                                                
         |
-| `polaris.authentication.token-broker.type`                                   
              | `rsa-key-pair`      | Define the Polaris token broker type.     
                                                                                
                                                                                
         |
-| `polaris.authentication.token-broker.max-token-generation`                   
              | `PT1H`              | Define the max token generation policy on 
the token broker.                                                               
                                                                                
         |
-| `polaris.authentication.token-broker.rsa-key-pair.public-key-file`           
              | `/tmp/public.key`   | Define the location of the public key 
file.                                                                           
                                                                                
             |
-| `polaris.authentication.token-broker.rsa-key-pair.private-key-file`          
              | `/tmp/private.key`  | Define the location of the private key 
file.                                                                           
                                                                                
            |
-| `polaris.authentication.token-broker.symmetric-key.secret`                   
              | `secret`            | Define the secret of the symmetric key.   
                                                                                
                                                                                
         |
-| `polaris.authentication.token-broker.symmetric-key.file`                     
              | `/tmp/symmetric.key` | Define the location of the symmetric key 
file.                                                                           
                                                                                
          |
-| `polaris.storage.aws.access-key`                                             
              | `accessKey`         | Define the AWS S3 access key. If unset, 
the default credential provider chain will be used.                             
                                                                                
           |
-| `polaris.storage.aws.secret-key`                                             
              | `secretKey`         | Define the AWS S3 secret key. If unset, 
the default credential provider chain will be used.                             
                                                                                
           |
-| `polaris.storage.gcp.token`                                                  
              | `token`             | Define the Google Cloud Storage token. If 
unset, the default credential provider chain will be used.                      
                                                                                
         |
-| `polaris.storage.gcp.lifespan`                                               
              | `PT1H`              | Define the Google Cloud Storage lifespan 
type. If unset, the default credential provider chain will be used.             
                                                                                
          |
-| `polaris.log.request-id-header-name`                                         
              | `Polaris-Request-Id` | Define the header name to match request 
ID in the log.                                                                  
                                                                                
           |
-| `polaris.log.mdc.aid`                                                        
              | `polaris`           | Define the log context (e.g. MDC) AID.    
                                                                                
                                                                                
         |
-| `polaris.log.mdc.sid`                                                        
              | `polaris-service`   | Define the log context (e.g. MDC) SID.    
                                                                                
                                                                                
         |
-| `polaris.rate-limiter.filter.type`                                           
              | `no-op`             | Define the Polaris rate limiter. 
Supported values are `no-op`, `token-bucket`.                                   
                                                                                
                  |
-| `polaris.rate-limiter.token-bucket.type`                                     
              | `default`           | Define the token bucket rate limiter.     
                                                                                
                                                                                
         |
-| `polaris.rate-limiter.token-bucket.requests-per-second`                      
              | `9999`              | Define the number of requests per second 
for the token bucket rate limiter.                                              
                                                                                
          |
-| `polaris.rate-limiter.token-bucket.window`                                   
              | `PT10S`             | Define the window type for the token 
bucket rate limiter.                                                            
                                                                                
              |
-| `polaris.metrics.tags.application`                                           
              | `Polaris`           | Define the application name tag in 
metrics.                                                                        
                                                                                
                |
-| `polaris.metrics.tags.service`                                               
              | `polaris`           | Define the service tag in metrics.        
                                                                                
                                                                                
         |
-| `polaris.metrics.tags.environment`                                           
              | `prod`              | Define the environement tag in metrics.   
                                                                                
                                                                                
         |
-| `polaris.metrics.tags.region`                                                
              | `us-west-2`         | Define the region tag in metrics.         
                                                                                
                                                                                
         |
-| `polaris.tasks.max-concurrent-tasks`                                         
              | `100`               | Define the max number of concurrent 
tasks.                                                                          
                                                                                
               |
-| `polaris.tasks.max-queued-tasks`                                             
              | `1000`              | Define the max number of tasks in queue.  
                                                                                
                                                                                
         |
+| Configuration Property                                                       
          | Default Value         | Description                                 
                                                                                
                                                                                
       |
+|----------------------------------------------------------------------------------------|-----------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `polaris.persistence.type`                                                   
          | `relational-jdbc`     | Define the persistence backend used by 
Polaris (`in-memory`, `relational-jdbc`, `eclipse-link` (deprecated)). See 
[Configuring Apache Polaris for Production)[{{% ref 
"configuring-polaris-for-production.md" %}}) | 
+| `polaris.persistence.relational.jdbc.max-retries`                            
          | `1`                   | Total number of retries JDBC persistence 
will attempt on connection resets or serialization failures before giving up.   
                                                                                
          |
+| `polaris.persistence.relational.jdbc.max_duaration_in_ms`                    
          | `5000 ms`             | Max time interval (ms) since the start of a 
transaction when retries can be attempted.                                      
                                                                                
       |
+| `polaris.persistence.relational.jdbc.initial_delay_in_ms`                    
          | `100 ms`              | Initial delay before retrying. The delay is 
doubled after each retry.                                                       
                                                                                
       |
+| `polaris.persistence.eclipselink.configurationFile`                          
          |                       | Define the location of the 
`persistence.xml`. By default, it's the built-in `persistence.xml` in use.      
                                                                                
                        |
+| `polaris.persistence.eclipselink.persistenceUnit`                            
          | `polaris`             | Define the name of the persistence unit to 
use, as defined in the `persistence.xml`.                                       
                                                                                
        |
+| `polaris.realm-context.type`                                                 
          | `default`             | Define the type of the Polaris realm to 
use.                                                                            
                                                                                
           |
+| `polaris.realm-context.realms`                                               
          | `POLARIS`             | Define the list of realms to use.           
                                                                                
                                                                                
       |
+| `polaris.realm-context.header-name`                                          
          | `Polaris-Realm`       | Define the header name defining the realm 
context.                                                                        
                                                                                
         |
+| `polaris.features."ENFORCE_PRINCIPAL_CREDENTIAL_ROTATION_REQUIRED_CHECKING"` 
          | `false`               | Flag to enforce check if credential 
rotation.                                                                       
                                                                                
               |
+| `polaris.features."SUPPORTED_CATALOG_STORAGE_TYPES"`                         
          | `FILE`                | Define the catalog supported storage. 
Supported values are `S3`, `GCS`, `AZURE`, `FILE`.                              
                                                                                
             |
+| 
`polaris.features.realm-overrides."my-realm"."SKIP_CREDENTIAL_SUBSCOPING_INDIRECTION"`
 | `true`                | "Override" realm features, here the skip credential 
subscoping indirection flag.                                                    
                                                                               |
+| `polaris.authentication.authenticator.type`                                  
          | `default`             | Define the Polaris authenticator type.      
                                                                                
                                                                                
       |
+| `polaris.authentication.token-service.type`                                  
          | `default`             | Define the Polaris token service type.      
                                                                                
                                                                                
       |
+| `polaris.authentication.token-broker.type`                                   
          | `rsa-key-pair`        | Define the Polaris token broker type.       
                                                                                
                                                                                
       |
+| `polaris.authentication.token-broker.max-token-generation`                   
          | `PT1H`                | Define the max token generation policy on 
the token broker.                                                               
                                                                                
         |
+| `polaris.authentication.token-broker.rsa-key-pair.public-key-file`           
          | `/tmp/public.key`     | Define the location of the public key file. 
                                                                                
                                                                                
       |
+| `polaris.authentication.token-broker.rsa-key-pair.private-key-file`          
          | `/tmp/private.key`    | Define the location of the private key 
file.                                                                           
                                                                                
            |
+| `polaris.authentication.token-broker.symmetric-key.secret`                   
          | `secret`              | Define the secret of the symmetric key.     
                                                                                
                                                                                
       |
+| `polaris.authentication.token-broker.symmetric-key.file`                     
          | `/tmp/symmetric.key`  | Define the location of the symmetric key 
file.                                                                           
                                                                                
          |
+| `polaris.storage.aws.access-key`                                             
          | `accessKey`           | Define the AWS S3 access key. If unset, the 
default credential provider chain will be used.                                 
                                                                                
       |
+| `polaris.storage.aws.secret-key`                                             
          | `secretKey`           | Define the AWS S3 secret key. If unset, the 
default credential provider chain will be used.                                 
                                                                                
       |
+| `polaris.storage.gcp.token`                                                  
          | `token`               | Define the Google Cloud Storage token. If 
unset, the default credential provider chain will be used.                      
                                                                                
         |
+| `polaris.storage.gcp.lifespan`                                               
          | `PT1H`                | Define the Google Cloud Storage lifespan 
type. If unset, the default credential provider chain will be used.             
                                                                                
          |
+| `polaris.log.request-id-header-name`                                         
          | `Polaris-Request-Id`  | Define the header name to match request ID 
in the log.                                                                     
                                                                                
        |
+| `polaris.log.mdc.aid`                                                        
          | `polaris`             | Define the log context (e.g. MDC) AID.      
                                                                                
                                                                                
       |
+| `polaris.log.mdc.sid`                                                        
          | `polaris-service`     | Define the log context (e.g. MDC) SID.      
                                                                                
                                                                                
       |
+| `polaris.rate-limiter.filter.type`                                           
          | `no-op`               | Define the Polaris rate limiter. Supported 
values are `no-op`, `token-bucket`.                                             
                                                                                
        |
+| `polaris.rate-limiter.token-bucket.type`                                     
          | `default`             | Define the token bucket rate limiter.       
                                                                                
                                                                                
       |
+| `polaris.rate-limiter.token-bucket.requests-per-second`                      
          | `9999`                | Define the number of requests per second 
for the token bucket rate limiter.                                              
                                                                                
          |
+| `polaris.rate-limiter.token-bucket.window`                                   
          | `PT10S`               | Define the window type for the token bucket 
rate limiter.                                                                   
                                                                                
       |
+| `polaris.metrics.tags.<tag-name>=<tag-value>`                                
          | `application=Polaris` | Define arbitrary metric tags to include in 
every request.                                                                  
                                                                                
        |
+| `polaris.metrics.realm-id-tag.api-metrics-enabled`                           
          | `false`               | Whether to enable the `realm_id` metric tag 
in API metrics.                                                                 
                                                                                
       |
+| `polaris.metrics.realm-id-tag.http-metrics-enabled`                          
          | `false`               | Whether to enable the `realm_id` metric tag 
in HTTP request metrics.                                                        
                                                                                
       |
+| `polaris.metrics.realm-id-tag.http-metrics-max-cardinality`                  
          | `100`                 | The maximum cardinality for the `realm_id` 
tag in HTTP request metrics.                                                    
                                                                                
        |
+| `polaris.tasks.max-concurrent-tasks`                                         
          | `100`                 | Define the max number of concurrent tasks.  
                                                                                
                                                                                
       |
+| `polaris.tasks.max-queued-tasks`                                             
          | `1000`                | Define the max number of tasks in queue.    
                                                                                
                                                                                
       |
 
 There are non Polaris configuration properties that can be useful:
 
diff --git a/site/content/in-dev/unreleased/telemetry.md 
b/site/content/in-dev/unreleased/telemetry.md
index 1e378991e..8df97f505 100644
--- a/site/content/in-dev/unreleased/telemetry.md
+++ b/site/content/in-dev/unreleased/telemetry.md
@@ -47,6 +47,25 @@ polaris.metrics.tags.region=us-west-2
 Note that by default Polaris adds one tag: `application=Polaris`. You can 
override this tag by
 setting the `polaris.metrics.tags.application=<new-value>` property.
 
+### Realm ID Tag
+
+Polaris can add the realm ID as a tag to all API and HTTP request metrics. 
This is disabled by 
+default to prevent high cardinality issues, but can be enabled by setting the 
following properties:
+
+```properties
+polaris.metrics.realm-id-tag.enable-in-api-metrics=true
+polaris.metrics.realm-id-tag.enable-in-http-metrics=true
+```
+
+You should be particularly careful when enabling the realm ID tag in HTTP 
request metrics, as these
+metrics typically have a much higher cardinality than API request metrics.
+
+In order to prevent the number of tags from growing indefinitely and causing 
performance issues or
+crashing the server, the number of unique realm IDs in HTTP request metrics is 
limited to 100 by 
+default. If the number of unique realm IDs exceeds this value, a warning will 
be logged and no more
+HTTP request metrics will be recorded. This threshold can be changed by 
setting the 
+`polaris.metrics.realm-id-tag.http-metrics-max-cardinality` property.
+
 ## Traces
 
 Traces are published using [OpenTelemetry]. 

Reply via email to