@@ -360,6 +360,15 @@ func TestNetworkRTTAndPolicyCalculations(t *testing.T) {
360360 "expected policy %v for RTT %v, got %v" ,
361361 tc .expectedPolicy , tc .networkRTT , policy )
362362
363+ // Test RTT -> Policy with 0 percent dampening. We expect the same outcome
364+ // as FindBucketBasedOnNetworkRTT regardless of oldPolicy.
365+ for oldPolicy := ctpb .LEAD_FOR_GLOBAL_READS_WITH_NO_LATENCY_INFO ; oldPolicy <= ctpb .LEAD_FOR_GLOBAL_READS_LATENCY_EQUAL_OR_GREATER_THAN_300MS ; oldPolicy ++ {
366+ newPolicy := FindBucketBasedOnNetworkRTTWithDampening (oldPolicy , tc .networkRTT , 0 )
367+ require .Equal (t , tc .expectedPolicy , newPolicy ,
368+ "expected policy %v for RTT %v, got %v" ,
369+ tc .expectedPolicy , tc .networkRTT , policy )
370+ }
371+
363372 // Test Policy -> RTT conversion.
364373 rtt := computeNetworkRTTBasedOnPolicy (policy )
365374 require .Equal (t , tc .expectedRTT , rtt ,
@@ -368,3 +377,198 @@ func TestNetworkRTTAndPolicyCalculations(t *testing.T) {
368377 })
369378 }
370379}
380+
381+ // TestRefreshPolicyWithDampening tests the RefreshPolicy method of
382+ // replica.RefreshPolicy works expectedly with different dampening fractions.
383+ func TestRefreshPolicyWithDampening (t * testing.T ) {
384+ defer leaktest .AfterTest (t )()
385+ defer log .Scope (t ).Close (t )
386+
387+ testCases := []struct {
388+ name string
389+ dampeningFraction float64
390+ oldPolicy ctpb.RangeClosedTimestampPolicy
391+ networkRTT time.Duration
392+ expectedPolicy ctpb.RangeClosedTimestampPolicy
393+ }{
394+ {
395+ name : "from no latency info to low latency" ,
396+ dampeningFraction : 0.2 ,
397+ oldPolicy : ctpb .LEAD_FOR_GLOBAL_READS_WITH_NO_LATENCY_INFO ,
398+ networkRTT : 10 * time .Millisecond ,
399+ expectedPolicy : ctpb .LEAD_FOR_GLOBAL_READS_LATENCY_LESS_THAN_20MS ,
400+ },
401+ {
402+ name : "from low latency to no latency info" ,
403+ dampeningFraction : 0.2 ,
404+ oldPolicy : ctpb .LEAD_FOR_GLOBAL_READS_LATENCY_LESS_THAN_20MS ,
405+ networkRTT : - 1 * time .Millisecond ,
406+ expectedPolicy : ctpb .LEAD_FOR_GLOBAL_READS_WITH_NO_LATENCY_INFO ,
407+ },
408+ {
409+ name : "latency increases but below the lower bound threshold" ,
410+ dampeningFraction : 0.2 ,
411+ oldPolicy : ctpb .LEAD_FOR_GLOBAL_READS_LATENCY_LESS_THAN_40MS ,
412+ // 42ms is above 40ms but below the 40+20ms*0.2=44ms boundary.
413+ networkRTT : 42 * time .Millisecond ,
414+ expectedPolicy : ctpb .LEAD_FOR_GLOBAL_READS_LATENCY_LESS_THAN_40MS ,
415+ },
416+ {
417+ name : "latency increases and above the lower bound threshold" ,
418+ dampeningFraction : 0.2 ,
419+ oldPolicy : ctpb .LEAD_FOR_GLOBAL_READS_LATENCY_LESS_THAN_40MS ,
420+ // 44ms is above the 40+20ms*0.2=44ms boundary.
421+ networkRTT : 44 * time .Millisecond ,
422+ expectedPolicy : ctpb .LEAD_FOR_GLOBAL_READS_LATENCY_LESS_THAN_60MS ,
423+ },
424+ {
425+ name : "latency increases to next bucket and above its upper bound threshold" ,
426+ dampeningFraction : 0.2 ,
427+ oldPolicy : ctpb .LEAD_FOR_GLOBAL_READS_LATENCY_LESS_THAN_20MS ,
428+ // 38 is above 20ms+20*0.2=24ms and above the 40-20*0.2=36ms threshold.
429+ networkRTT : 38 * time .Millisecond ,
430+ expectedPolicy : ctpb .LEAD_FOR_GLOBAL_READS_LATENCY_LESS_THAN_40MS ,
431+ },
432+ {
433+ name : "latency drops to previous bucket but above the upper bound threshold" ,
434+ dampeningFraction : 0.2 ,
435+ oldPolicy : ctpb .LEAD_FOR_GLOBAL_READS_LATENCY_LESS_THAN_40MS ,
436+ // 18ms is below 20ms but above the 20ms-20ms*0.2=16ms boundary.
437+ networkRTT : 18 * time .Millisecond ,
438+ expectedPolicy : ctpb .LEAD_FOR_GLOBAL_READS_LATENCY_LESS_THAN_40MS ,
439+ },
440+ {
441+ name : "latency drops to previous bucket and below the upper bound threshold" ,
442+ dampeningFraction : 0.2 ,
443+ oldPolicy : ctpb .LEAD_FOR_GLOBAL_READS_LATENCY_LESS_THAN_40MS ,
444+ // 14ms is below 20ms and below the 20ms-20ms*0.2=16ms boundary.
445+ networkRTT : 14 * time .Millisecond ,
446+ expectedPolicy : ctpb .LEAD_FOR_GLOBAL_READS_LATENCY_LESS_THAN_20MS ,
447+ },
448+ {
449+ name : "latency drops to previous bucket and below the lower bound threshold" ,
450+ dampeningFraction : 0.2 ,
451+ oldPolicy : ctpb .LEAD_FOR_GLOBAL_READS_LATENCY_LESS_THAN_40MS ,
452+ // 3ms is below 20ms and below the 20ms-20ms*0.2=16ms boundary and below 20ms*0.2=5ms.
453+ networkRTT : 3 * time .Millisecond ,
454+ expectedPolicy : ctpb .LEAD_FOR_GLOBAL_READS_LATENCY_LESS_THAN_20MS ,
455+ },
456+ {
457+ name : "boundary case at 300ms" ,
458+ dampeningFraction : 0.2 ,
459+ oldPolicy : ctpb .LEAD_FOR_GLOBAL_READS_LATENCY_LESS_THAN_300MS ,
460+ // 300ms is below the 300ms+20ms*0.2=304ms boundary.
461+ networkRTT : 300 * time .Millisecond ,
462+ expectedPolicy : ctpb .LEAD_FOR_GLOBAL_READS_LATENCY_LESS_THAN_300MS ,
463+ },
464+ {
465+ name : "boundary case at 320ms" ,
466+ dampeningFraction : 0.2 ,
467+ oldPolicy : ctpb .LEAD_FOR_GLOBAL_READS_LATENCY_LESS_THAN_300MS ,
468+ // 320ms is above the 300ms+20ms*0.2=304ms boundary.
469+ networkRTT : 320 * time .Millisecond ,
470+ expectedPolicy : ctpb .LEAD_FOR_GLOBAL_READS_LATENCY_EQUAL_OR_GREATER_THAN_300MS ,
471+ },
472+ {
473+ name : "jump to higher bucket case at 600ms" ,
474+ dampeningFraction : 0.2 ,
475+ oldPolicy : ctpb .LEAD_FOR_GLOBAL_READS_LATENCY_LESS_THAN_300MS ,
476+ // 600ms is above the 300ms+20ms*0.2=304ms boundary.
477+ networkRTT : 600 * time .Millisecond ,
478+ expectedPolicy : ctpb .LEAD_FOR_GLOBAL_READS_LATENCY_EQUAL_OR_GREATER_THAN_300MS ,
479+ },
480+ // Zero Dampening Cases (Most Sensitive)
481+ {
482+ name : "zero dampening - tiny increase" ,
483+ dampeningFraction : 0.0 ,
484+ oldPolicy : ctpb .LEAD_FOR_GLOBAL_READS_LATENCY_LESS_THAN_40MS ,
485+ networkRTT : 40 * time .Millisecond , // Tiny increase
486+ expectedPolicy : ctpb .LEAD_FOR_GLOBAL_READS_LATENCY_LESS_THAN_60MS ,
487+ },
488+ {
489+ name : "zero dampening - tiny decrease" ,
490+ dampeningFraction : 0.0 ,
491+ oldPolicy : ctpb .LEAD_FOR_GLOBAL_READS_LATENCY_LESS_THAN_60MS ,
492+ networkRTT : 39 * time .Millisecond ,
493+ expectedPolicy : ctpb .LEAD_FOR_GLOBAL_READS_LATENCY_LESS_THAN_40MS ,
494+ },
495+ // 100% Dampening Cases (Most Conservative)
496+ {
497+ name : "full dampening - significant increase" ,
498+ dampeningFraction : 1.0 ,
499+ oldPolicy : ctpb .LEAD_FOR_GLOBAL_READS_LATENCY_LESS_THAN_40MS ,
500+ networkRTT : 58 * time .Millisecond ,
501+ expectedPolicy : ctpb .LEAD_FOR_GLOBAL_READS_LATENCY_LESS_THAN_40MS ,
502+ },
503+ {
504+ name : "full dampening - multi-bucket jump" ,
505+ dampeningFraction : 1.0 ,
506+ oldPolicy : ctpb .LEAD_FOR_GLOBAL_READS_LATENCY_LESS_THAN_40MS ,
507+ networkRTT : 60 * time .Millisecond ,
508+ expectedPolicy : ctpb .LEAD_FOR_GLOBAL_READS_LATENCY_LESS_THAN_80MS ,
509+ },
510+ // 0.001 Dampening Cases (Very Sensitive but not quite zero)
511+ {
512+ name : "0.001 dampening - small increase" ,
513+ dampeningFraction : 0.001 ,
514+ oldPolicy : ctpb .LEAD_FOR_GLOBAL_READS_LATENCY_LESS_THAN_40MS ,
515+ // Just barely above 40ms + (20ms * 0.001) = 40.02ms.
516+ networkRTT : 41 * time .Millisecond ,
517+ expectedPolicy : ctpb .LEAD_FOR_GLOBAL_READS_LATENCY_LESS_THAN_60MS ,
518+ },
519+ {
520+ name : "0.001 dampening - small decrease" ,
521+ dampeningFraction : 0.001 ,
522+ oldPolicy : ctpb .LEAD_FOR_GLOBAL_READS_LATENCY_LESS_THAN_60MS ,
523+ // Just barely below 40ms - (20ms * 0.001) = 39.98ms.
524+ networkRTT : 39 * time .Millisecond ,
525+ expectedPolicy : ctpb .LEAD_FOR_GLOBAL_READS_LATENCY_LESS_THAN_40MS ,
526+ },
527+ {
528+ name : "0.001 dampening - no change on small increase" ,
529+ dampeningFraction : 0.001 ,
530+ oldPolicy : ctpb .LEAD_FOR_GLOBAL_READS_LATENCY_LESS_THAN_40MS ,
531+ // Just below 40ms + (20ms * 0.001) = 40.02ms.
532+ networkRTT : 40 * time .Millisecond ,
533+ expectedPolicy : ctpb .LEAD_FOR_GLOBAL_READS_LATENCY_LESS_THAN_40MS ,
534+ },
535+ {
536+ name : "0.001 dampening - no change on small decrease" ,
537+ dampeningFraction : 0.001 ,
538+ oldPolicy : ctpb .LEAD_FOR_GLOBAL_READS_LATENCY_LESS_THAN_60MS ,
539+ // Just above 40ms - (20ms * 0.001) = 39.98ms.
540+ networkRTT : time .Duration (39.99 * float64 (time .Millisecond )),
541+ expectedPolicy : ctpb .LEAD_FOR_GLOBAL_READS_LATENCY_LESS_THAN_60MS ,
542+ },
543+ {
544+ name : "0.001 dampening - boundary at 300ms" ,
545+ dampeningFraction : 0.001 ,
546+ oldPolicy : ctpb .LEAD_FOR_GLOBAL_READS_LATENCY_LESS_THAN_300MS ,
547+ // Just barely above 300ms + (20ms * 0.001) = 300.02ms.
548+ networkRTT : 301 * time .Millisecond ,
549+ expectedPolicy : ctpb .LEAD_FOR_GLOBAL_READS_LATENCY_EQUAL_OR_GREATER_THAN_300MS ,
550+ },
551+ {
552+ name : "0.001 dampening - multi-bucket jump to higher latency" ,
553+ dampeningFraction : 0.001 ,
554+ oldPolicy : ctpb .LEAD_FOR_GLOBAL_READS_LATENCY_LESS_THAN_40MS ,
555+ networkRTT : 100 * time .Millisecond ,
556+ expectedPolicy : ctpb .LEAD_FOR_GLOBAL_READS_LATENCY_LESS_THAN_120MS ,
557+ },
558+ {
559+ name : "0.001 dampening - multi-bucket jump to lower latency" ,
560+ dampeningFraction : 0.001 ,
561+ oldPolicy : ctpb .LEAD_FOR_GLOBAL_READS_LATENCY_LESS_THAN_80MS ,
562+ // Above 40ms - (20ms * 0.001) = 39.98ms, but it is a multi-bucket jump.
563+ networkRTT : time .Duration (39.99 * float64 (time .Millisecond )),
564+ expectedPolicy : ctpb .LEAD_FOR_GLOBAL_READS_LATENCY_LESS_THAN_40MS ,
565+ },
566+ }
567+
568+ for _ , tc := range testCases {
569+ t .Run (tc .name , func (t * testing.T ) {
570+ newPolicy := FindBucketBasedOnNetworkRTTWithDampening (tc .oldPolicy , tc .networkRTT , tc .dampeningFraction )
571+ require .Equal (t , tc .expectedPolicy , newPolicy )
572+ })
573+ }
574+ }
0 commit comments