Skip to content

feat: publish CDC stream via logical replication - #592

Merged
thlpkee20-wq merged 3 commits into
Stellabill:mainfrom
believetimothy:feat/cdc-logical-replication
Jul 29, 2026
Merged

thlpkee20-wq merged 3 commits into
Stellabill:mainfrom
believetimothy:feat/cdc-logical-replication

Conversation

@believetimothy

Copy link
Copy Markdown
Contributor

Summary

Adds a Change Data Capture consumer that streams row-level changes from PostgreSQL logical replication to pluggable sinks (Kafka, stdout, in-memory), enabling downstream analytics and search indexers without polling.

closes #417

Changes

Migration (migrations/0014_create_cdc_publication)

  • Creates stellabill_cdc publication using pgoutput plugin
  • PII-safe by design: column-level allowlists exclude PII columns:
    • subscriptions.customer, statements.customer_id
    • outbox_events.event_data, outbox_attempts.response_body, contract_events.payload
    • idempotency_keys.response_body, subscriber_keys.jwk
    • export_operations.caller_id, notification_preferences.*, saga_instances.context
    • outbox_attempts.error_message, saga_step_results.error_message
  • Publishes insert, update, delete operations on 11 tables

CDC Package (internal/cdc/)

File Purpose
consumer.go Replication consumer with reconnect loop, standby status updates, graceful shutdown
pgoutput.go Binary pgoutput protocol decoder (BEGIN, COMMIT, INSERT, UPDATE, DELETE, RELATION, ORIGIN, TYPE)
sink.go Pluggable Sink interface + MemorySink for testing + ConsumerConfig
kafka_sink.go Production Kafka sink with MessageWriter interface abstraction
stdout_sink.go Development/debug sink (JSON lines to stdout)

Key Design Decisions

  • LSN advancement gated on sink success: LSN only advances after all sinks confirm writes, preventing data loss on crash
  • Pluggable sinks: Sink interface allows adding any downstream (S3, BigQuery, Elasticsearch, etc.)
  • Reconnection with backoff: Exponential backoff capped at 30s, configurable max retry attempts
  • Kafka library-agnostic: MessageWriter interface decouples from specific Kafka client

Test Coverage

  • 77.5% statement coverage with 60+ test cases
  • Coverage breakdown:
    • pgoutput decoder: 88-100% per function
    • Consumer lifecycle: 84-100%
    • Sinks (Memory, Kafka, Stdout): 80-100%
  • runReplication (5.8%), execSimple (0%), sendStandbyStatusUpdate (0%) require real PG with wal_level=logical for full coverage
  • All tests pass with -race enabled

Security

  • PII columns excluded from publication via column allowlists
  • No sensitive data replicated: customer identifiers, JWK keys, raw JSON payloads excluded
  • Error message columns excluded post-review to prevent PII leakage in log strings

Add CDC consumer, pgoutput decoder, and pluggable sink interface (Kafka,
stdout, in-memory) for streaming row-level changes from PostgreSQL logical
replication to downstream consumers.

- Add migration 0014 to create "stellabill_cdc" publication with column
  allowlists that exclude PII columns (PII-safe by design)
- Implement pgoutput binary protocol decoder supporting INSERT, UPDATE,
  DELETE, BEGIN, COMMIT, RELATION, ORIGIN, and TYPE messages
- Implement Consumer with reconnection loop, standby status updates,
  and graceful shutdown via context cancellation
- Implement pluggable Sink interface with MemorySink (testing),
  KafkaSink (production), and StdoutSink (debugging)
- LSN advancement gated on successful sink writes to prevent data loss
- Build helper functions for crafting pgoutput binary messages in tests
- 77.5% test coverage with 60+ test cases covering decoder edge cases,
  consumer lifecycle, reconnect logic, and sink implementations

closes Stellabill#417
@drips-wave

drips-wave Bot commented Jul 29, 2026

Copy link
Copy Markdown

@believetimothy Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@thlpkee20-wq
thlpkee20-wq merged commit 057fcf0 into Stellabill:main Jul 29, 2026
5 of 22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Publish change data capture stream via logical replication slot

2 participants