Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
ab99206
feat(metrics): implement Table Metrics REST API
Apr 2, 2026
f9345c2
docs: add changelog and documentation for Table Metrics REST API
Apr 2, 2026
2106b0f
feat(metrics): adopt stable envelope design for metrics reports API
Apr 2, 2026
d21c8e5
fix(metrics): harden error handling and test coverage in MetricsRepor…
Apr 2, 2026
699405f
fix(metrics): remove internal catalogId/tableId from metrics report r…
Apr 2, 2026
2a7039a
refactor(metrics): remove AtomicReference and columns param per revie…
Apr 10, 2026
83f2292
Spotless fixes
Apr 10, 2026
0a2d009
refactor(metrics): address PR review comments
Apr 15, 2026
e6b75c2
docs(metrics): mark Metrics Reports API as beta in changelog and spec
Apr 16, 2026
94bece7
fix(metrics): add resteasy-reactive testRuntimeOnly to fix ClassNotFo…
Apr 16, 2026
ea89998
fix(metrics): add @Beta to PolarisMetricsManager and PolarisMetricsRe…
Apr 20, 2026
a0ffa8b
Review comments.
Apr 28, 2026
756bf49
Replace Map<String,Object> response with typed Jackson record classes
May 16, 2026
66bfedc
fix(jdbc): replace unresolved @Nonnull with @NonNull in JdbcBasePersi…
May 26, 2026
0800631
fix(metrics-api): use JSON arrays for projectedFieldIds/Names, add #4…
Jun 1, 2026
a0a4f62
fix(bom): add polaris-api-metrics-reports-service to BOM
Jun 1, 2026
f370e93
ci: retrigger CI (transient RH registry 500 on minio job)
Jun 1, 2026
8f5ae68
refactor(metrics): fix SPI layering — move IcebergMetricsReporter to …
Jun 14, 2026
4119788
fix(metrics): restore MetricsPersistence SPI types to polaris-core
Jun 14, 2026
86ee037
fix(metrics): remove MetricsPersistence from JdbcBasePersistenceImpl,…
Jun 14, 2026
e904d25
fix(metrics): rename lambda params to avoid shadowing createHandler's…
Jun 14, 2026
474c039
fix(metrics): fix three runtime failures from Scope 1 refactor
Jun 14, 2026
9d336c8
fix(metrics): use 'default' identifier for LoggingMetricsReporter to …
Jun 14, 2026
4702adf
docs(config): regenerate config reference for metrics reporting default
Jun 14, 2026
286adc6
refactor(metrics): move IcebergMetricsReporter SPI to extensions/metr…
Jun 15, 2026
48816e4
fix(metrics): use runtimeOnly for metrics-reports impl dep in runtime…
Jun 15, 2026
c8a050c
fix(metrics): remove metrics-reports impl dep from runtime/service
Jun 15, 2026
8a779cc
refactor(metrics): move NoOpMetricsReporter to SPI; address review co…
Jun 16, 2026
3548588
ci: retrigger CI (transient RH registry 500 on minio job)
Jun 1, 2026
849b07d
feat(metrics): add durable JDBC metrics extension (Scope 2)
Jun 14, 2026
8990ddf
fix(metrics): address review findings on durable JDBC extension
Jun 14, 2026
995ea9a
fix(metrics-jdbc): add missing compile deps and widen access on share…
Jun 14, 2026
8fd3009
refactor(metrics-jdbc): relocate MetricsRecordConverter out of polari…
Jun 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,14 @@ request adding CHANGELOG notes for breaking (!) changes and possibly other secti

