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

Add MinIdleConnsHeadroom support #10

Merged
merged 7 commits into from
Feb 21, 2023
Merged

Conversation

pracucci
Copy link

@pracucci pracucci commented Feb 15, 2023

Currently memcached client allows to configure the max number of idle connections. These idle connections are never closed, even if unused for a long time. There's some tensions on the value you set for idle connections:

  • Too low: new connections are opened for each Get operation when your concurrency is > max idle connections
  • Too high: a short spike in Get concurrency will open new connections which then are kept idle until the process restarts even if unused

In this PR I'm proposing to add a MinIdleConnsHeadroom config option. When configured, the client periodically closes idle connections up until MinIdleConnsHeadroom percentage of the recently used connections. If MinIdleConnsHeadroom is not configured (zero value) there's no difference compared to previous version.

In order to periodically close the connections, the client now runs a maintenance goroutine. The client goroutine can be stopped calling the new Close() function.

Other the new unit tests, I've manually tested it in Mimir (see grafana/mimir#4249) and looks working as designed.

memcache/memcache.go Outdated Show resolved Hide resolved
@colega
Copy link

colega commented Feb 16, 2023

I would consider an alternative logic: define the MinIdleConnsHeadroom as the percentage of conns using the last specified period.

Let me explain myself: consider a 1 minute period, if a connection has been used during that last 1 minute period, then we don't consider it "idle" at all: it is idle in terms of the network, but we know that some load spike during the last minute has used it, so we shouldn't really close it. Let's call these connections used during the last 1 minute "recently used".

Let's say we have 100 active connections, then with MinIdleConnsHeadroom=50 we should leave 50 really-idle connections on top of those 100 recently used, to account for the possible spikes.

If the use decreases, let's say to 50 active conns, then we'll close connections until we have 75 (50 recently used + 25 idle). This will not happen in a single close, as the number of active connections will naturally decrease over time.

I see two advantages in this:

  • The number of idle connections we keep depends on the number of used ones, rather than an absolute minimum.
  • The logic is (IMO) simpler to implement: take the slice of conns, count the recently used ones, and then skip the percentage of idle we want to keep, and close the rest.

This is just an alternative idea to consider, I'm not saying there's something wrong with your logic 👍

@pracucci
Copy link
Author

I would consider an alternative logic: define the MinIdleConnsHeadroom as the percentage of conns using the last specified period.

I like your idea because you don't have to think about the absolute minimum value to set. I'm going to change the implementation.

@pracucci pracucci changed the title Add MinIdleConns support Add MinIdleConnsHeadroom support Feb 18, 2023
Signed-off-by: Marco Pracucci <marco@pracucci.com>
Copy link

@colega colega left a comment

Choose a reason for hiding this comment

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

LGTM, thanks!

// 2. Keep at least 1 idle connection.
numRecentlyUsed := len(freeConnections) - numIdle
numIdleToKeep := int(math.Max(1, math.Ceil(float64(numRecentlyUsed)*minIdleHeadroom)))
numIdleToClose := len(freeConnections) - numRecentlyUsed - numIdleToKeep
Copy link

Choose a reason for hiding this comment

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

Maybe this is easier to follow? Not a strong opinion:

Suggested change
numIdleToClose := len(freeConnections) - numRecentlyUsed - numIdleToKeep
numIdleToClose := numIdle - numIdleToKeep

Copy link
Author

Choose a reason for hiding this comment

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

👍 Done in c873d2f.

@@ -238,13 +278,91 @@ func (c *Client) netTimeout() time.Duration {
return DefaultTimeout
}

func (c *Client) minIdleConnsHeadroom() float64 {
if c.MinIdleConnsHeadroom < 0 || c.MinIdleConnsHeadroom >= 1 {
Copy link

Choose a reason for hiding this comment

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

Why MinIdleConnsHeadroom can't be >= 1? It's valid to want to have twice as much connections as the recently used ones. Doesn't make much sense, but it's valid.

Copy link
Author

Choose a reason for hiding this comment

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

Reason was that to me didn't make much sense, but doesn't hurt to support it. Done in 4413597.


// Compute the number of connections to close, honoring two properties:
// 1. Keep a number of idle connections equal to the configured headroom.
// 2. Keep at least 1 idle connection.
Copy link

@colega colega Feb 20, 2023

Choose a reason for hiding this comment

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

Why do we need to keep at least 1 idle connection? You already have the number of recently used ones, you may not want to have any extra ones.

Which implies: should we allow a minIdleHeadroom=0 here? I think we should.

Copy link
Author

Choose a reason for hiding this comment

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

Agree. Done in 4413597.

@colega
Copy link

colega commented Feb 20, 2023

Left some opinionated comments, they're not blockers, but consider thinking about them.

…anged the value to the range 0-100+. A negative value disable the min idle connections headroom logic.

Signed-off-by: Marco Pracucci <marco@pracucci.com>
Signed-off-by: Marco Pracucci <marco@pracucci.com>
Signed-off-by: Marco Pracucci <marco@pracucci.com>
Signed-off-by: Marco Pracucci <marco@pracucci.com>
Signed-off-by: Marco Pracucci <marco@pracucci.com>
…oom percentage is set to a large number

Signed-off-by: Marco Pracucci <marco@pracucci.com>
@pracucci
Copy link
Author

Thanks @colega for your review. I should have addressed all comments, plus added few more test cases and made Close() idempotent. Can you take another look, please?

Copy link

@colega colega left a comment

Choose a reason for hiding this comment

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

Looks awesome! Thank you!

@pracucci pracucci merged commit 6cde04b into master Feb 21, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants