<?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: stringintech</title>
    <description>The latest articles on DEV Community by stringintech (@stringintech).</description>
    <link>https://dev.to/stringintech</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%2F2180525%2F32932cf3-c168-44b4-95a2-32e9386aadf0.jpg</url>
      <title>DEV Community: stringintech</title>
      <link>https://dev.to/stringintech</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/stringintech"/>
    <language>en</language>
    <item>
      <title>Fuzzing Bitcoin Core using AFL++ on Apple Silicon</title>
      <dc:creator>stringintech</dc:creator>
      <pubDate>Sun, 12 Oct 2025 11:25:56 +0000</pubDate>
      <link>https://dev.to/stringintech/fuzzing-bitcoin-core-using-afl-on-apple-silicon-23n</link>
      <guid>https://dev.to/stringintech/fuzzing-bitcoin-core-using-afl-on-apple-silicon-23n</guid>
      <description>&lt;p&gt;Steps to build and run a Bitcoin Core fuzz target on Apple Silicon using &lt;a href="https://github.com/AFLplusplus/AFLplusplus" rel="noopener noreferrer"&gt;AFL++&lt;/a&gt; (tested on M1 and M3):&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;First, install AFL++ via Homebrew:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;afl++
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This comes with the AFL++ instrumentation compiler &lt;code&gt;afl-clang-fast++&lt;/code&gt; (located in &lt;code&gt;/opt/homebrew/opt/afl++/bin&lt;/code&gt;) and LLVM from Homebrew (&lt;code&gt;/opt/homebrew/Cellar/llvm&lt;/code&gt;). Note that &lt;code&gt;afl-clang-lto&lt;/code&gt; is &lt;a href="https://github.com/AFLplusplus/AFLplusplus/blob/a3dbd38977fa1e87d29a9222aa7647422fdb0d43/docs/INSTALL.md?plain=1#L157" rel="noopener noreferrer"&gt;not available on macOS&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;code&gt;afl-clang-fast++&lt;/code&gt; is a wrapper around Homebrew's LLVM clang compiler (&lt;code&gt;/opt/homebrew/Cellar/llvm/&amp;lt;VERSION&amp;gt;/bin/clang++&lt;/code&gt;). When you compile with it, it produces binaries with the required runtime instrumentation that enables the AFL++ fuzz engine to track code coverage and guide fuzzing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuration
&lt;/h2&gt;

