<?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: Mujib</title>
    <description>The latest articles on DEV Community by Mujib (@mujib77).</description>
    <link>https://dev.to/mujib77</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%2F3921321%2F6034e2d1-e80b-44fa-ace6-25a959985cf1.png</url>
      <title>DEV Community: Mujib</title>
      <link>https://dev.to/mujib77</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mujib77"/>
    <language>en</language>
    <item>
      <title>How I Built a Real-Time PostgreSQL WAL Reader in GO</title>
      <dc:creator>Mujib</dc:creator>
      <pubDate>Sat, 09 May 2026 08:41:40 +0000</pubDate>
      <link>https://dev.to/mujib77/how-i-built-a-real-time-postgresql-wal-reader-in-go-4568</link>
      <guid>https://dev.to/mujib77/how-i-built-a-real-time-postgresql-wal-reader-in-go-4568</guid>
      <description>&lt;p&gt;Most developers use PostgreSQL every day without knowing &lt;br&gt;
what happens under the hood when they run an INSERT. I &lt;br&gt;
wanted to find out. So I built pgstream — a tool that &lt;br&gt;
reads every database change in real time directly from &lt;br&gt;
PostgreSQL's internal log.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is the WAL?
&lt;/h2&gt;

&lt;p&gt;Every time you insert, update, or delete a row, Postgres &lt;br&gt;
doesn't immediately write to the actual data files. It &lt;br&gt;
first writes the change to a file called the WAL — Write &lt;br&gt;
Ahead Log. This is how Postgres guarantees nothing gets &lt;br&gt;
lost if the server crashes mid-write.&lt;/p&gt;

&lt;p&gt;I had read about WAL in docs before but never really &lt;br&gt;
understood why it existed until I started building this. &lt;br&gt;
It clicked when I realized — the WAL is basically a &lt;br&gt;
journal. Postgres writes every intention down first, &lt;br&gt;
then acts on it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Logical Replication and pgoutput
&lt;/h2&gt;

&lt;p&gt;WAL records are stored in raw binary. You can't just &lt;br&gt;
read them like a text file. Postgres has a built-in &lt;br&gt;
plugin called pgoutput that decodes that binary into &lt;br&gt;
readable messages — INSERT, UPDATE, DELETE — with the &lt;br&gt;
actual row data included.&lt;/p&gt;

&lt;p&gt;To read from the WAL stream your program creates a &lt;br&gt;
replication slot. Think of it as a bookmark. Postgres &lt;br&gt;
tracks your position in the stream and holds WAL records &lt;br&gt;
until your reader has consumed them. This means you can &lt;br&gt;
stop and restart your reader without missing any changes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Building pgstream
&lt;/h2&gt;

&lt;p&gt;I built pgstream in Go using the pglogrepl library which &lt;br&gt;
handles the low level replication protocol. The project &lt;br&gt;
has four main parts:&lt;/p&gt;

&lt;p&gt;Connector — opens a replication connection to Postgres &lt;br&gt;
and creates the slot.&lt;/p&gt;

&lt;p&gt;Decoder — this was the hardest part. It reads raw bytes &lt;br&gt;
coming from Postgres and figures out what each message &lt;br&gt;
means. Postgres sends different message types — Relation &lt;br&gt;
messages that describe table schemas, Insert messages, &lt;br&gt;
Update messages, Delete messages, and keepalive &lt;br&gt;
heartbeats. The decoder has to handle all of them &lt;br&gt;
correctly.&lt;/p&gt;

&lt;p&gt;The tricky part was the Relation message. Before Postgres &lt;br&gt;
sends an INSERT event it sends a Relation message &lt;br&gt;
describing the table structure — column names, types. &lt;br&gt;
You have to store that and reference it later when &lt;br&gt;
decoding the actual row data. Without this you get raw &lt;br&gt;
bytes with no idea which column is which.&lt;/p&gt;

&lt;p&gt;Handler — takes the decoded event and prints it cleanly &lt;br&gt;
to the terminal.&lt;/p&gt;




&lt;h2&gt;
  
  
  The part that surprised me
&lt;/h2&gt;

&lt;p&gt;What surprised me most was how fast it is. The moment &lt;br&gt;
you run an INSERT in pgAdmin the change appears in the &lt;br&gt;
terminal almost instantly. There is basically no delay. &lt;/p&gt;

&lt;p&gt;I knew replication was fast in theory but seeing it &lt;br&gt;
happen live made it real. This is the same mechanism &lt;br&gt;
companies use to sync databases across regions in &lt;br&gt;
real time.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;p&gt;I understood Postgres as a database before this. Now I &lt;br&gt;
understand it as a system. WAL, logical decoding, &lt;br&gt;
replication slots, LSN — these are the internals that &lt;br&gt;
make Postgres reliable at scale.&lt;/p&gt;

&lt;p&gt;Go was also new to me coming from JavaScript and C. &lt;br&gt;
The concurrency model with goroutines and channels &lt;br&gt;
is genuinely different from anything I had used before.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try it yourself
&lt;/h2&gt;

&lt;p&gt;pgstream is open source.&lt;br&gt;
github.com/mujib77/pgstream&lt;/p&gt;

&lt;p&gt;If you want to understand what's happening inside your &lt;br&gt;
Postgres database — clone it, run it, and watch your &lt;br&gt;
changes stream in real time.&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>go</category>
      <category>opensource</category>
      <category>sql</category>
    </item>
  </channel>
</rss>
