<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Zhijun</title>
    <description>The latest articles on DEV Community by Zhijun (@zhijunl).</description>
    <link>https://dev.to/zhijunl</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1898139%2Fceb4a5b8-a93a-4a15-aee5-67614a337a57.jpg</url>
      <title>DEV Community: Zhijun</title>
      <link>https://dev.to/zhijunl</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/zhijunl"/>
    <language>en</language>
    <item>
      <title>From reversed linked-list to registers, memory and assembly</title>
      <dc:creator>Zhijun</dc:creator>
      <pubDate>Sun, 01 Jun 2025 13:44:49 +0000</pubDate>
      <link>https://dev.to/zhijunl/from-reversed-linked-list-to-registers-memory-and-assembly-2die</link>
      <guid>https://dev.to/zhijunl/from-reversed-linked-list-to-registers-memory-and-assembly-2die</guid>
      <description>&lt;p&gt;Reversing a linked-list is like the "Hello World" from programmers. In this article I'm gonna use it as an example (written below in C) to showcase how C code gets translated into assembly, with a specific focus on stack frames, register usage, memory layout, and the power of the popular debugging tool &lt;code&gt;gdb&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="k"&gt;typedef&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Node&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Reverses a singly linked list and returns the new head&lt;/span&gt;
&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;curr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;curr&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;curr&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;curr&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;curr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;curr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Prints the list&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;print_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Node ID: %d, Value: %d&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Declare four nodes on the stack and manually connect them&lt;/span&gt;
    &lt;span class="n"&gt;Node&lt;/span&gt; &lt;span class="n"&gt;node_4&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="n"&gt;Node&lt;/span&gt; &lt;span class="n"&gt;node_3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;node_4&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="n"&gt;Node&lt;/span&gt; &lt;span class="n"&gt;node_2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;node_3&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="n"&gt;Node&lt;/span&gt; &lt;span class="n"&gt;node_1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;node_2&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Original list:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;print_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;node_1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Reverse the list&lt;/span&gt;
    &lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;new_head&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;node_1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Reversed list:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;print_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_head&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code above should be pretty straightforward. Before we dig into this piece of code, let's refresh our mind with some basic knowledge on CPU registers, memory and assembly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Registers and Memory in a 64-bit World
&lt;/h2&gt;

&lt;p&gt;When writing low-level programs like C, the compiler transforms our code into assembly instructions that run on the CPU. Each CPU has a small set of registers, which are fast-access storage units. On a 64-bit machine, these registers are 64 bits wide — i.e., they can hold a number as large as 2⁶⁴.&lt;/p&gt;

&lt;p&gt;Computer memory is essentially a giant array of bytes. Every byte in memory has a unique address (think of it like array index), which is also a 64-bit number on modern architectures. So, each register can natually point to a particular memory address.&lt;/p&gt;

&lt;p&gt;Notice that the values in the registers don't always have to be memory address. They can also simply be numbers like -5, 42, etc. The CPU doesn't care about the meaning—it just sees binary values. It's up to us (and the compiler) to interpret them correctly. This dual interpretation — value or address — is what makes assembly programming flexible yet confusing to beginners.&lt;/p&gt;

&lt;p&gt;Essentially all data inside a machine is just bits. When we see two bytes 0x48 0x69, we can either intepret them as string "Hi", or as decimal number 18537, and the computer itself wouldn't know the difference. Note that the prefix '0x' means this a hex number (0x10 is 16 in decimal). You'll run into hex numbers all the time in the low-level world, because memory addresses are 64-bit and decimal is just too clumsy - you would definitely prefer using &lt;code&gt;0x401256&lt;/code&gt;to &lt;code&gt;010 000 000 001 001 001 010 110&lt;/code&gt; to denote an address. Hex is compact and maps neatly to binary.&lt;/p&gt;

