For monitoring on a meta-level, we used Prometheus and Grafana. Here's what that looked like near the very end: https://www.dropbox.com/s/ktlvvtmppq64u8o/trimmed%20grafana%... Since the ratelimit was heavily in place by then, the numbers are pretty low. For example, the headliner "blocks per second" was well over three thousand a few weeks before then.
One of the most common queries for actual usage though, was "what bases in the last 24 hours (or whatever) have the most chests currently". We'd then travel to these locations to borrow items. Here's that query https://pastebin.com/fjhHGSYz Roughly, it grabs chunks with chests in that timeframe, then uses a recursive Postgres CTE to traverse the https://en.wikipedia.org/wiki/Disjoint-set_data_structure that we used to implement DBSCAN, from the leaf to the root. Then it groups by the root, which in practice means grouping chunks together by which ones semantically have been determined to be part of the same base, then makes a report by quantity per base.
There was also a web UI that showed all active tracks. You could click any player and see their travel history, the route they had taken to get to that location. As well as clicking any cluster and seeing the usernames associated with it. We used this a lot to grab what members were present at a given item stash or base, to make sure we weren't stepping on anyone's toes.
In terms of what it took to make this happen, the stars of the show were probably the main Postgres, which was hand-tuned on top of a hand-tuned ZFS, on top of a NVME drive that was IOMMU passthrough'd to this VM for maximum performance. As well as https://github.com/OpenHFT/Chronicle-Map, which really helped out. We needed a mapping from block coordinate to a bit of data about that location, such as the last timestamp we checked it, what Minecraft block was there last, whether the block is interesting, uninteresting, different from seed, etc. High frequency trading software was perfect for this use case, because it greatly reduced GC pressure on the JVM, since the data was stored off-heap. It ended up at around (roughly) fifty million entries, served hundreds of thousands of requests for block data per second, and requested historical data (from the Postgres) at about thirty thousands SELECTs per second (from a table with over ten billion rows). The reason why this needed to be so fast, is that the use case is downloading bases. Sometimes someone would log in to 2b2t at their base, after having not played for weeks at a time (so the data is in Postgres, not the map in RAM), we wanted to be able to pick up right where we left off on downloading what they had built. This means it needs to get back up to speed with their entire base, as fast as possible, in the seconds before they move away to other chunks, or log off the server. We are downloading bases one click at a time, and bases are huge. The area that is loaded in by a player is 144 by 144 by 256. That’s 5 million blocks. And there are about 250 players online on 2b2t at any given moment in time. That’s well over a billion blocks that we could click on, and learn which Minecraft block they are. But, which locations should we use up our clicks per second budget on? It focused on areas that are being changed rapidly, and areas that are different from the base vanilla terrain. Those are certainly bases, large Minecraft builds. We do an expanding paintbucket pattern that expands in 3 dimensions to whatever has been built there. However, we have to do this quickly, and we have to pick up where we left off. Someone might be flying around their base with an elytra, at very fast speed. We would love to scoop up the terrain that they’re flying over. If the base is particularly interesting, someone flying over a certain area could have had about twenty Minecraft accounts spread all over 2b2t’s map suddenly refocus all of their left clicks on that one area, to grab all the interesting neighbors that it wasn’t able to last time. We peaked at about three thousand block clicks per second, averaged over weeks. So this was a massive data loading and management problem.
What do you mean by hand-tuned Postgres & hand-tuned ZFS. What did you change and why?
But for what I actually changed, off the top of my head, I set the recordsizes in ZFS large enough that Postgres could safely (because of ZFS CoW) have full_page_writes off, and combined with synchronous_commit off, that really sped up the overall system and made the WAL logs much smaller. After looking just now at postgresql.conf, various other things were tweaked, such as seq_page_cost, random_page_cost, effective_cache_size, effective_io_concurrency, max_worker_processes, default_statistics_target, dynamic_shared_memory_type, work_mem, maintenance_work_mem, shared_buffers, but those were not quite as important. (plus some uninteresting tweaks to WAL behavior, since we had replicas that got WAL logs shipped every few minutes with rsync)