DEV Community

Cover image for Eradicating Batch Jobs: Change Data Capture (CDC) Architecture 🔄
Prajapati Paresh
Prajapati Paresh

Posted on Originally published at smarttechdevs.in

Eradicating Batch Jobs: Change Data Capture (CDC) Architecture 🔄

The Death of the Nightly Cron Job

For decades, enterprise data synchronization has relied on a deeply flawed architectural pattern: the batch job. Imagine an e-commerce platform where the primary relational database manages orders, but a separate Elasticsearch cluster handles the search functionality, and a Snowflake data warehouse handles the analytics. To keep these systems in sync, developers typically write a Laravel scheduled task (a cron job) that runs every night at 2:00 AM.

This script queries the database for SELECT * FROM orders WHERE updated_at > [yesterday], pulling hundreds of thousands of rows into memory, and ships them to the warehouse. This architecture introduces severe bottlenecks. First, it causes massive CPU and memory spikes on your primary database during the export. Second, if a user updates an order at 9:00 AM, the search index and the analytics dashboard are completely out of date for the next 17 hours. In the modern era of real-time machine learning and instant inventory management, 17-hour data staleness is unacceptable.

At Smart Tech Devs, we eradicate batch processing by implementing Change Data Capture (CDC). CDC is an architectural pattern that instantly detects database changes as they happen, streaming them to external systems in real-time without executing a single SELECT query against your primary application database.

Understanding the Write-Ahead Log (WAL)

To achieve CDC without degrading application performance, we bypass the application layer entirely. Relational databases like PostgreSQL and MySQL are engineered for absolute data integrity. Before PostgreSQL writes a change to the actual hard drive sectors (which is slow), it instantly appends a record of that change to the Write-Ahead Log (WAL).

The WAL is a sequential, highly optimized binary file. If the server crashes during an operation, Postgres uses the WAL to perfectly reconstruct the database upon reboot. CDC tools exploit this native mechanism. They connect to the database, read the WAL stream continuously, and broadcast every INSERT, UPDATE, and DELETE operation as a real-time event, completely invisible to your primary application logic.

Phase 1: Architecting the Infrastructure with Debezium and Kafka

The industry-standard open-source tool for CDC is Debezium. Debezium acts as a connector that sits between your PostgreSQL database and an Apache Kafka event streaming cluster.

First, you must alter your PostgreSQL configuration to enable logical decoding, allowing Debezium to read the WAL.


# postgresql.conf
wal_level = logical # Crucial for CDC
max_wal_senders = 4
max_replication_slots = 4

Next, we configure the Debezium connector via a JSON payload. We instruct it to monitor our orders table. When an order changes, Debezium extracts the "before" state and the "after" state of the row, and pushes a JSON message to a Kafka topic named enterprise.public.orders.


// Debezium Connector Configuration (POST /connectors)
{
  "name": "orders-connector",
  "config": {
    "connector.class": "io.debezium.connector.postgresql.PostgresConnector",
    "database.hostname": "postgres-primary",
    "database.port": "5432",
    "database.user": "debezium_user",
    "database.password": "secure_password",
    "database.dbname": "enterprise_db",
    "database.server.name": "enterprise",
    "table.include.list": "public.orders",
    "plugin.name": "pgoutput"
  }
}

Phase 2: The Structure of a CDC Event Payload

When a customer updates their shipping address on an active order, Laravel executes a standard $order->update(). Laravel knows nothing about Kafka. However, milliseconds later, Debezium reads the WAL and pushes this exact payload into Kafka:


{
  "payload": {
    "before": {
      "id": 1045,
      "total_amount": 250.00,
      "status": "processing",
      "shipping_address": "123 Old Street"
    },
    "after": {
      "id": 1045,
      "total_amount": 250.00,
      "status": "processing",
      "shipping_address": "456 New Avenue"
    },
    "source": {
      "version": "1.9.5.Final",
      "connector": "postgresql",
      "name": "enterprise",
      "ts_ms": 1693564800000
    },
    "op": "u", // "u" means Update ("c" is Create, "d" is Delete)
    "ts_ms": 1693564800050
  }
}

Phase 3: Consuming the Stream in Downstream Microservices

Now that the data is flowing through Kafka, any downstream microservice can subscribe to the topic and react instantly. For example, our Laravel-based Search Microservice (which manages Elasticsearch) can run a continuous Kafka consumer to update the index the moment the address changes.


namespace App\Console\Commands;

use Illuminate\Console\Command;
use Junges\Kafka\Facades\Kafka;
use Junges\Kafka\Contracts\KafkaConsumerMessage;
use App\Services\ElasticsearchService;

class ConsumeOrderEvents extends Command
{
    protected $signature = 'kafka:consume-orders';

    public function handle(ElasticsearchService $elastic)
    {
        $consumer = Kafka::createConsumer(['enterprise.public.orders'])
            ->withConsumerGroupId('search-indexer-group')
            ->withHandler(function(KafkaConsumerMessage $message) use ($elastic) {
                
                $payload = $message->getBody()['payload'];
                $operation = $payload['op']; // c, u, or d

                if ($operation === 'd') {
                    // It was deleted. Remove from Elasticsearch
                    $elastic->deleteDocument('orders', $payload['before']['id']);
                    return;
                }

                // It was created or updated. Upsert into Elasticsearch
                $elastic->upsertDocument('orders', $payload['after']['id'], $payload['after']);
            })
            ->build();

        $consumer->consume(); // Runs continuously as a daemon process
    }
}

The Engineering ROI and Event-Driven Agility

Implementing Change Data Capture (CDC) via Debezium and Kafka completely revolutionizes enterprise data architecture. You permanently eradicate the crushing database load caused by massive nightly batch queries, smoothing out your CPU utilization profile. Your entire organization transitions to real-time analytics; your Snowflake dashboards and Elasticsearch indexes reflect reality within milliseconds of a customer action. Most importantly, it completely decouples your architecture. The primary Laravel application no longer needs to be bogged down with logic to update search indexes or sync external CRM systems. It simply writes to its own database, and the CDC infrastructure autonomously broadcasts those state changes to the rest of your enterprise ecosystem.

Top comments (0)