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]