Jackie-Jiang commented on code in PR #18977:
URL: https://github.com/apache/pinot/pull/18977#discussion_r4109596149
##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/loader/SegmentPreProcessor.java:
##########
@@ -160,6 +163,9 @@ public void process(@Nullable SegmentOperationsThrottlerSet
segmentOperationsThr
DefaultColumnHandlerFactory.getDefaultColumnHandler(indexDir,
segmentMetadata, _indexLoadingConfig,
segmentWriter);
defaultColumnHandler.updateDefaultColumns();
+ // Invalidate dependent indexes only for values that were actually
regenerated. A tolerated column-build failure
+ // leaves the original values and their indexes valid.
+ _columnsWithChangedTransformValues =
defaultColumnHandler.getColumnsWithChangedTransformValues();
Review Comment:
[CRITICAL] This changed-column signal survives only in memory.
`updateDefaultColumns()` saves replacement values and provenance before
StarTree and multi-column text indexes are rebuilt in the later preprocessing
phase. If the process stops between those phases, the next load sees current
provenance and matching index configs, so it can retain indexes over the old
values and return stale results. Please durably invalidate affected indexes
before committing the column replacement, or persist a pending-rebuild marker
until all dependent indexes have been rebuilt.
##########
pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/refreshsegment/RefreshSegmentTaskExecutor.java:
##########
@@ -196,6 +371,60 @@ 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();
+ properties.setProperty(V1Constants.MetadataKeys.Column.getKeyFor(column,
Review Comment:
[CRITICAL] Restoring the old `isAutoGenerated=false` flag is incorrect when
a known ingestion transform has been removed. The first refresh omits its
output and fills it with the schema default, but this line then marks the
rebuilt value as a source value. If the default later changes (for example 0 to
5), subsequent refreshes keep the stored 0 and the server default-column
handler skips the column. Please mark an output regenerated from a schema
default as auto-generated.
##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/TransformPipeline.java:
##########
@@ -52,23 +55,43 @@ public TransformPipeline(String tableNameWithType,
List<RecordTransformer> trans
_tableNameWithType = tableNameWithType;
_transformers = transformers;
FilterTransformer filterTransformer = null;
+ ExpressionTransformer expressionTransformer = null;
Set<String> cumulativeInputColumns = new HashSet<>();
for (int i = transformers.size() - 1; i >= 0; i--) {
RecordTransformer recordTransformer = transformers.get(i);
if (recordTransformer instanceof FilterTransformer) {
filterTransformer = (FilterTransformer) recordTransformer;
}
+ if (recordTransformer instanceof ExpressionTransformer) {
+ expressionTransformer = (ExpressionTransformer) recordTransformer;
+ }
recordTransformer.withInputColumnsForDownstreamTransformers(cumulativeInputColumns);
cumulativeInputColumns.addAll(recordTransformer.getInputColumns());
}
_inputColumns = cumulativeInputColumns;
_filterTransformer = filterTransformer;
+ _expressionTransformer = expressionTransformer;
}
public TransformPipeline(TableConfig tableConfig, Schema schema) {
this(tableConfig.getTableName(),
RecordTransformerUtils.getDefaultTransformers(tableConfig, schema));
}
+ /// Creates a pipeline for replaying stored rows while preserving
authoritative values for selected transform output
+ /// columns. Presence in the decoded row, including a null marker,
suppresses expression re-evaluation.
+ public TransformPipeline(TableConfig tableConfig, Schema schema, Set<String>
columnsToPreserve) {
Review Comment:
[MINOR] Neither new replay constructor is called on this PR head.
RefreshSegment builds its transformer list and calls the existing `(String,
List<RecordTransformer>)` constructor. The three-argument
`RecordTransformerUtils.getDefaultTransformers` overload exists only to support
these unused constructors. Please remove the unused overloads to keep the API
surface small.
##########
pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/ColumnMetadataTest.java:
##########
@@ -316,7 +316,7 @@ public void
testMaxRowLengthInBytesPersistedForMvVarLength() {
PropertiesConfiguration config = new PropertiesConfiguration();
BaseSegmentCreator.addColumnMetadataInfo(config, "mvString", stats, 10,
fieldSpec, false, -1,
- FieldConfig.EncodingType.RAW, false);
+ FieldConfig.EncodingType.RAW, false, null);
Review Comment:
[MINOR] This and the two similar changes below switch unrelated metadata
tests to the overload that stamps known-no-transform provenance. The tests
assert row-length and field-spec behavior, not provenance, and the original
overload still exists. Please revert these fixture changes so they exercise
only the behavior under test.
##########
pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/refreshsegment/RefreshSegmentTaskExecutor.java:
##########
@@ -145,12 +189,24 @@ protected SegmentConversionResult convert(PinotTaskConfig
pinotTaskConfig, File
// honored (needPreprocess=false: read-only).
ImmutableSegment segment = ImmutableSegmentLoader.load(indexDir,
indexLoadingConfig, false);
try (PinotSegmentRecordReader recordReader = new
PinotSegmentRecordReader()) {
- recordReader.init(segment);
- SegmentGeneratorConfig config = getSegmentGeneratorConfig(workingDir,
tableConfig, segmentMetadata, segmentName,
- getSchema(tableNameWithType));
+ Set<String> fieldsToRead = new
HashSet<>(segment.getPhysicalColumnNames());
+ fieldsToRead.removeAll(transformColumnsToRecompute);
+ recordReader.initWithFieldsToRead(segment, fieldsToRead);
+ SegmentGeneratorConfig config =
+ getSegmentGeneratorConfig(workingDir, tableConfig, segmentMetadata,
segmentName, schema);
SegmentIndexCreationDriverImpl driver = new
SegmentIndexCreationDriverImpl();
- driver.init(config, recordReader);
+ List<RecordTransformer> recordTransformers = new ArrayList<>();
+ if (!replayDefaultValues.isEmpty()) {
Review Comment:
[CRITICAL] `ReplayDefaultValueTransformer` runs before the configured
enrichers. For a new schema column populated by an enricher, it first calls
`putDefaultNullValue`, setting a null marker; the enricher later writes the
real value with `putValue`, which does not clear that marker. Segment creation
writes the column into the null vector, so null-enabled queries see NULL
despite the enriched value. Please inject defaults after enrichment and before
expression evaluation, only for fields that remain absent, or clear the marker
when a producer supplies a value.
##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/openstruct/OpenStructColumnSplitter.java:
##########
@@ -442,7 +442,7 @@ private void writeDenseKeyColumn(String key)
FieldConfig.EncodingType encoding =
useDictionary ? FieldConfig.EncodingType.DICTIONARY :
FieldConfig.EncodingType.RAW;
BaseSegmentCreator.addColumnMetadataInfo(props, materializedCol,
statsCollector, _numDocs, childFieldSpec,
- useDictionary, dictElementSize, encoding, false);
+ useDictionary, dictElementSize, encoding, false, null);
Review Comment:
[MINOR] Passing `null` to the new overload stamps materialized OpenStruct
children as known-no-transform rather than leaving their provenance unknown.
The same change appears for sparse children below. Is this metadata behavior
needed for the derived-column fix? If not, please keep the original overload
and avoid changing OpenStruct child metadata in this PR.
##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/loader/defaultcolumn/BaseDefaultColumnHandler.java:
##########
@@ -298,23 +445,33 @@ Map<String, DefaultColumnAction>
computeDefaultColumnActionMap() {
defaultColumnActionMap.put(column,
DefaultColumnAction.UPDATE_METRIC_DEFAULT_VALUE);
} else if (isSingleValueInMetadata != isSingleValueInSchema) {
defaultColumnActionMap.put(column,
DefaultColumnAction.UPDATE_METRIC_NUMBER_OF_VALUES);
+ } else if (isTransformFunctionChanged(column, columnMetadata)) {
+ defaultColumnActionMap.put(column,
DefaultColumnAction.UPDATE_METRIC_TRANSFORM_FUNCTION);
}
} else if (fieldTypeInMetadata == DATE_TIME) {
if (dataTypeInMetadata != dataTypeInSchema) {
defaultColumnActionMap.put(column,
DefaultColumnAction.UPDATE_DATE_TIME_DATA_TYPE);
} else if (!defaultValueInSchema.equals(defaultValueInMetadata)) {
defaultColumnActionMap.put(column,
DefaultColumnAction.UPDATE_DATE_TIME_DEFAULT_VALUE);
+ } else if (isTransformFunctionChanged(column, columnMetadata)) {
+ defaultColumnActionMap.put(column,
DefaultColumnAction.UPDATE_DATE_TIME_TRANSFORM_FUNCTION);
}
} else if (fieldTypeInMetadata == COMPLEX) {
if (dataTypeInMetadata != dataTypeInSchema) {
defaultColumnActionMap.put(column,
DefaultColumnAction.UPDATE_COMPLEX_DATA_TYPE);
} else if (!defaultValueInSchema.equals(defaultValueInMetadata)) {
defaultColumnActionMap.put(column,
DefaultColumnAction.UPDATE_COMPLEX_DEFAULT_VALUE);
+ } else if (isTransformFunctionChanged(column, columnMetadata)) {
+ defaultColumnActionMap.put(column,
DefaultColumnAction.UPDATE_COMPLEX_TRANSFORM_FUNCTION);
}
}
} else {
// Column does not exist in the segment, add default value for it.
+ if (_columnsInTransformChains.contains(column)) {
Review Comment:
[CRITICAL] This skips ADD for a newly introduced transform output when it
depends on an existing auto-generated column. For example, if the segment has
default column `A` and the schema adds `B = plus(A, 1)`, normal server reload
leaves `B` absent and queries serve its virtual schema default instead of `A +
1`. RefreshSegment is optional and is not scheduled for tables without task
configuration. Please materialize this chain before the reloaded segment is
served, either in place when its inputs are stable or through required record
replay.
##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/map/SimpleColumnMetadata.java:
##########
@@ -132,6 +132,17 @@ public boolean isAutoGenerated() {
return false;
}
+ @Nullable
Review Comment:
[MINOR] These two overrides return exactly the defaults already supplied by
`ColumnMetadata`. The same duplicate overrides were added to
`EmptyColumnMetadata`. Please remove both pairs and inherit the interface
defaults.
--
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]