-
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 7 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,6 @@ | |
| 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.collect.ImmutableMap; | ||
| import org.apache.iceberg.relocated.com.google.common.collect.Maps; | ||
| import org.apache.iceberg.relocated.com.google.common.collect.Sets; | ||
|
|
@@ -49,27 +48,39 @@ | |
| 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); | ||
| DEFAULT_MODE, | ||
| 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 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; | ||
| if (idToName != null) { | ||
|
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. Ok it's a bit confusing because there are accessor APIs below which assume that idToName is not null. Currently, we pass it in, but I'm not sure I understand the intent of if (idToName != null) check is. It's a private constructor, can't we force every where else to pass in the idToName and then work off that assumption that it's always defined?
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. Right now, I don't think that we can force every code path to provide a schema, which is how this is derived. That would be more changes and those code paths don't need to use IDs, so I left it out. For instance, The approach I took here was to use
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. Got it, okay I missed the original fromProperties, yeah in that case we can just keep it as is.
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. I added assertions to ensure we don't get NPEs. We can remove them once |
||
| this.idToName = SerializableMap.copyOf(idToName).immutableMap(); | ||
| } | ||
| } | ||
|
|
||
| public static MetricsConfig getDefault() { | ||
|
|
@@ -84,13 +95,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 +121,40 @@ 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() { | ||
| return idToName.keySet(); | ||
|
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. Should this return an empty list if idToName is null?
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. No, I think it should fail if
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. I've updated this (and |
||
| } | ||
|
|
||
| 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) { | ||
| 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); | ||
| } | ||
|
|
||
| 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); | ||
|
|
||
| return new MetricsConfig(columnModes.build(), defaultMode); | ||
| ValidationException.check( | ||
|
rdblue marked this conversation as resolved.
|
||
| null == idToName || idToName.get(field.fieldId()).equals(column), | ||
| "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 +254,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 +267,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 +291,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 +357,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"; | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.