DoomBench – Can Your Data Stack Run Doom?

SchwKatze2 pts0 comments

Introducing DoomBench - Can Your Data Stack Run DOOM? | CedarDB

Lukas Vogel

Contents

Lukas Vogel

Contents

Can Your Data Stack Run DOOM?<br>TL;DR<br>We ran a multiplayer DOOM server in pure SQL on different data stack architectures, recorded nice videos, and measured what breaks first.<br>Click here to go directly to the benchmark page (with videos).<br>Here is a video of CedarDB being &ldquo;DOOMbench&rdquo;-ed:

Your browser does not support the video tag.<br>Why DOOM?<br>Pedantic note: The original DOOMQL uses raycasting, not BSP trees, making it technically more Wolfenstein than DOOM as some people pointed out.

Last year, we published DOOMQL: a multiplayer DOOM-like game running entirely inside SQL, using recursive CTEs for raycasting and a real client-server architecture where players connect directly to the database. We were very excited when it hit the front page of Hacker News.<br>DOOMBench builds on DOOMQL and turns it into a stress-test for different data stacks.<br>Latency numbers and throughput charts are easy to report but hard to feel.<br>A video of a database struggling to render a video game frame can be felt instantly!<br>Let&rsquo;s look at the three dimensions DOOMQL covers:<br>Raw analytical performance : DOOMQL uses recursive CTEs to render a game world in ASCII-art using raycasting. That&rsquo;s as number-crunchy as SQL gets! Some might interject that this is not a workload representative of the real world, to which I reply: Might be true, but there&rsquo;s precedence.<br>Transaction Processing : DOOMQL uses a client-server architecture. Clients connect directly to the database and insert their inputs into an inputs table: WASD to move, X to shoot. That&rsquo;s not going to be a lot of transactions (think: 10 players sending an input every 200 ms each -> 50 transactions per second), but latency is a big issue here. If you&rsquo;ve ever played a multiplayer shooter with a high ping, you understand. Furthermore, there&rsquo;s also a game loop the server has to run multiple times a second. This can reach from 100 ticks per minute (Runescape) to 128 ticks per second (Valorant).<br>Atomicity : Nothing feels worse than being shot by a player who was already dead on your screen. Good database systems can execute transactions in an atomic fashion. Either everything applies or nothing: There cannot be a player that has 0 or less hitpoints but hasn&rsquo;t been killed and respawned yet. This is not really a metric to measure: It either applies, or it doesn&rsquo;t. Fortunately, nearly all serious databases implement such ACID guarantees nowadays.<br>The interesting part is that it&rsquo;s very hard for your data stack to be good at both analytical and transactional processing.<br>Analytics wants to crunch a lot of data and is usually bandwidth-bound (memory, disk, caches). Transaction processing wants writes to feel snappy and is usually latency-bound.<br>Both approaches traditionally use different data layouts, data structures, and system architecture. Let&rsquo;s explore them in the context of DOOM!<br>What does a database running DOOM look like?<br>Let&rsquo;s use Postgres as an example. Here&rsquo;s a video of it running DOOMbench:

Your browser does not support the video tag.Let&rsquo;s go over what we see:<br>The main view on the left shows the player&rsquo;s view: The Raycasted game view itself, a minimap with the player&rsquo;s sight cone, and a score screen including player health, ammo and kill count.<br>Right of that is a minimap of the world state. It&rsquo;s the state Postgres is currently in, i.e. not the view rendered to the client, but the state of the database at the current tick.<br>Below that, we see the player input, as well as some performance numbers: the server tick rate and FPS, both current and as historic chart.<br>So how does data flow through the whole system? Let&rsquo;s look at what has to happen for a new view to be rendered:<br>1. Inputs<br>The player presses &lsquo;W&rsquo; which appends a new row into the inputs table: insert into inputs(player_id, action, timestamp) values (47, 'W', now()).<br>2. Game tick<br>The next game tick will then read that row, update the player position (as long as the player isn&rsquo;t dead, the move is blocked by walls, etc&mldr;). We limit the server tick rate to 35 ticks per second (same as the original DOOM). Ticks and input are processed in lockstep.<br>3. Rendering<br>Clients can request a frame by querying a view that does all the raycasting behind the scenes on demand: select full_row from frames_by_row where player_id = 47 order by f.row asc (see here)<br>The rendering loop is decoupled from the game tick loop (game design 101), leading to a true mixed workload: Every client wants to maximize its own FPS (no VSYNC here, analytical workload), while the server also must be able to still process all the inputs at 35 ticks per second (transactional workload), while players continue to happily send their inputs whenever they please (transactional workload #2).<br>Since rendering is very(!) expensive and the game...

rsquo data doom game player server

Related Articles