Skip to content

Widhian Bramantya

coding is an art form

Menu
  • About Me
Menu
debezium

Production-Grade Debezium Connector with Kafka (Postgres Outbox Example – E-Commerce Orders)

Posted on September 27, 2025September 27, 2025 by admin

Introduction

Running Debezium in production is very different from running it locally. In production, the pipeline must be:

  • Reliable: no lost or duplicated events.
  • Ordered: events for the same entity must arrive in sequence.
  • Observable: operators must know if the system is alive.
  • Scalable: handle high throughput with multiple consumers.

This article shows how to build a production-grade Debezium + Kafka connector using the Outbox Pattern with PostgreSQL in an e-commerce order system.

The Outbox Pattern in E-Commerce

In an online shop:

  • A new order (ORD-20250927-123) is created in the orders table.
  • At the same time, the app inserts a record into the order_outbox table.

This guarantees:

  • If the order exists, the event also exists.
  • No “dual write” problem between DB and Kafka.
  • Debezium can safely capture outbox records and publish them to Kafka.

Production-Grade Connector Config

{
  "name": "ecommerce-order-outbox-connector",
  "config": {
    "connector.class": "io.debezium.connector.postgresql.PostgresConnector",
    "database.hostname": "ecom-prod-db.example.com",
    "database.port": "5432",
    "database.user": "cdc_user",
    "database.password": "******",
    "database.dbname": "ecommerce_production",
    "database.server.name": "ecommerce",
    "publication.name": "prd_order_outbox_cdc",
    "slot.name": "cdc_order_outbox",
    "plugin.name": "pgoutput",

    "table.include.list": "public.order_outbox(.*)", // assume table is partitioned
    "message.key.columns": "public.order_outbox(.*):order_id",

    "transforms": "Reroute, unwrap",
    "transforms.Reroute.type": "io.debezium.transforms.ByLogicalTableRouter",
    "transforms.Reroute.topic.regex": "(.*)order_outbox(.*)",
    "transforms.Reroute.topic.replacement": "ecommerce.public.order_outbox",
    "transforms.Reroute.key.enforce.uniqueness": false,
    "transforms.unwrap.type": "io.debezium.transforms.ExtractNewRecordState",

    "key.converter": "org.apache.kafka.connect.json.JsonConverter",
    "key.converter.schemas.enable": false,
    "value.converter": "org.apache.kafka.connect.json.JsonConverter",
    "value.converter.schemas.enable": false,

    "tasks.max": "3",
    "topic.creation.enable": "true",
    "topic.creation.default.partitions": "12",
    "topic.creation.default.replication.factor": "3",
    "topic.creation.default.retention.ms": "604800000",

    "exactly.once.support": "required",
    "exactly.once.source.support": "enabled",

    "errors.log.enable": "true",
    "errors.log.include.messages": "true",
    "errors.deadletterqueue.topic.name": "dead-letter-queue",
    "errors.deadletterqueue.context.headers.enable": "true",

    "heartbeat.interval.ms": "10000",
    "heartbeat.topics.prefix": "heartbeat_order_outbox",
    "heartbeat.action.query": "INSERT INTO public.\"_heartbeat_apps\" (name, heartbeat)\nVALUES ('order_outbox', NOW())\nON CONFLICT (name)\nDO UPDATE SET heartbeat = NOW();"
  }
}

Key Features

Partitioning by Order ID

Ensures that all events for the same order_id go to the same partition: correct order for shipping, billing, and fraud detection.

See also  Partitions, Replication, and Fault Tolerance in Kafka

Why Ordering Key Matters

  • The ordering key (partition key) decides event order in Kafka.
  • Wrong key → lost ordering → events like Shipped may arrive before Created.
  • This causes:
    • Out-of-order processing.
    • Broken business logic.
    • Race conditions.
    • Expensive recovery.
  • Solution: choose the key that matches the business entity needing strict sequence (e.g., order_id for order lifecycle, customer_id for customer-level workflows).

Topic Routing with Logical Table Router

Normalizes all order_outbox changes into one Kafka topic:

ecommerce.public.order_outbox

Clean Event Payload

ExtractNewRecordState transform unwraps the Debezium envelope: produces simple JSON.

Exactly-Once Semantics

Avoids duplicates and ensures at-least-once delivery is upgraded to exactly-once when combined with Kafka transactional producers.

Publisher (Debezium / Producer):

  • Uses idempotence → avoids duplicate sends.
  • Uses transactions → all events in batch are written atomically.
  • Commits WAL position + offsets inside the same transaction.

Consumer (Service):

  • Reads partition in order (one consumer per partition).
  • Commits offsets after successful processing.
  • Must be idempotent (e.g., UPSERT, unique keys, track processed events).

Dead Letter Queue

