-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Core: Add id tracking to MetricsConfig #17022
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4becc0c
173594a
42b26cc
8e1e7b2
ab334da
bdcb363
306effe
1cea2e1
ba1473c
19f5313
abb2417
a706936
de8f061
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,7 +32,7 @@ | |
| import javax.annotation.concurrent.Immutable; | ||
| import org.apache.iceberg.MetricsModes.MetricsMode; | ||
| import org.apache.iceberg.exceptions.ValidationException; | ||
| import org.apache.iceberg.relocated.com.google.common.base.Joiner; | ||
| import org.apache.iceberg.relocated.com.google.common.base.Preconditions; | ||
| import org.apache.iceberg.relocated.com.google.common.collect.ImmutableMap; | ||
| import org.apache.iceberg.relocated.com.google.common.collect.Maps; | ||
| import org.apache.iceberg.relocated.com.google.common.collect.Sets; | ||
|
|
@@ -49,27 +49,37 @@ | |
| public final class MetricsConfig implements Serializable { | ||
|
|
||
| private static final Logger LOG = LoggerFactory.getLogger(MetricsConfig.class); | ||
| private static final Joiner DOT = Joiner.on('.'); | ||
|
|
||
| // Disable metrics by default for wide tables to prevent excessive metadata | ||
| private static final MetricsMode DEFAULT_MODE = | ||
| MetricsModes.fromString(DEFAULT_WRITE_METRICS_MODE_DEFAULT); | ||
| private static final MetricsConfig DEFAULT = new MetricsConfig(ImmutableMap.of(), DEFAULT_MODE); | ||
| private static final MetricsConfig DEFAULT = | ||
| new MetricsConfig(ImmutableMap.of(), DEFAULT_MODE, ImmutableMap.of()); | ||
| private static final MetricsConfig POSITION_DELETE_MODE = | ||
| new MetricsConfig( | ||
| ImmutableMap.of( | ||
| MetadataColumns.DELETE_FILE_PATH.name(), | ||
| MetricsModes.Full.get(), | ||
| MetadataColumns.DELETE_FILE_POS.name(), | ||
| MetricsModes.Full.get()), | ||
| DEFAULT_MODE); | ||
| MetricsModes.None.get(), | ||
| ImmutableMap.of( | ||
| MetadataColumns.DELETE_FILE_PATH.fieldId(), | ||
| MetadataColumns.DELETE_FILE_PATH.name(), | ||
| MetadataColumns.DELETE_FILE_POS.fieldId(), | ||
| MetadataColumns.DELETE_FILE_POS.name())); | ||
|
|
||
| private final Map<String, MetricsMode> columnModes; | ||
| private final MetricsMode defaultMode; | ||
| private final Map<Integer, String> idToName; | ||
|
|
||
| private MetricsConfig(Map<String, MetricsMode> columnModes, MetricsMode defaultMode) { | ||
| private MetricsConfig( | ||
| Map<String, MetricsMode> columnModes, | ||
| MetricsMode defaultMode, | ||
| Map<Integer, String> idToName) { | ||
| this.columnModes = SerializableMap.copyOf(columnModes).immutableMap(); | ||
| this.defaultMode = defaultMode; | ||
| this.idToName = idToName != null ? SerializableMap.copyOf(idToName).immutableMap() : null; | ||
| } | ||
|
|
||
| public static MetricsConfig getDefault() { | ||
|
|
@@ -84,13 +94,23 @@ public static MetricsConfig forPositionDelete() { | |
| * Creates a metrics config from table configuration. | ||
| * | ||
| * @param props table configuration | ||
| * @deprecated use {@link MetricsConfig#forTable(Table)}. Will be removed in 2.0.0 | ||
| * @deprecated use {@link MetricsConfig#forTable(Table)}. Will be removed in 1.13.0 | ||
| */ | ||
| @Deprecated | ||
| public static MetricsConfig fromProperties(Map<String, String> props) { | ||
| return from(props, null, null); | ||
| } | ||
|
|
||
| /** | ||
| * Validates metrics config properties with the given schema. | ||
| * | ||
| * @param props table properties | ||
| * @param schema table schema | ||
| */ | ||
| public static void validate(Map<String, String> props, Schema schema) { | ||
| from(props, schema, null).validateReferencedColumns(schema); | ||
| } | ||
|
|
||
| /** | ||
| * Creates a metrics config from a table. | ||
| * | ||
|
|
@@ -100,32 +120,42 @@ public static MetricsConfig forTable(Table table) { | |
| return from(table.properties(), table.schema(), table.sortOrder()); | ||
| } | ||
|
|
||
| /** | ||
| * Creates a metrics config for a position delete file. | ||
| * | ||
| * @param table an Iceberg table | ||
| * @deprecated This method is deprecated as of version 1.11.0 and will be removed in 1.12.0. | ||
| * Position deletes that include row data are no longer supported. Use {@link | ||
| * #forPositionDelete()} instead. | ||
| */ | ||
| @Deprecated | ||
| public static MetricsConfig forPositionDelete(Table table) { | ||
| ImmutableMap.Builder<String, MetricsMode> columnModes = ImmutableMap.builder(); | ||
| public Iterable<Integer> metricsFieldIds() { | ||
| Preconditions.checkState(idToName != null, "Cannot resolve column mode by ID: missing schema"); | ||
| return idToName.keySet(); | ||
| } | ||
|
|
||
| columnModes.put(MetadataColumns.DELETE_FILE_PATH.name(), MetricsModes.Full.get()); | ||
| columnModes.put(MetadataColumns.DELETE_FILE_POS.name(), MetricsModes.Full.get()); | ||
| public MetricsMode columnMode(int id) { | ||
| Preconditions.checkState(idToName != null, "Cannot resolve column mode by ID: missing schema"); | ||
| String name = idToName.get(id); | ||
| if (name != null) { | ||
| return columnMode(name); | ||
| } | ||
|
|
||
| MetricsConfig tableConfig = forTable(table); | ||
| return defaultMode; | ||
| } | ||
|
|
||
| MetricsMode defaultMode = tableConfig.defaultMode; | ||
| tableConfig.columnModes.forEach( | ||
| (columnAlias, mode) -> { | ||
| String positionDeleteColumnAlias = | ||
| DOT.join(MetadataColumns.DELETE_FILE_ROW_FIELD_NAME, columnAlias); | ||
| columnModes.put(positionDeleteColumnAlias, mode); | ||
| }); | ||
| public MetricsMode columnMode(String columnAlias) { | ||
| return columnModes.getOrDefault(columnAlias, defaultMode); | ||
| } | ||
|
|
||
| return new MetricsConfig(columnModes.build(), defaultMode); | ||
| public void validateReferencedColumns(Schema schema) { | ||
| for (String column : columnModes.keySet()) { | ||
| Types.NestedField field = schema.findField(column); | ||
| ValidationException.check( | ||
| field != null, | ||
| "Invalid metrics config, could not find column %s from table prop %s in schema %s", | ||
| column, | ||
| METRICS_MODE_COLUMN_CONF_PREFIX + column, | ||
| schema); | ||
|
|
||
| ValidationException.check( | ||
|
rdblue marked this conversation as resolved.
|
||
| null == idToName || column.equals(idToName.get(field.fieldId())), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. was thinking about the field rename and preserve the field id but I believe it's already covered in schema update https://github.com/apache/iceberg/blob/main/core/src/main/java/org/apache/iceberg/SchemaUpdate.java#L517-L524 |
||
| "Incorrect field name for id %s: %s (expected %s)", | ||
| field.fieldId(), | ||
| column, | ||
| idToName.get(field.fieldId())); | ||
| } | ||
| } | ||
|
|
||
| static Set<Integer> limitFieldIds(Schema schema, int limit) { | ||
|
|
@@ -225,6 +255,7 @@ public Set<Integer> map( | |
| */ | ||
| public static MetricsConfig from(Map<String, String> props, Schema schema, SortOrder order) { | ||
| int maxInferredDefaultColumns = maxInferredColumnDefaults(props); | ||
| Map<Integer, String> idToName = Maps.newHashMap(); | ||
| Map<String, MetricsMode> columnModes = Maps.newHashMap(); | ||
|
|
||
| // Handle user override of default mode | ||
|
|
@@ -237,13 +268,20 @@ public static MetricsConfig from(Map<String, String> props, Schema schema, SortO | |
| } else if (schema == null) { | ||
| defaultMode = DEFAULT_MODE; | ||
| } else { | ||
| if (TypeUtil.getProjectedIds(schema).size() <= maxInferredDefaultColumns) { | ||
| Set<Integer> ids = TypeUtil.getProjectedIds(schema); | ||
|
rdblue marked this conversation as resolved.
|
||
| if (ids.size() <= maxInferredDefaultColumns) { | ||
| for (int id : ids) { | ||
| idToName.put(id, schema.findColumnName(id)); | ||
| } | ||
|
|
||
| // there are less than the inferred limit (including structs), so the default is used | ||
| // everywhere | ||
| defaultMode = DEFAULT_MODE; | ||
| } else { | ||
| for (Integer id : limitFieldIds(schema, maxInferredDefaultColumns)) { | ||
| columnModes.put(schema.findColumnName(id), DEFAULT_MODE); | ||
| String name = schema.findColumnName(id); | ||
| idToName.put(id, name); | ||
| columnModes.put(name, DEFAULT_MODE); | ||
| } | ||
|
|
||
| // all other columns don't use metrics | ||
|
|
@@ -254,18 +292,29 @@ public static MetricsConfig from(Map<String, String> props, Schema schema, SortO | |
| // First set sorted column with sorted column default (can be overridden by user) | ||
| MetricsMode sortedColDefaultMode = sortedColumnDefaultMode(defaultMode); | ||
| Set<String> sortedCols = SortOrderUtil.orderPreservingSortedColumns(order); | ||
| sortedCols.forEach(sc -> columnModes.put(sc, sortedColDefaultMode)); | ||
| sortedCols.forEach( | ||
| name -> { | ||
| columnModes.put(name, sortedColDefaultMode); | ||
| Types.NestedField field = schema != null ? schema.findField(name) : null; | ||
| if (field != null) { | ||
| idToName.put(field.fieldId(), name); | ||
| } | ||
| }); | ||
|
|
||
| // Handle user overrides of defaults | ||
| for (String key : props.keySet()) { | ||
| if (key.startsWith(METRICS_MODE_COLUMN_CONF_PREFIX)) { | ||
| String columnAlias = key.replaceFirst(METRICS_MODE_COLUMN_CONF_PREFIX, ""); | ||
| MetricsMode mode = parseMode(props.get(key), defaultMode, "column " + columnAlias); | ||
| columnModes.put(columnAlias, mode); | ||
| Types.NestedField field = schema != null ? schema.findField(columnAlias) : null; | ||
| if (field != null) { | ||
| idToName.put(field.fieldId(), columnAlias); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return new MetricsConfig(columnModes, defaultMode); | ||
| return new MetricsConfig(columnModes, defaultMode, idToName); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -309,19 +358,4 @@ private static MetricsMode parseMode(String modeString, MetricsMode fallback, St | |
| return fallback; | ||
| } | ||
| } | ||
|
|
||
| public void validateReferencedColumns(Schema schema) { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These were moved above the |
||
| for (String column : columnModes.keySet()) { | ||
| ValidationException.check( | ||
| schema.findField(column) != null, | ||
| "Invalid metrics config, could not find column %s from table prop %s in schema %s", | ||
| column, | ||
| METRICS_MODE_COLUMN_CONF_PREFIX + column, | ||
| schema); | ||
| } | ||
| } | ||
|
|
||
| public MetricsMode columnMode(String columnAlias) { | ||
| return columnModes.getOrDefault(columnAlias, defaultMode); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -59,7 +59,12 @@ public static MetricsMode fromString(String mode) { | |
| * | ||
| * <p>Implementations must be immutable. | ||
| */ | ||
| public interface MetricsMode extends Serializable {} | ||
| public interface MetricsMode extends Serializable { | ||
| default boolean hasBounds() { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is this used anywhere? it's not clear from the PR description why this is being added |
||
| throw new UnsupportedOperationException( | ||
| "Unexpected implementation of MetricsMode without hasBounds"); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Under this mode, value_counts, null_value_counts, nan_value_counts, lower_bounds, upper_bounds | ||
|
|
@@ -72,6 +77,11 @@ public static None get() { | |
| return INSTANCE; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean hasBounds() { | ||
| return false; | ||
| } | ||
|
|
||
| @Override | ||
| public String toString() { | ||
| return "none"; | ||
|
|
@@ -86,6 +96,11 @@ public static Counts get() { | |
| return INSTANCE; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean hasBounds() { | ||
| return false; | ||
| } | ||
|
|
||
| @Override | ||
| public String toString() { | ||
| return "counts"; | ||
|
|
@@ -108,6 +123,11 @@ public static Truncate withLength(int length) { | |
| return new Truncate(length); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean hasBounds() { | ||
| return true; | ||
| } | ||
|
|
||
| public int length() { | ||
| return length; | ||
| } | ||
|
|
@@ -145,6 +165,11 @@ public static Full get() { | |
| return INSTANCE; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean hasBounds() { | ||
| return true; | ||
| } | ||
|
|
||
| @Override | ||
| public String toString() { | ||
| return "full"; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this return an empty list if idToName is null?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, I think it should fail if
idToNameis null because there could be a configuration, but the config wasn't initialized the right way. IfidToNameis null, then it was initialized without a schema, which we want to stop doing.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've updated this (and
columnMode(int)) to check thatidToNameis present and throw an exception if not. That way, when we get rid offromProperties, we can get rid of the check.