&lt;p&gt;Configure the fuzz build using &lt;code&gt;afl-clang-fast&lt;/code&gt; (replacing &lt;code&gt;afl-clang-lto&lt;/code&gt; in the command mentioned in &lt;a href="https://github.com/bitcoin/bitcoin/blob/200150beba6601237036bc01ee10f6a0a2246c3d/doc/fuzzing.md?plain=1#L221-L224" rel="noopener noreferrer"&gt;Bitcoin Core's fuzzing docs&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cmake &lt;span class="nt"&gt;-B&lt;/span&gt; build_fuzz &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-DCMAKE_C_COMPILER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/opt/homebrew/bin/afl-clang-fast"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-DCMAKE_CXX_COMPILER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/opt/homebrew/bin/afl-clang-fast++"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-DBUILD_FOR_FUZZING&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ON
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Troubleshooting LLVM Issues
&lt;/h2&gt;

&lt;p&gt;However, you may run into the same issue I did after attempting to build with &lt;code&gt;cmake --build build_fuzz&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Undefined symbols &lt;span class="k"&gt;for &lt;/span&gt;architecture arm64:
  &lt;span class="s2"&gt;"std::__1::__hash_memory(void const*, unsigned long)"&lt;/span&gt;, referenced from:
      Arena::Arena&lt;span class="o"&gt;(&lt;/span&gt;void&lt;span class="k"&gt;*&lt;/span&gt;, unsigned long, unsigned long&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in &lt;/span&gt;libbitcoin_util.a[32]&lt;span class="o"&gt;(&lt;/span&gt;lockedpool.cpp.o&lt;span class="o"&gt;)&lt;/span&gt;
      ...
ld: symbol&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt; not found &lt;span class="k"&gt;for &lt;/span&gt;architecture arm64
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is caused by a mismatch: &lt;code&gt;afl-clang-fast++&lt;/code&gt; compiles with Homebrew's LLVM headers (which declare &lt;code&gt;__hash_memory&lt;/code&gt;), but the linker defaults to Apple's older system libc++ (which lacks this symbol).&lt;/p&gt;

&lt;p&gt;Fix by explicitly using Homebrew's LLVM for both compilation and linking:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cmake &lt;span class="nt"&gt;-B&lt;/span&gt; build_fuzz &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-DCMAKE_C_COMPILER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/opt/homebrew/bin/afl-clang-fast"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-DCMAKE_CXX_COMPILER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/opt/homebrew/bin/afl-clang-fast++"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-DBUILD_FOR_FUZZING&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ON &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-DCMAKE_CXX_FLAGS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"-stdlib=libc++ -I/opt/homebrew/Cellar/llvm/&amp;lt;YOUR_VERSION&amp;gt;/include/c++/v1"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-DCMAKE_EXE_LINKER_FLAGS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"-L/opt/homebrew/Cellar/llvm/&amp;lt;YOUR_VERSION&amp;gt;/lib/c++ -lc++ -lc++abi"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;&amp;lt;YOUR_VERSION&amp;gt;&lt;/code&gt; with your installed LLVM version (e.g., 21.1.2).&lt;/p&gt;

&lt;p&gt;Now build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cmake &lt;span class="nt"&gt;--build&lt;/span&gt; build_fuzz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Running the Fuzzer
&lt;/h2&gt;

&lt;p&gt;To run a specific fuzz target (I'm running the &lt;code&gt;cmpctblock&lt;/code&gt; target introduced in &lt;a href="https://github.com/bitcoin/bitcoin/pull/33300" rel="noopener noreferrer"&gt;PR#33300&lt;/a&gt;):&lt;/p&gt;

&lt;p&gt;Create input and output directories:&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="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; fuzz-inputs/ fuzz-outputs/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generate initial test input:&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="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; 1000 /dev/urandom &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; fuzz-inputs/input.dat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configure system for AFL++ (may require sudo):&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="nb"&gt;sudo &lt;/span&gt;afl-system-config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start fuzzing:&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;FUZZ&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cmpctblock afl-fuzz &lt;span class="nt"&gt;-i&lt;/span&gt; fuzz-inputs &lt;span class="nt"&gt;-o&lt;/span&gt; fuzz-outputs &lt;span class="nt"&gt;--&lt;/span&gt; build_fuzz/bin/fuzz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see AFL++ running successfully:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;american fuzzy lop ++4.33c &lt;span class="o"&gt;{&lt;/span&gt;default&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;build_fuzz/bin/fuzz&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;explore]
┌─ process timing ────────────────────────────────────┬─ overall results ────┐
│        run &lt;span class="nb"&gt;time&lt;/span&gt; : 0 days, 0 hrs, 0 min, 20 sec      │  cycles &lt;span class="k"&gt;done&lt;/span&gt; : 0     │
│   last new find : none seen yet                     │ corpus count : 1     │
│last saved crash : none seen yet                     │saved crashes : 0     │
│ last saved hang : none seen yet                     │  saved hangs : 0     │
├─ cycle progress ─────────────────────┬─ map coverage┴──────────────────────┤
│  now processing : 0.0 &lt;span class="o"&gt;(&lt;/span&gt;0.0%&lt;span class="o"&gt;)&lt;/span&gt;         │    map density : 1.45% / 1.47%      │
│  runs timed out : 0 &lt;span class="o"&gt;(&lt;/span&gt;0.00%&lt;span class="o"&gt;)&lt;/span&gt;          │ count coverage : 1.11 bits/tuple    │
├─ stage progress ─────────────────────┼─ findings &lt;span class="k"&gt;in &lt;/span&gt;depth ─────────────────┤
│  now trying : trim 4/4               │ favored items : 1 &lt;span class="o"&gt;(&lt;/span&gt;100.00%&lt;span class="o"&gt;)&lt;/span&gt;         │
│ stage execs : 87/250 &lt;span class="o"&gt;(&lt;/span&gt;34.80%&lt;span class="o"&gt;)&lt;/span&gt;        │  new edges on : 1 &lt;span class="o"&gt;(&lt;/span&gt;100.00%&lt;span class="o"&gt;)&lt;/span&gt;         │
│ total execs : 332                    │ total crashes : 0 &lt;span class="o"&gt;(&lt;/span&gt;0 saved&lt;span class="o"&gt;)&lt;/span&gt;         │
│  &lt;span class="nb"&gt;exec &lt;/span&gt;speed : 10.16/sec &lt;span class="o"&gt;(&lt;/span&gt;zzzz...&lt;span class="o"&gt;)&lt;/span&gt;    │  total tmouts : 0 &lt;span class="o"&gt;(&lt;/span&gt;0 saved&lt;span class="o"&gt;)&lt;/span&gt;         │
├─ fuzzing strategy yields ────────────┴─────────────┬─ item geometry ───────┤
│   bit flips : 0/0, 0/0, 0/0                        │    levels : 1         │
│  byte flips : 0/0, 0/0, 0/0                        │   pending : 1         │
│ arithmetics : 0/0, 0/0, 0/0                        │  pend fav : 1         │
│  known ints : 0/0, 0/0, 0/0                        │ own finds : 0         │
│  dictionary : 0/0, 0/0, 0/0, 0/0                   │  imported : 0         │
│havoc/splice : 0/0, 0/0                             │ stability : 99.08%    │
│py/custom/rq : unused, unused, unused, unused       ├───────────────────────┘
│    trim/eff : n/a, n/a                             │             &lt;span class="o"&gt;[&lt;/span&gt;cpu: 24%]
└─ strategy: explore ────────── state: started :-&lt;span class="o"&gt;)&lt;/span&gt; ──┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using &lt;code&gt;AFL_DEBUG&lt;/code&gt; and &lt;code&gt;AFL_NO_UI&lt;/code&gt; environment variables provides debug logs in a more readable format for troubleshooting:&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;FUZZ&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cmpctblock &lt;span class="nv"&gt;AFL_DEBUG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="nv"&gt;AFL_NO_UI&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 afl-fuzz &lt;span class="nt"&gt;-i&lt;/span&gt; fuzz-inputs &lt;span class="nt"&gt;-o&lt;/span&gt; fuzz-outputs &lt;span class="nt"&gt;--&lt;/span&gt; build_fuzz/bin/fuzz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;+] Enabled environment variable AFL_DEBUG with value 1
&lt;span class="o"&gt;[&lt;/span&gt;+] Enabled environment variable AFL_DEBUG with value 1
&lt;span class="o"&gt;[&lt;/span&gt;+] Enabled environment variable AFL_NO_UI with value 1
afl-fuzz++4.33c based on afl by Michal Zalewski and a large online community
&lt;span class="o"&gt;[&lt;/span&gt;+] AFL++ is maintained by Marc &lt;span class="s2"&gt;"van Hauser"&lt;/span&gt; Heuse, Dominik Maier, Andrea Fioraldi and Heiko &lt;span class="s2"&gt;"hexcoder"&lt;/span&gt; Eißfeldt
&lt;span class="o"&gt;[&lt;/span&gt;+] AFL++ is open &lt;span class="nb"&gt;source&lt;/span&gt;, get it at https://github.com/AFLplusplus/AFLplusplus
&lt;span class="o"&gt;[&lt;/span&gt;+] NOTE: AFL++ &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; v3 has changed defaults and behaviours - see README.md
&lt;span class="o"&gt;[&lt;/span&gt;+] No &lt;span class="nt"&gt;-M&lt;/span&gt;/-S &lt;span class="nb"&gt;set&lt;/span&gt;, autoconfiguring &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="s2"&gt;"-S default"&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; Getting to work...
&lt;span class="o"&gt;[&lt;/span&gt;+] Using exploration-based constant power schedule &lt;span class="o"&gt;(&lt;/span&gt;EXPLORE&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;+] Enabled testcache with 50 MB
&lt;span class="o"&gt;[&lt;/span&gt;+] Generating fuzz data with a length of &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1048576
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; Checking CPU scaling governor...
&lt;span class="o"&gt;[!]&lt;/span&gt; WARNING: Could not check CPU min frequency
&lt;span class="o"&gt;[&lt;/span&gt;+] Disabling the UI because AFL_NO_UI is set.
&lt;span class="o"&gt;[&lt;/span&gt;+] You have 8 CPU cores and 3 runnable tasks &lt;span class="o"&gt;(&lt;/span&gt;utilization: 38%&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;+] Try parallel &lt;span class="nb"&gt;jobs&lt;/span&gt; - see /opt/homebrew/Cellar/afl++/4.33c_1/share/doc/afl/fuzzing_in_depth.md#c-using-multiple-cores
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; Setting up output directories...
&lt;span class="o"&gt;[&lt;/span&gt;+] Output directory exists but deemed OK to reuse.
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; Deleting old session data...
&lt;span class="o"&gt;[&lt;/span&gt;+] Output &lt;span class="nb"&gt;dir &lt;/span&gt;cleanup successful.
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; Validating target binary...
&lt;span class="o"&gt;[&lt;/span&gt;+] Persistent mode binary detected.
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; Scanning &lt;span class="s1"&gt;'fuzz-inputs'&lt;/span&gt;...
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; Creating hard links &lt;span class="k"&gt;for &lt;/span&gt;all input files...
&lt;span class="o"&gt;[&lt;/span&gt;+] Loaded a total of 1 seeds.
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; Spinning up the fork server...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Troubleshooting Fork Server Issues
&lt;/h2&gt;

&lt;p&gt;With the fork server optimization enabled, you may face unexpected worker process terminations. I investigated the unexpected crashes caused by these terminations in the &lt;code&gt;cmpctblock&lt;/code&gt; fuzz harness and documented my findings in &lt;a href="https://github.com/bitcoin/bitcoin/pull/33300#discussion_r2417231848" rel="noopener noreferrer"&gt;this GitHub comment&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To avoid such issues, &lt;a href="https://github.com/AFLplusplus/AFLplusplus/blob/474ff18ba2a7999a518a4d194fcd5a1f87c3625d/docs/INSTALL.md?plain=1#L168-L170" rel="noopener noreferrer"&gt;disable the fork server optimization&lt;/a&gt;:&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;FUZZ&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cmpctblock &lt;span class="nv"&gt;AFL_NO_FORKSRV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 afl-fuzz &lt;span class="nt"&gt;-i&lt;/span&gt; fuzz-inputs &lt;span class="nt"&gt;-o&lt;/span&gt; fuzz-outputs &lt;span class="nt"&gt;--&lt;/span&gt; build_fuzz/bin/fuzz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>bitcoin</category>
    </item>
    <item>
      <title>How Bitcoin's 10-Minute Block Interval Ends Up Being 20</title>
      <dc:creator>stringintech</dc:creator>
      <pubDate>Sat, 12 Apr 2025 13:24:24 +0000</pubDate>
      <link>https://dev.to/stringintech/how-bitcoins-10-minute-block-interval-ends-up-being-20-16mf</link>
      <guid>https://dev.to/stringintech/how-bitcoins-10-minute-block-interval-ends-up-being-20-16mf</guid>
      <description>&lt;p&gt;Earlier, I was reading an interesting &lt;a href="https://r6.ca/blog/20180225T160548Z.html" rel="noopener noreferrer"&gt;mathematical explanation&lt;/a&gt; behind the paradox related to the interval between mined Bitcoin blocks. Despite the fact that the interval is 10 minutes on average, if you show up at a random time, you’ll see that the expected time until the next block appears is 10 minutes—regardless of how long you’ve already been waiting. Similarly, on average, you should expect that the previous block was mined 10 minutes ago. With this sampling approach, you end up with 10 minutes before and 10 minutes after, so overall, the interval between two consecutive blocks appears to be 20 minutes on average—not just 10 as you might initially think!&lt;/p&gt;

&lt;p&gt;To explore what the article was saying, I had an idea. I thought: What if I generate lots of intervals whose average is 10 minutes, and then string them together to create a timeline? Next, I’d generate a bunch of random points along this timeline, and for each one, check which interval it falls into, calculating the distance from the point to the start of the interval and to the end. By averaging these distances, I should be able to see for myself that, on average, the end of the interval is 10 minutes away, the start of the interval block 10 minutes ago, and the full interval adds up to 20 minutes.&lt;/p&gt;

&lt;p&gt;I wrote a little piece of &lt;a href="https://github.com/stringintech/btc-vault/blob/main/Chaincode%20Seminars/Bitcoin/Mining%20and%20Network%20Block%20Propagation/why-20-minutes/main.cpp" rel="noopener noreferrer"&gt;code&lt;/a&gt; to do just that—and, unsuprisingly, the results matched my expectations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Average block interval: 10.0339
Avg time to next block: 10.0594
Avg time since last block: 10.1122
Avg total interval length: 20.1717
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;iostream&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;vector&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;random&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;cmath&amp;gt;&lt;/span&gt;&lt;span class="cp"&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="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;INTERVAL_COUNT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;SAMPLE_COUNT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;AVG_BLOCK_TIME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;10.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Setup random number generation&lt;/span&gt;
    &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;random_device&lt;/span&gt; &lt;span class="n"&gt;rd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;mt19937&lt;/span&gt; &lt;span class="n"&gt;gen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rd&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;uniform_real_distribution&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;uniform_dist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Generate exponentially distributed block intervals&lt;/span&gt;
    &lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;random_interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&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="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;AVG_BLOCK_TIME&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;uniform_dist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gen&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="c1"&gt;// Create a timeline of block boundaries&lt;/span&gt;
    &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;block_times&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="k"&gt;for&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;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;INTERVAL_COUNT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;block_times&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;push_back&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block_times&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;back&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;random_interval&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Calculate the actual average interval&lt;/span&gt;
    &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;timeline_end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;block_times&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;back&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;achieved_mean&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;timeline_end&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;INTERVAL_COUNT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;cout&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s"&gt;"Average block interval: "&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;achieved_mean&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;endl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Sample random points on the timeline&lt;/span&gt;
    &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;uniform_real_distribution&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;timeline_dist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeline_end&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;time_to_next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;time_since_last&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;total_interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;for&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;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;SAMPLE_COUNT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Pick a random point in time&lt;/span&gt;
        &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;timeline_dist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gen&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Find which interval contains this point&lt;/span&gt;
        &lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;upper_bound&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block_times&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;block_times&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&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;idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;block_times&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Calculate times to adjacent blocks&lt;/span&gt;
        &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;next_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;block_times&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&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;-&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;prev_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;block_times&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;interval_length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;block_times&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&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;-&lt;/span&gt; &lt;span class="n"&gt;block_times&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

        &lt;span class="n"&gt;time_to_next&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;next_time&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;time_since_last&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;prev_time&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;total_interval&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;interval_length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;cout&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s"&gt;"Avg time to next block: "&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;time_to_next&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;SAMPLE_COUNT&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;endl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;cout&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s"&gt;"Avg time since last block: "&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;time_since_last&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;SAMPLE_COUNT&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;endl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;cout&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s"&gt;"Avg total interval length: "&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;total_interval&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;SAMPLE_COUNT&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;endl&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;



</description>
      <category>bitcoin</category>
      <category>blockchain</category>
    </item>
    <item>
      <title>PostgreSQL Buffer Cache: A Practical Guide</title>
      <dc:creator>stringintech</dc:creator>
      <pubDate>Sat, 07 Dec 2024 13:13:49 +0000</pubDate>
      <link>https://dev.to/stringintech/postgresql-buffer-cache-a-practical-guide-3251</link>
      <guid>https://dev.to/stringintech/postgresql-buffer-cache-a-practical-guide-3251</guid>
      <description>&lt;p&gt;Before adding indexes or application-level caching to optimize PostgreSQL performance, it's worth understanding how a relational database like PostgreSQL manages memory. While we'll focus on PostgreSQL's implementation, the concepts discussed here are fundamental to understanding memory management in most relational database systems.&lt;/p&gt;

&lt;p&gt;PostgreSQL keeps frequently accessed data in memory, sometimes providing the performance boost we need without additional complexity of introducing more fined-grained caches in application level. Let's try to come up with a basic idea of this mechanism through practical examples to hopefully better inform our optimization decisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding the Fundamentals
&lt;/h2&gt;

&lt;p&gt;Before diving into practical examples, let's clarify some key PostgreSQL concepts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Relation&lt;/strong&gt;: Any database object that contains rows. Tables, indexes, and sequences are all relations in PostgreSQL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Page&lt;/strong&gt;: The basic unit of storage in PostgreSQL (typically 8KB). Each relation is stored as a collection of pages on disk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Buffer&lt;/strong&gt;: When a page is loaded into memory, it becomes a buffer. Think of buffers as the in-memory representation of disk pages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Buffer Cache&lt;/strong&gt;: A shared memory area where PostgreSQL keeps frequently accessed pages.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Our Toolkit: PostgreSQL Buffer Cache Monitoring with &lt;code&gt;pg_buffercache&lt;/code&gt; and &lt;code&gt;pg_class&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;To explore buffer cache behavior, we'll use two main PostgreSQL tools:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;pg_buffercache&lt;/code&gt; extension&lt;/strong&gt;: Provides real-time visibility into shared buffer cache content, allowing us to track which pages are in memory and their current state. See the PostgreSQL &lt;a href="https://www.postgresql.org/docs/current/pgbuffercache.html" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; for more details.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;pg_class&lt;/code&gt; system catalog&lt;/strong&gt;: Contains metadata about database objects (tables, indexes, etc.)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's start by installing the &lt;code&gt;pg_buffercache&lt;/code&gt; extension:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;EXTENSION&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;pg_buffercache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Practical Exploration
&lt;/h2&gt;

&lt;p&gt;We'll follow these steps to understand buffer cache behavior:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a test table with predictable data size&lt;/li&gt;
&lt;li&gt;Observe how data is stored in pages&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;pg_buffercache&lt;/code&gt; to observe how pages are loaded into memory during queries&lt;/li&gt;
&lt;li&gt;Add an index to see how it affects page loading patterns&lt;/li&gt;
&lt;li&gt;Track how buffer cache state changes when we modify data&lt;/li&gt;
&lt;li&gt;See how system processes handle dirty (modified) pages&lt;/li&gt;
&lt;li&gt;Compare query performance for cached vs uncached data&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Setting Up Our Test Environment
&lt;/h3&gt;

&lt;p&gt;Let's create a test table &lt;strong&gt;without&lt;/strong&gt; any indexes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;buffer_test&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt;      &lt;span class="nb"&gt;SERIAL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;data&lt;/span&gt;    &lt;span class="nb"&gt;TEXT&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Insert 100k rows with 1KB data each&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;buffer_test&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'x'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;generate_series&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="mi"&gt;100000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Understanding Table Size
&lt;/h3&gt;

&lt;p&gt;First, let's examine our table's size using &lt;code&gt;pg_class&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;relpages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reltuples&lt;/span&gt; 
&lt;span class="k"&gt;FROM&lt;/span&gt;   &lt;span class="n"&gt;pg_class&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt;  &lt;span class="n"&gt;relname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'buffer_test'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;relpages&lt;/th&gt;
&lt;th&gt;reltuples&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The zero values indicate that table statistics haven't been updated. Let's fix that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ANALYZE&lt;/span&gt; &lt;span class="n"&gt;buffer_test&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;relpages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reltuples&lt;/span&gt; 
&lt;span class="k"&gt;FROM&lt;/span&gt;   &lt;span class="n"&gt;pg_class&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt;  &lt;span class="n"&gt;relname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'buffer_test'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;relpages&lt;/th&gt;
&lt;th&gt;reltuples&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;14,286&lt;/td&gt;
&lt;td&gt;100,000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;relpages&lt;/code&gt;: Number of disk pages the table uses&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;reltuples&lt;/code&gt;: Estimated number of rows&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Monitoring Cache Behavior
&lt;/h3&gt;

&lt;p&gt;Let's verify our cache is empty:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;span class="k"&gt;FROM&lt;/span&gt;      &lt;span class="n"&gt;pg_buffercache&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt;      &lt;span class="n"&gt;pg_class&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt; 
  &lt;span class="k"&gt;ON&lt;/span&gt;      &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relfilenode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relfilenode&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;     &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'buffer_test'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;count&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;relfilenode&lt;/code&gt; column in &lt;code&gt;pg_buffercache&lt;/code&gt; helps us identify which buffers belong to our table. Now, let's query a specific row:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;buffer_test&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;70000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Check cache after the query&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;span class="k"&gt;FROM&lt;/span&gt;      &lt;span class="n"&gt;pg_buffercache&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt;      &lt;span class="n"&gt;pg_class&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt; 
  &lt;span class="k"&gt;ON&lt;/span&gt;      &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relfilenode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relfilenode&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;     &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'buffer_test'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;count&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You may have expected to see only one page loaded into memory since the row we queried earlier belongs to one page. But it's not the case. Since we have not introduced any indexes on the id column yet, database cannot efficiently find that one page and it has to do a sequential scan which causes PostgreSQL to read through all table pages from the beginning until it finds our target row. The number of pages loaded depends on various factors including the database's buffer replacement strategy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding an Index
&lt;/h3&gt;

&lt;p&gt;Let's add an index:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;buffer_test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now restart PostgreSQL server to clear the cache. As an example, this is how I did it on my installed version on macOS using &lt;a href="https://www.postgresql.org/docs/current/app-pg-ctl.html" rel="noopener noreferrer"&gt;pg_ctl&lt;/a&gt;:&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="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; postgres pg_ctl restart &lt;span class="nt"&gt;-D&lt;/span&gt; /Library/PostgreSQL/17/data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After restart, query the same row:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;    &lt;span class="n"&gt;ctid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; 
&lt;span class="k"&gt;FROM&lt;/span&gt;      &lt;span class="n"&gt;buffer_test&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt;     &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;70000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ctid&lt;/th&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;(9999,7)&lt;/td&gt;
&lt;td&gt;70000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;ctid&lt;/code&gt; (Tuple ID) is a special system column in PostgreSQL that represents the physical location of a row &lt;strong&gt;version&lt;/strong&gt; within its table. Every row in a PostgreSQL table has a Tuple ID that consists of two numbers: the block number (or page number) and the tuple index within that block. Here &lt;code&gt;ctid&lt;/code&gt; shows our row is on page 9999. Let's check the buffer cache:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;    &lt;span class="n"&gt;bufferid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
          &lt;span class="n"&gt;relblocknumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
          &lt;span class="n"&gt;isdirty&lt;/span&gt; 
&lt;span class="k"&gt;FROM&lt;/span&gt;      &lt;span class="n"&gt;pg_buffercache&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;  
&lt;span class="k"&gt;JOIN&lt;/span&gt;      &lt;span class="n"&gt;pg_class&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt; 
  &lt;span class="k"&gt;ON&lt;/span&gt;      &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relfilenode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relfilenode&lt;/span&gt;  
&lt;span class="k"&gt;WHERE&lt;/span&gt;     &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'buffer_test'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;bufferid&lt;/th&gt;
&lt;th&gt;relblocknumber&lt;/th&gt;
&lt;th&gt;isdirty&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;149&lt;/td&gt;
&lt;td&gt;9999&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;With the index, PostgreSQL loaded only the needed page. Key columns used here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;bufferid&lt;/code&gt;: Unique identifier for the buffer in shared memory&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;relblocknumber&lt;/code&gt;: Page number within the relation&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;isdirty&lt;/code&gt;: Indicates if the page has been modified&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Observing Dirty Pages
&lt;/h3&gt;

&lt;p&gt;Let's modify our row:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt;    &lt;span class="n"&gt;buffer_test&lt;/span&gt; 
&lt;span class="k"&gt;SET&lt;/span&gt;       &lt;span class="k"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'modified data'&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt;     &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;70000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt;    &lt;span class="n"&gt;bufferid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
          &lt;span class="n"&gt;relblocknumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
          &lt;span class="n"&gt;isdirty&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;      &lt;span class="n"&gt;pg_buffercache&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt;      &lt;span class="n"&gt;pg_class&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt; 
  &lt;span class="k"&gt;ON&lt;/span&gt;      &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relfilenode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relfilenode&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;     &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'buffer_test'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;bufferid&lt;/th&gt;
&lt;th&gt;relblocknumber&lt;/th&gt;
&lt;th&gt;isdirty&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;149&lt;/td&gt;
&lt;td&gt;9999&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The page is now marked dirty, indicating pending changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Understanding Checkpoints
&lt;/h3&gt;

&lt;p&gt;By calling &lt;code&gt;CHECKPOINT&lt;/code&gt; we can force writing dirty pages to disk. By default, PostgreSQL runs automatic checkpoints:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Every &lt;code&gt;checkpoint_timeout&lt;/code&gt; seconds (default: 5 minutes)&lt;/li&gt;
&lt;li&gt;When WAL or &lt;a href="https://www.postgresql.org/docs/current/wal-intro.html" rel="noopener noreferrer"&gt;Write-Ahead Logging&lt;/a&gt; reaches &lt;code&gt;max_wal_size&lt;/code&gt; (default: 1 GB)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now let's force a checkpoint to write dirty pages to disk:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CHECKPOINT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Check cache state&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;    &lt;span class="n"&gt;bufferid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
          &lt;span class="n"&gt;relblocknumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
          &lt;span class="n"&gt;isdirty&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;      &lt;span class="n"&gt;pg_buffercache&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt;      &lt;span class="n"&gt;pg_class&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt; 
  &lt;span class="k"&gt;ON&lt;/span&gt;      &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relfilenode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relfilenode&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;     &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'buffer_test'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;bufferid&lt;/th&gt;
&lt;th&gt;relblocknumber&lt;/th&gt;
&lt;th&gt;isdirty&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;149&lt;/td&gt;
&lt;td&gt;9999&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Not dirty anymore but still in cache!&lt;/p&gt;

&lt;h3&gt;
  
  
  Comparing Cached vs Uncached Access
&lt;/h3&gt;

&lt;p&gt;Let's demonstrate the performance benefit of the buffer cache by comparing access times for cached and uncached data. First we query the page number for other rows around the row with id &lt;code&gt;70000&lt;/code&gt; that we have been working with so far:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;    &lt;span class="n"&gt;ctid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; 
&lt;span class="k"&gt;FROM&lt;/span&gt;      &lt;span class="n"&gt;buffer_test&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt;     &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;between&lt;/span&gt; &lt;span class="mi"&gt;69997&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="mi"&gt;70003&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ctid&lt;/th&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;(9999,4)&lt;/td&gt;
&lt;td&gt;69997&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;(9999,5)&lt;/td&gt;
&lt;td&gt;69998&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;(9999,6)&lt;/td&gt;
&lt;td&gt;69999&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;(9999,8)&lt;/td&gt;
&lt;td&gt;70000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;(10000,1)&lt;/td&gt;
&lt;td&gt;70001&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;(10000,2)&lt;/td&gt;
&lt;td&gt;70002&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;(10000,3)&lt;/td&gt;
&lt;td&gt;70003&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Now restart the database server once more to make sure we continue with a clean cache. Then query the id &lt;code&gt;70000&lt;/code&gt; as we did before to load the page &lt;code&gt;9999&lt;/code&gt; into buffer cache. Now we are ready to perform our comparison using &lt;code&gt;EXPLAIN ANALYSE&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;EXPLAIN&lt;/span&gt; &lt;span class="k"&gt;ANALYSE&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;    &lt;span class="n"&gt;ctid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; 
&lt;span class="k"&gt;FROM&lt;/span&gt;      &lt;span class="n"&gt;buffer_test&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt;     &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;69997&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- Execution Time: 0.046 ms&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;EXPLAIN&lt;/span&gt; &lt;span class="k"&gt;ANALYSE&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;    &lt;span class="n"&gt;ctid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; 
&lt;span class="k"&gt;FROM&lt;/span&gt;      &lt;span class="n"&gt;buffer_test&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt;     &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- Execution Time: 0.604 ms&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you see the execution time for accessing the cached data (row with id &lt;code&gt;69997&lt;/code&gt; belongs the cached page &lt;code&gt;9999&lt;/code&gt;) is about 13 times smaller than the uncached access!&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;We explored PostgreSQL's buffer cache through practical examples that demonstrated its basic memory management behavior. By creating a test table and using monitoring tools, we observed how data pages move between disk and memory during different operations. Our experiments showed how queries without indexes lead to sequential scans that load multiple pages into memory, while adding an index allowed PostgreSQL to load only the specific page needed. We also saw how pages get marked as "dirty" when modified and remain in cache even after a checkpoint writes them to disk. Finally, we demonstrated how PostgreSQL's buffer cache optimization works in practice by comparing query times between accessing rows from previously loaded pages versus pages that required fresh disk reads.&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>database</category>
    </item>
    <item>
      <title>Understanding JWT Authentication: Spring Security's Architecture and Go Implementation</title>
      <dc:creator>stringintech</dc:creator>
      <pubDate>Sat, 30 Nov 2024 15:40:47 +0000</pubDate>
      <link>https://dev.to/stringintech/understanding-jwt-authentication-spring-securitys-architecture-and-go-implementation-edk</link>
      <guid>https://dev.to/stringintech/understanding-jwt-authentication-spring-securitys-architecture-and-go-implementation-edk</guid>
      <description>&lt;p&gt;After setting up JWT stateless authentication (available &lt;a href="https://github.com/stringintech/security-101/tree/main/java" rel="noopener noreferrer"&gt;here&lt;/a&gt;), I wanted to understand what happens under Spring Security's abstractions by identifying key components and their interactions. To make this exploration more engaging, I reimplemented &lt;a href="https://github.com/stringintech/security-101/tree/main/go" rel="noopener noreferrer"&gt;a minimal version&lt;/a&gt; in Go using the standard HTTP library. By breaking down three core flows - registration, token generation, and protected resource access - and rebuilding them in Go, I set out to map Spring Security's authentication patterns to simpler components.&lt;/p&gt;

&lt;p&gt;This post focuses specifically on authentication flows - how the system verifies user identity - rather than authorization. We'll explore the flows with sequence diagrams that trace requests through different components in Spring Security's architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Main Components
&lt;/h2&gt;

&lt;p&gt;The system provides three endpoints:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User Registration: Accepts username and password from new users&lt;/li&gt;
&lt;li&gt;Token Generation (Login): Creates a JWT token when users successfully log in with valid credentials&lt;/li&gt;
&lt;li&gt;Protected Access: Enables authenticated users to access protected resources using their token. The &lt;code&gt;getAuthenticatedUser&lt;/code&gt; endpoint serves as an example, returning profile information for the authenticated token holder&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In the following sections, I explain the core components involved in each flow, with a sequence diagram for each.&lt;/p&gt;

&lt;h3&gt;
  
  
  Registration Flow
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpccqwinv4eqgput4yy5j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpccqwinv4eqgput4yy5j.png" alt="Registration Flow" width="800" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A registration request containing username and password passes through the &lt;strong&gt;Spring Security filter chain&lt;/strong&gt;, where minimal processing occurs since the registration endpoint was configured to not require authentication in &lt;a href="https://github.com/stringintech/security-101/blob/9be5bc387208fa8ade2edb35431ecace769f52f7/java/src/main/java/com/stringintech/security101/config/SecurityConfiguration.java#L35" rel="noopener noreferrer"&gt;SecurityConfiguration&lt;/a&gt;. The request then moves through Spring's &lt;code&gt;DispatcherServlet&lt;/code&gt;, which routes it to the appropriate method in &lt;code&gt;UserController&lt;/code&gt; based on the URL pattern. The request reaches UserController's &lt;a href="https://github.com/stringintech/security-101/blob/9be5bc387208fa8ade2edb35431ecace769f52f7/java/src/main/java/com/stringintech/security101/controller/UserController.java#L36" rel="noopener noreferrer"&gt;register&lt;/a&gt; endpoint, where the user information is stored along with a &lt;strong&gt;hashed&lt;/strong&gt; password.&lt;/p&gt;

&lt;h3&gt;
  
  
  Token Generation Flow
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdb2yavnjiudfezopijdr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdb2yavnjiudfezopijdr.png" alt="Token Generation Flow" width="800" height="438"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A login request containing username and password passes through the &lt;strong&gt;Spring Security filter chain&lt;/strong&gt;, where minimal processing occurs as this endpoint is also configured to not require authentication in &lt;a href="https://github.com/stringintech/security-101/blob/9be5bc387208fa8ade2edb35431ecace769f52f7/java/src/main/java/com/stringintech/security101/config/SecurityConfiguration.java#L35" rel="noopener noreferrer"&gt;SecurityConfiguration&lt;/a&gt;. The request moves through Spring's &lt;code&gt;DispatcherServlet&lt;/code&gt; to UserController's &lt;a href="https://github.com/stringintech/security-101/blob/9be5bc387208fa8ade2edb35431ecace769f52f7/java/src/main/java/com/stringintech/security101/controller/UserController.java#L44" rel="noopener noreferrer"&gt;login&lt;/a&gt; endpoint, which delegates to &lt;code&gt;AuthenticationManager&lt;/code&gt;. Using the configured beans defined in &lt;a href="https://github.com/stringintech/security-101/blob/main/java/src/main/java/com/stringintech/security101/config/ApplicationConfiguration.java" rel="noopener noreferrer"&gt;ApplicationConfiguration&lt;/a&gt;, AuthenticationManager verifies the provided credentials against stored ones. After successful authentication, the UserController uses &lt;code&gt;JwtService&lt;/code&gt; to generate a JWT token containing the user's information and metadata like creation time, which is returned to the client for subsequent authenticated requests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Protected Resource Access Flow
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Successful Authentication Flow (200)
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2lbl82wh9w4hl6dhll4n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2lbl82wh9w4hl6dhll4n.png" alt="Successful Authentication Flow (200)" width="800" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Failed Authentication Flow (401)
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd49tjsuaxxwznk34q7fz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd49tjsuaxxwznk34q7fz.png" alt="Failed Authentication Flow (401)" width="800" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When a request containing a JWT token in its Authorization header arrives, it passes through the &lt;a href="https://github.com/stringintech/security-101/blob/main/java/src/main/java/com/stringintech/security101/config/JwtAuthenticationFilter.java" rel="noopener noreferrer"&gt;JwtAuthenticationFilter&lt;/a&gt; - a custom defined &lt;code&gt;OncePerRequestFilter&lt;/code&gt; - which processes the token using &lt;code&gt;JwtService&lt;/code&gt;. If valid, the filter retrieves the user via &lt;code&gt;UserDetailsService&lt;/code&gt; configured in &lt;a href="https://github.com/stringintech/security-101/blob/9be5bc387208fa8ade2edb35431ecace769f52f7/java/src/main/java/com/stringintech/security101/config/ApplicationConfiguration.java#L25" rel="noopener noreferrer"&gt;ApplicationConfiguration&lt;/a&gt; and sets the authentication in &lt;code&gt;SecurityContextHolder&lt;/code&gt;. If the token is missing or invalid, the filter allows the request to continue without setting authentication.&lt;/p&gt;

&lt;p&gt;Later in the chain, &lt;code&gt;AuthorizationFilter&lt;/code&gt; checks if the request is properly authenticated via SecurityContextHolder. When it detects missing authentication, it throws an &lt;code&gt;AccessDeniedException&lt;/code&gt;. This exception is caught by &lt;code&gt;ExceptionTranslationFilter&lt;/code&gt;, which checks if the user is anonymous and delegates to the configured &lt;a href="https://github.com/stringintech/security-101/blob/main/java/src/main/java/com/stringintech/security101/config/JwtAuthenticationEntryPoint.java" rel="noopener noreferrer"&gt;JwtAuthenticationEntryPoint&lt;/a&gt; in &lt;a href="https://github.com/stringintech/security-101/blob/9be5bc387208fa8ade2edb35431ecace769f52f7/java/src/main/java/com/stringintech/security101/config/SecurityConfiguration.java#L41" rel="noopener noreferrer"&gt;SecurityConfiguration&lt;/a&gt; to return a 401 Unauthorized response.&lt;/p&gt;

&lt;p&gt;If all filters pass, the request reaches Spring's &lt;code&gt;DispatcherServlet&lt;/code&gt; which routes it to the &lt;a href="https://github.com/stringintech/security-101/blob/9be5bc387208fa8ade2edb35431ecace769f52f7/java/src/main/java/com/stringintech/security101/controller/UserController.java#L57" rel="noopener noreferrer"&gt;getAuthenticatedUser&lt;/a&gt; endpoint in &lt;code&gt;UserController&lt;/code&gt;. This endpoint retrieves the authenticated user information from SecurityContextHolder that was populated during the filter chain process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Spring Security employs a rich ecosystem of filters and specialized components to handle various security concerns. To understand the core authentication flow, I only focused on the key players in JWT token validation and user authentication.&lt;/p&gt;

&lt;h2&gt;
  
  
  Go Implementation: Mapping Components
&lt;/h2&gt;

&lt;p&gt;The Go implementation provides similar functionality through a simplified architecture that maps to key Spring Security components:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/stringintech/security-101/blob/main/go/auth/filter_chain.go" rel="noopener noreferrer"&gt;FilterChain&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Provides a minimal version of Spring Security's filter chain&lt;/li&gt;
&lt;li&gt;Processes filters sequentially for each request&lt;/li&gt;
&lt;li&gt;Uses a per-request chain instance (&lt;a href="https://github.com/stringintech/security-101/blob/main/go/auth/virtual_filter_chain.go" rel="noopener noreferrer"&gt;VirtualFilterChain&lt;/a&gt;) for &lt;strong&gt;thread safety&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/stringintech/security-101/blob/main/go/server/dispatcher.go" rel="noopener noreferrer"&gt;Dispatcher&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Maps to Spring's &lt;code&gt;DispatcherServlet&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Routes requests to appropriate handlers after security filter processing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Authentication &lt;a href="https://github.com/stringintech/security-101/blob/main/go/auth/context.go" rel="noopener noreferrer"&gt;Context&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Uses Go's &lt;code&gt;context&lt;/code&gt; package to store authentication state per request&lt;/li&gt;
&lt;li&gt;Maps to Spring's &lt;code&gt;SecurityContextHolder&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/stringintech/security-101/blob/main/go/auth/jwt_filter.go" rel="noopener noreferrer"&gt;JwtFilter&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Direct equivalent to Spring's &lt;code&gt;JwtAuthenticationFilter&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Extracts and validates JWT tokens&lt;/li&gt;
&lt;li&gt;Populates authentication context on successful validation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/stringintech/security-101/blob/main/go/auth/auth_filter.go" rel="noopener noreferrer"&gt;AuthenticationFilter&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simplified version of Spring's &lt;code&gt;AuthorizationFilter&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Solely focusing on authentication verification&lt;/li&gt;
&lt;li&gt;Checks authentication context and returns 401 if missing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/stringintech/security-101/blob/main/go/auth/jwt_service.go" rel="noopener noreferrer"&gt;JwtService&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Similar to Spring's &lt;code&gt;JwtService&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Handles token generation and validation&lt;/li&gt;
&lt;li&gt;Uses same core JWT operations but with simpler configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Test Coverage
&lt;/h2&gt;

&lt;p&gt;Both implementations include integration tests (&lt;a href="https://github.com/stringintech/security-101/blob/main/go/test/auth_test.go" rel="noopener noreferrer"&gt;auth_test.go&lt;/a&gt; and &lt;a href="https://github.com/stringintech/security-101/blob/main/java/src/test/java/com/stringintech/security101/AuthTest.java" rel="noopener noreferrer"&gt;AuthTest.java&lt;/a&gt;) verifying key authentication scenarios:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Registration Flow&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Successful user registration with valid credentials&lt;/li&gt;
&lt;li&gt;Duplicate username registration attempt&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Login Flow&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Successful login with valid credentials&lt;/li&gt;
&lt;li&gt;Login attempt with non-existent username&lt;/li&gt;
&lt;li&gt;Login attempt with incorrect password&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Protected Resource Access&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Successful access with valid token&lt;/li&gt;
&lt;li&gt;Access attempt without auth header&lt;/li&gt;
&lt;li&gt;Access attempt with invalid token format&lt;/li&gt;
&lt;li&gt;Access attempt with expired token&lt;/li&gt;
&lt;li&gt;Access attempt with valid token format but non-existent user&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Java implementation includes detailed comments explaining the flow of each test scenario through Spring Security's filter chain. These same flows are replicated in the Go implementation using equivalent components.&lt;/p&gt;

&lt;h2&gt;
  
  
  Journey Summary
&lt;/h2&gt;

&lt;p&gt;I looked at Spring Security's JWT auth by breaking it down into flows and test cases. Then I mapped these patterns to Go components. Integration tests showed me how requests flow through Spring Security's filter chain and components. Building simple versions of these patterns helped me understand Spring Security's design. The tests proved both implementations handle authentication the same way. Through analyzing, testing, and rebuilding, I gained a deeper understanding of how Spring Security's authentication works.&lt;/p&gt;

</description>
      <category>spring</category>
      <category>security</category>
      <category>java</category>
      <category>go</category>
    </item>
    <item>
      <title>Optimizing PostgreSQL Mass Deletions with Table Partitioning</title>
      <dc:creator>stringintech</dc:creator>
      <pubDate>Mon, 07 Oct 2024 19:41:46 +0000</pubDate>
      <link>https://dev.to/stringintech/optimizing-postgresql-mass-deletions-with-table-partitioning-4ai4</link>
      <guid>https://dev.to/stringintech/optimizing-postgresql-mass-deletions-with-table-partitioning-4ai4</guid>
      <description>&lt;p&gt;In database management, handling large-scale data operations efficiently is critical. One common challenge is executing mass deletions on large tables without dragging down overall performance. This article looks at how PostgreSQL's table partitioning feature can significantly speed up the process and &lt;br&gt;
help maintain smooth database operations.&lt;/p&gt;

&lt;p&gt;Checkout more of my work &lt;a href="https://stringintech.github.io/blog/p/optimizing-postgresql-mass-deletions-with-table-partitioning/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Challenge of Mass Deletions
&lt;/h2&gt;

&lt;p&gt;Deleting a large number of rows from a PostgreSQL table can be a time-consuming operation. It involves:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Scanning through the table to find the rows to delete&lt;/li&gt;
&lt;li&gt;Removing the rows and updating indexes&lt;/li&gt;
&lt;li&gt;Vacuuming the table to reclaim space&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For tables with millions of rows, this process can lead to long-running transactions and table locks, potentially impacting database responsiveness.&lt;/p&gt;
&lt;h2&gt;
  
  
  Enter Table Partitioning
&lt;/h2&gt;

&lt;p&gt;Table partitioning is a technique where a large table is divided into smaller, more manageable pieces called partitions. These partitions are separate tables that share the same schema as the parent table.&lt;/p&gt;
&lt;h2&gt;
  
  
  My Benchmark Setup
&lt;/h2&gt;

&lt;p&gt;To quantify the benefits of partitioning, I set up a benchmark with three scenarios using PostgreSQL in a containerized environment:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Simple Table:&lt;/strong&gt; A standard, non-partitioned table&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Partitioned Table (Row Deletion):&lt;/strong&gt; A table partitioned by week, deleting rows from the first week&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Partitioned Table (Partition Drop):&lt;/strong&gt; Same as #2, but dropping the entire first week's partition&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  PostgreSQL Container Specifications
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;PostgreSQL Version: 16.4&lt;/li&gt;
&lt;li&gt;Docker Version: 27.0.3&lt;/li&gt;
&lt;li&gt;Resource Limits:

&lt;ul&gt;
&lt;li&gt;CPU Limit: 8 CPUs&lt;/li&gt;
&lt;li&gt;Memory Limit: 1 GB&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Data Characteristics
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Total Records: 4 million&lt;/li&gt;
&lt;li&gt;Distribution: Evenly distributed over 4 weeks (1 million per week)&lt;/li&gt;
&lt;li&gt;Indexing: Both tables (simple and partitioned) have an index on the &lt;code&gt;time&lt;/code&gt; column&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Key Findings
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Deletion Time&lt;/th&gt;
&lt;th&gt;Table Size&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Simple Table&lt;/td&gt;
&lt;td&gt;1.26s&lt;/td&gt;
&lt;td&gt;728 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Partitioned (Delete Rows)&lt;/td&gt;
&lt;td&gt;734ms&lt;/td&gt;
&lt;td&gt;908 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Partitioned (Drop Partition)&lt;/td&gt;
&lt;td&gt;6.43ms&lt;/td&gt;
&lt;td&gt;908 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Dramatic Speed Improvement:&lt;/strong&gt; Dropping a partition is 196 times faster than deleting rows from a simple table.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage Trade-off:&lt;/strong&gt; Partitioned tables use about 25% more storage due to additional metadata and per-partition indexes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minimal Insertion Impact:&lt;/strong&gt; Partitioning only slightly increased data population time (by about 2.8%).&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Why It Works
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Targeted Operations:&lt;/strong&gt; Partitioning allows the database to work with a subset of the data, reducing the scope of operations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metadata Operations:&lt;/strong&gt; Dropping a partition is primarily a metadata operation, avoiding the need to scan and delete individual rows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reduced Lock Contention:&lt;/strong&gt; Smaller partitions mean fewer locks, allowing for better concurrency.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Implementation Highlights
&lt;/h2&gt;

&lt;p&gt;Here's a simplified example of how to set up a partitioned table in PostgreSQL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;BIGSERIAL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;time&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;RANGE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;records_week_1&lt;/span&gt; &lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;OF&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt;
    &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2023-01-01'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2023-01-08'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Create index on the partition&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_records_week_1_time&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;records_week_1&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- To delete a week's worth of data:&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt; &lt;span class="n"&gt;DETACH&lt;/span&gt; &lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="n"&gt;records_week_1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;records_week_1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;For databases dealing with time-series data or any scenario where large-scale deletions are common, implementing table partitioning can lead to significant performance improvements. While there's a small trade-off in storage and insertion speed, the gains in deletion efficiency often far outweigh these costs.&lt;/p&gt;

&lt;p&gt;By leveraging partitioning, you can maintain high performance even as your data grows, ensuring your PostgreSQL database remains responsive and efficient.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/stringintech/db-stuff/tree/main/postgres-partitioning" rel="noopener noreferrer"&gt;Link to the full benchmark code and detailed results&lt;/a&gt;&lt;/p&gt;

</description>
      <category>database</category>
      <category>postgres</category>
      <category>go</category>
    </item>
  </channel>
</rss>
