<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Golang | Tuan Le's Blog</title><link>https://tuanla.vn/tags/golang/</link><atom:link href="https://tuanla.vn/tags/golang/index.xml" rel="self" type="application/rss+xml"/><description>Golang</description><generator>Hugo Blox Builder (https://hugoblox.com)</generator><language>en-us</language><lastBuildDate>Sat, 25 Oct 2025 00:00:00 +0000</lastBuildDate><image><url>https://tuanla.vn/media/icon_hu_3b1c55030de81680.png</url><title>Golang</title><link>https://tuanla.vn/tags/golang/</link></image><item><title>Researching CDC: How to sync data between two mongo clusters using debezium, rabbitmq and golang consumer. Part 1</title><link>https://tuanla.vn/post/cdc/</link><pubDate>Sat, 25 Oct 2025 00:00:00 +0000</pubDate><guid>https://tuanla.vn/post/cdc/</guid><description>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Synchronizing data between two MongoDB clusters can be deceptively complex — especially when we need both consistency and low latency.
Traditional approaches either prioritize speed (but risk losing data) or safety (but introduce delays).&lt;br/&gt;
This article explores a more balanced solution using Change Data Capture (CDC) with Debezium, RabbitMQ, and a Golang consumer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The goal:&lt;/strong&gt; achieve reliable, real-time synchronization without sacrificing consistency.&lt;/p&gt;
&lt;details class="print:hidden xl:hidden" open&gt;
&lt;summary&gt;Table of Contents&lt;/summary&gt;
&lt;div class="text-sm"&gt;
&lt;nav id="TableOfContents"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#introduction"&gt;Introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#why-this-research-exists"&gt;Why This Research Exists?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#solutions-applied"&gt;Solutions Applied&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#1-direct-event-push"&gt;1. Direct Event Push&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#2-outbox-pattern-with-polling-publisher"&gt;2. Outbox Pattern with Polling publisher&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#trade-offs-summary"&gt;Trade-offs Summary&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#towards-a-better-approach"&gt;Towards a Better Approach&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#transaction-log-tailing-including-transaction-log"&gt;Transaction log tailing including transaction log&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#introducing-debezium"&gt;Introducing Debezium&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#architecture-overview"&gt;Architecture Overview&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#conclusion"&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/nav&gt;
&lt;/div&gt;
&lt;/details&gt;
&lt;h2 id="why-this-research-exists"&gt;Why This Research Exists?&lt;/h2&gt;
&lt;p&gt;When we follow a &lt;strong&gt;microservices architecture&lt;/strong&gt;, where each service owns its own database and domain logic.&lt;br/&gt;
This independence is great for scalability and deployment, but it creates a major challenge when customers need aggregated views across domains.&lt;/p&gt;
&lt;p&gt;A frequent request from our users was a &lt;strong&gt;centralized page&lt;/strong&gt; to &lt;strong&gt;search&lt;/strong&gt;, &lt;strong&gt;filter&lt;/strong&gt;, &lt;strong&gt;and view all products&lt;/strong&gt; in one place.&lt;br/&gt;
However, since each microservice stores its product data separately, there’s no single data source to support that unified search efficiently.&lt;br/&gt;
To handle this, we built a new aggregation service — essentially a search index that consolidates product data from multiple services into one dedicated database.&lt;/p&gt;
&lt;p&gt;The critical part was keeping this data continuously synchronized with upstream product services in real time.&lt;/p&gt;
&lt;h2 id="solutions-applied"&gt;Solutions Applied&lt;/h2&gt;
&lt;h3 id="1-direct-event-push"&gt;1. Direct Event Push&lt;/h3&gt;
&lt;p&gt;In this model, the service publishes an event to RabbitMQ immediately after writing to MongoDB.&lt;/p&gt;
&lt;div class="mermaid"&gt;flowchart TD
U["User"]
S["Product Service"]
DBM["MongoDB (Product)"]
MQ["RabbitMQ"]
CEH["Consumer"]
DBF["MongoDB (fusion)&lt;br/&gt;(Centralized Product Index)"]
%% User action and domain write
U --&gt; S --&gt;|Write product change| DBM
%% Event publish path
S --&gt;|Publish ProductChanged| MQ --&gt; CEH --&gt;|Upsert/Project product| DBF
%% (Optional) failure note
%% If publish fails: data in Product committed but event not delivered → inconsistency risk
%% Styling (optional)
style U fill:#f5f5f5,stroke:#aaa
style S fill:#b4d4f7,stroke:#333
style DBM fill:#fef6d8,stroke:#999
style MQ fill:#fdddb1,stroke:#999
style CEH fill:#c8f2c2,stroke:#999
style DBF fill:#f6c2f0,stroke:#999
&lt;/div&gt;
&lt;p&gt;This approach is simple and works well in happy cases. However, when failures occur (e.g., RabbitMQ is down), data
consistency cannot be guaranteed since the database update and event publishing are not atomic.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Low latency — target systems get updates almost instantly.&lt;/li&gt;
&lt;li&gt;Simple to implement — just hook after the database write.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ricky: data might be updated in MongoDB, but the message broker could fail to receive the event.&lt;/li&gt;
&lt;li&gt;Service must care about infra concerns (data synchronization).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2-outbox-pattern-with-polling-publisher"&gt;2. Outbox Pattern with Polling publisher&lt;/h3&gt;
&lt;p&gt;Here, both the data and the event are stored within the same MongoDB transaction.
A separate background process (cron/worker) later publishes the event to RabbitMQ.&lt;/p&gt;
&lt;div class="mermaid"&gt;flowchart TD
U["User"]
S["Product Service"]
subgraph Product["MongoDB (Product)"]
D1["Product Collection"]
O1["Outbox Collection"]
end
POLL["Polling Publisher (cron/worker)"]
MQ["RabbitMQ"]
CEH["Consumer"]
DBF["MongoDB (fusion)&lt;br/&gt;(Centralized Product Index)"]
%% User action and atomic DB changes
U --&gt; S --&gt;|TX: write product| D1
S --&gt;|TX: insert outbox event| O1
%% Background publisher flow
O1 -.-&gt;|poll &amp; fetch events| POLL --&gt;|publish| MQ --&gt; CEH --&gt;|Upsert/Project product| DBF
POLL --&gt;|mark processed| O1
%% Styling (optional)
style U fill:#f5f5f5,stroke:#aaa
style S fill:#b4d4f7,stroke:#333
style Product fill:#fef6d8,stroke:#999
style POLL fill:#ffe7a1,stroke:#999
style MQ fill:#fdddb1,stroke:#999
style CEH fill:#c8f2c2,stroke:#999
style DBF fill:#f6c2f0,stroke:#999
&lt;/div&gt;
&lt;p&gt;This approach resolves consistency issues but introduces operational complexity and latency from the polling process.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Strong consistency — events are recorded only after data is safely stored.&lt;/li&gt;
&lt;li&gt;Reliable delivery — supports retry until success (at-least-once guarantee).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Introduces delay — events are published on a schedule.&lt;/li&gt;
&lt;li&gt;Increased operational complexity (polling, cron, cleanup).&lt;/li&gt;
&lt;li&gt;Service must care about infra concerns (data synchronization).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="trade-offs-summary"&gt;Trade-offs Summary&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Consistency&lt;/th&gt;
&lt;th&gt;Latency&lt;/th&gt;
&lt;th&gt;Complexity&lt;/th&gt;
&lt;th&gt;Risk&lt;/th&gt;
&lt;th&gt;Infra Concern&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Direct Push&lt;/td&gt;
&lt;td&gt;Weak&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Outbox Pattern&lt;/td&gt;
&lt;td&gt;Strong&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="towards-a-better-approach"&gt;Towards a Better Approach&lt;/h2&gt;
&lt;p&gt;What if we could:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Send events right after the data is safely committed&lt;/li&gt;
&lt;li&gt;Without needing to modify application logic&lt;/li&gt;
&lt;li&gt;Without adding cron jobs or extra tables?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The answer lies in Change Data Capture (CDC) — tailing the database transaction log.&lt;/p&gt;
&lt;h3 id="transaction-log-tailing-including-transaction-log"&gt;Transaction log tailing including transaction log&lt;/h3&gt;
&lt;p&gt;Every MongoDB cluster maintains an oplog (operations log) that records all changes applied to the database. &lt;br/&gt;
By listening to this stream, we can detect inserts, updates, and deletes in real-time — even after the application finishes its writes.&lt;/p&gt;
&lt;div class="mermaid"&gt;flowchart TD
S["product Service"]
subgraph DBM["MongoDB (Product)"]
COLL["Product Collection"]
OPLOG["Oplog / Transaction Log"]
end
PROD["Change Stream Producer"]
MQ["RabbitMQ"]
CEH["Sink Consumer"]
DBF["MongoDB (central)"]
%% Flow connections
S --&gt;|"Write product changes &lt;br/&gt; [insert/update/delete]"| COLL
COLL --&gt;|Record operation| OPLOG
OPLOG --&gt;|Stream changes| PROD
PROD --&gt;|Publish event| MQ
MQ --&gt; CEH
CEH --&gt;|Upsert / Project product| DBF
%% Styling
style S fill:#b4d4f7,stroke:#333
style DBM fill:#fef6d8,stroke:#999
style COLL fill:#fffbea,stroke:#aaa
style OPLOG fill:#fff3e0,stroke:#aaa
style PROD fill:#cce0ff,stroke:#333
style MQ fill:#fdddb1,stroke:#999
style CEH fill:#c8f2c2,stroke:#999
style DBF fill:#f6c2f0,stroke:#999
&lt;/div&gt;
&lt;p&gt;MongoDB provides a built-in change stream API that allows us to listen to the oplog instead of directly tailing
it. It compromise that are optimized, providing more efficient resource utilization and faster execution from MongoDB version 5.1.
&lt;br/&gt;
However, this approach requires additional implementation effort. This led me to explore &lt;em&gt;Debezium&lt;/em&gt;, which I&amp;rsquo;ll discuss in the next section.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s review what we&amp;rsquo;ve achieved with this architecture:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Strong consistency&lt;/strong&gt;: since changes are captured post-commit.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Low latency&lt;/strong&gt;: since events are streamed continuously.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Non-intrusiveness&lt;/strong&gt;: no changes needed in the application code.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="introducing-debezium"&gt;Introducing Debezium&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. What is Debezium?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Debezium&lt;/strong&gt; is an open-source &lt;strong&gt;Change Data Capture (CDC)&lt;/strong&gt; platform built on top of &lt;strong&gt;Kafka Connect&lt;/strong&gt;. &lt;br/&gt;
It monitors databases for real-time changes — including &lt;strong&gt;MongoDB, MySQL, PostgreSQL&lt;/strong&gt;, and others — and publishes structured change events (in JSON or Avro) to your chosen message broker such as Kafka, RabbitMQ, or other supported systems.&lt;/p&gt;
&lt;p&gt;When used with MongoDB, Debezium reads from the oplog to detect inserts, updates, and deletes, and then streams those events downstream for consumers to process.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. why not just use MongoDB’s built-in features?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;First,&lt;/strong&gt; MongoDB already provides a Change Stream API, which allows applications to listen for changes directly without manually tailing the oplog.
However, this requires additional custom implementation, error handling, and message delivery logic — all of which add complexity and operational overhead.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Second,&lt;/strong&gt; MongoDB officially provides Kafka Connector tools for both source and sink connectors (
). &lt;br/&gt;
Unfortunately, our infrastructure uses RabbitMQ instead of Kafka. This means the native connectors do not fit into our existing message pipeline.&lt;/p&gt;
&lt;p&gt;That’s where Debezium comes in — it offers a reliable and flexible CDC layer that integrates smoothly with RabbitMQ through the Debezium Server. &lt;br/&gt;
By connecting to MongoDB’s oplog, Debezium can publish changes as events directly into RabbitMQ — eliminating the need to write a custom listener or message dispatcher.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. The Limitation&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;While Debezium simplifies the source side of CDC, it has one notable limitation:
The &lt;strong&gt;Debezium MongoDB Sink Connector&lt;/strong&gt; supports only &lt;strong&gt;relational database (RDBMS) sources&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This means it cannot process change events generated by another Debezium connector — including the &lt;strong&gt;Debezium MongoDB Source Connector&lt;/strong&gt;.
In our case, since both the source and sink are MongoDB clusters, the official sink connector cannot be used directly.&lt;/p&gt;
&lt;p&gt;So, we still need to &lt;strong&gt;implement our own sink logic&lt;/strong&gt; — a custom consumer that reads change events from RabbitMQ and writes them to the target MongoDB (fusion) cluster.&lt;/p&gt;
&lt;h3 id="architecture-overview"&gt;Architecture Overview&lt;/h3&gt;
&lt;p&gt;Here’s how the entire system fits together:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Product MongoDB Cluster → produces oplog entries.&lt;/li&gt;
&lt;li&gt;Debezium Server (Change Stream Producer) → tails the oplog, transforms to events.&lt;/li&gt;
&lt;li&gt;RabbitMQ Exchange/Queue → distributes events.&lt;/li&gt;
&lt;li&gt;Go App (Sink Consumer) → consumes, filters, transforms, and writes to Target MongoDB Cluster.&lt;/li&gt;
&lt;li&gt;Fusion MongoDB Cluster -&amp;gt; centralized products.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="mermaid"&gt;flowchart TD
S["Product Console Service"]
subgraph DBM["MongoDB (Product)"]
COLL["Product Collection"]
OPLOG["Oplog / Transaction Log"]
end
PROD["Debezium Server"]
MQ["RabbitMQ"]
CEH["Consumer(Golang)"]
DBF["MongoDB (fusion)"]
%% Flow connections
S --&gt;|"Write product changes &lt;br/&gt; [insert/update/delete]"| COLL
COLL --&gt;|Record operation| OPLOG
OPLOG --&gt;|Stream changes| PROD
PROD --&gt;|Publish event| MQ
MQ --&gt; CEH
CEH --&gt;|Upsert / Project product| DBF
%% Styling
style S fill:#b4d4f7,stroke:#333
style DBM fill:#fef6d8,stroke:#999
style COLL fill:#fffbea,stroke:#aaa
style OPLOG fill:#fff3e0,stroke:#aaa
style PROD fill:#cce0ff,stroke:#333
style MQ fill:#fdddb1,stroke:#999
style CEH fill:#c8f2c2,stroke:#999
style DBF fill:#f6c2f0,stroke:#999
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Benefits Achieved&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;✅ Consistency — events are emitted post-commit. &lt;br/&gt;
✅ Low Latency — oplog tailing means near-real-time sync. &lt;br/&gt;
✅ Resilience — RabbitMQ buffering ensures durability. &lt;br/&gt;
✅ Scalability — multiple consumers can handle different domains or tables.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In this first part, we explored the real-world challenge of keeping data synchronized across multiple MongoDB clusters — and how Change Data Capture (CDC) provides a modern, reliable way to solve it.
We walked through the evolution from traditional approaches to a CDC-based architecture, and saw how tools like &lt;strong&gt;Debezium&lt;/strong&gt; can help bridge consistency, latency, and scalability in distributed systems.&lt;/p&gt;
&lt;p&gt;In &lt;strong&gt;Part 2&lt;/strong&gt;, we’ll get hands-on with the implementation details, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Setting up Debezium with MongoDB&lt;/li&gt;
&lt;li&gt;Configuring RabbitMQ for CDC event routing&lt;/li&gt;
&lt;li&gt;Building a Golang consumer to apply data changes&lt;/li&gt;
&lt;li&gt;Sharing code samples, deployment notes, and best practices&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If this topic interests you — or you’d like to see the setup and code walkthrough in the next part — 💬 &lt;strong&gt;leave a comment below&lt;/strong&gt; to let me know! Your feedback gives me the motivation to keep sharing deeper, practical guides.&lt;/p&gt;</description></item></channel></rss>