Jackie-Jiang commented on code in PR #18977:
URL: https://github.com/apache/pinot/pull/18977#discussion_r4499108744
##########
pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/refreshsegment/RefreshSegmentTaskExecutor.java:
##########
@@ -196,6 +374,74 @@ private static SegmentGeneratorConfig
getSegmentGeneratorConfig(File workingDir,
return config;
}
+ private static void preserveOriginalColumnMetadata(File refreshedSegmentDir,
+ Map<String, ColumnMetadata> originalMetadata, Set<String>
transformColumnsToRecompute,
+ Set<String> changedTransformColumns, Map<String, String>
transformFunctionByColumn,
+ Set<String> replayDefaultColumns)
+ throws Exception {
+ if (originalMetadata.isEmpty() && replayDefaultColumns.isEmpty()) {
+ return;
+ }
+ var properties =
SegmentMetadataUtils.getPropertiesConfiguration(refreshedSegmentDir);
+ for (Map.Entry<String, ColumnMetadata> entry :
originalMetadata.entrySet()) {
+ String column = entry.getKey();
+ ColumnMetadata columnMetadata = entry.getValue();
+ // A column whose transform was removed was just regenerated from the
schema default, which makes it a default
+ // column even if the transform ran at ingestion time: a later
default-value change must regenerate it.
+ boolean regeneratedFromDefault =
+ changedTransformColumns.contains(column) &&
transformFunctionByColumn.get(column) == null;
+ properties.setProperty(V1Constants.MetadataKeys.Column.getKeyFor(column,
+ V1Constants.MetadataKeys.Column.IS_AUTO_GENERATED),
+ regeneratedFromDefault || columnMetadata.isAutoGenerated());
+ if (changedTransformColumns.contains(column)) {
+ // A removed transform is deliberately regenerated as schema default
values. Record that known no-transform
+ // state so adding an expression later is distinguishable from legacy
metadata with unknown provenance.
+ if (transformFunctionByColumn.get(column) == null) {
+ BaseSegmentCreator.addTransformFunction(properties, column, null);
+ }
+ } else if (!transformColumnsToRecompute.contains(column)) {
+ // Recomputed ancestors keep the provenance emitted by this build. In
particular, a continue-on-error fallback
+ // must not be overwritten with the old trusted fingerprint.
+ if (columnMetadata.getTransformFunctionProvenanceVersion() ==
ColumnMetadata.UNAVAILABLE) {
+
properties.clearProperty(V1Constants.MetadataKeys.Column.getKeyFor(column,
+ V1Constants.MetadataKeys.Column.TRANSFORM_FUNCTION));
+
properties.clearProperty(V1Constants.MetadataKeys.Column.getKeyFor(column,
+ V1Constants.MetadataKeys.Column.TRANSFORM_FUNCTION_BASE64));
+
properties.clearProperty(V1Constants.MetadataKeys.Column.getKeyFor(column,
+
V1Constants.MetadataKeys.Column.TRANSFORM_FUNCTION_PROVENANCE_VERSION));
+
properties.clearProperty(V1Constants.MetadataKeys.Column.getKeyFor(column,
+ V1Constants.MetadataKeys.Column.TRANSFORM_FUNCTION_FINGERPRINT));
+ } else {
+ BaseSegmentCreator.addTransformFunction(properties, column,
columnMetadata.getTransformFunction(),
+ columnMetadata.getTransformFunctionFingerprint());
+ // Preserve the exact provenance version. addTransformFunction()
normally infers the version for new writes,
+ // but an unrelated refresh must not silently upgrade or downgrade
metadata it did not regenerate.
+
properties.setProperty(V1Constants.MetadataKeys.Column.getKeyFor(column,
+
V1Constants.MetadataKeys.Column.TRANSFORM_FUNCTION_PROVENANCE_VERSION),
+ columnMetadata.getTransformFunctionProvenanceVersion());
+ }
+ }
+ }
+ for (String column : replayDefaultColumns) {
+ // Row replay supplies this value before expressions, but the regular
segment-generation path otherwise records
+ // it like a source field. Preserve its default-column identity so a
later structural or transform change can
+ // regenerate it instead of treating the previously materialized default
as authoritative input.
+ properties.setProperty(V1Constants.MetadataKeys.Column.getKeyFor(column,
Review Comment:
A column enters `replayDefaultColumns` when it was absent from the old
segment, even if an enricher supplies its value on every replayed row. This
loop then marks that enriched column auto-generated. On a later schema-default
change, server reload treats it as a default column and replaces its real
values (the new enriched-column test verifies values 15/25 immediately after
refresh, but not the next reload). Please record whether replay actually
supplied defaults, and avoid marking enriched or mixed columns wholly
default-backed.
##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/loader/defaultcolumn/BaseDefaultColumnHandler.java:
##########
@@ -414,6 +442,19 @@ Map<String, DefaultColumnAction>
computeDefaultColumnActionMap() {
return defaultColumnActionMap;
}
+ /// Returns `true` when a column with known provenance was generated by a
different transform expression than the one
+ /// configured now. Legacy columns have no provenance marker and are left
untouched because their generating
+ /// expression is unknowable. A null expression with known provenance is a
known no-transform state.
+ ///
+ /// The column is then regenerated like any other updated column, in
[#orderByDependencies] order, so a changed
+ /// input is rebuilt before the columns that read it. A dependent whose own
expression did not change is not
+ /// regenerated, which is why metadata written here records the expression
without a dependency fingerprint and stays
+ /// provenance version 1. Dependency-closed version-2 provenance is written
only by full record replay.
+ private boolean isTransformFunctionChanged(String column, ColumnMetadata
columnMetadata) {
Review Comment:
This compares only the column's direct expression. If existing
auto-generated `A` changes from `plus(raw,1)` to `plus(raw,2)` while existing
`B=plus(A,1)` is unchanged, reload updates `A` but gives `B` no action, so `B`
continues serving values computed from old `A`. Dependency ordering only orders
selected actions; it does not select `B`. Please include affected existing
dependents in the rebuild and index-invalidation set.
##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/loader/defaultcolumn/BaseDefaultColumnHandler.java:
##########
@@ -348,6 +368,8 @@ Map<String, DefaultColumnAction>
computeDefaultColumnActionMap() {
defaultColumnActionMap.put(column,
DefaultColumnAction.UPDATE_DIMENSION_DEFAULT_VALUE);
} else if (isSingleValueInMetadata != isSingleValueInSchema) {
defaultColumnActionMap.put(column,
DefaultColumnAction.UPDATE_DIMENSION_NUMBER_OF_VALUES);
+ } else if (isTransformFunctionChanged(column, columnMetadata)) {
Review Comment:
This new transform UPDATE reaches the V1/V3 handlers, which remove the old
column indexes and metadata before building the replacement. With the default
`errorOnColumnBuildFailure=false`, a valid expression whose result cannot
convert to the column type (for example a nonnumeric string for an INT derived
column) makes creation return `false`; `updateDefaultColumns()` then saves
metadata without the previously valid column. Please build and validate the
replacement before removing the old column, or restore the old state on
tolerated failure.
##########
pinot-segment-spi/src/main/java/org/apache/pinot/segment/spi/ColumnMetadata.java:
##########
@@ -85,6 +85,19 @@ default Map<String, DataType> getSparseKeyTypes() {
return null;
}
+ /// Returns the transform function expression used to generate the column,
if persisted in the segment metadata.
+ @Nullable
+ String getTransformFunction();
Review Comment:
Making these three new public SPI getters abstract breaks existing
downstream `ColumnMetadata` implementations: they no longer compile against
this interface, and an old compiled implementation throws `AbstractMethodError`
if a new getter is called. We have an existing downstream implementation in the
aggregate build that lacks all three methods. Please retain default
implementations returning `null`, `UNAVAILABLE`, and `null`; the in-repo mapped
implementation can still override them explicitly.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]