&lt;p&gt;There are 16 general-purpose registers in x86-64 systems:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%rax, %rbx, %rcx, %rdx,
%rsi, %rdi, %rbp, %rsp,
%r8 through %r15
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some registers have conventional purposes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;%rsp&lt;/code&gt;: stack pointer, always points to the top of the stack&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;%rax&lt;/code&gt;: return value from a function&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;%rdi&lt;/code&gt;, &lt;code&gt;%rsi&lt;/code&gt;, &lt;code&gt;%rdx&lt;/code&gt;, &lt;code&gt;%rcx&lt;/code&gt;, &lt;code&gt;%r8&lt;/code&gt;, &lt;code&gt;%r9&lt;/code&gt;: used to pass the first 6 function arguments&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Common Assembly Instructions
&lt;/h2&gt;

&lt;p&gt;Assembly instructions manipulate the data between registers and memory. These instructions are where we begin to see how higher-level constructs like linked lists are implemented under the hood.&lt;/p&gt;

&lt;p&gt;Here are a few of the most common instructions you'll run into all the time. These might seem overwhelming for now, but don't worry, we will see them work in action later.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;mov&lt;/code&gt;: Copy value from source to destination (register-to-register, memory-to-register, etc.). For example, say register %rax is now 0x402000

&lt;ul&gt;
&lt;li&gt; &lt;code&gt;mov $0x28, %rax&lt;/code&gt; updates register %rax to value 0x28. &lt;/li&gt;
&lt;li&gt; &lt;code&gt;mov $0x28, (%rax)&lt;/code&gt; puts integer 0x28 into memory starting at address 0x402000&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;lea&lt;/code&gt;: Load effective address. Usually used to perform simple arthmetic operations.

&lt;ul&gt;
&lt;li&gt;Say %rax is 0x402000, and %rbx is 0x2. Instruction &lt;code&gt;lea 0x1(%rax, %rbx, 4), %rdx&lt;/code&gt; sets %rdx =  0x402000 + 2 * 4 + 0x1 = 0x402009.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;call&lt;/code&gt;: Push return address onto stack and jump to the function&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;ret&lt;/code&gt;: Pop return address and jump back to where the function call was made&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;push&lt;/code&gt;: Decrease &lt;code&gt;%rsp&lt;/code&gt; and store a value at the new top of the stack&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;pop&lt;/code&gt;: Opposite of &lt;code&gt;push&lt;/code&gt;: Load value from top of stack and increase &lt;code&gt;%rsp&lt;/code&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;sub&lt;/code&gt;, &lt;code&gt;add&lt;/code&gt;: Perform arithmetic (often used to grow/shrink stack frame)&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;cmp&lt;/code&gt;: Compare two values&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;jmp&lt;/code&gt;, &lt;code&gt;je&lt;/code&gt;, &lt;code&gt;jne&lt;/code&gt;, &lt;code&gt;jg&lt;/code&gt;, etc.: Control flow via unconditional or conditional jumps. Usually used together with &lt;code&gt;cmp&lt;/code&gt; instruction, for example, &lt;code&gt;cmp $0x3, %rax&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;je&lt;/code&gt; equal: %rax == 3&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;jne&lt;/code&gt; not equak:  %rax != 3&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;jg&lt;/code&gt; greater: %rax &amp;gt; 3&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Dissecting the main Function
&lt;/h2&gt;

&lt;p&gt;I use the following code to compile the C file and then disassemble it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcc &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="nt"&gt;-no-pie&lt;/span&gt; &lt;span class="nt"&gt;-Og&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; linked_list linked_list.c
objdump &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--no-show-raw-insn&lt;/span&gt; linked_list &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; linked_list.s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's look at the disassembled version of our C program’s &lt;code&gt;main&lt;/code&gt; function. I've added lots of annotations to help reading through. At each line, the leftmost hex number is the address of the instruction.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;00000000004011c5 &amp;lt;main&amp;gt;:
  4011c5:   endbr64                    # Control-flow enforcement. Let's ignore it
  4011c9:   push   %rbx                  # Register rbx will be modified in the process. We push it onto the stack so that we can retrieve later 
  4011ca:   sub    $0x50,%rsp          # Move the top of the stack to allocate 80 bytes on stack(0x50 is 80 in decimal) 
  4011ce:   mov    %fs:0x28,%rax       
  4011d7:   mov    %rax,0x48(%rsp)     # Put special value in the stack to detect stack buffer overflows. We can ignore for now.
  4011dc:   xor    %eax,%eax           # Clear %eax

