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

xiangfu0 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 e244b41dec7 Sort lineage entries by timestamp then id for 
deterministic list output (#18859)
e244b41dec7 is described below

commit e244b41dec7abdbc5ff1bc42b62fd838644eb433
Author: Samuel Papin <[email protected]>
AuthorDate: Fri Jul 10 14:36:19 2026 -0400

    Sort lineage entries by timestamp then id for deterministic list output 
(#18859)
    
    SegmentLineage.toJsonObject() sorted entries by millisecond timestamp only, 
over a
    HashMap, so entries sharing a millisecond came out in arbitrary (UUID-hash) 
order
    -- non-deterministic list output, and a ~29% flake in 
testListSegmentLineage (two
    back-to-back replaces share a millisecond ~57% of the time on a fast 
machine).
    
    Add the entry id as a sort tiebreaker so the output is deterministic for a 
given
    set of entries: (timestamp, then id). No change to id generation -- entries 
remain
    identified by UUID. testListSegmentLineage derives the expected order with 
the same
    (timestamp, id) sort and asserts the response matches.
---
 .../org/apache/pinot/common/lineage/SegmentLineage.java     |  7 +++++--
 .../controller/api/PinotSegmentRestletResourceTest.java     | 13 ++++++++++++-
 2 files changed, 17 insertions(+), 3 deletions(-)

diff --git 
a/pinot-common/src/main/java/org/apache/pinot/common/lineage/SegmentLineage.java
 
b/pinot-common/src/main/java/org/apache/pinot/common/lineage/SegmentLineage.java
index ad5d533af45..dcd92c65134 100644
--- 
a/pinot-common/src/main/java/org/apache/pinot/common/lineage/SegmentLineage.java
+++ 
b/pinot-common/src/main/java/org/apache/pinot/common/lineage/SegmentLineage.java
@@ -191,14 +191,17 @@ public class SegmentLineage {
 
   /**
    * Returns a json representation of the segment lineage.
-   * Segment lineage entries are sorted in chronological order by default.
+   * Segment lineage entries are sorted by timestamp, with the entry id as a 
tiebreaker so that entries
+   * sharing the same millisecond are still ordered deterministically (instead 
of by HashMap iteration).
    */
   public ObjectNode toJsonObject() {
     ObjectNode jsonObject = JsonUtils.newObjectNode();
     jsonObject.put("tableNameWithType", _tableNameWithType);
     LinkedHashMap<String, LineageEntry> sortedLineageEntries = new 
LinkedHashMap<>();
     _lineageEntries.entrySet().stream()
-        
.sorted(Map.Entry.comparingByValue(Comparator.comparingLong(LineageEntry::getTimestamp)))
+        .sorted(Map.Entry.<String, LineageEntry>comparingByValue(
+                Comparator.comparingLong(LineageEntry::getTimestamp))
+            .thenComparing(Map.Entry.comparingByKey()))
         .forEachOrdered(x -> sortedLineageEntries.put(x.getKey(), 
x.getValue()));
     jsonObject.set("lineageEntries", 
JsonUtils.objectToJsonNode(sortedLineageEntries));
     if (_customMap != null) {
diff --git 
a/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotSegmentRestletResourceTest.java
 
b/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotSegmentRestletResourceTest.java
index 300590bbfcd..dd5008a0fae 100644
--- 
a/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotSegmentRestletResourceTest.java
+++ 
b/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotSegmentRestletResourceTest.java
@@ -19,11 +19,15 @@
 package org.apache.pinot.controller.api;
 
 import java.util.Arrays;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.Stream;
 import org.apache.pinot.client.admin.PinotAdminClient;
+import org.apache.pinot.common.lineage.SegmentLineage;
+import org.apache.pinot.common.lineage.SegmentLineageAccessHelper;
 import org.apache.pinot.controller.helix.ControllerTest;
 import org.apache.pinot.controller.helix.core.PinotHelixResourceManager;
 import org.apache.pinot.controller.utils.SegmentMetadataMockUtils;
@@ -99,8 +103,15 @@ public class PinotSegmentRestletResourceTest {
     
assertTrue(segmentLineageResponse.contains("\"segmentsTo\":[\"some_segment\"]"));
     
assertTrue(segmentLineageResponse.contains("\"segmentsFrom\":[\"s2\",\"s3\"]"));
     
assertTrue(segmentLineageResponse.contains("\"segmentsTo\":[\"another_segment\"]"));
+    SegmentLineage lineage =
+        
SegmentLineageAccessHelper.getSegmentLineage(resourceManager.getPropertyStore(),
 offlineTableName);
+    // Sort the two entry ids the same way toJsonObject does -- by timestamp, 
then by id.
+    List<String> orderedIds = Stream.of(segmentLineageId, nextSegmentLineageId)
+        .sorted(Comparator.comparingLong((String id) -> 
lineage.getLineageEntry(id).getTimestamp())
+            .thenComparing(Comparator.naturalOrder()))
+        .toList();
     // Ensures the two entries are sorted in chronological order by timestamp.
-    assertTrue(segmentLineageResponse.indexOf(segmentLineageId) < 
segmentLineageResponse.indexOf(nextSegmentLineageId));
+    assertTrue(segmentLineageResponse.indexOf(orderedIds.get(0)) < 
segmentLineageResponse.indexOf(orderedIds.get(1)));
 
     // List segment lineage should fail for non-existing table
     assertThrows(Exception.class,


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

Reply via email to