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

Avoid unnecessary system calls in redisAsyncCommand() #1089

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

tezc
Copy link
Contributor

@tezc tezc commented Aug 27, 2022

Currently, this is how async operations work:

1- Append new command to the output buffer.
2- __redisAsyncCommand() registers write event unconditionally.
3- redisAsyncWrite() will be called on the next iteration of the event loop.
4- Write event will be cleared.

Steps 2 and 4 are system calls. They introduce unnecessary overhead.

For better performance, we can directly write to the socket(step-3)
without registering a write event. Still, it is better to detect if the write
event is already registered. In this case, we won't try to write to the socket
as it will probably fail (because the socket buffer is full).

@michael-grunder michael-grunder self-assigned this Aug 29, 2022
@michael-grunder
Copy link
Collaborator

This looks correct to me at first glance, although I didn't write the async layer.

How big a performance improvement are you seeing with it, assuming you're using the new logic?

@tezc
Copy link
Contributor Author

tezc commented Sep 1, 2022

Hi, @michael-grunder

I see %2 - %20 improvement in my use case. (I use hiredis as part of a bigger project, so a loop benchmark with hiredis commands only probably would give us even better results).

I'm pretty sure this is how we are supposed to use event loops. (Write to socket first, if it is partial, register for the write event).

The downside is, now inside redisAsyncCommand(), we can call __redisAsyncDisconnect() which will trigger disconnect/cleanup callbacks. Not sure if this is a problem or surprising to anybody. Other than that, I don't see a problem but maybe I'm missing something, let's bring more people with async API experience, @yossigo wdty?

Edit: Maybe another side effect is about batching. Today, you can call redisAsyncCommand() multiple times but it will call redisAsyncWrite() once when the write event is triggered. With this PR, this is not going to be the case. I'm not sure if anybody has any assumptions on this.

@yossigo
Copy link
Member

yossigo commented Sep 1, 2022

@tezc I agree that generally, one should create the write event only after failing to write. In this specific case, I think it will take a bit more to handle it well.

Invoking the disconnect callback directly from redisAsyncCommand() is a significant breaking change and makes the API less intuitive. We could return an error and defer the callback to the next event loop iteration, but that will be a lot more complex than this simple change.

The other point is that the async interface does not have explicit pipelining support (like redisAppendCommand()), so pipelining has to rely on this behavior, and we'll need to come up with an alternative.

Another option to consider is to create a new redisUnbufferredAsyncCommand(), to be used when the extra delay is not desired.

@michael-grunder
Copy link
Collaborator

I think the improvement is worth making, but we'll need to make sure not to introduce any breaking changes.

I'm planning on putting together v1.1.0 this weekend, and then we can revisit this PR for the next release.

@tezc
Copy link
Contributor Author

tezc commented Sep 2, 2022

Okay, I'll see what I can do. Sure, we can revisit this later.

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.

3 participants