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

coalesce writes #54

Merged
merged 8 commits into from
May 21, 2019
Merged

coalesce writes #54

merged 8 commits into from
May 21, 2019

Conversation

vyzo
Copy link
Contributor

@vyzo vyzo commented May 11, 2019

This is an experiment in adding a write coalescer to mplex. Although I do think we need a generic one that sites between the security module and the multiplexer, perhaps instantiated at user's choice.

@vyzo vyzo requested a review from Stebalien May 11, 2019 12:46
@ghost ghost assigned vyzo May 11, 2019
@ghost ghost added the status/in-progress In progress label May 11, 2019
@vyzo
Copy link
Contributor Author

vyzo commented May 11, 2019

test failures are #47

@marten-seemann
Copy link
Contributor

I’m not sure if this belongs in a stream multiplexer. The stream multiplexer’s responsibility is to, well, multiplex streams.
Having a 100ms delay for sending a small message doesn’t seem like something every application protocol would want. That’s why I’d argue that this belongs in a layer above the stream multiplexer.

@vyzo
Copy link
Contributor Author

vyzo commented May 11, 2019

Maybe so, but we do have a problem with doing small writes all over the place.

@vyzo
Copy link
Contributor Author

vyzo commented May 11, 2019

An alternative is to have a generic coalescer (not a protocol specific one, like here), and put it under the multiplexer, perhaps at a user's preference.

@marten-seemann
Copy link
Contributor

marten-seemann commented May 11, 2019 via email

raulk
raulk previously requested changes May 11, 2019
Copy link
Member

@raulk raulk left a comment

Choose a reason for hiding this comment

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

This is an interesting finding and experiment, but we really don’t want this at this layer.

I agree that this doesn’t belong in a multiplexer. Even if we pushed it down the stack, it would belong in the transport.

But let’s not add a delay to everything. There’s a reason why people disable Nagle in most backend processes.

The application protocol is the one that knows its write pattern, and whether its use case is latency-sensitive or not. Ideally we should expose metadata about the transport at play: is fragmentation costly or not? In UDP the answer might be no, in TCP it would be yes, all of this omitting the cost of the syscall.

Providing a toolbox utility that protocols can opt in, like Marten suggests, is a reasonable tradeoff.

I’m also worried we’re fine-tuning a lot for the specific usecase of relay infrastructure.

@vyzo
Copy link
Contributor Author

vyzo commented May 11, 2019

@raulk I wasn't really intending to merge this in mplex, it was an experiment to open the conversation.
I would like this to be a generic option, which the user can configure, and would be turned on in relays.

@vyzo vyzo changed the title coalesce writes [DO NOT MERGE] coalesce writes May 11, 2019
@vyzo
Copy link
Contributor Author

vyzo commented May 11, 2019

Marked as DO NOT MERGE.

@vyzo
Copy link
Contributor Author

vyzo commented May 11, 2019

Here is a proposal: We can add the coaslescer under the multiplexer and also extend our NewStream interface to provide options. One such option would be for a real-time stream, which instructs the coalescer to send immediately (together with what data it might have accumulated).
We are going to have to extend our stream interface in order to handle the closure/reset mess, so we might as well take the opportunity to do this there.

@vyzo
Copy link
Contributor Author

vyzo commented May 11, 2019

Continuation of discussion in libp2p/go-libp2p#633

Will close it now, but do not delete the branch as it is in use in relays.

@vyzo vyzo closed this May 11, 2019
@ghost ghost removed the status/in-progress In progress label May 11, 2019
@vyzo vyzo reopened this May 13, 2019
@ghost ghost added the status/in-progress In progress label May 13, 2019
@vyzo
Copy link
Contributor Author

vyzo commented May 13, 2019

reopened this so that we avoid deleting the branch by accident.

@vyzo
Copy link
Contributor Author

vyzo commented May 21, 2019

@raulk would you be comfortable merging with a configurable coalesce delay, that defaults to 1ms?
The relays can set it to 100ms.

Note that we are adding write coalescing to yamux as well in whyrusleeping/yamux#28

