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

[MRG] add "sticky builds" functionality #949

Merged
merged 10 commits into from
Sep 25, 2019

Conversation

betatim
Copy link
Member

@betatim betatim commented Sep 12, 2019

The ranking is computed using the Rendez-vous hashing algorithm.

In the abstract this can be used for all sorts of stateless ranking, however this PR is part of #946 (comment) and provides a method for ranking possible nodes to schedule build pods on.

The goal is to eventually have a setup where rendezvous_rank() is called just before submitting the build pod spec to compute a preferred node for the pod:

ranking = rendezvous_rank(['node1', 'node2', 'node3'], 'https://github.com/org/repo')
# now attach ranking[0][1] as the preferred node affinity to the build pod

Let me know what you think of building this in small PRs. I'd propose to review and merge this without having to build out the rest.

The ranking is computed using the Rendez-vous hashing algorithm.
@betatim betatim requested a review from minrk September 12, 2019 05:51
@minrk minrk requested a review from consideRatio September 12, 2019 08:56
@minrk
Copy link
Member

minrk commented Sep 12, 2019

I'm happy to do this in small PRs if that feels right. It does make more reviewable bite-size chunks, but at the same time it makes it harder to figure out "why is it like this?" when the answer is in an as-yet unwritten PR.

So my rough idea for breaking into smaller PRs:

  • do the PRs make sense on their own (a dedicated PR for an unused utility might not, for instance)
  • is it clear what early PRs should look like before the later ones are written (i.e. do we know that the signature for this utility should be before we write the code that uses it? That may be easy for something like picking an item from a list, but harder for something more complex)

So if I were breaking this into bits, I would probably do it the other way around - start with the PR where we add the ability to pick a node, with a no-op implementation to start, then the PR implementing a strategy using rendezvous hashing, etc.

@betatim
Copy link
Member Author

betatim commented Sep 12, 2019

That makes sense. I'll try again with a second PR from the other end.

Copy link
Member

@consideRatio consideRatio left a comment

Choose a reason for hiding this comment

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

This PR ended up really fun to review, I ended up thinking a lot about many things and learning more about BinderHub. I found some ideas on improvements I hope you find concrete enough to apply if you agree they make sense!

binderhub/app.py Show resolved Hide resolved
binderhub/utils.py Outdated Show resolved Hide resolved
binderhub/utils.py Outdated Show resolved Hide resolved
binderhub/utils.py Outdated Show resolved Hide resolved
binderhub/utils.py Outdated Show resolved Hide resolved
binderhub/utils.py Outdated Show resolved Hide resolved
binderhub/tests/test_utils.py Outdated Show resolved Hide resolved
binderhub/build.py Outdated Show resolved Hide resolved
@consideRatio
Copy link
Member

Notes

  • A good learning resource about the algorithms.
  • We could support sticky build pods even without dind enabled, but our current way of finding the valid nodes to schedule on (which isn't obvious because of custom required affinities etc) is made by identifying the dind pods within the namespace.

Copy link
Member

@consideRatio consideRatio left a comment

Choose a reason for hiding this comment

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

About the algorithm tests

Currently we have two tests. I think it is fine to have multiple tests and make them harder in descending sequence so we can fail on simple to understand test cases initially but also ensure statistically that we have a robust algorithm by making a strong test in the end.

binderhub/tests/test_utils.py Show resolved Hide resolved
binderhub/tests/test_utils.py Outdated Show resolved Hide resolved

def test_rendezvous_redistribution():
# check that approximately a third of keys move to the new bucket
# when one is added
Copy link
Member Author

Choose a reason for hiding this comment

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

An idea for how to test that keys move when a new bucket becomes available and that the pattern of movement is right. I think this is how it should be but not sure. WDYT?

Copy link
Member

Choose a reason for hiding this comment

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

Excellent!

Since we actually hash "nodes-key", this test doesn't need to be run multiple times, but if we would hash node separately from key and as consistent hashing was described to do as compared to our rendezvous hashing, then we could by fluke have two node hashes that are spaced luckily allow for the new node to to catch 1/3 of the stuff.

But hmmm, could you position nodes like pointers on a clock to initially have a fair share and then also after have a fair share?

Thats a clean test as well to have I think, to check that we have a 1/2 distribution initially and then get a 1/3 distribution after, combined with the previous test about perfectly stable we capture all kinds of logic. You are already doing this to some degree but it is captures mostly by the abs(from_b1 - from_b2) < 10 statement.

assert 0.31 < n_moved / n_keys < 0.35
# keys should move from the two original buckets with approximately
# equal probability
assert abs(from_b1 - from_b2) < 10
Copy link
Member

Choose a reason for hiding this comment

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

Nice that you add a redistribution test, it makes me happy to grasp that this really works as we expect!

Assuming 1000 keys moved from either b1 or b2 and this is considered a coin flip, then the average difference will be sqrt(1000) which is about 31.6.

Copy link
Member Author

Choose a reason for hiding this comment

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

I rounded it to 30. My feeling is that we just need to get the right order of magnitude here that fails the test when something really weird is happening and minimises false alarms.

Copy link
Member

@consideRatio consideRatio left a comment

Choose a reason for hiding this comment

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

This currently LGTM!! Awesome work on this Tim!

I left some notes on the redistribution test but as they passed the narrow range by luck we can stick with it if you want!

@betatim betatim changed the title [MRG] Add function to rank buckets for a key [MRG] add "sticky builds" functionality Sep 25, 2019
assert abs(from_bucket["b1"] - from_bucket["b2"]) < 30
# the initial distribution of keys should be roughly the same
# We pick 30 because it is "about right"
assert abs(start_in["b1"] - start_in["b2"]) < 30
Copy link
Member

Choose a reason for hiding this comment

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

The scale of the about right is different between the from_bucket and start_in difference, on average, the random walk distance if you flip a +1 or -1 over and over, is sqrt(N).

For the from_bucket case it is sqrt(~1000) == ~32 and for the start_in case it is sqrt(3000) == ~55.

@consideRatio
Copy link
Member

Keeps LGTM if you want to self-merge, I don't want to do it as you may want to finish up some detail though.

@betatim betatim merged commit fcc4a4b into jupyterhub:master Sep 25, 2019
@betatim betatim deleted the rendez-vous-hash branch September 25, 2019 12:22
@betatim
Copy link
Member Author

betatim commented Sep 25, 2019

Thanks for the ideas and patience!! I will try to find time today to deploy this on staging and watch what happens.

yuvipanda pushed a commit to jupyterhub/helm-chart that referenced this pull request Sep 25, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants