@@ -88,7 +88,10 @@ public class VersionedIntervalTimeline<VersionType, ObjectType extends Overshado
8888 Comparators .intervalsByStartThenEnd ()
8989 );
9090 // true interval -> version -> timelineEntry
91- private final Map <Interval , TreeMap <VersionType , TimelineEntry >> allTimelineEntries = new HashMap <>();
91+ // NavigableMap so the private remove() can range-scan for overlapping intervals instead of a full O(N) pass.
92+ private final NavigableMap <Interval , TreeMap <VersionType , TimelineEntry >> allTimelineEntries = new TreeMap <>(
93+ Comparators .intervalsByStartThenEnd ()
94+ );
9295 private final AtomicInteger numObjects = new AtomicInteger ();
9396
9497 private final Comparator <? super VersionType > versionComparator ;
@@ -118,7 +121,7 @@ public static <VersionType, ObjectType extends Overshadowable<ObjectType>> Itera
118121 .iterator ();
119122 }
120123
121- public Map <Interval , TreeMap <VersionType , TimelineEntry >> getAllTimelineEntries ()
124+ public NavigableMap <Interval , TreeMap <VersionType , TimelineEntry >> getAllTimelineEntries ()
122125 {
123126 return allTimelineEntries ;
124127 }
@@ -715,7 +718,15 @@ private void remove(
715718 {
716719 timeline .remove (interval );
717720
718- for (Entry <Interval , TreeMap <VersionType , TimelineEntry >> versionEntry : allTimelineEntries .entrySet ()) {
721+ // allTimelineEntries stores TRUE intervals which CAN overlap each other (e.g. a wide interval
722+ // [Jan01/Dec31] and a narrow one [Jun01/Jun30] may both start before the removed interval).
723+ // We therefore cannot use floorKey — entries starting well before interval.start may still extend
724+ // into the interval and must be re-added. Use headMap to skip entries whose start >= interval.end
725+ // (those can never overlap), then filter for actual overlap. Reduces the scan from O(M) to O(M')
726+ // where M' is the count of entries with start < interval.end.
727+ final Interval headBound = new Interval (interval .getEnd (), interval .getEnd ());
728+ for (Entry <Interval , TreeMap <VersionType , TimelineEntry >> versionEntry :
729+ allTimelineEntries .headMap (headBound , false ).entrySet ()) {
719730 if (versionEntry .getKey ().overlap (interval ) != null ) {
720731 if (incompleteOk ) {
721732 add (timeline , versionEntry .getKey (), versionEntry .getValue ().lastEntry ().getValue ());
@@ -747,8 +758,27 @@ private List<TimelineObjectHolder<VersionType, ObjectType>> lookup(Interval inte
747758 timeline = completePartitionsTimeline ;
748759 }
749760
750- for (Entry <Interval , TimelineEntry > entry : timeline .entrySet ()) {
761+ // Both completePartitionsTimeline and incompletePartitionsTimeline contain non-overlapping adjusted intervals
762+ // sorted by (start, end). To find entries overlapping [interval.start, interval.end) we only need to scan
763+ // the range [floorKey(interval.start), first key with start >= interval.end). The floor handles the one
764+ // entry that may have started before interval.start but still extends into the query window.
765+ final Interval searchStart = new Interval (interval .getStart (), DateTimes .MAX );
766+ Interval startKey = timeline .floorKey (searchStart );
767+ if (startKey == null ) {
768+ // No entry starts at or before interval.start; begin from the first entry in the map.
769+ startKey = timeline .isEmpty () ? null : timeline .firstKey ();
770+ }
771+ if (startKey == null ) {
772+ return retVal ;
773+ }
774+
775+ for (Entry <Interval , TimelineEntry > entry : timeline .tailMap (startKey , true ).entrySet ()) {
751776 Interval timelineInterval = entry .getKey ();
777+ // All entries from here forward start at or after startKey.start. Once we reach an entry whose
778+ // start is at or past the query end, there can be no more overlaps.
779+ if (timelineInterval .getStartMillis () >= interval .getEndMillis ()) {
780+ break ;
781+ }
752782 TimelineEntry val = entry .getValue ();
753783
754784 // exclude empty partition holders (i.e. tombstones) since they do not add value
0 commit comments