storj / drpc

drpc is a lightweight, drop-in replacement for gRPC
MIT License
1.49k stars 49 forks source link

Concurrent RPCs #14

Closed zeebo closed 1 year ago

zeebo commented 3 years ago

A common issue that everyone will hit when using DRPC is how to handle concurrent RPCs. We should have some answer for this.

maxtruxa commented 3 years ago

Some thoughts on the topic:

Connection pooling can be one such answer, that can even be used without modifying DRPC at all. But while connection pooling can be an effective technique, it is not always possible. I have a somewhat particular use case where an agent establishes a TCP connection to a server and offers RPC services that the server can then invoke. In other words, the TCP client is the RPC server and the TCP server is the RPC client. That means it's impossible for the RPC client to create new connections. (That is btw the reason I can't use gRPC at all and was very happy when I stumbled upon DRPC, which was extremely easy to hook up this way :))

A more integrated but also more complex solution is stream multiplexing, i.e. sending concurrent streams over a single transport. The wire format supports this already, because each frame contains a stream ID. Stream multiplexing is nice but brings its own set of problems (e.g. head-of-line blocking). For any kind of stream (server, client or bidirectional), some sort of back pressure mechanism is probably required to prevent the remote from being flooded with messages if it can't keep up - which would negatively impact or even kill other streams using the same connection. This is not an issue with the current non-multiplexing behavior, when a transport with its own pressure mechanism is used (e.g. TCP).

zeebo commented 3 years ago

Connection pooling can be one such answer, that can even be used without modifying DRPC at all. But while connection pooling can be an effective technique, it is not always possible. I have a somewhat particular use case where an agent establishes a TCP connection to a server and offers RPC services that the server can then invoke. In other words, the TCP client is the RPC server and the TCP server is the RPC client. That means it's impossible for the RPC client to create new connections.

Connection pooling is currently the answer that Storj uses (https://pkg.go.dev/storj.io/common/rpc/rpcpool) and works well. I think the "reverse connection" use case may be niche enough that an in-library answer need not apply to it. I totally didn't anticipate that, so it's great that it worked out :smile:.

Stream multiplexing is nice but brings its own set of problems (e.g. head-of-line blocking).

Yeah, this HoL blocking and buffering were the main problems I wanted to avoid by adding a multiplexing solution at the deepest layers. As you noticed, the wire format initially was meant to allow concurrent streams. At some point, I decided to remove concurrent stream support, and the readers now will error if received stream ids are not monotonically increasing. This greatly simplified the code to read and reassemble packets from the transport.

That said, multiplexing can be added on top of DRPC without changes as well by using something like https://pkg.go.dev/github.com/hashicorp/yamux. Or, if your transport is something like QUIC, you can open new streams from a single session.

In summary, I agree that connection pooling and multiplexing are the two main ways of solving this.


Some scattered thoughts:

bcessa commented 3 years ago

hey hi @zeebo, first of all DRPC is really cool :) thanks for sharing. I recently wrote a library of utilities to use it on production services. The package includes basic things like:

I was wondering if it's ok to add the link here (or somewhere else) for people looking for that kind of thing 😄 not trying to do any form of shameless plug so feel free to remove this message if it's not appropriate to do so 👍🏼

zeebo commented 3 years ago

Awesome! I've added your description and a link to the package to the README. Let me know if it doesn't look right.

Elara6331 commented 1 year ago

Hello, I recently wrote a module for concurrent RPC support with DRPC using stream multiplexing via yamux. It contains drop-in replacements for DRPC's server and conn.

Here are the links:

Can this be added to the README as well for people looking for something like this? Also, I can submit a PR adding it to DRPC as drpcmuxconn and drpcmuxserver if that seems like a good idea.

zeebo commented 1 year ago

Great! I'll add a link to the README. I don't think it's right to add it to the main repo at least in part due to the 3rd party dependency. An external package is great. I reviewed the code and I have a suggestion:

https://gitea.arsenm.dev/Arsen6331/drpc/src/commit/a85a9613bd6a/muxconn/muxconn.go#L39-L45

Everything else looked awesome. Thanks!

Elara6331 commented 1 year ago

Thanks for your suggestions. I've just implemented them.

zeebo commented 1 year ago

There's now a bunch of different options for this.