- The (Before/After)CommitViewEvent has been removed.
- The (Before/After)CommitTableEvent has been removed.
- The `PolarisMetricsReporter.reportMetric()` method signature has been extended to include a `receivedTimestamp` parameter of type `java.time.Instant`.
- The `PolarisMetricsReporter` SPI (previously in `runtime/service`) has been replaced by `IcebergMetricsReporter` in `polaris-core`. Custom reporters must implement `org.apache.polaris.core.metrics.IcebergMetricsReporter` and update the `@Identifier` annotation for CDI selection via `polaris.iceberg-metrics.reporting.type`. The method signature now includes a `receivedTimestamp` parameter of type `java.time.Instant`.
- The `ExternalCatalogFactory.createCatalog()` and `createGenericCatalog()` method signatures have been extended to include a `catalogProperties` parameter of type `Map<String, String>` for passing through proxy and timeout settings to federated catalog HTTP clients.
- Metrics reporting now requires the `TABLE_READ_DATA` privilege on the target table for read (scan) metrics and `TABLE_WRITE_DATA` for write (commit) metrics.
- The `REVOKE_CATALOG_ROLE_FROM_PRINCIPAL_ROLE` operation no longer requires the `PRINCIPAL_ROLE_MANAGE_GRANTS_FOR_GRANTEE` privilege. Only `CATALOG_ROLE_MANAGE_GRANTS_ON_SECURABLE` on the catalog role is now required, making revoke symmetric with assign. This allows catalog administrators to fully manage catalog role assignments without requiring elevated privileges on principal roles.

### New Features

- Added a **beta** Table Metrics REST API (`/api/metrics-reports/v1/`) for querying Iceberg scan and commit metrics. In this release the read path returns an empty result set; durable storage backing is provided by the `polaris-extensions-metrics-reports-jdbc` extension in a follow-up release. Querying requires the new `TABLE_READ_METRICS` privilege on the target table.
- Added `deploymentAnnotations` support in Helm chart.
- Added KMS properties (optional) to catalog storage config to enable S3 data encryption.
- Added `topologySpreadConstraints` support in Helm chart.
Expand Down
93 changes: 93 additions & 0 deletions api/metrics-reports-service/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import org.openapitools.generator.gradle.plugin.tasks.GenerateTask

plugins {
alias(libs.plugins.openapi.generator)
id("polaris-client")
id("org.kordamp.gradle.jandex")
}

dependencies {
implementation(project(":polaris-core"))

compileOnly(platform(libs.jackson.bom))
compileOnly("com.fasterxml.jackson.core:jackson-annotations")

compileOnly(libs.jakarta.annotation.api)
compileOnly(libs.jakarta.inject.api)
compileOnly(libs.jakarta.validation.api)
compileOnly(libs.microprofile.fault.tolerance.api)
compileOnly(libs.swagger.annotations)

implementation(libs.jakarta.servlet.api)
implementation(libs.jakarta.ws.rs.api)

compileOnly(platform(libs.micrometer.bom))
compileOnly("io.micrometer:micrometer-core")

implementation(libs.slf4j.api)
}

val rootDir = rootProject.layout.projectDirectory
val specsDir = rootDir.dir("spec")
val templatesDir = rootDir.dir("server-templates")
val generatedDir = project.layout.buildDirectory.dir("generated-openapi")
val generatedOpenApiSrcDir = project.layout.buildDirectory.dir("generated-openapi/src/main/java")

openApiGenerate {
inputSpec = provider { specsDir.file("metrics-reports-service.yml").asFile.absolutePath }
generatorName = "jaxrs-resteasy"
outputDir = provider { generatedDir.get().asFile.absolutePath }
apiPackage = "org.apache.polaris.service.metrics.api"
modelPackage = "org.apache.polaris.core.metrics.api.model"
ignoreFileOverride.set(provider { rootDir.file(".openapi-generator-ignore").asFile.absolutePath })
removeOperationIdPrefix.set(true)
templateDir.set(provider { templatesDir.asFile.absolutePath })
globalProperties.put("apis", "")
globalProperties.put("models", "")
globalProperties.put("apiDocs", "false")
globalProperties.put("modelTests", "false")
configOptions.put("openApiNullable", "false")
configOptions.put("useBeanValidation", "true")
configOptions.put("sourceFolder", "src/main/java")
configOptions.put("useJakartaEe", "true")
configOptions.put("generateBuilders", "true")
configOptions.put("generateConstructorWithAllArgs", "true")
configOptions.put("hideGenerationTimestamp", "true")
additionalProperties.put("apiNamePrefix", "Polaris")
additionalProperties.put("apiNameSuffix", "Api")
additionalProperties.put("metricsPrefix", "polaris")
serverVariables.put("basePath", "api/metrics-reports/v1")
}