# === Initialize 4 linked list nodes ===
  4011de:   movl   $0x28,(%rsp)        # node4.val = 40
  4011e5:   movl   $0x4,0x4(%rsp)      # node4.id = 4
  4011ed:   movq   $0x0,0x8(%rsp)      # node4.next = NULL

  4011f6:   movl   $0x1e,0x10(%rsp)    # node3.val = 30
  4011fe:   movl   $0x3,0x14(%rsp)     # node3.id = 3
  401206:   mov    %rsp,%rax           # address of node4
  401209:   mov    %rax,0x18(%rsp)     # node3.next = &amp;amp;node4

  40120e:   movl   $0x14,0x20(%rsp)    # node2.val = 20
  401216:   movl   $0x2,0x24(%rsp)     # node2.id = 2
  40121e:   lea    0x10(%rsp),%rax     # address of node3
  401223:   mov    %rax,0x28(%rsp)     # node2.next = &amp;amp;node3

  401228:   movl   $0xa,0x30(%rsp)     # node1.val = 10
  401230:   movl   $0x1,0x34(%rsp)     # node1.id = 1
  401238:   lea    0x20(%rsp),%rax     # address of node2
  40123d:   mov    %rax,0x38(%rsp)     # node1.next = &amp;amp;node2

# === Print Original List ===
  401242:   lea    0xdd3(%rip),%rdi    # Load "Original list:" format string
  401249:   call   401060 &amp;lt;puts@plt&amp;gt;   # Print string to our screen

  40124e:   lea    0x30(%rsp),%rbx     # Set rbp to point to node1
  401253:   mov    %rbx,%rdi           # Set pointer to node1 as the first argument to function call `print_list` so that we're ready for the next line
  401256:   call   401195 &amp;lt;print_list&amp;gt;

# === Reverse the List ===
  40125b:   mov    %rbx,%rdi           # first arg: head node
  40125e:   call   401176 &amp;lt;reverse&amp;gt;    # returns new head
  401263:   mov    %rax,%rbx           # save new head

# === Print Reversed List ===
  401266:   lea    0xdbe(%rip),%rdi    # Load "Reversed list:" string
  40126d:   call   401060 &amp;lt;puts@plt&amp;gt;

  401272:   mov    %rbx,%rdi
  401275:   call   401195 &amp;lt;print_list&amp;gt;

# === Stack Canary Check ===
  40127a:   mov    0x48(%rsp),%rax
  40127f:   sub    %fs:0x28,%rax
  401288:   jne    401295 &amp;lt;main+0xd0&amp;gt;   # if canary changed → stack overflow → crash

# === Return Normally ===
  40128a:   mov    $0x0,%eax           # 0 is the success return code
  40128f:   add    $0x50,%rsp          # Clean up stack frame
  401293:   pop    %rbx                # Retrieve the original value of register rbx
  401294:   ret                        # Return 0
  401295:   call   401070 &amp;lt;__stack_chk_fail@plt&amp;gt;  # stack corrupted → crash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A very important concept is stack frame - a block of memory to use whenever a function is called. It stores everything that the function needs while it runs, such as return address (where to jump back after the function finishes), local variables,  function arguments (if passed via the stack) and sometimes, saved registers (like %rbp, %rbx) to restore later. It’s created at the start of a function and destroyed at the end.&lt;/p&gt;

&lt;p&gt;The register %rsp is the top of the stack frame, and is extremely important since it marks the boundary of the stack frame. When we say the stack frame is destroyed, we don't mean physically destroying the hardware. Instead, it means the previous chunk of memory is no longered considered part of the current stack frame, and other programs are now allowed to write new values to this memory space to overwrite the existing ones.&lt;/p&gt;

