Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Degrade provider handling quality gracefully under load #730

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

Stebalien
Copy link
Member

@Stebalien Stebalien commented Jul 22, 2021

This PR contains two changes to scale provider handling:

  1. It drops inbound provider records when we're under heavy load.
  2. When under load, it only returns provider records to clients when the DHT node is in the provider record's "closest" bucket.

Ideally we'd have a some form of parallel provider record retrieval from the datastore, but this is still a good first step.

fixes #675

That way, overloaded nodes can drop provides.
And return when the process is closign. This will help speed up the main
loop a bit.
Let's assume there's one (or zero) providers for a record and everyone
is looking for 10 providers.

- Before, peers would wait for all peers along the path to return this
  one provider. However, because there's only one provider, peers won't be
  able to short-circuit anyways.
- Now, peers will go to the end of the path before waiting.

This may make some queries slower, but it attempts to give "priority" to
peers that actually _need_ responses as opposed to peers that are
"optimistically" waiting for responses.
@aschmahmann
Copy link
Contributor

aschmahmann commented Jul 22, 2021

Still need to come back to take a look more carefully, but a few thoughts:

  1. I don't think dropping requests is really a reasonable thing to do unless:
    a. You have some metric you can track telling you how many requests you're dropping
    b. You have an option that allows you to increase the amount of resources you throw at the problem (i.e. Allow the ProviderManager to have more paralleism #729)
  2. If we're concerned about this do we need to worry about fairness and have some sort of round-robin like we do for Bitswap?
    a. Maybe not necessary to fix right now, but this is a server side change so upgrades here are pretty slow
  3. How are we (people who design these networks) going to determine the correct parameters here? Is the idea that we keep the behavior the same by default, but allow some experimentation on busy nodes to determine what their loads tend to look like, before making any more general server-side changes?

@Stebalien
Copy link
Member Author

I don't think dropping requests is really a reasonable thing to do unless:

1.b. #729 (comment)

And yes, you're right, we need to track this.

If we're concerned about this do we need to worry about fairness and have some sort of round-robin like we do for Bitswap?

In theory? But I still feel like this change is a strict improvement. It'll only kick in when overloaded anyways.

How are we (people who design these networks) going to determine the correct parameters here?

Really, I don't think there's too much to tune here.

  1. The limit on the inbound provider queue size is more based on memory than anything. It helps with bursts, but won't actually increase throughput (much).
  2. The limits on the get side should probably be tuned based on the expected latency. But that limit will only kick in if we're (a) under load and (b) not "responsible" for the record, so I'm not really concerned.

@@ -366,7 +390,10 @@ func (dht *IpfsDHT) handleAddProvider(ctx context.Context, p peer.ID, pmes *pb.M
// add the received addresses to our peerstore.
dht.peerstore.AddAddrs(pi.ID, pi.Addrs, peerstore.ProviderAddrTTL)
}
dht.ProviderManager.AddProvider(ctx, key, p)
err := dht.ProviderManager.AddProviderNonBlocking(ctx, key, p)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not do a mybucket style check here as well?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I understand, we're always in the bucket here, right?

@aschmahmann
Copy link
Contributor

In theory? But I still feel like this change is a strict improvement. It'll only kick in when overloaded anyways.

👍

The limit on the inbound provider queue size is more based on memory than anything. It helps with bursts, but won't actually increase throughput (much).

Ok, I think you've reasonably convinced me. Since the datastore is batching it seems reasonable to expect that puts should be quick here (as long as they're not blocked on the event queue by gets). In which case if your datastore's slow there's not much to be done about anything else here (aside from queues for bursts like you mentioned).

However, since Gets can block Puts we still need to do some estimation on queue size (and parallelism) required for what we expect an average node to need. I suspect this won't be so awful given that the existing networks are pretty functional without this, so we mostly need some conservative estimates and let power users like infra providers tune more accurately over time.

@Stebalien
Copy link
Member Author

However, since Gets can block Puts we still need to do some estimation on queue size (and parallelism) required for what we expect an average node to need. I suspect this won't be so awful given that the existing networks are pretty functional without this, so we mostly need some conservative estimates and let power users like infra providers tune more accurately over time.

What about having get workers? Basically, we could:

  1. Quickly check the cache (inline).
  2. If not there, pass the task off to a get worker.
  3. Get the result back and cache it.

Doing this without blocking the main event loop is going to be a bit tricky, but doable.

@petar petar mentioned this pull request Jul 30, 2021
9 tasks
@petar petar changed the title Scale provider handling Degrade provider handling quality gracefully under load Jul 30, 2021
@mvdan
Copy link

mvdan commented Aug 10, 2021

There's a bunch of servers using this library to subscribe to a topic and publish messages to one another, and from time to time there are huge goroutine spikes that straight up take the process down due to memory usage:
image

I'm fairly certain that I'm experiencing the same issue that Steven is trying to tackle here. All of those goroutines get stuck on the "select" trying to get their incoming request handled, in calls like (*ProviderManager).AddProvider and (*ProviderManager).GetProviders.

It's yet unclear why there are suddenly those spikes in requests, but at least I want the processes to not get taken down that easily by just a spike of 50-100k requests. So I think this pull request would help.

@Stebalien do you need help with reviews or testing? I'm not an expert in go-libp2p or this particular library, but I'm happy to help where I can or run this branch for a few days, if you think it's ready enough to give it a go.

@Stebalien
Copy link
Member Author

The one thing I still wanted was "get" workers. At the moment, gets are serial and puts can easily get backed up on a single slow get.

@aschmahmann aschmahmann marked this pull request as draft September 15, 2021 14:57
@aschmahmann
Copy link
Contributor

@petar when performance of large DHT nodes (e.g. the hydras) comes up on your radar again could you take a look at this and decide if it's ready for merge as-is, or what changes need to be made?

mvdan added a commit to mvdan/go-dvote that referenced this pull request Sep 21, 2021
This pulls in libp2p/go-libp2p-kad-dht#730,
which hangs on a branch shortly after 0.12.2.

Essentially, this handles priority for requests a bit better,
and drops unimportant requests if they come in too fast.
This should prevent kad-dht from using tons of memory.

Updates vocdoni#243.
p4u pushed a commit to vocdoni/vocdoni-node that referenced this pull request Sep 21, 2021
This pulls in libp2p/go-libp2p-kad-dht#730,
which hangs on a branch shortly after 0.12.2.

Essentially, this handles priority for requests a bit better,
and drops unimportant requests if they come in too fast.
This should prevent kad-dht from using tons of memory.

Updates #243.
jordipainan pushed a commit to vocdoni/vocdoni-node that referenced this pull request Sep 23, 2021
This pulls in libp2p/go-libp2p-kad-dht#730,
which hangs on a branch shortly after 0.12.2.

Essentially, this handles priority for requests a bit better,
and drops unimportant requests if they come in too fast.
This should prevent kad-dht from using tons of memory.

Updates #243.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Drop inbound provider records when overloaded
3 participants