@@ -57,11 +57,35 @@ const (
57
57
outboxChanBuffer = 0
58
58
// maxMessageSize is the maximum size of the batched payload
59
59
maxMessageSize = 512 * 1024
60
- // tagPrefix is the tag given to peers associated an engine
61
- tagPrefix = "bs-engine-%s"
60
+ // tagFormat is the tag given to peers associated an engine
61
+ tagFormat = "bs-engine-%s -%s"
62
62
63
- // tagWeight is the default weight for peers associated with an engine
64
- tagWeight = 5
63
+ // queuedTagWeight is the default weight for peers that have work queued
64
+ // on their behalf.
65
+ queuedTagWeight = 10
66
+
67
+ // the alpha for the EWMA used to track short term usefulness
68
+ shortTermAlpha = 0.5
69
+
70
+ // the alpha for the EWMA used to track long term usefulness
71
+ longTermAlpha = 0.05
72
+
73
+ // long term ratio defines what "long term" means in terms of the
74
+ // shortTerm duration. Peers that interact once every longTermRatio are
75
+ // considered useful over the long term.
76
+ longTermRatio = 10
77
+
78
+ // long/short term scores for tagging peers
79
+ longTermScore = 10 // this is a high tag but it grows _very_ slowly.
80
+ shortTermScore = 10 // this is a high tag but it'll go away quickly if we aren't using the peer.
81
+ )
82
+
83
+ var (
84
+ // how frequently the engine should sample usefulness. Peers that
85
+ // interact every shortTerm time period are considered "active".
86
+ //
87
+ // this is only a variable to make testing easier.
88
+ shortTerm = 10 * time .Second
65
89
)
66
90
67
91
// Envelope contains a message for a Peer.
@@ -105,7 +129,8 @@ type Engine struct {
105
129
106
130
peerTagger PeerTagger
107
131
108
- tag string
132
+ tagQueued , tagUseful string
133
+
109
134
lock sync.Mutex // protects the fields immediatly below
110
135
// ledgerMap lists Ledgers by their Partner key.
111
136
ledgerMap map [peer.ID ]* ledger
@@ -123,18 +148,118 @@ func NewEngine(ctx context.Context, bs bstore.Blockstore, peerTagger PeerTagger)
123
148
workSignal : make (chan struct {}, 1 ),
124
149
ticker : time .NewTicker (time .Millisecond * 100 ),
125
150
}
126
- e .tag = fmt .Sprintf (tagPrefix , uuid .New ().String ())
151
+ e .tagQueued = fmt .Sprintf (tagFormat , "queued" , uuid .New ().String ())
152
+ e .tagUseful = fmt .Sprintf (tagFormat , "useful" , uuid .New ().String ())
127
153
e .peerRequestQueue = peertaskqueue .New (peertaskqueue .OnPeerAddedHook (e .onPeerAdded ), peertaskqueue .OnPeerRemovedHook (e .onPeerRemoved ))
128
154
go e .taskWorker (ctx )
155
+ go e .scoreWorker (ctx )
129
156
return e
130
157
}
131
158
159
+ // scoreWorker keeps track of how "useful" our peers are, updating scores in the
160
+ // connection manager.
161
+ //
162
+ // It does this by tracking two scores: short-term usefulness and long-term
163
+ // usefulness. Short-term usefulness is sampled frequently and highly weights
164
+ // new observations. Long-term usefulness is sampled less frequently and highly
165
+ // weights on long-term trends.
166
+ //
167
+ // In practice, we do this by keeping two EWMAs. If we see an interaction
168
+ // within the sampling period, we record the score, otherwise, we record a 0.
169
+ // The short-term one has a high alpha and is sampled every shortTerm period.
170
+ // The long-term one has a low alpha and is sampled every
171
+ // longTermRatio*shortTerm period.
172
+ //
173
+ // To calculate the final score, we sum the short-term and long-term scores then
174
+ // adjust it ±25% based on our debt ratio. Peers that have historically been
175
+ // more useful to us than we are to them get the highest score.
176
+ func (e * Engine ) scoreWorker (ctx context.Context ) {
177
+ ticker := time .NewTicker (shortTerm )
178
+ defer ticker .Stop ()
179
+
180
+ type update struct {
181
+ peer peer.ID
182
+ score int
183
+ }
184
+ var (
185
+ lastShortUpdate , lastLongUpdate time.Time
186
+ updates []update
187
+ )
188
+
189
+ for i := 0 ; ; i = (i + 1 ) % longTermRatio {
190
+ var now time.Time
191
+ select {
192
+ case now = <- ticker .C :
193
+ case <- ctx .Done ():
194
+ return
195
+ }
196
+
197
+ // The long term update ticks every `longTermRatio` short
198
+ // intervals.
199
+ updateLong := i == 0
200
+
201
+ e .lock .Lock ()
202
+ for _ , ledger := range e .ledgerMap {
203
+ ledger .lk .Lock ()
204
+
205
+ // Update the short-term score.
206
+ if ledger .lastExchange .After (lastShortUpdate ) {
207
+ ledger .shortScore = ewma (ledger .shortScore , shortTermScore , shortTermAlpha )
208
+ } else {
209
+ ledger .shortScore = ewma (ledger .shortScore , 0 , shortTermAlpha )
210
+ }
211
+
212
+ // Update the long-term score.
213
+ if updateLong {
214
+ if ledger .lastExchange .After (lastLongUpdate ) {
215
+ ledger .longScore = ewma (ledger .longScore , longTermScore , longTermAlpha )
216
+ } else {
217
+ ledger .longScore = ewma (ledger .longScore , 0 , longTermAlpha )
218
+ }
219
+ }
220
+
221
+ // Calculate the new score.
222
+ //
223
+ // The accounting score adjustment prefers peers _we_
224
+ // need over peers that need us. This doesn't help with
225
+ // leeching.
226
+ score := int ((ledger .shortScore + ledger .longScore ) * ((ledger .Accounting .Score ())* .5 + .75 ))
227
+
228
+ // Avoid updating the connection manager unless there's a change. This can be expensive.
229
+ if ledger .score != score {
230
+ // put these in a list so we can perform the updates outside _global_ the lock.
231
+ updates = append (updates , update {ledger .Partner , score })
232
+ ledger .score = score
233
+ }
234
+ ledger .lk .Unlock ()
235
+ }
236
+ e .lock .Unlock ()
237
+
238
+ // record the times.
239
+ lastShortUpdate = now
240
+ if updateLong {
241
+ lastLongUpdate = now
242
+ }
243
+
244
+ // apply the updates
245
+ for _ , update := range updates {
246
+ if update .score == 0 {
247
+ e .peerTagger .UntagPeer (update .peer , e .tagUseful )
248
+ } else {
249
+ e .peerTagger .TagPeer (update .peer , e .tagUseful , update .score )
250
+ }
251
+ }
252
+ // Keep the memory. It's not much and it saves us from having to allocate.
253
+ updates = updates [:0 ]
254
+ }
255
+ }
256
+
132
257
func (e * Engine ) onPeerAdded (p peer.ID ) {
133
- e .peerTagger .TagPeer (p , e .tag , tagWeight )
258
+ e .peerTagger .TagPeer (p , e .tagQueued , queuedTagWeight )
134
259
}
135
260
136
261
func (e * Engine ) onPeerRemoved (p peer.ID ) {
137
- e .peerTagger .UntagPeer (p , e .tag )
262
+ e .peerTagger .UntagPeer (p , e .tagQueued )
138
263
}
139
264
140
265
// WantlistForPeer returns the currently understood want list for a given peer
0 commit comments