@vyzo vyzo changed the title [DO NOT MERGE] coalesce writes coalesce writes May 21, 2019
@vyzo vyzo requested a review from raulk May 21, 2019 09:12
@marten-seemann
Copy link
Contributor

Where is the main source of small writes? I’d still prefer to fix it there.
Introducing artificial delays might break applications, most obviously the ping protocol, but also other real time protocols.

@vyzo
Copy link
Contributor Author

vyzo commented May 21, 2019

1ms delay will hardly break anything.
The writes are coming from multiple streams, so they need to be combined in the multiplexer or under it.

@vyzo
Copy link
Contributor Author

vyzo commented May 21, 2019

In short, we can't fix it above the multiplexer, it either has to be in the multiplexer or below it.

Copy link
Member

@Stebalien Stebalien left a comment

Choose a reason for hiding this comment

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

I think we can simplify this a bit but it should work. The only thing we really need to change is the default delay.

multiplex.go Outdated
@@ -37,6 +37,8 @@ var ErrInvalidState = errors.New("received an unexpected message from the peer")
var (
NewStreamTimeout = time.Minute
ResetStreamTimeout = 2 * time.Minute

WriteCoalesceDelay = 1 * time.Millisecond
Copy link
Member

Choose a reason for hiding this comment

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

Let's default to 100us for now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok, is that the default in yamux too?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ah it's 10us in yamux.

n := copy(buf, data)
pool.Put(data)

if !mp.writeTimerFired {
Copy link
Member

Choose a reason for hiding this comment

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

I'd just combine this with handleOutgoing and avoid storing the timer on the instance (makes it clear that it's local only).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Then we'd have to explicitly pass it to writeMsg and mutate in there, which makes it a little messy.
It seemed nicer to just have the timer in the instance.

Copy link
Member

Choose a reason for hiding this comment

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

My suggestion was to ditch writeMsg and combine everything. But we can do that later.

defer pool.Put(buf)

n := copy(buf, data)
pool.Put(data)
Copy link
Member

Choose a reason for hiding this comment

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

Wouldn't it be simpler to just use a pool of buffered writers?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

hrm, maybe. I find it simpler to reason with the buffer actually.

@Stebalien
Copy link
Member

Well, except that the tests are failing.

multiplex.go Outdated
case data := <-mp.writeCh:
err := mp.writeMsg(data)
if err != nil {
log.Warningf("Error writing data: %s", err.Error())
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't we return here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We probably should. There is an implicit return because the write failure closes the connection, which triggers the shutdown channel, but better to be explicit.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done.

@vyzo
Copy link
Contributor Author

vyzo commented May 21, 2019

The test failure is #47 -- this test has been failing for a while.

@vyzo vyzo requested a review from Stebalien May 21, 2019 19:10
@vyzo
Copy link
Contributor Author

vyzo commented May 21, 2019

The test passes if I add a small delay.

@vyzo
Copy link
Contributor Author

vyzo commented May 21, 2019

Fixed the test as well.

@vyzo vyzo dismissed raulk’s stale review May 21, 2019 19:58

main objection (delay too large) has been lifted, raul unavailable.

@vyzo vyzo merged commit aaf9be9 into master May 21, 2019
@vyzo vyzo deleted the feat/coalesce-write branch May 21, 2019 21:17
Copy link
Member

@raulk raulk left a comment

Choose a reason for hiding this comment

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

Only got to it after the fact.

// only return this error if we don't *already* have an error from the write.
if err2 := mp.con.SetWriteDeadline(time.Time{}); err == nil && err2 != nil {
return err2
buf := pool.Get(4096)
Copy link
Member

Choose a reason for hiding this comment

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

Can we hold on to a static byte slice to avoid the pool operation? This method is not called concurrently, so it should be safe.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sure, that's probably a worthwhile optimization.

Copy link
Member

Choose a reason for hiding this comment

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

That'll take 4KiB when idle which can add up pretty quickly.

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

Successfully merging this pull request may close these issues.

4 participants