@@ -1291,6 +1291,9 @@ private void applyTo(SketchBasedJoinEstimator estimator, State state, byte slot)
12911291 /** True when persisted index metadata changed and index rewrite is pending. */
12921292 private final AtomicBoolean indexDirty = new AtomicBoolean (false );
12931293 private final Map <EstimateCacheKey , EstimateCacheEntry > estimateCache = new ConcurrentHashMap <>();
1294+ private static final PositiveReadinessCache NO_POSITIVE_READINESS_CACHE = new PositiveReadinessCache (
1295+ Long .MIN_VALUE , null , (byte ) -1 , -1L );
1296+ private volatile PositiveReadinessCache positiveReadinessCache = NO_POSITIVE_READINESS_CACHE ;
12941297
12951298 private volatile BooleanSupplier rebuildAllowedSupplier ;
12961299 private final Object persistLock = new Object ();
@@ -1523,27 +1526,57 @@ public void close() {
15231526
15241527 public boolean isReady () {
15251528 OptimizationScopeState scope = optimizationScope .get ();
1526- if (scope == null ) {
1527- return isReadyUnscoped (null );
1528- }
15291529 long epoch = rebuildEpoch .get ();
15301530 boolean rebuildRequiredNow = rebuildRequired .get ();
15311531 boolean sketchesLoadedNow = sketchesLoaded ;
1532- if (scope .readyKnown && scope .readyEpoch == epoch && scope .readyRebuildRequired == rebuildRequiredNow
1532+ if (scope != null && scope .readyKnown && scope .readyEpoch == epoch
1533+ && scope .readyRebuildRequired == rebuildRequiredNow
15331534 && scope .readySketchesLoaded == sketchesLoadedNow ) {
15341535 return scope .ready ;
15351536 }
1537+ State readyState = current ;
1538+ byte readySlot = slotByte (slotOf (readyState ));
1539+ PositiveReadinessCache cache = positiveReadinessCache ;
1540+ if (cache .matches (epoch , rebuildRequiredNow , sketchesLoadedNow , readyState , readySlot ,
1541+ slotGenerations [readySlot ])) {
1542+ if (scope != null ) {
1543+ cacheScopeReadiness (scope , epoch , true );
1544+ }
1545+ return true ;
1546+ }
15361547 boolean ready = isReadyUnscoped (scope );
15371548 if (rebuildEpoch .get () == epoch ) {
1538- scope .readyEpoch = epoch ;
1539- scope .readyRebuildRequired = rebuildRequired .get ();
1540- scope .readySketchesLoaded = sketchesLoaded ;
1541- scope .ready = ready ;
1542- scope .readyKnown = true ;
1549+ if (scope != null ) {
1550+ cacheScopeReadiness (scope , epoch , ready );
1551+ }
1552+ if (ready ) {
1553+ rememberPositiveReadiness (epoch );
1554+ }
15431555 }
15441556 return ready ;
15451557 }
15461558
1559+ private void cacheScopeReadiness (OptimizationScopeState scope , long epoch , boolean ready ) {
1560+ scope .readyEpoch = epoch ;
1561+ scope .readyRebuildRequired = rebuildRequired .get ();
1562+ scope .readySketchesLoaded = sketchesLoaded ;
1563+ scope .ready = ready ;
1564+ scope .readyKnown = true ;
1565+ }
1566+
1567+ private void rememberPositiveReadiness (long epoch ) {
1568+ if ((epoch & 1L ) != 0L || rebuildRequired .get () || !sketchesLoaded ) {
1569+ return ;
1570+ }
1571+ State readyState = current ;
1572+ byte readySlot = slotByte (slotOf (readyState ));
1573+ positiveReadinessCache = new PositiveReadinessCache (epoch , readyState , readySlot , slotGenerations [readySlot ]);
1574+ }
1575+
1576+ private void clearPositiveReadinessCache () {
1577+ positiveReadinessCache = NO_POSITIVE_READINESS_CACHE ;
1578+ }
1579+
15471580 int scopedReadinessScansForTesting () {
15481581 OptimizationScopeState scope = optimizationScope .get ();
15491582 return scope == null ? 0 : scope .readinessScans ;
@@ -1899,6 +1932,7 @@ private void shutdownAsyncIncrementalIngest() {
18991932 public synchronized long rebuild () {
19001933 flushPendingIncremental ();
19011934 clearEstimateCacheIfPopulated ();
1935+ clearPositiveReadinessCache ();
19021936
19031937 boolean rebuildIntoA = !usingA ; // remember before toggling
19041938
@@ -2570,13 +2604,12 @@ public JoinEstimate estimate(Component joinVar, String s, String p, String o, St
25702604 synchronized (snap ) {
25712605 PatternStats st = statsOf (snap , joinVar , s , p , o , c );
25722606 ArrayOfDoublesSketch bindings = st .sketch == null ? EMPTY : st .sketch ;
2573- return new JoinEstimate (snap , joinVar , bindings , TupleSketchOps .estimatePositiveDistinct (bindings ),
2574- st .card );
2607+ return new JoinEstimate (snap , joinVar , bindings , st .distinct , st .card );
25752608 }
25762609 }
25772610 PatternStats st = patternStats (snap , joinVar , s , p , o , c );
25782611 ArrayOfDoublesSketch bindings = st .sketch == null ? EMPTY : st .sketch ;
2579- return new JoinEstimate (snap , joinVar , bindings , TupleSketchOps . estimatePositiveDistinct ( bindings ) , st .card );
2612+ return new JoinEstimate (snap , joinVar , bindings , st . distinct , st .card );
25802613 }
25812614
25822615 public double estimateCount (Component joinVar , String s , String p , String o , String c ) {
@@ -2657,7 +2690,7 @@ private static final class PatternStats {
26572690
26582691 PatternStats (ArrayOfDoublesSketch s , double card ) {
26592692 this .sketch = s ;
2660- this .distinct = TupleSketchOps .estimatePositiveDistinct (s );
2693+ this .distinct = TupleSketchOps .estimateDistinct (s );
26612694 this .card = card ;
26622695 }
26632696 }
@@ -2985,7 +3018,7 @@ private BindingSketchResult bindingsSketch(State st, Component j, EnumMap<Compon
29853018 if (candidate == null ) {
29863019 continue ;
29873020 }
2988- double candidateDistinct = TupleSketchOps .estimatePositiveDistinct (candidate );
3021+ double candidateDistinct = TupleSketchOps .estimateDistinct (candidate );
29893022 if (candidateDistinct < best ) {
29903023 best = candidateDistinct ;
29913024 candidateForBest = BindingSketchResult .of (candidate , pr );
@@ -3775,7 +3808,7 @@ private Map<String, VarPlanStats> estimateAllUnboundPatternVarStats(StatementPat
37753808 Component component = getComponent (pattern , var );
37763809 ArrayOfDoublesSketch sketch = getSketchForRead (snap ,
37773810 new SketchAddress (REC_GLOBAL_COMPONENT , false , (byte ) component .ordinal (), (byte ) 0 , 0 , 0 ));
3778- double distinct = clampDistinct (TupleSketchOps .estimatePositiveDistinct (sketch ), rows );
3811+ double distinct = clampDistinct (TupleSketchOps .estimateDistinct (sketch ), rows );
37793812 if (!(Double .isFinite (distinct ) && distinct > 0.0d )) {
37803813 distinct = clampDistinct (rows , rows );
37813814 sketch = null ;
@@ -3786,6 +3819,21 @@ private Map<String, VarPlanStats> estimateAllUnboundPatternVarStats(StatementPat
37863819 }
37873820
37883821 private TuplePlanEstimate estimateBindingSetAssignment (BindingSetAssignment assignment ) {
3822+ OptimizationScopeState scope = optimizationScope .get ();
3823+ if (scope != null ) {
3824+ BindingSetAssignmentEstimateCacheKey key = new BindingSetAssignmentEstimateCacheKey (assignment );
3825+ Optional <TuplePlanEstimate > cached = scope .bindingSetAssignmentEstimateCache .get (key );
3826+ if (cached != null ) {
3827+ return cached .orElse (null );
3828+ }
3829+ TuplePlanEstimate estimate = computeBindingSetAssignmentEstimate (assignment );
3830+ scope .bindingSetAssignmentEstimateCache .put (key , Optional .ofNullable (estimate ));
3831+ return estimate ;
3832+ }
3833+ return computeBindingSetAssignmentEstimate (assignment );
3834+ }
3835+
3836+ private TuplePlanEstimate computeBindingSetAssignmentEstimate (BindingSetAssignment assignment ) {
37893837 Iterable <BindingSet > bindingSets = assignment .getBindingSets ();
37903838 double rows = 0.0d ;
37913839 Map <String , Set <Value >> valueSets = new HashMap <>();
@@ -4999,9 +5047,34 @@ private static final class OptimizationScopeState {
49995047 private final VariableDictionary variableDictionary = new VariableDictionary ();
50005048 private final JoinOrderingSketchIntersectionCache sketchIntersectionCache = new JoinOrderingSketchIntersectionCache ();
50015049 private final Map <TuplePlanEstimateCacheKey , Optional <TuplePlanEstimate >> tupleEstimateCache = new HashMap <>();
5050+ private final Map <BindingSetAssignmentEstimateCacheKey , Optional <TuplePlanEstimate >> bindingSetAssignmentEstimateCache = new HashMap <>();
50025051 private final Map <AccessShapeCacheKey , AccessShape > accessShapeCache = new HashMap <>();
50035052 }
50045053
5054+ private static final class PositiveReadinessCache {
5055+ private final long epoch ;
5056+ private final State state ;
5057+ private final byte slot ;
5058+ private final long slotGeneration ;
5059+
5060+ private PositiveReadinessCache (long epoch , State state , byte slot , long slotGeneration ) {
5061+ this .epoch = epoch ;
5062+ this .state = state ;
5063+ this .slot = slot ;
5064+ this .slotGeneration = slotGeneration ;
5065+ }
5066+
5067+ private boolean matches (long currentEpoch , boolean rebuildRequired , boolean sketchesLoaded , State currentState ,
5068+ byte currentSlot , long currentSlotGeneration ) {
5069+ return !rebuildRequired
5070+ && sketchesLoaded
5071+ && epoch == currentEpoch
5072+ && state == currentState
5073+ && slot == currentSlot
5074+ && slotGeneration == currentSlotGeneration ;
5075+ }
5076+ }
5077+
50055078 private static final class TuplePlanEstimateCacheKey {
50065079 private final TupleExpr tupleExpr ;
50075080 private final long boundVarMask ;
@@ -5031,6 +5104,35 @@ public int hashCode() {
50315104 }
50325105 }
50335106
5107+ private static final class BindingSetAssignmentEstimateCacheKey {
5108+ private final Iterable <BindingSet > bindingSets ;
5109+ private final Set <String > bindingNames ;
5110+ private final int hashCode ;
5111+
5112+ private BindingSetAssignmentEstimateCacheKey (BindingSetAssignment assignment ) {
5113+ this .bindingSets = assignment .getBindingSets ();
5114+ this .bindingNames = Set .copyOf (assignment .getBindingNames ());
5115+ this .hashCode = 31 * System .identityHashCode (bindingSets ) + bindingNames .hashCode ();
5116+ }
5117+
5118+ @ Override
5119+ public boolean equals (Object other ) {
5120+ if (this == other ) {
5121+ return true ;
5122+ }
5123+ if (!(other instanceof BindingSetAssignmentEstimateCacheKey )) {
5124+ return false ;
5125+ }
5126+ BindingSetAssignmentEstimateCacheKey that = (BindingSetAssignmentEstimateCacheKey ) other ;
5127+ return bindingSets == that .bindingSets && bindingNames .equals (that .bindingNames );
5128+ }
5129+
5130+ @ Override
5131+ public int hashCode () {
5132+ return hashCode ;
5133+ }
5134+ }
5135+
50345136 private static final class AccessShapeCacheKey {
50355137 private final TupleExpr tupleExpr ;
50365138 private final long boundVarMask ;
@@ -5328,16 +5430,30 @@ private TuplePlanEstimate estimateTupleExprPlan(TupleExpr tupleExpr, long initia
53285430
53295431 private TuplePlanEstimate estimateTupleExprPlan (TupleExpr tupleExpr , OptimizationScopeState scope ,
53305432 long initiallyBoundVarMask ) {
5331- TuplePlanEstimateCacheKey key = new TuplePlanEstimateCacheKey (tupleExpr , initiallyBoundVarMask );
5433+ long cacheBoundVarMask = canonicalTupleEstimateBoundMask (tupleExpr , scope , initiallyBoundVarMask );
5434+ TuplePlanEstimateCacheKey key = new TuplePlanEstimateCacheKey (tupleExpr , cacheBoundVarMask );
53325435 Optional <TuplePlanEstimate > cached = scope .tupleEstimateCache .get (key );
53335436 if (cached != null ) {
53345437 return cached .orElse (null );
53355438 }
5336- TuplePlanEstimate estimate = computeTupleExprPlan (tupleExpr , scope , initiallyBoundVarMask );
5439+ TuplePlanEstimate estimate = computeTupleExprPlan (tupleExpr , scope , cacheBoundVarMask );
53375440 scope .tupleEstimateCache .put (key , Optional .ofNullable (estimate ));
53385441 return estimate ;
53395442 }
53405443
5444+ private long canonicalTupleEstimateBoundMask (TupleExpr tupleExpr , OptimizationScopeState scope ,
5445+ long initiallyBoundVarMask ) {
5446+ if (initiallyBoundVarMask == 0L || initiallyBoundVarMask == BOUND_VAR_MASK_OVERFLOW
5447+ || !(tupleExpr instanceof BindingSetAssignment )) {
5448+ return initiallyBoundVarMask ;
5449+ }
5450+ long assignmentMask = scope .variableDictionary .maskOf (((BindingSetAssignment ) tupleExpr ).getBindingNames ());
5451+ if (assignmentMask == BOUND_VAR_MASK_OVERFLOW ) {
5452+ return initiallyBoundVarMask ;
5453+ }
5454+ return initiallyBoundVarMask & assignmentMask ;
5455+ }
5456+
53415457 private TuplePlanEstimate computeTupleExprPlan (TupleExpr tupleExpr , Set <String > initiallyBoundVars ) {
53425458 if (tupleExpr == null ) {
53435459 return null ;
@@ -7912,6 +8028,7 @@ public void discardAndMarkForRebuild() {
79128028
79138029 private void unloadInternal (boolean markRebuildRequired ) {
79148030 clearEstimateCache ();
8031+ clearPositiveReadinessCache ();
79158032 if (persistenceEnabled && persistenceStore != null ) {
79168033 if ((dirty .get () || indexDirty .get ()) && !persistIfDirty ()) {
79178034 return ;
@@ -8001,6 +8118,7 @@ private void loadDirectoryStore(boolean markLoaded) throws IOException {
80018118 if (store == null || !store .hasMetadata ()) {
80028119 return ;
80038120 }
8121+ clearPositiveReadinessCache ();
80048122 SketchEstimatorMetadata metadata = store .readMetadata ();
80058123 if (metadata .subjectBucketCount != subjectBucketCount || metadata .predicateBucketCount != predicateBucketCount
80068124 || metadata .objectBucketCount != objectBucketCount
@@ -8101,6 +8219,7 @@ private void clearPersistedSketchDirectory() {
81018219
81028220 indexDirty .set (false );
81038221 }
8222+ clearPositiveReadinessCache ();
81048223 }
81058224
81068225 private boolean isRebuildAllowed () {
0 commit comments