This is an automated email from the ASF dual-hosted git repository.

Jackie-Jiang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pinot.git


The following commit(s) were added to refs/heads/master by this push:
     new 9fd510fe81f Enforce multi-value row limits before mutable-segment 
writes (#19399)
9fd510fe81f is described below

commit 9fd510fe81f00ffdd8c3bceb07dfebcae3fd5aa9
Author: deepinsight coder <[email protected]>
AuthorDate: Tue Sep 1 15:05:41 2026 -0700

    Enforce multi-value row limits before mutable-segment writes (#19399)
---
 .../indexsegment/mutable/MutableSegmentImpl.java   | 162 +++++----
 .../forward/FixedByteMVMutableForwardIndex.java    |  12 +-
 .../segment/index/forward/ForwardIndexType.java    |   8 +-
 .../MutableSegmentImplMVLengthValidationTest.java  | 374 +++++++++++++++++++++
 .../FixedByteMVMutableForwardIndexTest.java        |  33 ++
 .../mutable/provider/MutableIndexContext.java      |  22 +-
 6 files changed, 546 insertions(+), 65 deletions(-)

diff --git 
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/indexsegment/mutable/MutableSegmentImpl.java
 
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/indexsegment/mutable/MutableSegmentImpl.java
index 681c546d418..6f93fad11e2 100644
--- 
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/indexsegment/mutable/MutableSegmentImpl.java
+++ 
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/indexsegment/mutable/MutableSegmentImpl.java
@@ -166,6 +166,7 @@ public class MutableSegmentImpl implements MutableSegment {
   private final File _consumerDir;
 
   private final Map<String, IndexContainer> _indexContainerMap = new 
HashMap<>();
+  private final MultiValueRowLimit[] _multiValueRowLimits;
   private final IdMap<FixedIntArray> _recordIdMap;
   private final int _numKeyColumns;
   // Cache the physical (non-virtual) field specs
@@ -177,6 +178,7 @@ public class MutableSegmentImpl implements MutableSegment {
   private final PartitionDedupMetadataManager _partitionDedupMetadataManager;
   private final String _dedupTimeColumn;
   private final PartitionUpsertMetadataManager _partitionUpsertMetadataManager;
+  private final boolean _isPartialUpsert;
   private final List<String> _upsertComparisonColumns;
   private final String _deleteRecordColumn;
   private final boolean _upsertDropOutOfOrderRecord;
@@ -309,6 +311,7 @@ public class MutableSegmentImpl implements MutableSegment {
 
     // Initialize for each column
     boolean hasColumnWithReuseMutableTextIndex = false;
+    List<MultiValueRowLimit> multiValueRowLimits = new ArrayList<>();
     for (FieldSpec fieldSpec : _physicalFieldSpecs) {
       String column = fieldSpec.getName();
 
@@ -325,6 +328,7 @@ public class MutableSegmentImpl implements MutableSegment {
 
       FieldIndexConfigs indexConfigs =
           
Optional.ofNullable(config.getIndexConfigByCol().get(column)).orElse(FieldIndexConfigs.EMPTY);
+      VectorIndexConfig vectorIndexConfig = 
indexConfigs.getConfig(StandardIndexes.vector());
       boolean isDictionary = !isNoDictionaryColumn(indexConfigs, fieldSpec, 
column);
       MutableIndexContext context =
           MutableIndexContext.builder()
@@ -337,6 +341,8 @@ public class MutableSegmentImpl implements MutableSegment {
               
.withEstimatedCardinality(_statsHistory.getEstimatedCardinality(column))
               
.withEstimatedColSize(_statsHistory.getEstimatedAvgColSize(column))
               
.withAvgNumMultiValues(_statsHistory.getEstimatedAvgColSize(column))
+              .withMaxNumMultiValuesPerRowOverride(
+                  vectorIndexConfig.isEnabled() ? 
vectorIndexConfig.getVectorDimension() : 0)
               .withConsumerDir(_consumerDir)
               .withFixedLengthBytes(fixedByteSize).build();
 
@@ -393,7 +399,7 @@ public class MutableSegmentImpl implements MutableSegment {
       }
 
       Map<IndexType, MutableIndex> mutableIndexes =
-          new MutableIndexes(indexConfigs.getConfig(StandardIndexes.vector()));
+          new MutableIndexes(vectorIndexConfig);
       for (IndexType<?, ?, ?> indexType : 
IndexService.getInstance().getAllIndexes()) {
         if (!specialIndexes.contains(indexType)) {
           addMutableIndex(mutableIndexes, indexType, context, indexConfigs);
@@ -405,6 +411,14 @@ public class MutableSegmentImpl implements MutableSegment {
       String sourceColumn = columnAggregatorPair.getLeft();
       ValueAggregator valueAggregator = columnAggregatorPair.getRight();
 
+      // Capture the row cap from the concrete writer before a SameValue 
wrapper hides the type.
+      MutableIndex unwrappedForwardIndex = 
mutableIndexes.get(StandardIndexes.forward());
+      if (!fieldSpec.isSingleValueField()
+          && unwrappedForwardIndex instanceof FixedByteMVMutableForwardIndex 
fixedByteMVIndex) {
+        multiValueRowLimits.add(
+            new MultiValueRowLimit(column, 
fixedByteMVIndex.getMaxNumberOfMultiValuesPerRow()));
+      }
+
       // TODO this can be removed after forward index contents no longer 
depends on text index configs
       // If the raw value is provided, use it for the forward/dictionary index 
of this column by wrapping the
       // already created MutableIndex with a SameValue implementation. This 
optimization can only be done when
@@ -439,6 +453,7 @@ public class MutableSegmentImpl implements MutableSegment {
               nullValueVector, sourceColumn, valueAggregator));
     }
     _hasColumnWithReuseMutableTextIndex = hasColumnWithReuseMutableTextIndex;
+    _multiValueRowLimits = 
multiValueRowLimits.toArray(MultiValueRowLimit[]::new);
 
     _partitionDedupMetadataManager = config.getPartitionDedupMetadataManager();
     _dedupTimeColumn =
@@ -450,6 +465,7 @@ public class MutableSegmentImpl implements MutableSegment {
       Preconditions.checkState(!isAggregateMetricsEnabled(),
           "Metrics aggregation and upsert cannot be enabled together");
       UpsertContext upsertContext = 
_partitionUpsertMetadataManager.getContext();
+      _isPartialUpsert = upsertContext.getUpsertMode() == 
UpsertConfig.Mode.PARTIAL;
       _upsertComparisonColumns = upsertContext.getComparisonColumns();
       _deleteRecordColumn = upsertContext.getDeleteRecordColumn();
       _upsertDropOutOfOrderRecord = upsertContext.isDropOutOfOrderRecord();
@@ -462,6 +478,7 @@ public class MutableSegmentImpl implements MutableSegment {
         _queryableDocIds = null;
       }
     } else {
+      _isPartialUpsert = false;
       _upsertComparisonColumns = null;
       _deleteRecordColumn = null;
       _upsertDropOutOfOrderRecord = false;
@@ -614,6 +631,9 @@ public class MutableSegmentImpl implements MutableSegment {
   @Override
   public boolean index(GenericRow row, @Nullable StreamMessageMetadata 
metadata)
       throws IOException {
+    IndexContainer mismatchedPartitionIndexContainer = null;
+    String mismatchedPartitionValue = null;
+    int mismatchedPartition = -1;
     if (_partitionColumn != null) {
       Object value = row.getValue(_partitionColumn);
       Preconditions.checkState(value != null, "Failed to find value for 
partition column: %s", _partitionColumn);
@@ -628,37 +648,25 @@ public class MutableSegmentImpl implements MutableSegment 
{
           updateIndexedAndIngestionTime(metadata);
           return true;
         }
-        if (indexContainer._partitions.add(partition)) {
-          // for every partition other than mainPartitionId, log a warning once
-          _logger.warn("Found new partition: {} from partition column: {}, 
value: {}", partition, _partitionColumn,
-              stringValue);
-        }
-      }
-    }
-
-    if (isDedupEnabled()) {
-      DedupRecordInfo dedupRecordInfo = getDedupRecordInfo(row);
-      if 
(_partitionDedupMetadataManager.checkRecordPresentOrUpdate(dedupRecordInfo, 
this)) {
-        if (_serverMetrics != null) {
-          _serverMetrics.addMeteredTableValue(_realtimeTableName, 
ServerMeter.REALTIME_DEDUP_DROPPED, 1);
-        }
-        updateIndexedAndIngestionTime(metadata);
-        return true;
+        mismatchedPartitionIndexContainer = indexContainer;
+        mismatchedPartitionValue = stringValue;
+        mismatchedPartition = partition;
       }
     }
 
-    // Validate the length of each multi-value to ensure it can be properly 
stored in the underlying forward index.
-    // If the length of any MV column exceeds the capacity of a chunk in the 
forward index, an exception is thrown.
-    // If an exception is not thrown, it leads to a mismatch in the number of 
values in the MV column compared to
-    // other columns when sealing the segment (due to the overflow), causing 
the sealing process to fail.
-    // NOTE: We must do this before we index a single column to avoid 
partially indexing the row
-    validateLengthOfMVColumns(row);
-
-    boolean canTakeMore;
     int numDocsIndexed = _numDocsIndexed;
     if (isUpsertEnabled()) {
+      // Validate the incoming row before partial-upsert strategies can copy 
or expand oversized MV values.
+      validateLengthOfMVColumns(row);
       RecordInfo recordInfo = getRecordInfo(row, numDocsIndexed);
       GenericRow updatedRow = 
_partitionUpsertMetadataManager.updateRecord(row, recordInfo);
+      if (_isPartialUpsert) {
+        // Strategies such as APPEND and UNION can produce a merged row that 
is larger than the incoming row.
+        validateLengthOfMVColumns(updatedRow);
+      }
+      trackMismatchedPartition(mismatchedPartitionIndexContainer, 
mismatchedPartition, mismatchedPartitionValue);
+
+      boolean canTakeMore;
       // NOTE: out-of-order records can not be dropped or marked when 
consistent upsert view is enabled.
       // Since Indexing the record and updation of _numDocsIndexed counter 
happens before updating the upsert
       // metadata, we wouldn't be able to actually drop or mark those records 
as dropped. This order is important for
@@ -695,31 +703,58 @@ public class MutableSegmentImpl implements MutableSegment 
{
         canTakeMore = numDocsIndexed < _capacity;
         _numDocsIndexed = numDocsIndexed;
       }
-    } else {
-      // Update dictionary first
-      updateDictionary(row);
-
-      // If metrics aggregation is enabled and if the dimension values were 
already seen, this will return existing
-      // docId, else this will return a new docId.
-      int docId = getOrCreateDocId();
-
-      if (docId == numDocsIndexed) {
-        // New row
-        addNewRow(numDocsIndexed, row);
-        // Update number of documents indexed at last to make the latest row 
queryable
-        canTakeMore = numDocsIndexed++ < _capacity;
-      } else {
-        assert isAggregateMetricsEnabled();
-        aggregateMetrics(row, docId);
-        canTakeMore = true;
+      updateIndexedAndIngestionTime(metadata);
+      return canTakeMore;
+    }
+
+    // Validate before dedup or partition tracking so a rejected row cannot 
leave metadata state behind.
+    validateLengthOfMVColumns(row);
+    trackMismatchedPartition(mismatchedPartitionIndexContainer, 
mismatchedPartition, mismatchedPartitionValue);
+
+    if (isDedupEnabled()) {
+      DedupRecordInfo dedupRecordInfo = getDedupRecordInfo(row);
+      if 
(_partitionDedupMetadataManager.checkRecordPresentOrUpdate(dedupRecordInfo, 
this)) {
+        if (_serverMetrics != null) {
+          _serverMetrics.addMeteredTableValue(_realtimeTableName, 
ServerMeter.REALTIME_DEDUP_DROPPED, 1);
+        }
+        updateIndexedAndIngestionTime(metadata);
+        return true;
       }
-      _numDocsIndexed = numDocsIndexed;
     }
 
+    // Update dictionary first
+    updateDictionary(row);
+
+    // If metrics aggregation is enabled and if the dimension values were 
already seen, this will return existing
+    // docId, else this will return a new docId.
+    int docId = getOrCreateDocId();
+
+    boolean canTakeMore;
+    if (docId == numDocsIndexed) {
+      // New row
+      addNewRow(numDocsIndexed, row);
+      // Update number of documents indexed at last to make the latest row 
queryable
+      canTakeMore = numDocsIndexed++ < _capacity;
+    } else {
+      assert isAggregateMetricsEnabled();
+      aggregateMetrics(row, docId);
+      canTakeMore = true;
+    }
+    _numDocsIndexed = numDocsIndexed;
+
     updateIndexedAndIngestionTime(metadata);
     return canTakeMore;
   }
 
+  private void trackMismatchedPartition(@Nullable IndexContainer 
indexContainer, int partition,
+      @Nullable String partitionValue) {
+    if (indexContainer != null && indexContainer._partitions.add(partition)) {
+      // for every partition other than mainPartitionId, log a warning once
+      _logger.warn("Found new partition: {} from partition column: {}, value: 
{}", partition, _partitionColumn,
+          partitionValue);
+    }
+  }
+
   private void updateIndexedAndIngestionTime(@Nullable StreamMessageMetadata 
metadata) {
     _lastIndexedTimeMs = System.currentTimeMillis();
     if (metadata != null) {
@@ -790,27 +825,20 @@ public class MutableSegmentImpl implements MutableSegment 
{
   }
 
   /// @param row
-  /// @throws UnsupportedOperationException if the length of an MV column 
would exceed the
-  /// capacity of a chunk in the ForwardIndex
+  /// @throws UnsupportedOperationException if the length of an MV column 
exceeds the maximum number of values allowed
+  /// in a single row of the forward index
   private void validateLengthOfMVColumns(GenericRow row)
       throws UnsupportedOperationException {
-    for (Map.Entry<String, IndexContainer> entry : 
_indexContainerMap.entrySet()) {
-      IndexContainer indexContainer = entry.getValue();
-      FieldSpec fieldSpec = indexContainer._fieldSpec;
-      MutableIndex forwardIndex = 
indexContainer._mutableIndexes.get(StandardIndexes.forward());
-      if (fieldSpec.isSingleValueField() || !(forwardIndex instanceof 
FixedByteMVMutableForwardIndex)) {
+    for (int i = 0; i < _multiValueRowLimits.length; i++) {
+      MultiValueRowLimit rowLimit = _multiValueRowLimits[i];
+      Object value = row.getValue(rowLimit._column);
+      if (value == null) {
         continue;
       }
-
-      Object[] values = (Object[]) row.getValue(entry.getKey());
-      // Note that max chunk capacity is derived from 
"FixedByteMVMutableForwardIndex._maxNumberOfMultiValuesPerRow"
-      // which is set to "1000" in 
"ForwardIndexType.MAX_MULTI_VALUES_PER_ROW". If the number of values in the
-      // multi-value entry that we are attempting to ingest is greater than 
the maximum accepted value, we throw an
-      // UnsupportedOperationException.
-      int maxChunkCapacity = ((FixedByteMVMutableForwardIndex) 
forwardIndex).getMaxChunkCapacity();
-      if (values.length > maxChunkCapacity) {
-        throw new UnsupportedOperationException(
-            "Length of MV column " + entry.getKey() + " is longer than 
ForwardIndex's capacity per chunk.");
+      Object[] values = (Object[]) value;
+      if (values.length > rowLimit._maxNumberOfMultiValuesPerRow) {
+        throw new UnsupportedOperationException("MV column '" + 
rowLimit._column + "' has " + values.length
+            + " values, exceeding the maximum of " + 
rowLimit._maxNumberOfMultiValuesPerRow + " values per row.");
       }
     }
   }
@@ -970,6 +998,9 @@ public class MutableSegmentImpl implements MutableSegment {
             MutableIndex mutableIndex = indexEntry.getValue();
             mutableIndex.add(values, dictIds, docId);
             updateIndexCapacityThresholdBreached(mutableIndex, 
indexEntry.getKey(), column);
+          } catch (IllegalArgumentException e) {
+            // Row-limit violations must fail the document. Other index errors 
stay fail-soft (#16316).
+            throw e;
           } catch (Exception e) {
             recordIndexingError(indexEntry.getKey(), e);
           }
@@ -1662,6 +1693,17 @@ public class MutableSegmentImpl implements 
MutableSegment {
     }
   }
 
+  /// Immutable fixed-byte MV column descriptor used by the per-record length 
validation hot path.
+  private static final class MultiValueRowLimit {
+    private final String _column;
+    private final int _maxNumberOfMultiValuesPerRow;
+
+    private MultiValueRowLimit(String column, int 
maxNumberOfMultiValuesPerRow) {
+      _column = column;
+      _maxNumberOfMultiValuesPerRow = maxNumberOfMultiValuesPerRow;
+    }
+  }
+
   private class IndexContainer implements Closeable {
     final FieldSpec _fieldSpec;
     final PartitionFunction _partitionFunction;
diff --git 
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/realtime/impl/forward/FixedByteMVMutableForwardIndex.java
 
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/realtime/impl/forward/FixedByteMVMutableForwardIndex.java
index 6fcac48ab2c..bf91fb2b06a 100644
--- 
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/realtime/impl/forward/FixedByteMVMutableForwardIndex.java
+++ 
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/realtime/impl/forward/FixedByteMVMutableForwardIndex.java
@@ -18,6 +18,7 @@
  */
 package org.apache.pinot.segment.local.realtime.impl.forward;
 
+import com.google.common.base.Preconditions;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
@@ -207,7 +208,11 @@ public class FixedByteMVMutableForwardIndex implements 
MutableForwardIndex {
   }
 
   private int updateHeader(int row, int numValues) {
-    assert (numValues <= _maxNumberOfMultiValuesPerRow);
+    if (numValues > _maxNumberOfMultiValuesPerRow) {
+      Preconditions.checkArgument(numValues <= _maxNumberOfMultiValuesPerRow,
+          "Row %s has %s multi-values, exceeding the maximum of %s", row, 
numValues,
+          _maxNumberOfMultiValuesPerRow);
+    }
     _numValues += numValues;
     int newStartIndex = _prevRowStartIndex + _prevRowLength;
     if (newStartIndex + numValues > _currentCapacity) {
@@ -222,6 +227,11 @@ public class FixedByteMVMutableForwardIndex implements 
MutableForwardIndex {
     return newStartIndex;
   }
 
+  /// Returns the maximum number of values allowed in a single row.
+  public int getMaxNumberOfMultiValuesPerRow() {
+    return _maxNumberOfMultiValuesPerRow;
+  }
+
   public int getMaxChunkCapacity() {
     // The incremental capacity will be >= the initial capacity and (the way 
the code is currently written) will be
     // the largest the buffer could ever get.
diff --git 
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/forward/ForwardIndexType.java
 
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/forward/ForwardIndexType.java
index 7f4ae0a210b..33089d6ec06 100644
--- 
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/forward/ForwardIndexType.java
+++ 
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/forward/ForwardIndexType.java
@@ -73,7 +73,7 @@ public class ForwardIndexType extends 
AbstractIndexType<ForwardIndexConfig, Forw
 
   public static final String INDEX_DISPLAY_NAME = "forward";
   // For multi-valued column, forward-index.
-  // Maximum number of multi-values per row. We assert on this.
+  // Default maximum number of multi-values per row. Some indexes, such as 
vectors, configure a different row limit.
   public static final int MAX_MULTI_VALUES_PER_ROW = 1000;
   private static final int 
NODICT_VARIABLE_WIDTH_ESTIMATED_AVERAGE_VALUE_LENGTH_DEFAULT = 100;
   private static final int 
NODICT_VARIABLE_WIDTH_ESTIMATED_NUMBER_OF_VALUES_DEFAULT = 100_000;
@@ -368,6 +368,8 @@ public class ForwardIndexType extends 
AbstractIndexType<ForwardIndexConfig, Forw
     FieldSpec.DataType storedType = dataType.getStoredType();
     int fixedLengthBytes = context.getFixedLengthBytes();
     boolean isSingleValue = context.getFieldSpec().isSingleValueField();
+    int maxNumMultiValuesPerRow = context.getMaxNumMultiValuesPerRowOverride() 
> 0
+        ? context.getMaxNumMultiValuesPerRowOverride() : 
MAX_MULTI_VALUES_PER_ROW;
     if (!context.hasDictionary()) {
       if (isSingleValue) {
         String allocationContext =
@@ -409,7 +411,7 @@ public class ForwardIndexType extends 
AbstractIndexType<ForwardIndexConfig, Forw
             IndexUtil.buildAllocationContext(context.getSegmentName(), 
context.getFieldSpec().getName(),
                 V1Constants.Indexes.RAW_MV_FORWARD_INDEX_FILE_EXTENSION);
         // TODO: Start with a smaller capacity on 
FixedByteMVForwardIndexReaderWriter and let it expand
-        return new FixedByteMVMutableForwardIndex(MAX_MULTI_VALUES_PER_ROW, 
context.getAvgNumMultiValues(),
+        return new FixedByteMVMutableForwardIndex(maxNumMultiValuesPerRow, 
context.getAvgNumMultiValues(),
             context.getCapacity(), dataType.size(), 
context.getMemoryManager(), allocationContext, false, storedType,
             dataType);
       }
@@ -423,7 +425,7 @@ public class ForwardIndexType extends 
AbstractIndexType<ForwardIndexConfig, Forw
         String allocationContext = 
IndexUtil.buildAllocationContext(segmentName, column,
             V1Constants.Indexes.UNSORTED_MV_FORWARD_INDEX_FILE_EXTENSION);
         // TODO: Start with a smaller capacity on 
FixedByteMVForwardIndexReaderWriter and let it expand
-        return new FixedByteMVMutableForwardIndex(MAX_MULTI_VALUES_PER_ROW, 
context.getAvgNumMultiValues(),
+        return new FixedByteMVMutableForwardIndex(maxNumMultiValuesPerRow, 
context.getAvgNumMultiValues(),
             context.getCapacity(), Integer.BYTES, context.getMemoryManager(), 
allocationContext, true,
             FieldSpec.DataType.INT);
       }
diff --git 
a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/indexsegment/mutable/MutableSegmentImplMVLengthValidationTest.java
 
b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/indexsegment/mutable/MutableSegmentImplMVLengthValidationTest.java
new file mode 100644
index 00000000000..9af323b19f3
--- /dev/null
+++ 
b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/indexsegment/mutable/MutableSegmentImplMVLengthValidationTest.java
@@ -0,0 +1,374 @@
+/**
+ * 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.pinot.segment.local.indexsegment.mutable;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.pinot.common.partition.function.ModuloPartitionFunction;
+import org.apache.pinot.segment.local.PinotBuffersAfterClassCheckRule;
+import org.apache.pinot.segment.local.data.manager.TableDataManager;
+import org.apache.pinot.segment.local.dedup.DedupContext;
+import org.apache.pinot.segment.local.dedup.DedupRecordInfo;
+import org.apache.pinot.segment.local.dedup.PartitionDedupMetadataManager;
+import 
org.apache.pinot.segment.local.realtime.impl.forward.FixedByteMVMutableForwardIndex;
+import 
org.apache.pinot.segment.local.realtime.impl.invertedindex.RealtimeLuceneTextIndexSearcherPool;
+import org.apache.pinot.segment.local.upsert.PartitionUpsertMetadataManager;
+import org.apache.pinot.segment.local.upsert.RecordInfo;
+import org.apache.pinot.segment.local.upsert.TableUpsertMetadataManager;
+import org.apache.pinot.segment.local.upsert.TableUpsertMetadataManagerFactory;
+import org.apache.pinot.segment.spi.datasource.DataSource;
+import org.apache.pinot.segment.spi.index.creator.VectorIndexConfig;
+import org.apache.pinot.segment.spi.index.reader.Dictionary;
+import org.apache.pinot.spi.config.table.TableConfig;
+import org.apache.pinot.spi.config.table.TableType;
+import org.apache.pinot.spi.config.table.UpsertConfig;
+import org.apache.pinot.spi.data.FieldSpec;
+import org.apache.pinot.spi.data.Schema;
+import org.apache.pinot.spi.data.readers.GenericRow;
+import org.apache.pinot.spi.data.readers.PrimaryKey;
+import org.apache.pinot.spi.env.PinotConfiguration;
+import org.apache.pinot.spi.utils.builder.TableConfigBuilder;
+import org.roaringbitmap.buffer.ImmutableRoaringBitmap;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+
+/// Verifies mutable-segment enforcement of the fixed-byte multi-value row 
limit. Each test owns its mutable segment,
+/// so no state is shared between test invocations.
+public class MutableSegmentImplMVLengthValidationTest implements 
PinotBuffersAfterClassCheckRule {
+  private static final String PARTITION_COLUMN = "partitionColumn";
+  private static final String PRIMARY_KEY_COLUMN = "primaryKey";
+  private static final String COMPARISON_COLUMN = "comparisonColumn";
+  private static final String MV_COLUMN = "mvColumn";
+  private static final int MAX_MULTI_VALUES_PER_ROW = 1000;
+  private static final int SMALL_VECTOR_DIMENSION = 768;
+  private static final int VECTOR_DIMENSION = 1536;
+
+  @BeforeClass
+  public void setUpSearcherPool() {
+    RealtimeLuceneTextIndexSearcherPool.init(1);
+  }
+
+  @Test
+  public void testRejectOversizedMultiValueRowBeforeWrites()
+      throws Exception {
+    Schema schema = new 
Schema.SchemaBuilder().setSchemaName("mvLengthValidation")
+        .addMultiValueDimension(MV_COLUMN, FieldSpec.DataType.INT)
+        .build();
+    MutableSegmentImpl mutableSegment = 
MutableSegmentImplTestUtils.createMutableSegmentImpl(schema);
+    try {
+      Object[] firstValues = createValues(3, 0);
+      mutableSegment.index(createRow(firstValues), null);
+
+      DataSource dataSource = mutableSegment.getDataSource(MV_COLUMN);
+      FixedByteMVMutableForwardIndex forwardIndex =
+          (FixedByteMVMutableForwardIndex) dataSource.getForwardIndex();
+      Assert.assertTrue(forwardIndex.getMaxChunkCapacity() > 
MAX_MULTI_VALUES_PER_ROW + 1);
+
+      UnsupportedOperationException exception = 
Assert.expectThrows(UnsupportedOperationException.class,
+          () -> 
mutableSegment.index(createRow(createValues(MAX_MULTI_VALUES_PER_ROW + 1, 10)), 
null));
+      Assert.assertTrue(exception.getMessage().contains(MV_COLUMN));
+      
Assert.assertTrue(exception.getMessage().contains(Integer.toString(MAX_MULTI_VALUES_PER_ROW
 + 1)));
+      Assert.assertTrue(exception.getMessage().contains("exceeding the maximum 
of " + MAX_MULTI_VALUES_PER_ROW),
+          exception.getMessage());
+      Assert.assertEquals(mutableSegment.getNumDocsIndexed(), 1);
+      assertValues(dataSource, forwardIndex, 0, firstValues);
+
+      Object[] maxLengthValues = createValues(MAX_MULTI_VALUES_PER_ROW, 10000);
+      mutableSegment.index(createRow(maxLengthValues), null);
+      Assert.assertEquals(mutableSegment.getNumDocsIndexed(), 2);
+      assertValues(dataSource, forwardIndex, 1, maxLengthValues);
+    } finally {
+      mutableSegment.destroy();
+    }
+  }
+
+  @Test
+  public void testAcceptVectorDimensionAboveDefaultMultiValueLimit()
+      throws Exception {
+    Schema schema = new 
Schema.SchemaBuilder().setSchemaName("vectorMvLengthValidation")
+        .addMultiValueDimension(MV_COLUMN, FieldSpec.DataType.FLOAT)
+        .build();
+    VectorIndexConfig vectorIndexConfig = new VectorIndexConfig(false, "HNSW", 
VECTOR_DIMENSION, 1,
+        VectorIndexConfig.VectorDistanceFunction.COSINE,
+        Map.of("vectorIndexType", "HNSW", "vectorDimension", 
Integer.toString(VECTOR_DIMENSION), "commitDocs", "1"));
+    MutableSegmentImpl mutableSegment = 
MutableSegmentImplTestUtils.createMutableSegmentImplWithVectorIndexConfigs(
+        schema, Set.of(MV_COLUMN), Set.of(), Set.of(), Map.of(MV_COLUMN, 
vectorIndexConfig), null);
+    try {
+      Object[] vector = createFloatValues(VECTOR_DIMENSION);
+      mutableSegment.index(createRow(vector), null);
+
+      DataSource dataSource = mutableSegment.getDataSource(MV_COLUMN);
+      FixedByteMVMutableForwardIndex forwardIndex =
+          (FixedByteMVMutableForwardIndex) dataSource.getForwardIndex();
+      float[] queryVector = toPrimitiveFloatArray(vector);
+      Assert.assertEquals(forwardIndex.getMaxNumberOfMultiValuesPerRow(), 
VECTOR_DIMENSION);
+      Assert.assertEquals(forwardIndex.getFloatMV(0), queryVector);
+      Assert.assertNotNull(dataSource.getVectorIndex());
+      Assert.assertEquals(dataSource.getVectorIndex().getDocIds(queryVector, 
1).toArray(), new int[]{0});
+      Assert.assertEquals(mutableSegment.getNumDocsIndexed(), 1);
+    } finally {
+      mutableSegment.destroy();
+    }
+  }
+
+  @Test
+  public void testRejectVectorAboveConfiguredDimensionBeforeWrites()
+      throws Exception {
+    Schema schema = new 
Schema.SchemaBuilder().setSchemaName("smallVectorMvLengthValidation")
+        .addMultiValueDimension(MV_COLUMN, FieldSpec.DataType.FLOAT)
+        .build();
+    VectorIndexConfig vectorIndexConfig = new VectorIndexConfig(false, "HNSW", 
SMALL_VECTOR_DIMENSION, 1,
+        VectorIndexConfig.VectorDistanceFunction.COSINE,
+        Map.of("vectorIndexType", "HNSW", "vectorDimension", 
Integer.toString(SMALL_VECTOR_DIMENSION),
+            "commitDocs", "1"));
+    MutableSegmentImpl mutableSegment = 
MutableSegmentImplTestUtils.createMutableSegmentImplWithVectorIndexConfigs(
+        schema, Set.of(MV_COLUMN), Set.of(), Set.of(), Map.of(MV_COLUMN, 
vectorIndexConfig), null);
+    try {
+      Assert.expectThrows(UnsupportedOperationException.class,
+          () -> 
mutableSegment.index(createRow(createFloatValues(SMALL_VECTOR_DIMENSION + 1)), 
null));
+
+      DataSource dataSource = mutableSegment.getDataSource(MV_COLUMN);
+      FixedByteMVMutableForwardIndex forwardIndex =
+          (FixedByteMVMutableForwardIndex) dataSource.getForwardIndex();
+      Assert.assertEquals(forwardIndex.getMaxNumberOfMultiValuesPerRow(), 
SMALL_VECTOR_DIMENSION);
+      Assert.assertEquals(dataSource.getDataSourceMetadata().getNumValues(), 
0);
+      Assert.assertTrue(dataSource.getVectorIndex().getDocIds(new 
float[SMALL_VECTOR_DIMENSION], 1).isEmpty());
+      Assert.assertEquals(mutableSegment.getNumDocsIndexed(), 0);
+    } finally {
+      mutableSegment.destroy();
+    }
+  }
+
+  @Test
+  public void testRejectOversizedMultiValueRowBeforeDedupUpdate()
+      throws Exception {
+    Schema schema = new 
Schema.SchemaBuilder().setSchemaName("dedupMvLengthValidation")
+        .addSingleValueDimension(PRIMARY_KEY_COLUMN, FieldSpec.DataType.STRING)
+        .addMultiValueDimension(MV_COLUMN, FieldSpec.DataType.INT)
+        .setPrimaryKeyColumns(List.of(PRIMARY_KEY_COLUMN))
+        .build();
+    PartitionDedupMetadataManager dedupMetadataManager = 
mock(PartitionDedupMetadataManager.class);
+    
when(dedupMetadataManager.getContext()).thenReturn(mock(DedupContext.class));
+    Set<PrimaryKey> seenPrimaryKeys = new HashSet<>();
+    
when(dedupMetadataManager.checkRecordPresentOrUpdate(any(DedupRecordInfo.class),
 any()))
+        .thenAnswer(invocation -> {
+          DedupRecordInfo recordInfo = invocation.getArgument(0);
+          return !seenPrimaryKeys.add(recordInfo.getPrimaryKey());
+        });
+
+    MutableSegmentImpl mutableSegment =
+        MutableSegmentImplTestUtils.createMutableSegmentImpl(schema, false, 
null, null, dedupMetadataManager);
+    try {
+      String primaryKey = "same-key";
+      Assert.expectThrows(UnsupportedOperationException.class, () -> 
mutableSegment.index(
+          createDedupRow(primaryKey, createValues(MAX_MULTI_VALUES_PER_ROW + 
1, 0)), null));
+      Assert.assertTrue(seenPrimaryKeys.isEmpty());
+      Assert.assertEquals(mutableSegment.getNumDocsIndexed(), 0);
+
+      Object[] validValues = createValues(3, 10000);
+      mutableSegment.index(createDedupRow(primaryKey, validValues), null);
+      Assert.assertEquals(seenPrimaryKeys.size(), 1);
+      Assert.assertEquals(mutableSegment.getNumDocsIndexed(), 1);
+
+      DataSource dataSource = mutableSegment.getDataSource(MV_COLUMN);
+      assertValues(dataSource, (FixedByteMVMutableForwardIndex) 
dataSource.getForwardIndex(), 0, validValues);
+    } finally {
+      mutableSegment.destroy();
+    }
+  }
+
+  @Test(dataProvider = "collectionMergeStrategies")
+  public void 
testRejectOversizedMultiValueRowProducedByPartialUpsert(UpsertConfig.Strategy 
strategy)
+      throws Exception {
+    Schema schema = new 
Schema.SchemaBuilder().setSchemaName("partialUpsertMvLengthValidation")
+        .addSingleValueDimension(PRIMARY_KEY_COLUMN, FieldSpec.DataType.STRING)
+        .addMultiValueDimension(MV_COLUMN, FieldSpec.DataType.INT)
+        .addDateTime(COMPARISON_COLUMN, FieldSpec.DataType.LONG, 
"1:MILLISECONDS:EPOCH", "1:MILLISECONDS")
+        .setPrimaryKeyColumns(List.of(PRIMARY_KEY_COLUMN))
+        .build();
+    UpsertConfig upsertConfig = new UpsertConfig(UpsertConfig.Mode.PARTIAL);
+    upsertConfig.setComparisonColumns(List.of(COMPARISON_COLUMN));
+    upsertConfig.setPartialUpsertStrategies(Map.of(MV_COLUMN, strategy));
+    TableConfig tableConfig = new TableConfigBuilder(TableType.REALTIME)
+        .setTableName("partialUpsertMvLengthValidation")
+        .setTimeColumnName(COMPARISON_COLUMN)
+        .setUpsertConfig(upsertConfig)
+        .setNullHandlingEnabled(true)
+        .build();
+    TableUpsertMetadataManager tableUpsertMetadataManager = 
TableUpsertMetadataManagerFactory.create(
+        new PinotConfiguration(), tableConfig, schema, 
mock(TableDataManager.class), null);
+    PartitionUpsertMetadataManager upsertMetadataManager =
+        spy(tableUpsertMetadataManager.getOrCreatePartitionManager(0));
+    MutableSegmentImpl mutableSegment = 
MutableSegmentImplTestUtils.createMutableSegmentImpl(schema, true,
+        COMPARISON_COLUMN, upsertMetadataManager, null);
+    try {
+      String primaryKey = "same-key";
+      Assert.expectThrows(UnsupportedOperationException.class,
+          () -> mutableSegment.index(
+              createUpsertRow(primaryKey, 0L, 
createValues(MAX_MULTI_VALUES_PER_ROW + 1, 0)), null));
+      Assert.assertEquals(mutableSegment.getNumDocsIndexed(), 0);
+      verify(upsertMetadataManager, 
times(0)).updateRecord(any(GenericRow.class), any(RecordInfo.class));
+      verify(upsertMetadataManager, times(0)).addRecord(eq(mutableSegment), 
any(RecordInfo.class));
+
+      Object[] firstValues = createValues(600, 0);
+      mutableSegment.index(createUpsertRow(primaryKey, 1L, firstValues), null);
+
+      DataSource dataSource = mutableSegment.getDataSource(MV_COLUMN);
+      FixedByteMVMutableForwardIndex forwardIndex =
+          (FixedByteMVMutableForwardIndex) dataSource.getForwardIndex();
+      int cardinalityBeforeRejection = dataSource.getDictionary().length();
+      int numValuesBeforeRejection = 
dataSource.getDataSourceMetadata().getNumValues();
+      ImmutableRoaringBitmap validDocIds = 
mutableSegment.getValidDocIds().getMutableRoaringBitmap();
+      Assert.assertEquals(validDocIds.toArray(), new int[]{0});
+      verify(upsertMetadataManager, times(1)).addRecord(eq(mutableSegment), 
any(RecordInfo.class));
+
+      Assert.expectThrows(UnsupportedOperationException.class,
+          () -> mutableSegment.index(createUpsertRow(primaryKey, 2L, 
createValues(500, 600)), null));
+
+      DataSource dataSourceAfterRejection = 
mutableSegment.getDataSource(MV_COLUMN);
+      Assert.assertEquals(mutableSegment.getNumDocsIndexed(), 1);
+      Assert.assertEquals(dataSourceAfterRejection.getDictionary().length(), 
cardinalityBeforeRejection);
+      
Assert.assertEquals(dataSourceAfterRejection.getDataSourceMetadata().getNumValues(),
 numValuesBeforeRejection);
+      
Assert.assertEquals(mutableSegment.getValidDocIds().getMutableRoaringBitmap().toArray(),
 new int[]{0});
+      verify(upsertMetadataManager, times(1)).addRecord(eq(mutableSegment), 
any(RecordInfo.class));
+      assertValues(dataSourceAfterRejection, forwardIndex, 0, firstValues);
+
+      Object[] validUpdate = createValues(3, 600);
+      mutableSegment.index(createUpsertRow(primaryKey, 3L, validUpdate), null);
+      Assert.assertEquals(mutableSegment.getNumDocsIndexed(), 2);
+      
Assert.assertEquals(mutableSegment.getValidDocIds().getMutableRoaringBitmap().toArray(),
 new int[]{1});
+      verify(upsertMetadataManager, times(2)).addRecord(eq(mutableSegment), 
any(RecordInfo.class));
+      assertValues(mutableSegment.getDataSource(MV_COLUMN), forwardIndex, 1, 
createValues(603, 0));
+    } finally {
+      mutableSegment.destroy();
+      upsertMetadataManager.stop();
+      upsertMetadataManager.close();
+    }
+  }
+
+  @Test
+  public void testRejectOversizedMultiValueRowBeforePartitionTracking()
+      throws Exception {
+    Schema schema = new 
Schema.SchemaBuilder().setSchemaName("partitionMvLengthValidation")
+        .addSingleValueDimension(PARTITION_COLUMN, FieldSpec.DataType.INT)
+        .addMultiValueDimension(MV_COLUMN, FieldSpec.DataType.INT)
+        .build();
+    MutableSegmentImpl mutableSegment = 
MutableSegmentImplTestUtils.createMutableSegmentImpl(schema, PARTITION_COLUMN,
+        new ModuloPartitionFunction(4, null), 0, false);
+    try {
+      
Assert.assertEquals(mutableSegment.getDataSource(PARTITION_COLUMN).getDataSourceMetadata().getPartitions(),
+          Set.of(0));
+      Assert.expectThrows(UnsupportedOperationException.class,
+          () -> mutableSegment.index(createPartitionedRow(1, 
createValues(MAX_MULTI_VALUES_PER_ROW + 1, 0)), null));
+      Assert.assertEquals(mutableSegment.getNumDocsIndexed(), 0);
+      
Assert.assertEquals(mutableSegment.getDataSource(PARTITION_COLUMN).getDataSourceMetadata().getPartitions(),
+          Set.of(0));
+
+      Object[] validValues = createValues(3, 0);
+      mutableSegment.index(createPartitionedRow(1, validValues), null);
+      Assert.assertEquals(mutableSegment.getNumDocsIndexed(), 1);
+      
Assert.assertEquals(mutableSegment.getDataSource(PARTITION_COLUMN).getDataSourceMetadata().getPartitions(),
+          Set.of(0, 1));
+      DataSource dataSource = mutableSegment.getDataSource(MV_COLUMN);
+      assertValues(dataSource, (FixedByteMVMutableForwardIndex) 
dataSource.getForwardIndex(), 0, validValues);
+    } finally {
+      mutableSegment.destroy();
+    }
+  }
+
+  @DataProvider(name = "collectionMergeStrategies")
+  private static Object[][] collectionMergeStrategies() {
+    return new Object[][]{
+        {UpsertConfig.Strategy.APPEND},
+        {UpsertConfig.Strategy.UNION}
+    };
+  }
+
+  private static GenericRow createRow(Object[] values) {
+    GenericRow row = new GenericRow();
+    row.putValue(MV_COLUMN, values);
+    return row;
+  }
+
+  private static GenericRow createPartitionedRow(int partitionValue, Object[] 
values) {
+    GenericRow row = createRow(values);
+    row.putValue(PARTITION_COLUMN, partitionValue);
+    return row;
+  }
+
+  private static GenericRow createUpsertRow(String primaryKey, long 
comparisonValue, Object[] values) {
+    GenericRow row = createDedupRow(primaryKey, values);
+    row.putValue(COMPARISON_COLUMN, comparisonValue);
+    return row;
+  }
+
+  private static GenericRow createDedupRow(String primaryKey, Object[] values) 
{
+    GenericRow row = createRow(values);
+    row.putValue(PRIMARY_KEY_COLUMN, primaryKey);
+    return row;
+  }
+
+  private static Object[] createValues(int length, int offset) {
+    Object[] values = new Object[length];
+    for (int i = 0; i < length; i++) {
+      values[i] = offset + i;
+    }
+    return values;
+  }
+
+  private static Object[] createFloatValues(int length) {
+    Object[] values = new Object[length];
+    for (int i = 0; i < length; i++) {
+      values[i] = (float) i;
+    }
+    return values;
+  }
+
+  private static float[] toPrimitiveFloatArray(Object[] values) {
+    float[] result = new float[values.length];
+    for (int i = 0; i < values.length; i++) {
+      result[i] = (float) values[i];
+    }
+    return result;
+  }
+
+  private static void assertValues(DataSource dataSource, 
FixedByteMVMutableForwardIndex forwardIndex, int docId,
+      Object[] expectedValues) {
+    Dictionary dictionary = dataSource.getDictionary();
+    Assert.assertNotNull(dictionary);
+    int[] dictIds = forwardIndex.getDictIdMV(docId);
+    Assert.assertEquals(dictIds.length, expectedValues.length);
+    for (int i = 0; i < expectedValues.length; i++) {
+      Assert.assertEquals(dictionary.get(dictIds[i]), expectedValues[i]);
+    }
+  }
+}
diff --git 
a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/forward/mutable/FixedByteMVMutableForwardIndexTest.java
 
b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/forward/mutable/FixedByteMVMutableForwardIndexTest.java
index 993c9ee7c36..dabf1f95ace 100644
--- 
a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/forward/mutable/FixedByteMVMutableForwardIndexTest.java
+++ 
b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/forward/mutable/FixedByteMVMutableForwardIndexTest.java
@@ -71,6 +71,39 @@ public class FixedByteMVMutableForwardIndexTest implements 
PinotBuffersAfterClas
     }
   }
 
+  @Test
+  public void testRejectMultiValuesExceedingMaxPerRow()
+      throws Exception {
+    int maxNumberOfMultiValuesPerRow = 5;
+    FixedByteMVMutableForwardIndex readerWriter =
+        new FixedByteMVMutableForwardIndex(maxNumberOfMultiValuesPerRow, 2, 
10, Integer.BYTES, _memoryManager,
+            "RejectMultiValuesExceedingMaxPerRow", true, 
FieldSpec.DataType.INT);
+    try {
+      Assert.expectThrows(IllegalArgumentException.class,
+          () -> readerWriter.setIntMV(0, new int[maxNumberOfMultiValuesPerRow 
+ 1]));
+      Assert.assertEquals(getNumValues(readerWriter), 0);
+      Assert.assertEquals(readerWriter.getNumValuesMV(0), 0);
+    } finally {
+      readerWriter.close();
+    }
+  }
+
+  @Test
+  public void testAcceptMultiValuesAtMaxPerRow()
+      throws Exception {
+    int maxNumberOfMultiValuesPerRow = 5;
+    FixedByteMVMutableForwardIndex readerWriter =
+        new FixedByteMVMutableForwardIndex(maxNumberOfMultiValuesPerRow, 2, 
10, Integer.BYTES, _memoryManager,
+            "AcceptMultiValuesAtMaxPerRow", true, FieldSpec.DataType.INT);
+    try {
+      int[] values = new int[]{1, 2, 3, 4, 5};
+      readerWriter.setIntMV(0, values);
+      Assert.assertEquals(readerWriter.getIntMV(0), values);
+    } finally {
+      readerWriter.close();
+    }
+  }
+
   public void testIntArray(final long seed, boolean isDictionaryEncoded)
       throws IOException {
     FixedByteMVMutableForwardIndex readerWriter;
diff --git 
a/pinot-segment-spi/src/main/java/org/apache/pinot/segment/spi/index/mutable/provider/MutableIndexContext.java
 
b/pinot-segment-spi/src/main/java/org/apache/pinot/segment/spi/index/mutable/provider/MutableIndexContext.java
index 57d71aa2788..010baead912 100644
--- 
a/pinot-segment-spi/src/main/java/org/apache/pinot/segment/spi/index/mutable/provider/MutableIndexContext.java
+++ 
b/pinot-segment-spi/src/main/java/org/apache/pinot/segment/spi/index/mutable/provider/MutableIndexContext.java
@@ -33,6 +33,7 @@ public class MutableIndexContext {
   private final int _estimatedColSize;
   private final int _estimatedCardinality;
   private final int _avgNumMultiValues;
+  private final int _maxNumMultiValuesPerRowOverride;
   private final String _segmentName;
   private final PinotDataBufferMemoryManager _memoryManager;
   private final File _consumerDir;
@@ -40,6 +41,13 @@ public class MutableIndexContext {
   public MutableIndexContext(FieldSpec fieldSpec, int fixedLengthBytes, 
boolean hasDictionary, String segmentName,
       PinotDataBufferMemoryManager memoryManager, int capacity, boolean 
offHeap, int estimatedColSize,
       int estimatedCardinality, int avgNumMultiValues, File consumerDir) {
+    this(fieldSpec, fixedLengthBytes, hasDictionary, segmentName, 
memoryManager, capacity, offHeap, estimatedColSize,
+        estimatedCardinality, avgNumMultiValues, 0, consumerDir);
+  }
+
+  public MutableIndexContext(FieldSpec fieldSpec, int fixedLengthBytes, 
boolean hasDictionary, String segmentName,
+      PinotDataBufferMemoryManager memoryManager, int capacity, boolean 
offHeap, int estimatedColSize,
+      int estimatedCardinality, int avgNumMultiValues, int 
maxNumMultiValuesPerRowOverride, File consumerDir) {
     _fieldSpec = fieldSpec;
     _fixedLengthBytes = fixedLengthBytes;
     _hasDictionary = hasDictionary;
@@ -50,6 +58,7 @@ public class MutableIndexContext {
     _estimatedColSize = estimatedColSize;
     _estimatedCardinality = estimatedCardinality;
     _avgNumMultiValues = avgNumMultiValues;
+    _maxNumMultiValuesPerRowOverride = maxNumMultiValuesPerRowOverride;
     _consumerDir = consumerDir;
   }
 
@@ -93,6 +102,11 @@ public class MutableIndexContext {
     return _avgNumMultiValues;
   }
 
+  /// Returns the configured maximum number of values for one MV row, or 0 
when the index default should be used.
+  public int getMaxNumMultiValuesPerRowOverride() {
+    return _maxNumMultiValuesPerRowOverride;
+  }
+
   public File getConsumerDir() {
     return _consumerDir;
   }
@@ -112,6 +126,7 @@ public class MutableIndexContext {
     private int _estimatedColSize;
     private int _estimatedCardinality;
     private int _avgNumMultiValues;
+    private int _maxNumMultiValuesPerRowOverride;
     private File _consumerDir;
 
     public Builder withMemoryManager(PinotDataBufferMemoryManager 
memoryManager) {
@@ -159,6 +174,11 @@ public class MutableIndexContext {
       return this;
     }
 
+    public Builder withMaxNumMultiValuesPerRowOverride(int 
maxNumMultiValuesPerRowOverride) {
+      _maxNumMultiValuesPerRowOverride = maxNumMultiValuesPerRowOverride;
+      return this;
+    }
+
     public Builder withConsumerDir(File consumerDir) {
       _consumerDir = consumerDir;
       return this;
@@ -172,7 +192,7 @@ public class MutableIndexContext {
     public MutableIndexContext build() {
       return new MutableIndexContext(Objects.requireNonNull(_fieldSpec), 
_fixedLengthBytes, _hasDictionary,
           Objects.requireNonNull(_segmentName), 
Objects.requireNonNull(_memoryManager), _capacity, _offHeap,
-          _estimatedColSize, _estimatedCardinality, _avgNumMultiValues, 
_consumerDir);
+          _estimatedColSize, _estimatedCardinality, _avgNumMultiValues, 
_maxNumMultiValuesPerRowOverride, _consumerDir);
     }
   }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to