&lt;p&gt;Now let’s run GDB (&lt;a href="https://en.wikipedia.org/wiki/GNU_Debugger" rel="noopener noreferrer"&gt;GNU Debugger&lt;/a&gt;) to execute the program.  add a breakpoint to address 401256 (you can find this number in the disassembled code above), right before calling &lt;code&gt;print_list&lt;/code&gt; function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ gdb linked_list
GNU gdb (Ubuntu 12.1-0ubuntu1~22.04.2) 12.1
Copyright (C) 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later &amp;lt;http://gnu.org/licenses/gpl.html&amp;gt;
This is free software: you are free to change and redistribute it.
(gdb) break *0x401256
(gdb) run
Starting program: /home/ubuntu/linked_list 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let’s examine the stack frame. The GDB command to examine memory (a very powerful tool!) is &lt;code&gt;x/&amp;lt;count&amp;gt;&amp;lt;format&amp;gt;&amp;lt;size&amp;gt; &amp;lt;address&amp;gt;&lt;/code&gt;. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;codes for [format]: &lt;strong&gt;x = hex&lt;/strong&gt;, d = decimal, u = unsigned, c = char, s = string&lt;/li&gt;
&lt;li&gt;codes for [size]: b = byte (1 byte), h = halfword (2 bytes), w = word (4 bytes), g = giant (8 bytes)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, the following code examines 20 words (4 bytes each) at the current stack pointer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;(&lt;/span&gt;gdb&lt;span class="o"&gt;)&lt;/span&gt; x/20wx &lt;span class="nv"&gt;$rsp&lt;/span&gt;
0x7fffffffe350: 0x00000028      0x00000004      0x00000000      0x00000000
0x7fffffffe360: 0x0000001e      0x00000003      0xffffe350      0x00007fff
0x7fffffffe370: 0x00000014      0x00000002      0xffffe360      0x00007fff
0x7fffffffe380: 0x0000000a      0x00000001      0xffffe370      0x00007fff
0x7fffffffe390: 0x00000000      0x00000000      0xfbeca800      0x9629f2d4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first 4 lines correspond to the 4 linked-list nodes we just created, and we can clearly see each node’s value, id and the pointer to next node. Each node consumes 16bytes (two integers, 4 bytes for each, and a 8-byte pointer), and that’s why their memory addresses all differ by 0x10.&lt;/p&gt;

&lt;p&gt;Here’s how the stack looks during execution.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;|------------------------| &amp;lt;- at 0x50(%rsp), 0x7fffffffe3a0
| stack overflow check   | 
|------------------------| 
|                        | 
|------------------------| &amp;lt;- at 0x40(%rsp), 0x7fffffffe390
| node1 (next)           | 
|------------------------| &amp;lt;- at 0x38(%rsp), 0x7fffffffe368
| node1 (val, id)        | 
|------------------------| &amp;lt;- at 0x30(%rsp), 0x7fffffffe370
| node2 (next)           | 
|------------------------| &amp;lt;- at 0x28(%rsp), 0x7fffffffe368
| node2 (val, id)        | 
|------------------------| &amp;lt;- at 0x20(%rsp), 0x7fffffffe370
| node3 (next)           | 
|------------------------| &amp;lt;- at 0x18(%rsp), 0x7fffffffe368
| node3 (val, id)        | 
|------------------------| &amp;lt;- at 0x10(%rsp), 0x7fffffffe360
| node4 (next)           | 
|------------------------| &amp;lt;- at 0x8(%rsp), 0x7fffffffe358
| node4 (val, id)        | 
|------------------------| &amp;lt;- %rsp, 0x7fffffffe350
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The stack frame contains exactly 0x50 bytes of memory, as allocated by the instruction   &lt;code&gt;4011ca: sub  $0x50,%rsp&lt;/code&gt;  above. You may notice that the 8 bytes at address 0x40(%rsp) are simply unused, and you may wonder why did we bother to allocate space for it. On x86-64, the stack must be aligned to 16 bytes before a call instruction, and we need those “0x00” bytes for padding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dissecting the print_list Function
&lt;/h2&gt;

&lt;p&gt;Now let's resume from we set the breakpoint, and dig into the &lt;code&gt;print_list&lt;/code&gt; function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0000000000401195 &amp;lt;print_list&amp;gt;:
  401195:   endbr64

# === Function Prologue ===
  401199:   push   %rbx            # Save %rbx
  40119a:   mov    %rdi,%rbx       # Move first arg (head pointer) into %rbx