Failed records are pushed into dead-letter-queue for later inspection. Pipeline never blocks.

flowchart LR
  A[Debezium Connector] -->|Valid event| K[Kafka Topic]
  A -->|Error: bad record, schema, serialization| DLQ[Dead Letter Queue Topic]

  K --> C1[Consumer Service]
  DLQ --> OP[Ops Team / Monitoring]

Flow Explanation

  1. Debezium reads a DB change.
  2. If the event is valid: it is published to the normal Kafka topic.
  3. If the event fails (schema error, invalid payload, serialization issue): it is redirected to the DLQ topic.
  4. Consumers continue reading from the normal topic without interruption.
  5. Ops/Monitoring team inspects the DLQ, fixes root cause, and optionally reprocesses the events.
See also  Producers and Consumers: How Data Flows in Kafka

Heartbeats for Monitoring

Debezium supports two types of heartbeat:

Database Heartbeat (configured in connector):

  • Inserts or updates a row in the DB (_heartbeat_apps table).Confirms Debezium is actively reading and writing.Useful for DBAs to check connector health from the database side.

    nameheartbeat
    order_outbox2025-09-27 13:45:00.123

    Kafka Heartbeat Topic (heartbeat_order_outbox):

    • Debezium publishes a heartbeat event into Kafka.Consumers can subscribe to this topic to check pipeline liveness.

    Example Kafka heartbeat event: { "status": "alive", "ts_ms": 1758967500123 }

    Consumer Metrics (additional best practice):

    • Consumer services (e.g., billing or shipping service) emit their own metrics:
      • Lag between event timestamp and processing time.
      • Last processed order_id.
    • Sent to Prometheus/Grafana for monitoring end-to-end latency.
    Pages: 1 2
    Category: Debezium, Kafka

    Leave a Reply Cancel reply

    Your email address will not be published. Required fields are marked *

    Linkedin

    Widhian Bramantya

    Recent Posts

    • Log Management at Scale: Integrating Elasticsearch with Beats, Logstash, and Kibana
    • Index Lifecycle Management (ILM) in Elasticsearch: Automatic Data Control Made Simple
    • Blue-Green Deployment in Elasticsearch: Safe Reindexing and Zero-Downtime Upgrades
    • Maintaining Super Large Datasets in Elasticsearch
    • Elasticsearch Best Practices for Beginners
    • Implementing the Outbox Pattern with Debezium
    • Production-Grade Debezium Connector with Kafka (Postgres Outbox Example – E-Commerce Orders)
    • Connecting Debezium with Kafka for Real-Time Streaming
    • Debezium Architecture – How It Works and Core Components
    • What is Debezium? – An Introduction to Change Data Capture
    • Offset Management and Consumer Groups in Kafka
    • Partitions, Replication, and Fault Tolerance in Kafka
    • Delivery Semantics in Kafka: At Most Once, At Least Once, Exactly Once
    • Producers and Consumers: How Data Flows in Kafka
    • Kafka Architecture Explained: Brokers, Topics, Partitions, and Offsets
    • Getting Started with Apache Kafka: Core Concepts and Use Cases
    • Security Best Practices for RabbitMQ in Production
    • Understanding RabbitMQ Virtual Hosts (vhosts) and Their Uses
    • RabbitMQ Performance Tuning: Optimizing Throughput and Latency
    • High Availability in RabbitMQ: Clustering and Mirrored Queues Explained

    Recent Comments

    1. Playing with VPC AWS (Part 2) – Widhian's Blog on Playing with VPC AWS (Part 1): VPC, Subnet, Internet Gateway, Route Table, NAT, and Security Group
    2. Basic Concept of ElasticSearch (Part 3): Translog, Flush, and Refresh – Widhian's Blog on Basic Concept of ElasticSearch (Part 1): Introduction
    3. Basic Concept of ElasticSearch (Part 2): Architectural Perspective – Widhian's Blog on Basic Concept of ElasticSearch (Part 3): Translog, Flush, and Refresh
    4. Basic Concept of ElasticSearch (Part 3): Translog, Flush, and Refresh – Widhian's Blog on Basic Concept of ElasticSearch (Part 2): Architectural Perspective
    5. Basic Concept of ElasticSearch (Part 1): Introduction – Widhian's Blog on Basic Concept of ElasticSearch (Part 2): Architectural Perspective

    Archives

    • October 2025
    • September 2025
    • August 2025
    • November 2021
    • October 2021
    • August 2021
    • July 2021
    • June 2021
    • March 2021
    • January 2021

    Categories

    • Debezium
    • Devops
    • ElasticSearch
    • Golang
    • Kafka
    • Lua
    • NATS
    • Programming
    • RabbitMQ
    • Redis
    • VPC
    © 2025 Widhian Bramantya | Powered by Minimalist Blog WordPress Theme