MRogers and Anonymous have been arguing that we should limit load only on the 
immediate bandwidth level. Some of the thread is below, and my response 
including a proposal for a bandwidth limiting mechanism:

----- mrogers at UU62+3E1vKT1k+7fR0Gx7ZN2IB0 ----- 2007.06.14 - 21:32:43GMT 
-----

No, what I've been arguing is that we don't need to *explicitly* take the 
lifetime overhead into account, because the feedback loop should be slow 
enough that it will *implicitly* take it into account anyway. If the feedback 
loop takes a few minutes to adapt (which it must if we don't want to 
over-adapt to short-term conditions) then we're implicitly measuring the 
lifetime overhead of a search, and we don't need to make an additional 
explicit estimate of the lifetime overhead.

----- toad at zceUWxlSaHLmvEMnbr4RHnVfehA ----- 2007.06.15 - 17:12:52GMT -----

What makes you think it would work at all? It seems to me that it would simply 
oscillate - we accept too many requests, most of them time out, the we accept 
too many requests again, and most of them timeout again. KISS is one thing, 
but not slitting your own throat with occam's razor is another!

----- mrogers at UU62+3E1vKT1k+7fR0Gx7ZN2IB0 ----- 2007.06.15 - 21:44:34GMT 
-----

:) If the feedback loop is slow enough it shouldn't oscillate - that applies 
equally to bandwidth liability estimation, token passing or any other 
mechanism - we musn't get into the situation of saying "I accepted 100 
requests in the last 10 seconds and nothing bad has happened yet, therefore I 
can accept another 100". As far as I can see, the only way to avoid that 
situation is by adapting very slowly, ie on the order of the longest timeout. 
Luckily we expect the connections to be relatively long-lived (minutes or 
hours) so we can afford to take a few minutes to get up to speed.

Assuming that we have a suitably slow feedback loop in place, the next 
question is how to tell our peers when we're ready to accept another search. 
We could use tokens, preemptive rejection or blocking sockets. I don't have 
any religious opinions on this issue, but I thought Anonymous made a fairly 
good case for handling it in the same way as any other network app: don't 
read from the socket until you're ready to process more data. I realise 
there's a highly variable relationship between bytes in and bytes out, but 
regardless of which mechanism we use we'll have to rely on the slow feedback 
loop to smooth that out, because so far nobody's suggested a way of dealing 
with it that doesn't involve favouring some kinds of traffic over others. (I 
hope we agree that we don't want the whole network to end up favouring SSK 
traffic over CHK traffic just because it happens to come in smaller quanta. 
Maybe there are reasons for giving SSK traffic priority, but that isn't one 
of them.)

----- toad at zceUWxlSaHLmvEMnbr4RHnVfehA ----- 2007.06.18 - 14:25:49GMT -----

It's not possible using the mechanism you propose *on its own*, while it *is* 
interesting. Here's why: I have 48kB/sec output. With your proposed 
mechanism, the fact that I have 1MB/sec input won't matter. So a leaf node 
requests a splitfile through me, which is all in the network, but is not in 
my datastore. I route his requests. It takes 5 seconds for enough of the 
requests to start transferring to make a difference. Over that 5 seconds, 
he's sent me 240kB of requests. That's around 240k/50 = 4,915 requests. Which 
will yield 160MB of data. Unfortunately it will take me 160M/48k = nearly an 
hour to transfer all the data.

This is not acceptable, because we want Freenet requests to complete in a 
reasonable amount of time - if only for fproxy support, which IMHO is an 
important application. And I don't like the idea of having different request 
priorities either; it gives away information on the traffic and allows for 
Bad Things.

Imposing an arbitrary limit on the number of requests running is not the 
solution either. A limit may be imposed because of threads/memory usage, but 
this is likely to be heavily architecture dependant. The solution IMHO is to 
take into account likely future bandwidth usage. If we want to guarantee that 
there are no timeouts, this means bandwidth liability limiting. The current 
code will accept an SSK request only if it could also accept a CHK request, 
and vice versa, so I don't see any reason to think it is excessively biased 
in favour of CHKs. However if you like I will add code to collect the 
probability of rejection for SSKs and CHKs individually.

For data blocks, clearly we cannot send one if there is no available space in 
the congestion window to the peer in question. However we may want the data 
for ourself, or for multiple peers, in which case the slowest peer should not 
necessarily dictate the transfer rate; AFAICS we must accept the data as fast 
as we can manage within memory/cpu/etc limits, and limit the incoming 
bandwidth at a higher level.

So given that we must limit traffic on the request level (as well as the 
congestion control level), how can we do this? We can either:

A) Wait for a request, and then either accept it or reject it, based on e.g. 
how many requests are running already.
PRO:
- Simple
CON:
- Wasteful of time and bandwidth when we have to ask multiple peers before one 
accepts
- Difficult to adapt to propagate load back to source

Queueing doesn't help much here because we still have to complete the 
request - including queueing it - in a reasonable time.

or

B) Tell our peers when they can send us a request. The obvious way to do this 
is to compute our overall quota of requests (or request-success-bytes), and 
allocate it to our peers (and tell them on every packet/response/etc), 
initially equally, but progressively allowing more to busier nodes and less 
to nodes that don't use their quota (but with diminishing returns: a node 
with a low quota that suddenly starts using more will take quota away from an 
established heavy requestor). Thus, initially, if most nodes aren't busy we 
won't run many requests, but as we identify those nodes which need a bigger 
quota, more requests run overall. Note that running fewer requests than we 
could isn't necessarily a problem anyway unless they complete really slowly.

How to avoid excessive misrouting? 
Options:
1: We don't need to queue requests, as they are queued on the next node for us 
(running requests can be regarded as queued requests). When we get a request, 
we identify the set of nodes that we are willing to route it to, and if none 
of them have any free request slots, we reject it.
2: Queueing requests helps, because we can match requests with nodes. When we 
get a request, (if it's allowable by the node's current quota), we queue it. 
When we have a slot in an outgoing node's quota, we send the request which is 
closest to the node's location (which hasn't already been to that node), 
regardless of the time it's been queued for (if multiple nodes have space, we 
find the best request/node pair until they don't or we run out of requests). 
If after a certain period a request hasn't been sent, we reject it. This 
avoids excessive misrouting without requiring arbitrary parameters (as the 
first option does), and sends more specialised requests to slower nodes. 
3: Simulated queueing: remember the locations of recent requests we could have 
sent to a node, and calculate a threshold, which decays over time if we don't 
accept a request (obviously this will result in fewer requests being sent, 
but lower latency).

Does this prevent flooding? Yes: we only accept, and offer, as many requests 
as we can handle, and with the right fairness algorithms, a flood will be 
limited to what the other peers don't need, although if our overall quota 
calculation is too generous flooding may still be an effective attack. 
Obviously on opennet, connecting to several hundred peers and flooding to 
each of them will be fairly effective; opennet sucks, we all know this, it's 
a transition phase.

Does this propagate load back to the originator? Not explicitly, as it's not 
an originator-throttles scheme (such schemes don't prevent flooding and may 
make it easier to identify the originator). However, the rate at which a node 
can send requests is quite clearly limited by the rate at which the rest of 
the network can handle them:

O - A - B - C

If B is a bottleneck, then O will only send requests at the rate that can be 
funneled through it (or satisfied on A).
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 189 bytes
Desc: not available
URL: 
<https://emu.freenetproject.org/pipermail/devl/attachments/20070618/1f587d75/attachment.pgp>

Reply via email to