xiangfu0 commented on code in PR #19998:
URL: https://github.com/apache/pinot/pull/19998#discussion_r4429123752


##########
pinot-segment-spi/src/main/java/org/apache/pinot/segment/spi/index/metadata/MappedSegmentMetadataStore.java:
##########
@@ -0,0 +1,970 @@
+/**
+ * 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.spi.index.metadata;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.Interner;
+import com.google.common.collect.Interners;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.AtomicMoveNotSupportedException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.zip.CRC32C;
+import javax.annotation.Nullable;
+import org.apache.commons.configuration2.Configuration;
+import org.apache.commons.configuration2.PropertiesConfiguration;
+import org.apache.commons.configuration2.ex.ConfigurationException;
+import org.apache.pinot.segment.spi.ColumnMetadata;
+import org.apache.pinot.segment.spi.V1Constants;
+import org.apache.pinot.segment.spi.index.IndexService;
+import org.apache.pinot.segment.spi.index.IndexType;
+import org.apache.pinot.segment.spi.memory.PinotDataBuffer;
+import org.apache.pinot.segment.spi.partition.PartitionFunction;
+import org.apache.pinot.segment.spi.store.SegmentDirectoryPaths;
+import org.apache.pinot.segment.spi.utils.SegmentMetadataUtils;
+import org.apache.pinot.spi.config.table.FieldConfig.EncodingType;
+import org.apache.pinot.spi.data.ComplexFieldSpec;
+import org.apache.pinot.spi.data.FieldSpec;
+import org.apache.pinot.spi.data.FieldSpec.DataType;
+import org.apache.pinot.spi.env.CommonsConfigurationUtils;
+import org.apache.pinot.spi.utils.ByteArray;
+import org.apache.pinot.spi.utils.ColumnNameInterner;
+import org.apache.pinot.spi.utils.JsonUtils;
+import org.apache.pinot.spi.utils.ReadMode;
+
+/// Versioned, file-backed metadata for a sealed segment. The source 
`metadata.properties` remains the canonical
+/// representation for old readers and preprocessing.
+///
+/// The big-endian file starts with a 64-byte header containing the magic, 
format version, total length, source
+/// `metadata.properties` and `index_map` length/CRC fingerprints, registered 
index IDs, payload CRC,
+/// segment-property blob reference,
+/// column count, and record-table bounds. Sorted columns occupy fixed 
180-byte records, followed by variable blobs;
+/// each blob reference is an absolute 32-bit offset and length. Numeric 
column statistics are read directly from the
+/// backing buffer. The column layout retains names and reusable [FieldSpec] 
references on heap.
+///
+/// A stale or corrupt sidecar is rebuilt from the canonical source and 
replaced locally, using atomic rename where
+/// supported. Change `VERSION` whenever the encoding or the values derived 
from canonical metadata change;
+/// unsupported versions are rebuilt rather than migrated.
+final class MappedSegmentMetadataStore implements Closeable {
+  static final String FILE_NAME = "segment_metadata.bin";
+  private static final String TEMP_FILE_PREFIX = ".segment_metadata.";
+  private static final String TEMP_FILE_SUFFIX = ".tmp";
+
+  static boolean isSidecarFileName(String fileName) {
+    return FILE_NAME.equals(fileName) || isTempFileName(fileName);
+  }
+
+  static boolean isTempFileName(String fileName) {
+    return fileName.startsWith(TEMP_FILE_PREFIX) && 
fileName.endsWith(TEMP_FILE_SUFFIX);
+  }
+
+  private static final int MAGIC = 0x504d4431; // PMD1
+  private static final int VERSION = 1;
+  private static final int HEADER_SIZE = 64;
+  private static final int RECORD_SIZE = 180;
+
+  static final int NAME = 0;
+  static final int FIELD_SPEC = 8;
+  static final int TOTAL_DOCS = 16;
+  static final int CARDINALITY = 20;
+  static final int FLAGS = 24;
+  static final int ENCODING = 28;
+  static final int SHORTEST = 32;
+  static final int LONGEST = 36;
+  static final int TOTAL_ENTRIES = 40;
+  static final int MAX_MV = 44;
+  static final int MAX_ROW = 48;
+  static final int BITS = 52;
+  static final int MIN_TAG = 56;
+  static final int MIN_RAW = 60;
+  static final int MIN_BLOB = 68;
+  static final int MAX_TAG = 76;
+  static final int MAX_RAW = 80;
+  static final int MAX_BLOB = 88;
+  static final int PARTITION_FUNCTION = 96;
+  static final int PARTITIONS = 104;
+  static final int PARENT = 112;
+  static final int SPARSE_KEYS = 120;
+  static final int SPARSE_MULTI_VALUE_KEYS = 128;
+  static final int SPARSE_KEY_TYPES = 136;
+  static final int RAW_UNCOMPRESSED_SIZE = 144;
+  static final int DICTIONARY_UNCOMPRESSED_SIZE = 152;
+  static final int COMPRESSION_TYPE = 160;
+  static final int INDEXES = 168;
+  static final int INDEX_COUNT = 176;
+
+  static final int HAS_DICTIONARY = 1;
+  static final int SORTED = 1 << 1;
+  static final int NON_NULL = 1 << 2;
+  static final int INVALID_MIN_MAX = 1 << 3;
+  static final int ASCII = 1 << 4;
+  static final int AUTO_GENERATED = 1 << 5;
+
+  private static final int NO_VALUE = 0;
+  private static final int INT_VALUE = 1;
+  private static final int LONG_VALUE = 2;
+  private static final int FLOAT_VALUE = 3;
+  private static final int DOUBLE_VALUE = 4;
+  private static final int DECIMAL_VALUE = 5;
+  private static final int STRING_VALUE = 6;
+  private static final int BYTES_VALUE = 7;
+
+  private static final Interner<FieldSpec> FIELD_SPECS = 
Interners.newWeakInterner();
+  private static final Interner<ColumnLayout> COLUMN_LAYOUTS = 
Interners.newWeakInterner();
+
+  private final PinotDataBuffer _buffer;
+  private final ColumnLayout _columns;
+  private final int _tableOffset;
+  private final int _segmentConfigOffset;
+  private final int _segmentConfigLength;
+  private boolean _closed;
+
+  private MappedSegmentMetadataStore(PinotDataBuffer buffer, ColumnLayout 
columns, int tableOffset,
+      int segmentConfigOffset, int segmentConfigLength) {
+    _buffer = buffer;
+    _columns = columns;
+    _tableOffset = tableOffset;
+    _segmentConfigOffset = segmentConfigOffset;
+    _segmentConfigLength = segmentConfigLength;
+  }
+
+  static SegmentMetadataImpl.EagerSnapshot captureEagerSnapshot(File indexDir)

Review Comment:
   This is the right way to do the reuse I'd hesitated to suggest fully: 
parsing from the exact bytes you fingerprint makes the metadata↔fingerprint 
coupling hold by construction, and build() refusing the snapshot on any 
fingerprint or IndexService mismatch means a stale snapshot can never leak into 
a published sidecar. I also checked parse parity — fromInputStream(bytes) and 
the incumbent fromFile(file) configure identically (default delimiter, 
DefaultIOFactory), so the snapshot view is equivalent to a fresh file parse of 
the same bytes. One optional follow-up: reloadMetadata() could re-capture a 
snapshot instead of a plain parse, which would extend the single-parse property 
to post-preprocess rebuilds too — but that path is rarer and this is fine as is.



##########
pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/SegmentMetadataImplTest.java:
##########
@@ -169,6 +199,513 @@ public void testIndexSizesOnlyFromIndexDir()
     }
   }
 
+  @Test
+  public void testServingLoadFallsBackOrMappingIsDisabled()
+      throws Exception {
+    InstanceDataManagerConfig instanceConfig = 
mock(InstanceDataManagerConfig.class);
+    PinotConfiguration config = new PinotConfiguration();
+    when(instanceConfig.getConfig()).thenReturn(config);
+    config.setProperty(IndexLoadingConfig.MAPPED_SEGMENT_METADATA_ENABLED, 
true);
+
+    File sidecar = new File(_segmentDirectory, "segment_metadata.bin");
+    assertTrue(sidecar.mkdir());
+    Files.writeString(new File(sidecar, "blocker").toPath(), "keep directory 
non-empty");
+    try {
+      ImmutableSegment segment =
+          ImmutableSegmentLoader.load(_segmentDirectory, new 
IndexLoadingConfig(instanceConfig, null, null), false);
+      try {
+        assertFalse(((SegmentMetadataImpl) 
segment.getSegmentMetadata()).isMapped());
+        assertTrue(segment.getColumnNames().contains("daysSinceEpoch"));
+      } finally {
+        segment.destroy();
+      }
+    } finally {
+      FileUtils.deleteQuietly(sidecar);
+    }
+
+    config.setProperty(IndexLoadingConfig.MAPPED_SEGMENT_METADATA_ENABLED, 
false);
+    ImmutableSegment disabled =
+        ImmutableSegmentLoader.load(_segmentDirectory, new 
IndexLoadingConfig(instanceConfig, null, null), false);
+    try {
+      assertFalse(((SegmentMetadataImpl) 
disabled.getSegmentMetadata()).isMapped());
+      assertFalse(sidecar.exists());
+    } finally {
+      disabled.destroy();
+    }
+
+    ImmutableSegment reader = ImmutableSegmentLoader.load(_segmentDirectory, 
ReadMode.mmap);
+    try {
+      assertFalse(((SegmentMetadataImpl) 
reader.getSegmentMetadata()).isMapped());
+      assertFalse(sidecar.exists());
+    } finally {
+      reader.destroy();
+    }
+
+    config.setProperty(IndexLoadingConfig.MAPPED_SEGMENT_METADATA_ENABLED, 
true);
+    ImmutableSegment segment =
+        ImmutableSegmentLoader.load(_segmentDirectory, new 
IndexLoadingConfig(instanceConfig, null, null), false);
+    try {
+      assertTrue(((SegmentMetadataImpl) 
segment.getSegmentMetadata()).isMapped());
+    } finally {
+      segment.destroy();
+    }
+
+    IndexLoadingConfig heapConfig = new IndexLoadingConfig(instanceConfig, 
null, null);
+    heapConfig.setReadMode(ReadMode.heap);
+    long mappings = PinotDataBuffer.getMmapBufferCount();
+    ImmutableSegment heap = ImmutableSegmentLoader.load(_segmentDirectory, 
heapConfig, false);
+    try {
+      assertTrue(((SegmentMetadataImpl) heap.getSegmentMetadata()).isMapped());
+      assertEquals(PinotDataBuffer.getMmapBufferCount(), mappings);
+    } finally {
+      heap.destroy();
+    }
+  }
+
+  @Test
+  public void testServingLoadFallsBackWhenMetadataMappingFails()
+      throws Exception {
+    InstanceDataManagerConfig instanceConfig = 
mock(InstanceDataManagerConfig.class);
+    PinotConfiguration config = new PinotConfiguration();
+    when(instanceConfig.getConfig()).thenReturn(config);
+    config.setProperty(IndexLoadingConfig.MAPPED_SEGMENT_METADATA_ENABLED, 
true);
+    PinotBufferFactory delegate = PinotDataBuffer.getFactory();
+    PinotDataBuffer.useFactory(new PinotBufferFactory() {
+      @Override
+      public PinotDataBuffer allocateDirect(long size, ByteOrder byteOrder) {
+        return delegate.allocateDirect(size, byteOrder);
+      }
+
+      @Override
+      public PinotDataBuffer mapFile(File file, boolean readOnly, long offset, 
long size, ByteOrder byteOrder)
+          throws IOException {
+        if (file.getName().equals("segment_metadata.bin")) {
+          throw new RuntimeException("metadata mapping failed");
+        }
+        return delegate.mapFile(file, readOnly, offset, size, byteOrder);
+      }
+    });
+    try {
+      ImmutableSegment segment =
+          ImmutableSegmentLoader.load(_segmentDirectory, new 
IndexLoadingConfig(instanceConfig, null, null), false);
+      try {
+        assertFalse(((SegmentMetadataImpl) 
segment.getSegmentMetadata()).isMapped());
+        assertTrue(segment.getColumnNames().contains("daysSinceEpoch"));
+      } finally {
+        segment.destroy();
+      }
+    } finally {
+      PinotDataBuffer.useFactory(delegate);
+    }
+  }
+
+  @Test
+  public void testMappedColumnReadsAfterClose()
+      throws Exception {
+    for (ReadMode readMode : List.of(ReadMode.mmap, ReadMode.heap)) {
+      SegmentMetadataImpl mapped = 
SegmentMetadataImpl.loadMapped(_segmentDirectory, readMode);
+      ColumnMetadata column = mapped.getColumnMetadataFor("daysSinceEpoch");
+      mapped.close();
+      expectThrows(IllegalStateException.class, column::getCardinality);
+      expectThrows(IllegalStateException.class, column::getParentColumn);
+    }
+  }
+
+  @Test
+  public void testDetachedMappedMetadataPreservesServingColumnsAfterClose()
+      throws Exception {
+    new SegmentV1V2ToV3FormatConverter().convert(_segmentDirectory);
+    SegmentMetadataImpl detached;
+    String retainedColumn;
+    int retainedCardinality;
+    Map<IndexType<?, ?, ?>, Long> retainedIndexSizes;
+    long retainedCrc;
+    long retainedCreationTime;
+    String removedColumn;
+    String virtualColumn = "virtualMetadataTest";
+    File movedDirectory = new File(INDEX_DIR, _segmentDirectory.getName() + 
"-moved");
+    try {
+      try (SegmentMetadataImpl mapped = 
SegmentMetadataImpl.loadMapped(_segmentDirectory)) {
+        removedColumn = mapped.getPhysicalColumnNames().stream()
+            .filter(column -> 
!column.equals(mapped.getTimeColumn())).findFirst().orElseThrow();
+        retainedColumn = mapped.getPhysicalColumnNames().stream()
+            .filter(column -> 
!column.equals(removedColumn)).findFirst().orElseThrow();
+        ColumnMetadata retainedMetadata = 
mapped.getColumnMetadataFor(retainedColumn);
+        retainedCardinality = retainedMetadata.getCardinality();
+        retainedIndexSizes = ((MappedColumnMetadata) 
retainedMetadata).getIndexSizeMap();
+        assertFalse(retainedIndexSizes.isEmpty());
+        retainedCrc = mapped.getCrc();
+        retainedCreationTime = mapped.getIndexCreationTime();
+        mapped.removeColumn(removedColumn);
+        DimensionFieldSpec virtualField = new 
DimensionFieldSpec(virtualColumn, FieldSpec.DataType.STRING, true);
+        virtualField.setVirtualColumnProvider("test");
+        ColumnMetadataImpl virtualMetadata = new 
ColumnMetadataImpl.Builder().setFieldSpec(virtualField)
+            .setTotalDocs(mapped.getTotalDocs()).setCardinality(1).build();
+        mapped.addVirtualColumnMetadata(virtualColumn, virtualMetadata);
+        mapped.setZkCreationTime(42L);
+        mapped.setZkPushTime(43L);
+        Files.move(_segmentDirectory.toPath(), movedDirectory.toPath());
+        detached = mapped.detachedCopy();
+        assertNotSame(detached, mapped);
+      }
+      assertFalse(detached.isMapped());
+      assertFalse(detached.getAllColumns().contains(removedColumn));
+      assertNull(detached.getColumnMetadataFor(removedColumn));
+      assertTrue(detached.getAllColumns().contains(virtualColumn));
+      assertNotNull(detached.getSchema().getFieldSpecFor(virtualColumn));
+      
assertEquals(detached.getColumnMetadataFor(virtualColumn).getCardinality(), 1);
+      
assertEquals(detached.getColumnMetadataFor(retainedColumn).getCardinality(), 
retainedCardinality);
+      assertEquals(((ColumnMetadataImpl) 
detached.getColumnMetadataFor(retainedColumn)).getIndexSizeMap(),
+          retainedIndexSizes);
+      assertEquals(detached.getCrc(), retainedCrc);
+      assertEquals(detached.getIndexCreationTime(), retainedCreationTime);
+      assertEquals(detached.getZkCreationTime(), 42L);
+      assertEquals(detached.getZkPushTime(), 43L);
+    } finally {
+      if (movedDirectory.exists()) {
+        Files.move(movedDirectory.toPath(), _segmentDirectory.toPath());
+      }
+    }
+  }
+
+  @Test
+  public void testMappedMetadataRoundTripAndRebuild()
+      throws Exception {
+    new SegmentV1V2ToV3FormatConverter().convert(_segmentDirectory);
+    File properties = 
SegmentDirectoryPaths.findMetadataFile(_segmentDirectory);
+    Files.writeString(properties.toPath(), "\ncustom.escaped = 
alpha\\,beta\n", StandardCharsets.UTF_8,
+        StandardOpenOption.APPEND);
+    SegmentMetadataImpl eager = new SegmentMetadataImpl(_segmentDirectory);
+    long mappings = PinotDataBuffer.getMmapBufferCount();
+    File sidecar = new File(_segmentDirectory, "segment_metadata.bin");
+    try (SegmentMetadataImpl mapped = 
SegmentMetadataImpl.loadMapped(_segmentDirectory)) {
+      assertTrue(mapped.isMapped());
+      assertTrue(sidecar.isFile());
+      assertEquals(mapped.getName(), eager.getName());
+      assertEquals(mapped.getTotalDocs(), eager.getTotalDocs());
+      assertEquals(mapped.getVersion(), eager.getVersion());
+      assertEquals(mapped.getTimeColumn(), eager.getTimeColumn());
+      assertEquals(mapped.getCustomMap(), eager.getCustomMap());
+      assertEquals(mapped.getPhysicalColumnNames(), 
eager.getSchema().getPhysicalColumnNames());
+      assertEquals(mapped.getAllColumns(), 
mapped.getSchema().getColumnNames());
+      assertEquals(mapped.toJson(null), eager.toJson(null));
+      for (String column : mapped.getPhysicalColumnNames()) {
+        ColumnMetadata expected = eager.getColumnMetadataFor(column);
+        ColumnMetadata actual = mapped.getColumnMetadataFor(column);
+        assertTrue(actual instanceof MappedColumnMetadata);
+        assertSame(mapped.getColumnMetadataFor(column), actual);
+        assertEquals(actual.getFieldSpec(), expected.getFieldSpec(), column);
+        assertEquals(actual.getCardinality(), expected.getCardinality(), 
column);
+        assertEquals(actual.getMinValue(), expected.getMinValue(), column);
+        assertEquals(actual.getMaxValue(), expected.getMaxValue(), column);
+        assertEquals(actual.getNumIndexes(), expected.getNumIndexes(), column);
+        assertEquals(((MappedColumnMetadata) actual).getIndexSizeMap(),
+            ((ColumnMetadataImpl) expected).getIndexSizeMap(), column);
+      }
+      Set<String> columns = mapped.getAllColumns();
+      assertSame(mapped.getAllColumns(), columns);
+      String physicalColumn = mapped.getPhysicalColumnNames().stream()
+          .filter(column -> 
!column.equals(mapped.getTimeColumn())).findFirst().orElseThrow();
+      String virtualColumn = "virtualMetadataTest";
+      mapped.addVirtualColumnMetadata(virtualColumn, 
mapped.getColumnMetadataFor(physicalColumn));
+      assertTrue(mapped.getAllColumns().contains(virtualColumn));
+      assertFalse(columns.contains(virtualColumn));
+      mapped.removeColumn(virtualColumn);
+      assertFalse(mapped.getAllColumns().contains(virtualColumn));
+      mapped.removeColumn(physicalColumn);
+      assertFalse(mapped.getAllColumns().contains(physicalColumn));
+      assertTrue(columns.contains(physicalColumn));
+    }
+    assertEquals(PinotDataBuffer.getMmapBufferCount(), mappings);
+    long directBuffers = PinotDataBuffer.getDirectBufferCount();
+    try (SegmentMetadataImpl heap = 
SegmentMetadataImpl.loadMapped(_segmentDirectory, ReadMode.heap)) {
+      assertEquals(heap.toJson(null), eager.toJson(null));
+      assertEquals(PinotDataBuffer.getMmapBufferCount(), mappings);
+      assertEquals(PinotDataBuffer.getDirectBufferCount(), directBuffers + 1);
+    }
+    assertEquals(PinotDataBuffer.getDirectBufferCount(), directBuffers);
+
+    byte[] sidecarBytes = Files.readAllBytes(sidecar.toPath());
+    
assertEquals(ByteBuffer.wrap(sidecarBytes).order(ByteOrder.BIG_ENDIAN).getInt(40),
 sidecarCrc(sidecarBytes));
+
+    // An invalid sidecar is regenerated from canonical metadata.properties.
+    Files.write(sidecar.toPath(), new byte[]{1, 2, 3});
+    try (SegmentMetadataImpl mapped = 
SegmentMetadataImpl.loadMapped(_segmentDirectory)) {
+      assertEquals(mapped.getTotalDocs(), eager.getTotalDocs());
+    }
+    // A source-only update invalidates a valid sidecar as well.
+    Files.writeString(properties.toPath(), "\ncustom.mappedRefresh = yes\n", 
StandardCharsets.UTF_8,
+        StandardOpenOption.APPEND);
+    try (SegmentMetadataImpl mapped = 
SegmentMetadataImpl.loadMapped(_segmentDirectory)) {
+      assertEquals(mapped.getCustomMap().get("mappedRefresh"), "yes");
+    }
+    // A zero-sized local v3 index is still present; a root-only map does not 
supply eager or mapped index sizes.
+    File indexMap = new File(new File(_segmentDirectory, "v3"), 
V1Constants.INDEX_MAP_FILE_NAME);
+    PropertiesConfiguration indexConfig = 
CommonsConfigurationUtils.fromFile(indexMap);
+    String sizeKey = CommonsConfigurationUtils.getKeys(indexConfig).stream()
+        .filter(key -> ColumnIndexUtils.MAP_KEY_NAME_SIZE.equals(
+            ColumnIndexUtils.parseIndexMapKeys(key, 
_segmentDirectory.getPath())[2])).findFirst().orElseThrow();
+    String[] parsedKey = ColumnIndexUtils.parseIndexMapKeys(sizeKey, 
_segmentDirectory.getPath());
+    indexConfig.setProperty(sizeKey, 0);
+    CommonsConfigurationUtils.saveToFile(indexConfig, indexMap);
+    try (SegmentMetadataImpl mapped = 
SegmentMetadataImpl.loadMapped(_segmentDirectory)) {
+      ColumnMetadata zeroSizedColumn = 
mapped.getColumnMetadataFor(parsedKey[0]);
+      
assertEquals(zeroSizedColumn.getIndexSizeFor(IndexService.getInstance().get(parsedKey[1])),
 0L);
+    }
+    IndexService originalIndexService = IndexService.getInstance();
+    Set<IndexPlugin<?>> plugins = new HashSet<>();
+    for (IndexType<?, ?, ?> type : originalIndexService.getAllIndexes()) {
+      if (!type.getId().equals(parsedKey[1])) {
+        IndexPlugin<IndexType<?, ?, ?>> plugin = () -> type;
+        plugins.add(plugin);
+      }
+    }
+    try {
+      IndexService.setInstance(new IndexService(plugins));
+      try (SegmentMetadataImpl mapped = 
SegmentMetadataImpl.loadMapped(_segmentDirectory)) {
+        assertEquals(mapped.getColumnMetadataFor(parsedKey[0]).getIndexSizeFor(
+            originalIndexService.get(parsedKey[1])), 
ColumnMetadata.UNAVAILABLE);
+      }
+    } finally {
+      IndexService.setInstance(originalIndexService);
+    }
+    try (SegmentMetadataImpl mapped = 
SegmentMetadataImpl.loadMapped(_segmentDirectory)) {
+      assertEquals(mapped.getColumnMetadataFor(parsedKey[0]).getIndexSizeFor(
+          originalIndexService.get(parsedKey[1])), 0L);
+    }
+    File staging = new File(INDEX_DIR, "mapped-staging");
+    try {
+      FileUtils.forceMkdir(staging);
+      FileUtils.copyFile(properties, new File(staging, properties.getName()));
+      File creationMeta = 
SegmentDirectoryPaths.findCreationMetaFile(_segmentDirectory);
+      FileUtils.copyFile(creationMeta, new File(staging, 
creationMeta.getName()));
+      indexConfig.setProperty(sizeKey, 123);
+      CommonsConfigurationUtils.saveToFile(indexConfig, new File(staging, 
V1Constants.INDEX_MAP_FILE_NAME));
+      SegmentMetadataImpl stagedEager = new SegmentMetadataImpl(staging);
+      try (SegmentMetadataImpl mapped = 
SegmentMetadataImpl.loadMapped(staging)) {
+        
assertEquals(stagedEager.getColumnMetadataFor(parsedKey[0]).getIndexSizeFor(
+            IndexService.getInstance().get(parsedKey[1])), 
ColumnMetadata.UNAVAILABLE);
+        assertEquals(mapped.getColumnMetadataFor(parsedKey[0]).getIndexSizeFor(
+            IndexService.getInstance().get(parsedKey[1])),
+            ColumnMetadata.UNAVAILABLE);
+      }
+    } finally {
+      FileUtils.deleteQuietly(staging);
+    }
+    assertEquals(PinotDataBuffer.getMmapBufferCount(), mappings);
+  }
+
+  @Test
+  public void testMappedMetadataIgnoresStaleEagerSnapshot()

Review Comment:
   Nice test construction — swapping content with a same-length replacement so 
only the CRC differs is exactly the adversarial case for a length+CRC 
fingerprint, and covering it for both metadata.properties and index_map pins 
the reuse guard on its narrowest margin.



-- 
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]

Reply via email to