-
Notifications
You must be signed in to change notification settings - Fork 826
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
Game Server Allocation advanced filtering: player count, state, reallocation #1239
Comments
ObjectiveAlready defined above BackgroundThese are terms that are currently used in Player Tracking:
Requirements and Scale
Design IdeasWe extend the This should be extensible to other types of allocation strategies down the line, as well as having applicability For example, the current way of allocating against Ready GameServers (which would be default, and therefore backward compatible) would become: apiVersion: "allocation.agones.dev/v1"
kind: GameServerAllocation
spec:
required:
matchLabels:
agones.dev/fleet: simple-udp
gameServerState: Ready # Allocate out of the Ready Pool (which would be default, so backward compatible) Wherein:
Therefore, to attempt to find a GameServer that has room for between 2 and 10 players, that is already Allocated, and if apiVersion: "allocation.agones.dev/v1"
kind: GameServerAllocation
spec:
preferred:
- matchLabels:
agones.dev/fleet: simple-udp
gameServerState: Allocated # new state filter: allocate from Allocated servers
players: # new player availability filter
minAvailable: 2
maxAvailable: 10
required:
matchLabels:
agones.dev/fleet: simple-udp
players: # new player availability filter
minAvailable: 2
maxAvailable: 10
gameServerState: Ready # Allocate out of the Ready Pool (which would be default, so backward compatible) Wherein:
This works since the This would also need to eventually be expanded to the gRPC Allocation Service API, but the same format could be reused. Removing GameServers from the pool on re-allocationOne concern with this design is that you could continually get the same GameServer over and over again on re-allocation, which could send way more players to a particular GameServer than may be desired. To solve that problem, we can use already existing Agones alloction constructs to solve this: matchLabel selectors For example, we can extend the example above and also add a user defined label apiVersion: "allocation.agones.dev/v1"
kind: GameServerAllocation
spec:
preferred:
- matchLabels:
agones.dev/fleet: simple-udp
agones.dev/sdk-available: "true" # this is important
gameServerState: Allocated # new state filter: allocate from Allocated servers
players: # new player availability filter
minAvailable: 2
maxAvailable: 10
required:
matchLabels:
agones.dev/fleet: simple-udp
gameServerState: Ready # Allocate out of the Ready Pool (which would be default, so backward compatible)
metadata:
annotations:
waitForPlayers: "2" # user defined data to pass to the game server so it know when to switch back the agones.dev/sdk-available label
labels:
agones.dev/sdk-available: "false" # this removes it from the pool Upon allocation the value of This strategy will need to be documented, but also have applicability outside of only allocating by player count It also allows users to utilise their own labelling names and systems that make sense for their game. The downside here being that the onus is on the user to pass the required information down to the GameServer to know when to add the GameServer back into the pool. Long term, we could look at doing some of this automatically, but it would be best to get user feedback first on this initial implementation before going down that path. Technical Implementation
Alternatives ConsideredNew Allocation PathwayWe could implement a whole new Allocation pathway (which we discussed earlier), which seems untenable. Allocation "policy"Previous design used an allocation policy to switch out strategies. That was deemed too inflexible when compared to |
/cc @domgreen who I know is working on similar things right now |
@pooneh-m do you see any issues here? Especially when it comes to multi-cluster allocation? I couldn't see any, but wanted to double check. |
One thought that came to mind - if I send multiple requests for a GameServer with available for 1 player - I shouldn't repeatedly get the same GameServer -- at least not in the some n time period. Otherwise, race condition city. One thing we could probably take advantage of in that GameServer get refreshed every 30 seconds even if it hasn't changed -- so we can leverage the GameServer capacity cache, such that when we pass back an Allocated GameServer with capacity, it gets removed from the GameServer availability cache. That being said, when each GameServer gets refreshed is not in our control - so it would be any time up to 30 seconds -- so this might be a good first step -- but we may also want to keep our own list of GameServers that we hold onto for a little while as "allocated, but via availability" unless it gets genuinely updated (i.e. status change, player count change etc), in which case, update the cache, to try and mitigate this race condition. |
For multi-cluster allocation, it translates the allocation status to gRPC error; so for example for the lack of capacity, For the overall API design, I wonder if we should still categorize the player count criterium as part of
Should it also support range?
If there are multiple criterium, how can we stack rank them? Is it part of the user journey? |
AAAAAH YES! 🤸 I was trying to see if there was a way to do something like this, but for whatever reason did not see this path! I like this a lot! Thank you @pooneh-m for this direction! I also love the idea of supporting ranges as well. Makes perfect sense. I'm actually now wondering if we do away with policy all together, and expand on So, say you wanted to do a standard situation of: give me a game server with an availability of 2 or more players, but if not available, allocate a Ready GameServer, you could do something like: apiVersion: "allocation.agones.dev/v1"
kind: GameServerAllocation
spec:
preferred:
- matchLabels:
agones.dev/fleet: simple-udp
gameServerState: Allocated # new state filter: allocate from Allocated servers
players: # new player availability filter
minAvailable: 2
maxAvailable: 999
required:
matchLabels:
agones.dev/fleet: simple-udp
gameServerState: Ready # Allocate out of the Ready Pool (which would be default, so backward compatible) So first pass of the allocation system would look at the This would remove You could do things like Give me an already Allocated GameServer with availability for 2 players, but no nothing if you can't find one., which you absolutely couldn't before, which I think could be quite useful 👍 Thoughts? |
That's really much more flexible design, I like it. |
I don't think we should get in the way of a player reconnecting since this is usually a game design decision. Furthermore, if integration has been done correctly, the user reconnecting should have disconnected beforehand so the used capacity wouldn't change. The reconnect flow is supported in multiple games. Also, I am not sure how reactive the player tracking is but if a developer elect to use I also have a question on the design, will the capacity based allocation also respect allocation strategy? By that I mean in a |
So to all the points about returning the previously returned GameServer are 100% valid. One thought I had though was to have an extra new element of apiVersion: "allocation.agones.dev/v1"
kind: GameServerAllocation
spec:
preferred:
- matchLabels:
agones.dev/fleet: simple-udp
gameServerState: Allocated # new state filter: allocate from Allocated servers
players: # new player availability filter
minAvailable: 2
maxAvailable: 999
required:
matchLabels:
agones.dev/fleet: simple-udp
gameServerState: Ready # Allocate out of the Ready Pool (which would be default, so backward compatible)
hold: 5s So this would make the request for the GameServer with at least 2 players, and then give the client 5 seconds to do it's connection and registration before opening it up again for new players to come back in. This would give you a way to limit the race conditions you might get when re-allocating the same GameServer, but allow for a GameServer to do whatever game specific thing we expect it to do in that time (we could track this on an GameServer Annotation) -- or you could opt out entirely, and leave it at it's default of 0. The other option is tracking what we expect the capacity should be post Allocation, but I think that's too complicated -- at least for a first pass. How do we feel about something like that?
Absolutely - we mentioned that in the initial design 👍 Optimisation strategies are a definite must for sure.
That what you expected? |
Just gently bumping this, to see if the above makes sense. If so, I can update the design above to incorporate these changes. |
Perhaps I think a matchmaker should reserve a game server, allocate it for x number of players and release it. When a game server is reserved, it will not be returned to any other matchmaker until it is ready to accept more players (was released). So another approach could be to provide an API call for releasing an allocation lock, controlled by a matchmaker. |
Let's remove the
I'm a bit hesitant to add yet another API endpoint -- but your comment here made me realise something cool, we could actually do exactly what you suggest now, but without another API! We can do it with labels, and the Agones game server SDK! For example, if I did: apiVersion: "allocation.agones.dev/v1"
kind: GameServerAllocation
spec:
preferred:
- matchLabels:
agones.dev/fleet: simple-udp
agones.dev/sdk-available: "true" # this is important
gameServerState: Allocated # new state filter: allocate from Allocated servers
players: # new player availability filter
minAvailable: 2
maxAvailable: 999
required:
matchLabels:
agones.dev/fleet: simple-udp
gameServerState: Ready # Allocate out of the Ready Pool (which would be default, so backward compatible)
metadata:
labels:
agones.dev/sdk-available: "false" # this removes it from the pool So what does this do?
This would mean if we re-allocated a GameServer that was in an Allocated state, it automatically drops itself out of the pool of re-allocatable GameServers, because it no longer is valid for the search conditions 😄 How then would the GameServer set itself to being available again to being re-allocated? It can do this now through SDK.WatchGameServer(...), and then set the label The downside here being that it is onto the user to pass the relevant information down to the gameserver (likely through annotations) to determine when it should reset itself - but maybe that's a great first pass at this functionality, and we can then get more sophisticated (maybe extend (A) Did that make sense and (B) WDYT of that approach? |
I like the idea! GameServer should have enough information on the capacity to lock/unlock itself for allocation by setting the labels. Maybe the label should be more self explanatory e.g. |
The label could be whatever the user wants! (we should document this as a solution once we implement this - how to use these type of strategies for different types of scenarios). What's nice about this too - you can allocate based on player capacity, but you could also use this for running multiple sessions per single Game Server as well. It has a really flexible set of applications. Sounds like I need to update the design doc above though! |
Updated design above: |
Catching up on this one very late but this looks fantastic ... the ability to prefer a I am currently doing this by using the k8s APIs and label selectors to find a pod/gs so bundling it into the allocator makes perfect sense. |
@markmandel thinking around this as I'm redoing some matchmaking myself ... will this also allow us to reserve those spots on the allocated gs? Main issue I'm looking at is joinig race condition:
Actually see there was some discussion between yourself and @pooneh-m around this #1239 (comment) |
I think the approach outlined above essentially does, because the GameServer gets to decide when to put itself back into the pool of selectable allocated game servers when it is appropriate for that game. Some of this will need to be handled by the matchmaker though to align matchmaking requests with appropriate game servers. So in your example (I'm assuming A, B and C are requests for GameServers - or are they players?): Assuming A, B and C are requests for GameServers:
Assuming A, B and C are players:
Since K8s gives us generational locking on Resources, we can't accidentally allocate a GameServer to both requests. I would still be inclined to have some race condition checking at the GameServer level anyway - just because I don't know if we can always totally remove that race condition, or at least as a safety measure, but I think we can keep it to a minimum. |
Just a heads up - starting some work on this. 😃 |
This PR wraps up a few things into a single bunch: * enable feature flags for the advanced allocation * e2e tests for the `GameServerAllocation` resource * Updated documentation to cover: * The new feature gates * Reference documentation * Examples. This specifically doesn't include the Allocation gRPC api changes, and/or the updated guides for allocations. They will come in future pull requests. Work on googleforgames#1239
* GSA: Advanced Filtering via resource API This PR wraps up a few things into a single bunch: * enable feature flags for the advanced allocation * e2e tests for the `GameServerAllocation` resource * Updated documentation to cover: * The new feature gates * Reference documentation * Examples. This specifically doesn't include the Allocation gRPC api changes, and/or the updated guides for allocations. They will come in future pull requests. Work on #1239 * Fix cloud build FeatureFlags Tidy up e2e/TestFleetNameValidation
Here is a dynamic way around the hold/lock issue, however it would require coupling between the server and agones which I'm not sure is desirable. Due to this, the feature could be disabled by default and enabled in the server sdk on a per-server basis. This way it would not effect performance of either agones or the server in anyway when disabled. You could send a playerId as metadata with a game server allocation. The game server sdk watches for changes to this specific metadata. When PlayerConnect is called, it removes the metadata matching the connection id. The allocation controller then considers minAvailable, it adds the current player capacity plus the number of playerIds in the metadata. The metadata could also contain a player allocation time, and after a set time it is forcefully deleted. This would deal with race conditions and it is still on the game server to be put back in the pool. |
This sets us up to write the series of guides for Game Server Allocation advanced filtering (googleforgames#1239), as well as adding extra guides for integrations such as: * Websocket servers * Reusing GameServers * Canary Testing And I'm sure more will show up!
This sets us up to write the series of guides for Game Server Allocation advanced filtering (googleforgames#1239), as well as adding extra guides for integrations such as: * Websocket servers * Reusing GameServers * Canary Testing And I'm sure more will show up!
This sets us up to write the series of guides for Game Server Allocation advanced filtering (googleforgames#1239), as well as adding extra guides for integrations such as: * Websocket servers * Reusing GameServers * Canary Testing And I'm sure more will show up!
This sets us up to write the series of guides for Game Server Allocation advanced filtering (#1239), as well as adding extra guides for integrations such as: * Websocket servers * Reusing GameServers * Canary Testing And I'm sure more will show up!
Under `preferred`, the tabbing for `players` was slightly off. This fixes that. Work on googleforgames#1239
Integration pattern documentation that outlines how to implement allocation based on player capacity with an accompanying sequence diagram. To implement this, I also had to extend alpha.html shortcode such that it would be able to handle multiple feature gates. Work on googleforgames#1239
Under `preferred`, the tabbing for `players` was slightly off. This fixes that. Work on #1239
Integration pattern documentation that outlines how to implement allocation based on player capacity with an accompanying sequence diagram. To implement this, I also had to extend alpha.html shortcode such that it would be able to handle multiple feature gates. Work on googleforgames#1239
Integration pattern documentation that outlines how to implement allocation based on player capacity with an accompanying sequence diagram. To implement this, I also had to extend alpha.html shortcode such that it would be able to handle multiple feature gates. Work on googleforgames#1239
Integration pattern documentation that outlines how to implement allocation based on player capacity with an accompanying sequence diagram. To implement this, I also had to extend alpha.html shortcode such that it would be able to handle multiple feature gates. Work on googleforgames#1239
Just a heads up: Functionality and reference docs are complete and ready to go in the upcoming release 🙌🏻 - I'm just finishing up the accompanying documentation guides to go with it during the RC freeze this week. |
Integration pattern documentation that outlines how to implement allocation based on player capacity with an accompanying sequence diagram. To implement this, I also had to extend alpha.html shortcode such that it would be able to handle multiple feature gates. Work on googleforgames#1239
* Docs: Player Capacity Integration Pattern Integration pattern documentation that outlines how to implement allocation based on player capacity with an accompanying sequence diagram. To implement this, I also had to extend alpha.html shortcode such that it would be able to handle multiple feature gates. Work on #1239
Documentation explaining how to use the new advanced allocation features to run multiple concurrent game sessions in a single `GameServer` instance. Closes googleforgames#1239
Documentation explaining how to use the new advanced allocation features to run multiple concurrent game sessions in a single `GameServer` instance. Closes googleforgames#1239
Documentation explaining how to use the new advanced allocation features to run multiple concurrent game sessions in a single `GameServer` instance. Closes googleforgames#1239
Documentation explaining how to use the new advanced allocation features to run multiple concurrent game sessions in a single `GameServer` instance. Closes googleforgames#1239
Documentation explaining how to use the new advanced allocation features to run multiple concurrent game sessions in a single `GameServer` instance. Closes googleforgames#1239
* Docs: High Density Integration Pattern Documentation explaining how to use the new advanced allocation features to run multiple concurrent game sessions in a single `GameServer` instance. Closes #1239 * Review updates. Co-authored-by: Robert Bailey <robertbailey@google.com>
Dependent on #1033
(Not a detailed design ticket, just putting down the basic idea to get the conversation started)
Is your feature request related to a problem? Please describe.
In both session based games and persistent games, it would be useful to be able to do something akin to:
Just give me a GameServer that has room for 2 (or n number) players, regardless whether it is Allocated or not
Which would pass back a GameServer if it had capacity available, and if not, would Allocate a whole new one (with enough initial capacity), and return that instead.
This would be useful for:
Describe the solution you'd like
Some kind of adjustment to
GameServerAllocation
that accounts for capacity of players that are available. Maybe a way to choose what State(s) are in the Allocation pool? (Ready or Allocated??)This probably requires much thought, and would want to tie into #1197 so they don't conflict with each other.
Describe alternatives you've considered
Having a totally different Allocation path for player capacity based allocations -- but then we will likely end up with a huge number of paths for different types of allocations.,
Additional context
Can't think of anything else.
The text was updated successfully, but these errors were encountered: