routing: stronger use of direct bimodal probability

The process how we calculate a total probability from the direct and
node probability is modified to give more importance to the direct
probability.

This is important if we know about a recent failure. We should not try
to send over this channel again if we know we can't. Otherwise this can
lead to infinite retrials over the channel, when the probability is
pinned at high probabilities by the other node results.
This commit is contained in:
bitromortac
2023-03-17 09:32:52 +01:00
parent 4aa90cf474
commit 170677be2b
2 changed files with 70 additions and 12 deletions

View File

@@ -308,6 +308,32 @@ func (p *BimodalEstimator) calculateProbability(directProbability float64,
return directProbability
}
// If we have up-to-date information about the channel we want to use,
// i.e. the info stems from results not longer ago than the decay time,
// we will only use the direct probability. This is needed in order to
// avoid that other previous results (on all other channels of the same
// routing node) will distort and pin the calculated probability even if
// we have accurate direct information. This helps to dip the
// probability below the min probability in case of failures, to start
// the splitting process.
directResult, ok := results[toNode]
if ok {
latest := directResult.SuccessTime
if directResult.FailTime.After(latest) {
latest = directResult.FailTime
}
// We use BimonodalDecayTime to judge the currentness of the
// data. It is the time scale on which we assume to have lost
// information.
if now.Sub(latest) < p.BimodalDecayTime {
log.Tracef("Using direct probability for node %v: %v",
toNode, directResult)
return directProbability
}
}
// w is a parameter which determines how strongly the other channels of
// a node should be incorporated, the higher the stronger.
w := p.BimodalNodeWeight

View File

@@ -379,35 +379,44 @@ func TestComputeProbability(t *testing.T) {
nodeWeight := 1 / 5.
toNode := route.Vertex{10}
tolerance := 0.01
now := time.Unix(0, 0)
decayTime := time.Duration(1) * time.Hour * 24
// makeNodeResults prepares forwarding data for the other channels of
// the node.
makeNodeResults := func(successes []bool, now time.Time) NodeResults {
makeNodeResults := func(successes []bool, resultsTime time.Time,
directResultTime time.Time) NodeResults {
results := make(NodeResults, len(successes))
for i, s := range successes {
vertex := route.Vertex{byte(i)}
results[vertex] = TimedPairResult{
FailTime: now, FailAmt: 1,
FailTime: resultsTime, FailAmt: 1,
}
if s {
results[vertex] = TimedPairResult{
SuccessTime: now, SuccessAmt: 1,
SuccessTime: resultsTime, SuccessAmt: 1,
}
}
}
// Include a direct result.
results[toNode] = TimedPairResult{
SuccessTime: directResultTime, SuccessAmt: 1,
}
return results
}
tests := []struct {
name string
directProbability float64
recentDirectResult bool
otherResults []bool
expectedProbability float64
delay time.Duration
resultsTimeAgo time.Duration
}{
// If no other information is available, use the direct
// probability.
@@ -526,7 +535,7 @@ func TestComputeProbability(t *testing.T) {
"time",
directProbability: 1.0,
otherResults: []bool{true},
delay: decayTime,
resultsTimeAgo: decayTime,
expectedProbability: 1.00,
},
// A failure that was experienced some time ago won't influence
@@ -535,7 +544,7 @@ func TestComputeProbability(t *testing.T) {
name: "success, single fail, decay time",
directProbability: 1.0,
otherResults: []bool{false},
delay: decayTime,
resultsTimeAgo: decayTime,
expectedProbability: 0.9314,
},
// Information from a long time ago doesn't have any effect.
@@ -543,7 +552,7 @@ func TestComputeProbability(t *testing.T) {
name: "success, single fail, long ago",
directProbability: 1.0,
otherResults: []bool{false},
delay: 10 * decayTime,
resultsTimeAgo: 10 * decayTime,
expectedProbability: 1.0,
},
{
@@ -552,7 +561,7 @@ func TestComputeProbability(t *testing.T) {
otherResults: []bool{
true, true, true, true, true,
},
delay: decayTime,
resultsTimeAgo: decayTime,
expectedProbability: 0.269,
},
// Very recent info approaches the case with no time decay.
@@ -562,9 +571,22 @@ func TestComputeProbability(t *testing.T) {
otherResults: []bool{
true, true, true, true, true,
},
delay: decayTime / 10,
resultsTimeAgo: decayTime / 10,
expectedProbability: 0.741,
},
// If we have recent info on the direct probability, we don't
// include node-wide info. Here we check that a recent failure
// is not pinned to a high probability by many successes on the
// node.
{
name: "recent direct result",
directProbability: 0.1,
recentDirectResult: true,
otherResults: []bool{
true, true, true, true, true,
},
expectedProbability: 0.1,
},
}
estimator := BimodalEstimator{
@@ -580,9 +602,19 @@ func TestComputeProbability(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
then := time.Unix(0, 0)
results := makeNodeResults(test.otherResults, then)
now := then.Add(test.delay)
var directResultTime time.Time
if test.recentDirectResult {
directResultTime = now.Add(-decayTime / 2)
} else {
directResultTime = now.Add(-2 * decayTime)
}
resultsTime := now.Add(-test.resultsTimeAgo)
results := makeNodeResults(
test.otherResults, resultsTime,
directResultTime,
)
p := estimator.calculateProbability(
test.directProbability, now, results, toNode,