Skip to content

Commit aa4032d

Browse files
authored
Docs: Counters with High Density (googleforgames#4085)
* Docs: Counters with High Density Implements documentation for the High Density Integration pattern using Counters. Leaves in the "label locking" technique as well, as it is still a valid technique! Closes googleforgames#3655 * Updates based on review comments.
1 parent 97c1f51 commit aa4032d

File tree

6 files changed

+236
-22
lines changed

6 files changed

+236
-22
lines changed

build/includes/website.mk

+2-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ site-images: $(site_path)/static/diagrams/canary-testing.puml.png
8383
site-images: $(site_path)/static/diagrams/allocation-player-capacity-tracking.puml.png
8484
site-images: $(site_path)/static/diagrams/allocation-player-capacity-list.puml.png
8585
site-images: $(site_path)/static/diagrams/reusing-gameservers.puml.png
86-
site-images: $(site_path)/static/diagrams/high-density.puml.png
86+
site-images: $(site_path)/static/diagrams/high-density-label-lock.puml.png
87+
site-images: $(site_path)/static/diagrams/high-density-counters.puml.png
8788

8889
# generate pngs from dot files
8990
%.dot.png: %.dot

site/content/en/docs/Integration Patterns/high-density-gameservers.md

+103-21
Original file line numberDiff line numberDiff line change
@@ -13,34 +13,113 @@ use of resources to run multiple concurrent game sessions from within a single `
1313
The tradeoff here is that this requires more management on behalf of the integrated game server process and external
1414
systems, since it works around the common Kubernetes and/or Agones container lifecycle.
1515

16-
Utilising the new allocation `gameServerState` filter as well as the existing ability to edit the
16+
Here are two different approaches to solving this problem with Agones:
17+
18+
## Session/Room Counters
19+
20+
{{< beta title="Counters And Lists" gate="CountsAndLists" >}}
21+
22+
Utilising the allocation `gameServerState` filter as well as the new ability to add Counter capacity and counts to
23+
`GameServer` records at both [allocation time]({{% ref "/docs/Reference/gameserverallocation.md" %}}), and from
24+
within the game server process, [via the SDK][sdk-counter], means Agones is able to atomically track how many sessions
25+
are available on a given a `GameServer` from the list of potentially Ready or Allocated `GameServers` when making an
26+
allocation request.
27+
28+
By also using Counters, we can provide Agones the allocation metadata it needs to pack appropriately across the high
29+
density `GameServer` instances as well.
30+
31+
<a href="../../../diagrams/high-density-counters.puml.png" target="_blank">
32+
<img src="../../../diagrams/high-density-counters.puml.png" alt="High Density Allocation Diagram (Session Counters)" />
33+
</a>
34+
35+
### Example `GameServerAllocation`
36+
37+
The below `Allocation` will first attempt to find a `GameServer` from the `Fleet` `simple-game-server` that is already
38+
Allocated and also available capacity under the `rooms` Counter.
39+
40+
If an Allocated `GameServer` does not exist with available capacity, then use the next selector to allocate a Ready
41+
`GameServer` from the `simple-game-server` `Fleet`.
42+
43+
Whichever condition is met, once allocation is made against a `GameServer`, the `rooms` Counter will be incremented by
44+
one, thereby decrementing the available capacity of the `room` Counter on the `GameServer` instance. Generally
45+
speaking, once there is no available capacity on the most full `GameServer`, the allocation will prioritise the next
46+
least full `GameServer` to ensure packing across `GameServer` instances.
47+
48+
It will then be up to the game server process to decrement the `rooms` Counter via the SDK when a session comes to end,
49+
to increase the amount of available capacity within the `GameServer` instance.
50+
51+
```yaml
52+
apiVersion: allocation.agones.dev/v1
53+
kind: GameServerAllocation
54+
spec:
55+
scheduling: Packed
56+
priorities:
57+
- type: Counter
58+
key: rooms
59+
order: Ascending # Ensures the "rooms" with the least available capacity (most full rooms) get prioritised.
60+
selectors:
61+
# Check if there is an already Allocated GameServer with room for at least one more session.
62+
- gameServerState: Allocated
63+
matchLabels:
64+
agones.dev/fleet: simple-game-server
65+
counters:
66+
rooms:
67+
minAvailable: 1
68+
# If we can't find an Allocated GameServer, then go get a `Ready` `GameServer`.
69+
- gameServerState: Ready
70+
matchLabels:
71+
agones.dev/fleet: simple-game-server
72+
counters:
73+
rooms:
74+
minAvailable: 1 # not 100% necessary, since our Ready GameServers don't change their count value, but a good practice.
75+
counters:
76+
rooms:
77+
action: Increment
78+
amount: 1 # Bump up the room count by one on Allocation.
79+
```
80+
81+
{{% alert title="Note" color="info" %}}
82+
When using `Packed` `scheduling`, Counter and List `priorities` are used as a tiebreaker within nodes, to ensure packing
83+
across the nodes is done as efficiently as possible first, and the packing within each `GameServer` on the node is done
84+
second.
85+
86+
For a `Distributed` `scheduling` implementation, Counter and List `priorities` are the only sorting that occurs across
87+
the potential set of GameServers that are to be allocated.
88+
{{% /alert %}}
89+
90+
## GameServer Label Locking
91+
92+
Utilising the allocation `gameServerState` filter as well as the existing ability to edit the
1793
`GameServer` labels at both [allocation time]({{% ref "/docs/Reference/gameserverallocation.md" %}}), and from
18-
within the game server process, [via the SDK][sdk],
94+
within the game server process, [via the SDK][sdk-label],
1995
means Agones is able to atomically remove a `GameServer` from the list of potentially allocatable
2096
`GameServers` at allocation time, and then return it back into the pool of allocatable `GameServers` if and when the
21-
game server process deems that is has room to host another game session.
97+
game server process deems that is has room to host another game session.
2298

23-
<a href="../../../diagrams/high-density.puml.png" target="_blank">
24-
<img src="../../../diagrams/high-density.puml.png" alt="High Density Allocation Diagram" />
99+
The downside to this approach is that there is no packing across re-allocated `GameServer` instances, but it is a very
100+
flexible approach if utilising Counters or Lists is not a viable option.
101+
102+
<a href="../../../diagrams/high-density-label-lock.puml.png" target="_blank">
103+
<img src="../../../diagrams/high-density-label-lock.puml.png" alt="High Density Allocation Diagram (Label Lock)" />
25104
</a>
26105

27106
{{< alert title="Info" color="info">}}
28107
To watch for Allocation events, there is the initial `GameServer.status.state` change from `Ready` to `Allocated`,
29108
but it is also useful to know that the value of `GameServer.metadata.annotations["agones.dev/last-allocated"]` will
30-
change as it is set by Agones with each allocation with the current timestamp, regardless of if there
109+
change as it is set by Agones with each allocation with the current timestamp, regardless of if there
31110
is a state change or not.
32111
{{< /alert >}}
33112

34-
## Example `GameServerAllocation`
113+
### Example `GameServerAllocation`
35114

36-
The below `Allocation` will first attempt to find a `GameServer` from the `Fleet` `simple-udp` that is already
115+
The below `Allocation` will first attempt to find a `GameServer` from the `Fleet` `simple-game-server` that is already
37116
Allocated and also has the label `agones.dev/sdk-gs-session-ready` with the value of `true`.
38117

39118
The above condition indicates that the matching game server process behind the matched `GameServer` record is able to
40119
accept another game session at this time.
41120

42121
If an Allocated `GameServer` does not exist with the desired labels, then use the next selector to allocate a Ready
43-
`GameServer` from the `simple-udp` `Fleet`.
122+
`GameServer` from the `simple-game-server` `Fleet`.
44123

45124
Whichever condition is met, once allocation is made against a `GameServer`, its label of `agones.dev/sdk-gs-session-ready`
46125
will be set to the value of `false` and it will no longer match the first selector, thereby removing it from any
@@ -56,34 +135,37 @@ kind: GameServerAllocation
56135
spec:
57136
selectors:
58137
- matchLabels:
59-
agones.dev/fleet: simple-udp
138+
agones.dev/fleet: simple-game-server
60139
agones.dev/sdk-gs-session-ready: "true" # this is important
61140
gameServerState: Allocated # new state filter: allocate from Allocated servers
62141
- matchLabels:
63-
agones.dev/fleet: simple-udp
142+
agones.dev/fleet: simple-game-server
64143
gameServerState: Ready # Allocate out of the Ready Pool (which would be default, so backward compatible)
65144
metadata:
66145
labels:
67146
agones.dev/sdk-gs-session-ready: "false" # this removes it from the pool
68147
```
69148

70-
{{< alert title="Info" color="info">}}
149+
{{% alert title="Info" color="info" %}}
71150
It's important to note that the labels that the `GameServer` process use to add itself back into the pool of
72151
allocatable instances, must start with the prefix `agones.dev/sdk-`, since only labels that have this prefix are
73-
available to be [updated from the SDK][sdk].
74-
{{< /alert >}}
152+
available to be [updated from the SDK]({{% ref "/docs/Guides/Client SDKs/_index.md#setlabelkey-value" %}}).
153+
{{% /alert %}}
75154

76155
## Consistency
77156

78-
Agones, and Kubernetes itself are built as eventually consistent, self-healing systems. To that end, it is worth
79-
noting that there may be minor delays between each of the operations in the above flow. For example, depending on the
80-
cluster load, it may take up to a second for an [SDK driven label change][sdk] on a `GameServer` record to be
81-
visible to the Agones allocation system. We recommend building your integrations with Agones with this in mind.
157+
Agones, and Kubernetes itself are built as eventually consistent, self-healing systems. To that end, it is worth
158+
noting that there may be minor delays between each of the operations in either of the above flows. For example,
159+
depending on the cluster load, it may take approximately a second for an SDK driven
160+
[counter change][sdk-counter] or [label change][sdk-label] on a `GameServer` record to be visible to the Agones
161+
allocation system. We recommend building your integrations with Agones with this in mind.
82162

83163
## Next Steps
84164

85-
* View the details about [using the SDK][sdk] to set
86-
labels on the `GameServer`.
165+
* Read the [Counters and Lists]({{< ref "/docs/Guides/counters-and-lists.md" >}}) guide.
166+
* View the details about [using the SDK]({{% ref "/docs/Guides/Client SDKs/_index.md#setlabelkey-value" %}}) to change
167+
values on the `GameServer`.
87168
* Check all the options available on [`GameServerAllocation`]({{% ref "/docs/Reference/gameserverallocation.md" %}}).
88169

89-
[sdk]: {{% ref "/docs/Guides/Client SDKs/_index.md#setlabelkey-value" %}}
170+
[sdk-label]: {{% ref "/docs/Guides/Client SDKs/_index.md#setlabelkey-value" %}}
171+
[sdk-counter]: {{% ref "/docs/Guides/Client SDKs/_index.md#counters-and-lists" %}}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
@startuml
2+
/'
3+
Copyright 2024 Google LLC All Rights Reserved.
4+
5+
Licensed under the Apache License, Version 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
'/
17+
18+
participant Matchmaker
19+
participant Agones
20+
participant "Game Server\nProcess" as Binary
21+
participant SDK
22+
participant "GameServer\nResource" as GameServer
23+
box "Game Server Pod"
24+
participant Binary
25+
participant SDK
26+
end box
27+
28+
== GameServer Start ==
29+
30+
Agones -> GameServer: GameServer created through\na <i>Fleet</i> configuration
31+
note left
32+
<i>Fleet</i> configuration includes a <i>rooms</i> Counter with an initial value of 0
33+
and a capacity set to the total number of sessions that be can be hosted on a <i>GameServer</i>.
34+
end note
35+
activate GameServer
36+
GameServer -> Binary: Agones creates a Pod with the\nconfigured Game Server Container
37+
activate Binary
38+
activate SDK
39+
Binary -> SDK: SDK.WatchGameServer()
40+
note right
41+
Use the SDK Watch function
42+
to watch and react to allocation
43+
events
44+
end note
45+
46+
Binary -> SDK: SDK.Ready()
47+
note right
48+
Call <i>Ready()</i> when the
49+
Game Server can take player
50+
connections and is able to
51+
be allocated.
52+
end note
53+
GameServer <-- SDK: Update to <i>Ready</i> State
54+
55+
== No allocated <i>GameServers</i> ==
56+
57+
Matchmaker -> Agones: Create: <i>GameServerAllocation</i>
58+
note left
59+
The <i>GameServerAllocation</i> is implemented to
60+
optionally select an already allocated <i>GameServer</i>
61+
with a available capacity under Counter <i>rooms</i>
62+
of 1. If one cannot be found, allocate a <i>Ready</i>
63+
<i>GameServer</i> instead.
64+
65+
Since at this stage there are no Allocated <i>GameServer<i>
66+
Agones will allocate a <i>Ready</i> GameServer.
67+
end note
68+
Agones -> GameServer: Finds a <i>Ready</i> <i>GameServer</i>.\n\nSets <i>status.state</i> to <i>Allocated</i> State\nand <i>status.counters["rooms"}.count++</i>\nand <i>metadata.annotations["agones.dev/last-allocated"] = current timestamp</i>
69+
note left
70+
By incrementing the <i>rooms</i> counter by one,
71+
this reduces the available capacity for this
72+
<i>GameServer</i> by one.
73+
end note
74+
Matchmaker <-- Agones : <i>GameServerAllocation</i> is returned\nwith <i>GameServer</i> details\nincluding IP and port to connect to.
75+
76+
SDK --> Binary: Sends SDK.WatchGameServer()\nevent for Allocation.
77+
note right
78+
This initial allocation can be determined
79+
as a change in <i>GameServer.status.state</i>
80+
from <i>Ready</i> to <i>Allocated</i>, as well as an
81+
increment of one on
82+
<i>GameServer.status.counters["rooms"].count</i>
83+
end note
84+
85+
== Allocated <i>GameServers</i> with available capacity for more sessions ==
86+
87+
Matchmaker -> Agones: Create: <i>GameServerAllocation</i>
88+
note left
89+
The <i>GameServerAllocation</i> will this time find the
90+
Allocated <i>GameServer</i> with the <i>rooms</i> Counter
91+
with available capacity, inculcating it has room for
92+
more game sessions.
93+
end note
94+
Agones -> GameServer: Finds the <i>Allocated</i> <i>GameServer</i> where\n<i>status["rooms"].count < status["rooms"].capacity</i>.\n\nSets <i>status.counters["rooms"}.count++</i>\nand <i>metadata.annotations["agones.dev/last-allocated"] = current timestamp</i>
95+
note right
96+
This is a <i>GameServer</i> that has room
97+
for another concurrent game session.
98+
end note
99+
Matchmaker <-- Agones: returns <i>Allocated GameServer</i> record
100+
101+
SDK --> Binary: Sends SDK.WatchGameServer()\nevent for Allocation.
102+
note right
103+
The game server process can watch for an
104+
increment of <i>status.counters["rooms"}.count</i> and/or
105+
change in <i>metadata.annotations["agones.dev/last-allocated"]</i>
106+
to determine if there is an allocation event.
107+
end note
108+
109+
alt <i>GameServer</i> finishes a game session
110+
Binary -> SDK: SDK.Beta().DecrementCounter("rooms", 1)
111+
SDK --> GameServer: Sets <i>status["rooms"].count--</i>
112+
end alt
113+
note right
114+
When a session ends, decrement the session counter
115+
to increase the available capacity on this <i>GameServer</i>.
116+
end note
117+
118+
== <i>GameServer</i> has completed <i>n</i> number of complete sessions ==
119+
120+
Binary -> SDK: SDK.Shutdown()
121+
note left
122+
The <i>GameServer</i> process tracks the total number of sessions
123+
that it hosts, and after <i>n</i> number, calls <i>Shutdown()</i>
124+
to delete the <i>GameServer</i> resource and backing Pod.
125+
end note
126+
SDK --> GameServer: Update to <i>Shutdown</i> state.
127+
Agones -> GameServer: Deletes GameServer resource and backing Pod.
128+
destroy Binary
129+
destroy SDK
130+
destroy GameServer
131+
@enduml
Loading

0 commit comments

Comments
 (0)