# === Loop Condition ===
  40119d:   jmp    4011be          # Jump to address 4011be for loop condition check

# === Loop Body ===
  40119f:   mov    (%rbx),%ecx     # Load val into %ecx
  4011a1:   mov    0x4(%rbx),%edx  # Load id into %edx
  4011a4:   lea    0xe59(%rip),%rsi # Load the address of format string "Node ID: %d, Value: %d\n" into %rsi
  4011ab:   mov    $0x1,%edi       # First arg to printf (fd = stdout)
  4011b0:   mov    $0x0,%eax       # Clear %eax before variadic call
  4011b5:   call   401080 &amp;lt;__printf_chk@plt&amp;gt;  # secure version of function printf

  4011ba:   mov    0x8(%rbx),%rbx  # Move to next node (follow .next)

# === Loop Condition ===
  4011be:   test   %rbx,%rbx       # Check if current node is NULL
  4011c1:   jne    40119f          # If not NULL, jump to address 40119f to continue loop

# === Function Epilogue ===
  4011c3:   pop    %rbx            # Restore %rbx
  4011c4:   ret                    # Return
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can add a second breakpoint to the function to examine the stack&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;(&lt;/span&gt;gdb&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;break&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;0x4011ba
Breakpoint 2 at 0x4011ba: file linked_list.c, line 30.
&lt;span class="o"&gt;(&lt;/span&gt;gdb&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;continue
&lt;/span&gt;Continuing.
Node ID: 1, Value: 10

Breakpoint 2, print_list &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;head&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;head&lt;/span&gt;@entry&lt;span class="o"&gt;=&lt;/span&gt;0x7fffffffe380&lt;span class="o"&gt;)&lt;/span&gt; at linked_list.c:30
30              &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; head-&amp;gt;next&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;(&lt;/span&gt;gdb&lt;span class="o"&gt;)&lt;/span&gt; x/20wx &lt;span class="nv"&gt;$rsp&lt;/span&gt;
0x7fffffffe340: 0xffffe380      0x00007fff      0x0040125b      0x00000000
0x7fffffffe350: 0x00000028      0x00000004      0x00000000      0x00000000
0x7fffffffe360: 0x0000001e      0x00000003      0xffffe350      0x00007fff
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We notice that the stack pointer register %rsp has been changed from &lt;code&gt;0x7fffffffe350&lt;/code&gt; to &lt;code&gt;0x7fffffffe340&lt;/code&gt;, even though the &lt;code&gt;print_list&lt;/code&gt; function doesn't allocate any space to the stack frame. So, why did the stack frame grow by 0x10 (16bytes)?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In the &lt;code&gt;main&lt;/code&gt; function, when we execute instruction &lt;code&gt;401256: call 401195 &amp;lt;print_list&amp;gt;&lt;/code&gt;, we push the address of next instruction &lt;code&gt;40125b: mov  %rbx,%rdi&lt;/code&gt; onto the stack, so that when function call &lt;code&gt;print_list&lt;/code&gt; finishes, we can return to the correct location of  &lt;code&gt;main&lt;/code&gt; function and continue executing. This return addreses takes 8 bytes.&lt;/li&gt;
&lt;li&gt;Inside the &lt;code&gt;print_list&lt;/code&gt; function , we run &lt;code&gt;401199: push %rbx&lt;/code&gt; to push the value of register %rbx onto the stack. That takes another 8 bytes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, even though register %rsp isn't directly called, it was modified by instructions &lt;code&gt;call&lt;/code&gt; and &lt;code&gt;push&lt;/code&gt;. Notice that instructions  &lt;code&gt;ret&lt;/code&gt; and &lt;code&gt;pop&lt;/code&gt;  are counterparts of them, and will do the opposite - pop data out of the stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dissecting the reverse Function
&lt;/h2&gt;

&lt;p&gt;Now let's move on to the last function &lt;code&gt;reverse&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0000000000401176 &amp;lt;reverse&amp;gt;:
  401176:   endbr64

# === Function Prologue ===
  40117a:   mov    $0x0,%eax       # Set return register %rax = NULL (this will track the new head)

# === Loop Condition (initial jump) ===
  40117f:   jmp    40118f          # Jump to loop condition before first iteration

# === Loop Body ===
  401181:   mov    0x8(%rdi),%rdx  # Load next node: %rdx = current-&amp;gt;next
  401185:   mov    %rax,0x8(%rdi)  # Reverse the link: current-&amp;gt;next = previous
  401189:   mov    %rdi,%rax       # Update previous: previous = current
  40118c:   mov    %rdx,%rdi       # Move to next: current = next

# === Loop Condition ===
  40118f:   test   %rdi,%rdi       # Check if current (now in %rdi) is NULL
  401192:   jne    401181          # If not, continue the loop

# === Function Epilogue ===
  401194:   ret                    # Return; %rax holds new head of reversed list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This piece of assembly should be rather straightforward now.&lt;/p&gt;

&lt;p&gt;One thing interesting here is that in my C function &lt;code&gt;reverse&lt;/code&gt; , I declare 3 local variables &lt;code&gt;prev&lt;/code&gt;, &lt;code&gt;curr&lt;/code&gt; and &lt;code&gt;next&lt;/code&gt;, but in the assembly code there isn't any stack space allocated for them. It's because the compiler tries to optimize away unnecessary memory operations, and it's smart enough to realize that we don't actually need any additional memory - simply using registers to update pointers is enough. And this process is just beautiful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thanks
&lt;/h2&gt;

&lt;p&gt;I just started learning fundamentals about machines recently, although I've been a developer for many years. This article is my personal attempt to internalize my learnings and share them with others. If you're just getting into the low-level world, I hope you're just as excited as I'm.&lt;/p&gt;

&lt;p&gt;One last note: I'm using my M2 Macbook Air for daily task and it doesn't work well with GDB and all those x86-64 stuff. I ended up getting an AWS ECS Ubuntu t2.micro machine.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt;
Linux ip-172-31-18-227 6.8.0-1024-aws &lt;span class="c"&gt;#26~22.04.1-Ubuntu SMP Wed Feb 19 06:54:57 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;gcc &lt;span class="nt"&gt;--version&lt;/span&gt;
gcc &lt;span class="o"&gt;(&lt;/span&gt;Ubuntu 11.4.0-1ubuntu1~22.04&lt;span class="o"&gt;)&lt;/span&gt; 11.4.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>assembly</category>
      <category>cpu</category>
      <category>datastructures</category>
    </item>
    <item>
      <title>Distributed Systems | What can we learn from Roblox's 3-day outage?</title>
      <dc:creator>Zhijun</dc:creator>
      <pubDate>Sun, 01 Jun 2025 13:41:40 +0000</pubDate>
      <link>https://dev.to/zhijunl/distributed-systems-what-can-we-learn-from-robloxs-3-day-outage-4p5g</link>
      <guid>https://dev.to/zhijunl/distributed-systems-what-can-we-learn-from-robloxs-3-day-outage-4p5g</guid>
      <description>&lt;p&gt;In October 2021, Roblox suffered the longest outage in its history—73 hours of complete downtime, affecting millions of players worldwide. The root cause? A subtle yet devastating issue buried deep inside an outdated database structure. What makes this case fascinating is that the issue wasn’t an obvious system failure or an external attack—it was a slow-burning technical debt hidden inside the database.&lt;/p&gt;

&lt;p&gt;I recently did a deep dive into &lt;a href="https://corp.roblox.com/newsroom/2022/01/roblox-return-to-service-10-28-10-31-2021" rel="noopener noreferrer"&gt;the post-mortem&lt;/a&gt; and gained valuable insights. This is arguably one of the most detailed and thorough outage reports available online.&lt;/p&gt;

&lt;h3&gt;
  
  
  🌐 Background:
&lt;/h3&gt;

&lt;p&gt;Roblox employs a microservices architecture for its backend, using &lt;strong&gt;HashiCorp’s Consul&lt;/strong&gt; for service discovery, enabling internal services to locate and communicate with each other. On the afternoon of October 28th, a single Consul server experienced high CPU load. The Consul cluster's performance continued to degrade, ultimately bringing down the entire Roblox system since Consul acted as a &lt;strong&gt;single point of failure&lt;/strong&gt;. Engineers from both Roblox and HashiCorp collaborated to diagnose and resolve the issue.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔧 Debugging Attempts:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Suspected Hardware Failure&lt;/strong&gt;: The team replaced one of the Consul cluster nodes, but the issue persisted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Suspected Increased Traffic&lt;/strong&gt;: The team replaced all Consul cluster nodes with more powerful machines featuring 128 cores (2x increase) and faster NVMe SSD disks. This also did not resolve the issue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resetting Consul’s State&lt;/strong&gt;: The team shut down the entire Consul cluster and restored its state using a snapshot from a few hours before the outage began. Initially, the system appeared stable, but it soon degraded again, returning to an unhealthy state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reducing Consul Usage&lt;/strong&gt;: Roblox services that typically had hundreds of instances running were scaled down to single digits. This approach provided temporary relief for a few hours before Consul again became unhealthy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Identifying Resource Contention Issues&lt;/strong&gt;: Upon deeper inspection of debug logs, the team discovered resource contention problems. They reverted to machines similar to those used before the outage. Eventually, they identified the issue: Consul’s new streaming feature. This feature utilized fewer concurrency control elements (Go channels), which led to excessive contention on a single Go channel under high read/write loads. Disabling streaming dramatically improved the Consul cluster’s health.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leader Election Optimization&lt;/strong&gt;: The team observed Consul intermittently electing new cluster leaders, which was normal. However, some leaders exhibited the same latency issues. The team worked around this by preventing problematic leaders from staying elected.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;✅ Following these measures, the system was finally stable. The team carefully brought Roblox back online by restoring caching systems and gradually allowing randomly selected players to reconnect. &lt;strong&gt;After 73 hours, Roblox was fully operational.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  🔍 BoltDB’s Freelist: A Silent Culprit
&lt;/h3&gt;

&lt;p&gt;The root cause of the outage stemmed from two key issues: the &lt;strong&gt;Consul streaming feature&lt;/strong&gt; (as mentioned above) and &lt;strong&gt;Consul’s underlying database&lt;/strong&gt; &lt;strong&gt;BoltDB&lt;/strong&gt; exhibiting severe performance degradation, which I find pretty interesting.&lt;/p&gt;

&lt;p&gt;Consul uses Raft consensus for leader election to ensure data consistency in a distributed environment. For persistence, it relies on BoltDB, a popular embedded key-value store, to store Raft logs.&lt;/p&gt;

&lt;p&gt;Like many databases, BoltDB has a &lt;strong&gt;freelist&lt;/strong&gt;, which tracks &lt;strong&gt;free pages&lt;/strong&gt;—disk space that was previously occupied but is now available for reuse. This mechanism is crucial for efficient database performance, preventing unnecessary disk growth and optimizing read/write operations.&lt;/p&gt;

&lt;p&gt;However, BoltDB’s freelist implementation had a critical inefficiency (see source code &lt;a href="https://github.com/boltdb/bolt/blob/master/freelist.go#L65-L106" rel="noopener noreferrer"&gt;here&lt;/a&gt;). It used an array to store the ID of each free page, meaning that every database read/write operation involved a linear scan of the freelist. As the freelist grew large, the cost of operations increased significantly.&lt;/p&gt;

&lt;p&gt;📈 Interestingly, this performance issue was first reported in 2016 (&lt;a href="https://github.com/boltdb/bolt/issues/640" rel="noopener noreferrer"&gt;GitHub Issue&lt;/a&gt;), but it was never fixed. The author of BoltDB, Ben Johnson, stopped maintaining the project in 2017, stating:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Maintaining an open-source database requires an immense amount of time and energy. Changes to the code can have unintended and sometimes catastrophic effects, so even simple changes require hours of careful testing and validation. &lt;/p&gt;

&lt;p&gt;Unfortunately, I no longer have the time or energy to continue this work. Bolt is in a stable state and has years of successful production use. As such, I feel that leaving it in its current state is the most prudent course of action&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Although BoltDB was no longer maintained, the Go community forked it into a new project called bbolt (&lt;a href="https://github.com/etcd-io/bbolt" rel="noopener noreferrer"&gt;bbolt GitHub&lt;/a&gt;) to continue active maintenance and add new features. Unfortunately, Consul was still using the outdated, unmaintained version of BoltDB.&lt;/p&gt;

&lt;p&gt;In 2019, the freelist performance issue was finally resolved in bbolt (&lt;a href="https://www.alibabacloud.com/blog/594750" rel="noopener noreferrer"&gt;Alibaba Cloud Blog&lt;/a&gt;). The fix was straightforward: using a hashmap instead of an array, reducing the linear scan overhead and allowing instant lookups. ( 🚀 I love how a simple idea brings huge performance boost!)&lt;/p&gt;

&lt;p&gt;Since this fix was committed to bbolt—not BoltDB—Consul did not benefit from the improvement, ultimately leading to the three-day Roblox outage in 2021.&lt;/p&gt;

&lt;h3&gt;
  
  
  🤔 Unanswered Questions
&lt;/h3&gt;

&lt;p&gt;This post has covered a lot, but there’s only so much that can fit into a single post. As an engineer, I find myself eager to explore further details. Several intriguing questions remain unanswered:&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Why didn’t Roblox roll back Consul’s streaming feature sooner?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Given that Consul was clearly the culprit early on and a significant change had just been made to its infrastructure, rolling back should have been one of the first things attempted. What factors delayed this decision?&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Why did only some Consul servers experience the BoltDB freelist performance issue?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In theory, all servers should have been in a similar state since the leader is usually ahead of its followers only by a small margin. Yet, only some instances suffered severe degradation. What caused this inconsistency?&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Why didn’t restoring Consul’s state using a previous snapshot fix the issue?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;My hypothesis is that restoring Consul’s state did not reset BoltDB’s underlying raft.db file on each server, meaning the bloated freelist persisted even after the rollback. If true, this suggests snapshots do not include critical optimizations for internal database structures.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Why did reducing Consul usage work temporarily before failing again?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the freelist was already too large, reducing usage shouldn’t have provided any relief. Did scaling down slow the growth of the freelist temporarily, delaying the inevitable, or was another factor at play?&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Why did the new streaming feature work for a day before the outage occurred?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the new Consul streaming feature was inherently flawed, why didn’t the system immediately degrade? Was there an initial buffer that temporarily masked the issue, or did a specific traffic pattern trigger the breakdown?&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Since the BoltDB freelist performance issue has been around for years, why didn’t Roblox experience such system performance degradation in earlier months?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;BoltDB’s freelist inefficiency had been a known issue since 2016. What changed in Roblox’s workload or data structure that made this issue surface now? Did the new Consul streaming feature exacerbate the problem by dramatically increasing write operations to BoltDB?&lt;/p&gt;

&lt;h3&gt;
  
  
  💡 Endnotes
&lt;/h3&gt;

&lt;p&gt;This post-mortem report offers invaluable lessons, and I highly encourage everyone to check it out!  There are also extensive discussions about this outage on &lt;a href="https://news.ycombinator.com/item?id=30013919" rel="noopener noreferrer"&gt;Hacker News&lt;/a&gt;, where BoltDB's author, Ben Johnson, also participated in the conversations.&lt;/p&gt;

&lt;p&gt;As a software engineer, I firmly believe that &lt;strong&gt;a key trait of a great engineer is the ability to efficiently navigate large, complex systems and diagnose issues under pressure&lt;/strong&gt;. I deeply admire and respect the engineers from Roblox and HashiCorp, who worked tirelessly under immense pressure to investigate and resolve the issue. Hats off to them for their resilience and expertise.&lt;/p&gt;

&lt;p&gt;Thank you for reading this far!  If you found this post useful, I’d truly appreciate it if you could share it with others.&lt;/p&gt;

</description>
      <category>distributedsystems</category>
      <category>sitereliabilityengineering</category>
      <category>database</category>
      <category>go</category>
    </item>
  </channel>
</rss>
