How does Go runtime work internally?

Hello, I am interested in having a general idea of what is happening with the Go Runtime code from the server startup to the matches being created. I really dislike being in the dark of whats happening behind the scenes and trying to make sense of what I am writing (eg where something will go in memory and does it make sense to do something like X etc… )

My understanding is that init module is called, initializes all the RPC functions, sets up everything. Then all the calls to the game server are the regular HTTP/ RPC requests with a match id that is probably stored somewhere in a map. Match is basically a struct that lives on the heap that has match state struct in itself.

I am mostly asking because of the way I use memory, I have an authoritative server, that includes influence maps, bunch of components so misusing memory can be rather costly. I don’t think about it that much nor should at this point I but I’d love to have a general idea of what I am doing.

Because of the age old question in Golang is pointer vs value :joy: . Do all the regular advice for pointer vs value apply here?

@Nikola-Milovic This is a very broad question, so I’ll summarize a few highlights here.

You’re on the right track on initialization. At server startup we’ll observe registered RPC functions, before/after hooks, event handlers, matchmaker hooks, match handlers and so on - these are mapped, cached, calls to them might be wrapped in interceptors that track metrics (throughput, execution time, data in/out etc) and more. If you want minute detail here the best path is to have a look through the server code, remember Nakama is open-source.

That said I’d recommend not worrying about the server internals unless it’s absolutely unavoidable. We’ve designed the server to scale well, and are constantly improving every aspect of its internals and feature set. Time spent executing match handler and RPC functions will almost always eclipse the cost of the server’s own internals, so spend most of your time on the former.

Focus on writing the best version of your RPC functions and match handler code, then optimize what you need as you go along. For match handlers you should think in terms of individual match units - build each match as if it’s the only one that will be running on the server, logically speaking, and build it as an isolated unit. In practice the main consideration is to avoid using global fields and put everything you need in the match state. Beyond this general best practices apply: avoid allocating more than you need, don’t copy if you can avoid it, no O(n^2) algorithms etc :smile:

You can always come back with specific questions if you run into issues, the forums are here to help!

5 Likes

This being said, we are getting tempted to use the before and after hooks more and more to intercept for example our storage object changes. What is the best approach for cost, that in this example.

We store experience points in a user owned storage object.
When we update experience, we also want to set a Best score on the XP leaderboard.

A) Do we call our updateXpRPC() → updateStorageObject() then leaderboardRecordWrite()
or
B) updateXpRPC() → updateStorageObject() and let nakama afterHookSOWrite → if(xpUpdate) → leaderboardRecordWrite()

or other ?
thanks !