listOf("sourcesJar", "compileJava", "processResources").forEach { task ->
tasks.named(task) { dependsOn("openApiGenerate") }
}

sourceSets { main { java { srcDir(generatedOpenApiSrcDir) } } }

tasks.named<GenerateTask>("openApiGenerate") {
inputs.dir(templatesDir)
inputs.dir(specsDir)
actions.addFirst { delete { delete(generatedDir) } }
}

tasks.named("javadoc") { dependsOn("jandex") }
4 changes: 4 additions & 0 deletions bom/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ dependencies {
api(project(":polaris-api-iceberg-service"))
api(project(":polaris-api-management-model"))
api(project(":polaris-api-management-service"))
api(project(":polaris-api-metrics-reports-service"))

api(project(":polaris-container-spec-helper"))
api(project(":polaris-azurite-testcontainer"))
Expand Down Expand Up @@ -101,6 +102,8 @@ dependencies {
api(project(":polaris-extensions-federation-bigquery"))
api(project(":polaris-extensions-federation-hadoop"))
api(project(":polaris-extensions-federation-hive"))
api(project(":polaris-extensions-metrics-reports-jdbc"))
api(project(":polaris-extensions-metrics-reports-spi"))
api(project(":polaris-hms-testcontainer"))

api(project(":polaris-spark-3.5_2.12"))
Expand All @@ -112,6 +115,7 @@ dependencies {
api(project(":polaris-spark-4.0_2.13"))
api(project(":polaris-spark-integration-4.0_2.13"))
}
api(project(":polaris-extensions-metrics-reports"))

api(project(":polaris-admin"))
api(project(":polaris-runtime-common"))
Expand Down
51 changes: 51 additions & 0 deletions extensions/metrics-reports/impl/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

plugins {
id("polaris-server")
id("org.kordamp.gradle.jandex")
}

dependencies {
implementation(project(":polaris-core"))
implementation(project(":polaris-api-metrics-reports-service"))
implementation(project(":polaris-extensions-metrics-reports-spi"))

implementation(platform(libs.iceberg.bom))
implementation("org.apache.iceberg:iceberg-api")

implementation(libs.jakarta.enterprise.cdi.api)
implementation(libs.jakarta.inject.api)
implementation(libs.jakarta.ws.rs.api)
implementation(libs.smallrye.common.annotation)
implementation(libs.slf4j.api)
compileOnly(libs.jakarta.annotation.api)

implementation(platform(libs.jackson.bom))
implementation("com.fasterxml.jackson.core:jackson-annotations")

testImplementation(platform(libs.junit.bom))
testImplementation("org.junit.jupiter:junit-jupiter")
testImplementation(libs.assertj.core)
testImplementation(libs.mockito.core)

// Provides jakarta.ws.rs.ext.RuntimeDelegate needed to build Response objects in unit tests
testRuntimeOnly(enforcedPlatform(libs.quarkus.bom))
testRuntimeOnly("io.quarkus.resteasy.reactive:resteasy-reactive")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.polaris.extension.metrics.reports;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;

/** A single commit metrics report entry returned by the list API. */
public record CommitMetricsReport(
@JsonProperty("id") String id,
@JsonProperty("timestampMs") long timestampMs,
@JsonProperty("actor") MetricsReportActor actor,
@JsonProperty("request") MetricsReportRequest request,
@JsonProperty("object") TableObject object,
@JsonProperty("payload") Payload payload) {

/** The Iceberg object (table snapshot) associated with the commit. */
public record TableObject(@JsonProperty("snapshotId") long snapshotId) {}

/** The commit metrics payload envelope. */
public record Payload(
@JsonProperty("type") String type,
@JsonProperty("version") int version,
@JsonProperty("data") Data data) {

/** Iceberg commit metrics data fields. */
@JsonInclude(JsonInclude.Include.NON_NULL)
public record Data(
@JsonProperty("sequenceNumber") Long sequenceNumber,
@JsonProperty("operation") String operation,
@JsonProperty("addedDataFiles") long addedDataFiles,
@JsonProperty("removedDataFiles") long removedDataFiles,
@JsonProperty("totalDataFiles") long totalDataFiles,
@JsonProperty("addedDeleteFiles") long addedDeleteFiles,
@JsonProperty("removedDeleteFiles") long removedDeleteFiles,
@JsonProperty("totalDeleteFiles") long totalDeleteFiles,
@JsonProperty("addedEqualityDeleteFiles") long addedEqualityDeleteFiles,
@JsonProperty("removedEqualityDeleteFiles") long removedEqualityDeleteFiles,
@JsonProperty("addedPositionalDeleteFiles") long addedPositionalDeleteFiles,
@JsonProperty("removedPositionalDeleteFiles") long removedPositionalDeleteFiles,
@JsonProperty("addedRecords") long addedRecords,
@JsonProperty("removedRecords") long removedRecords,
@JsonProperty("totalRecords") long totalRecords,
@JsonProperty("addedFileSizeBytes") long addedFileSizeBytes,
@JsonProperty("removedFileSizeBytes") long removedFileSizeBytes,
@JsonProperty("totalFileSizeBytes") long totalFileSizeBytes,
@JsonProperty("totalDurationMs") Long totalDurationMs,
@JsonProperty("attempts") int attempts) {}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,37 +16,30 @@
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.polaris.service.reporting;
package org.apache.polaris.extension.metrics.reports;

import com.google.common.annotations.VisibleForTesting;
import io.smallrye.common.annotation.Identifier;
import jakarta.enterprise.context.ApplicationScoped;
import java.time.Instant;
import org.apache.iceberg.catalog.TableIdentifier;
import org.apache.iceberg.metrics.MetricsReport;
import org.apache.polaris.core.metrics.IcebergMetricsReporter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Default implementation of {@link PolarisMetricsReporter} that logs metrics to the configured
* logger.
* Log-only implementation of {@link IcebergMetricsReporter}.
*
* <p>This implementation is selected when {@code polaris.iceberg-metrics.reporting.type} is set to
* {@code "default"} (the default value).
* <p>Selected when {@code polaris.iceberg-metrics.reporting.type} is set to {@code "log"}.
*
* <p>By default, logging is disabled. To enable metrics logging, set the logger level for {@code
* org.apache.polaris.service.reporting} to {@code INFO} in your logging configuration.
*
* @see PolarisMetricsReporter
* <p>Logging is at INFO level. Enable it by setting the logger level for {@code
* org.apache.polaris.extension.metrics.reports} to {@code INFO}.
*/
@ApplicationScoped
@Identifier("default")
public class DefaultMetricsReporter implements PolarisMetricsReporter {
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultMetricsReporter.class);

private final ReportConsumer reportConsumer;
public class LoggingMetricsReporter implements IcebergMetricsReporter {
private static final Logger LOGGER = LoggerFactory.getLogger(LoggingMetricsReporter.class);

/** Functional interface for consuming metrics reports with timestamp. */
@FunctionalInterface
interface ReportConsumer {
void accept(
Expand All @@ -58,15 +51,15 @@ void accept(
Instant receivedTimestamp);
}

/** Creates a new DefaultMetricsReporter that logs metrics to the class logger. */
public DefaultMetricsReporter() {
private final ReportConsumer reportConsumer;

public LoggingMetricsReporter() {
this(
(catalogName, catalogId, table, tableId, metricsReport, receivedTimestamp) ->
LOGGER.info("{}.{} (ts={}): {}", catalogName, table, receivedTimestamp, metricsReport));
}

@VisibleForTesting
DefaultMetricsReporter(ReportConsumer reportConsumer) {
LoggingMetricsReporter(ReportConsumer reportConsumer) {
this.reportConsumer = reportConsumer;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.polaris.extension.metrics.reports;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;

/** Paginated list response for metrics reports. */
public record MetricsListResponse<T>(
@JsonProperty("metricType") String metricType,
@JsonInclude(JsonInclude.Include.NON_NULL) @JsonProperty("nextPageToken") String nextPageToken,
@JsonProperty("reports") List<T> reports) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.polaris.extension.metrics.reports;

import com.fasterxml.jackson.annotation.JsonProperty;

/** The principal that triggered the metrics report. */
public record MetricsReportActor(@JsonProperty("principalName") String principalName) {}
Loading