<?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: Bruin</title>
    <description>The latest articles on DEV Community by Bruin (bruin-data).</description>
    <link>https://dev.to/bruin-data</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.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F15587%2F1a80443c-3b85-43e8-9974-8d5f65a89f8c.png</url>
      <title>DEV Community: Bruin</title>
      <link>https://dev.to/bruin-data</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bruin-data"/>
    <language>en</language>
    <item>
      <title>What Is a Data Orchestrator? And When a Small Team Does Not Need One</title>
      <dc:creator>Kateryna Kozachenko</dc:creator>
      <pubDate>Sun, 25 Oct 2026 19:38:46 +0000</pubDate>
      <link>https://dev.to/bruin-data/what-is-a-data-orchestrator-and-when-a-small-team-does-not-need-one-822</link>
      <guid>https://dev.to/bruin-data/what-is-a-data-orchestrator-and-when-a-small-team-does-not-need-one-822</guid>
      <description>&lt;p&gt;&lt;em&gt;This article was first published on the &lt;a href="https://getbruin.com/blog/what-is-a-data-orchestrator/" rel="noopener noreferrer"&gt;Bruin blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A data orchestrator is the system that runs a pipeline's tasks in the right order, on a schedule, retries the ones that fail, and records what ran and when.&lt;/strong&gt; Apache Airflow is the archetype: you describe tasks and their dependencies as a DAG, and the orchestrator's scheduler, workers, and metadata database do the rest. Dagster and Prefect are the modern alternatives. The question a small team should ask first is not which orchestrator but whether they need one, because a pipeline framework that resolves its own dependency graph, such as &lt;a href="https://getbruin.com/platform" rel="noopener noreferrer"&gt;Bruin&lt;/a&gt;, removes the layer entirely for pipelines made of loads, transformations, and checks.&lt;/p&gt;

&lt;h2&gt;
  
  
  What an orchestrator actually does
&lt;/h2&gt;

&lt;p&gt;Four jobs, and every orchestrator does all four:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Job&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;th&gt;Airflow's implementation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Dependency resolution&lt;/td&gt;
&lt;td&gt;Run B after A, C after both&lt;/td&gt;
&lt;td&gt;The DAG, written in Python&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scheduling&lt;/td&gt;
&lt;td&gt;Start the DAG at 02:00 daily, or when a file lands&lt;/td&gt;
&lt;td&gt;The scheduler process&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Execution&lt;/td&gt;
&lt;td&gt;Run the tasks, in parallel where possible, with retries&lt;/td&gt;
&lt;td&gt;Workers, plus an executor to distribute them&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bookkeeping&lt;/td&gt;
&lt;td&gt;Which run did what, when, with what result&lt;/td&gt;
&lt;td&gt;The metadata database and the web UI&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The value is real. Without those four, a pipeline is a cron entry and a prayer. The cost is also real: each of the four is a component someone operates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it became a layer of its own
&lt;/h2&gt;

&lt;p&gt;Orchestrators exist because the modern data stack was assembled from single-purpose tools. Fivetran loads, dbt transforms, Great Expectations checks, a BI tool reads. None of them knows about the others, so a fifth tool has to know about all of them and call them in order. That is what most Airflow DAGs in data teams are: glue that runs an ingestion sync, then a &lt;code&gt;dbt run&lt;/code&gt;, then a check, then a dashboard refresh.&lt;/p&gt;

&lt;p&gt;That glue has a price. A self-hosted Airflow is a scheduler, workers, a metadata database, a web server, a deployment image, secrets handling, and someone who understands all of it. Managed Airflow removes the servers but keeps the concepts and the bill: Amazon MWAA's own pricing example for a small environment with typical retention comes to about $449 a month before the warehouse and the tools it orchestrates. Astronomer is the other managed route. Either way the team is paying to run a tool whose job is to call other tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  When you do not need one
&lt;/h2&gt;

&lt;p&gt;If the DAG mostly runs data work, loads, SQL and Python transformations, quality checks, and a schedule, then the orchestrator is doing dependency resolution for tasks that could declare their dependencies themselves. A pipeline framework that owns all of those steps does not need a separate system to order them.&lt;/p&gt;

&lt;p&gt;That is the design of Bruin. Each asset is a file that declares what it depends on, and the SQL is parsed for the rest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* @bruin
name: mart.orders
type: sf.sql
depends: [raw.orders, mart.customers]
materialization:
  type: table
columns:
  - name: order_id
    checks:
      - name: not_null
      - name: unique
@bruin */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;bruin run ./pipeline.yml&lt;/code&gt; resolves the graph across ingestion assets, SQL and Python models, and checks, and runs them in order with parallelism where the graph allows. The schedule is one line in &lt;code&gt;pipeline.yml&lt;/code&gt;. Locally or in CI that is the whole orchestrator; Bruin Cloud runs the same project on the schedule with retries, backfills, alerts to Slack or Teams, lineage, and a catalog. There is no scheduler, worker pool, or metadata database to operate, because the dependency graph is a property of the pipeline rather than a separate program.&lt;/p&gt;

&lt;h2&gt;
  
  
  When you do
&lt;/h2&gt;

&lt;p&gt;Keep, or adopt, a general orchestrator when the DAG coordinates things that are not data pipelines: infrastructure jobs, ML training runs, calls to external systems, multi-day business workflows. Dagster fits data platforms that want software-defined assets across many tools, Prefect fits Python-heavy teams that want the lightest operational footprint, and Airflow fits teams with hundreds of working DAGs and someone who owns it. Temporal and Argo are for application workflows and Kubernetes batch, respectively, and are not data orchestrators at all.&lt;/p&gt;

&lt;p&gt;The rule of thumb: count what the DAGs do. If most tasks are load, transform, check, and schedule, a framework that owns those four removes the layer. If most tasks are something else, an orchestrator is the right tool and the data pipeline becomes one of the things it calls.&lt;/p&gt;

&lt;p&gt;For the tool comparison see &lt;a href="https://getbruin.com/blog/best-data-pipeline-tools-2026/" rel="noopener noreferrer"&gt;the best data pipeline tools in 2026&lt;/a&gt;, and for the migration path, &lt;a href="https://getbruin.com/alternatives/airflow-alternative/" rel="noopener noreferrer"&gt;alternatives to Airflow&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is the best way to schedule and orchestrate data pipelines?
&lt;/h3&gt;

&lt;p&gt;Declare the dependencies on the pipeline assets and let the framework resolve the order, then attach a schedule to the pipeline rather than to each task. Bruin does this from the asset definitions: bruin run resolves the graph and runs loads, models, and checks in order, and pipeline.yml carries the schedule, with Bruin Cloud running it with retries and alerts. Airflow, Dagster, and Prefect do the same for pipelines assembled from separate tools, at the cost of operating the orchestrator itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  What are the best data orchestration tools in 2026?
&lt;/h3&gt;

&lt;p&gt;Airflow remains the default general-purpose orchestrator with the largest ecosystem, Dagster is the asset-centric choice for data platforms, Prefect the lightest for Python-native workflows, and Kestra the declarative YAML option. For teams whose pipelines are ingestion, SQL and Python transforms, and checks, Bruin removes the orchestration layer instead of adding one, because the framework schedules its own dependency graph.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the best orchestration tool that does not need a platform team?
&lt;/h3&gt;

&lt;p&gt;One you do not have to operate. Managed Airflow through Astronomer or MWAA removes the servers but not the Airflow concepts. Bruin removes both for data pipelines: an open-source CLI resolves and runs the dependency graph locally or in CI, and Bruin Cloud runs the schedule, retries, backfills, and alerts, with no scheduler, workers, or metadata database to own.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the best orchestration tool for a small data team?
&lt;/h3&gt;

&lt;p&gt;If the team's pipelines glue together loads, SQL, and checks, Bruin, because ingestion, transformation, checks, and scheduling come from one CLI and there is no orchestrator to run. If the team is Python-first and wants to keep separate ingestion and transformation tools, Prefect is the lightest orchestrator to operate and Dagster the most complete. Airflow makes sense once someone owns it full time.&lt;/p&gt;

</description>
      <category>dataengineering</category>
      <category>airflow</category>
      <category>orchestration</category>
      <category>opensource</category>
    </item>
    <item>
      <title>What is CDC in data engineering? Change data capture explained</title>
      <dc:creator>Kateryna Kozachenko</dc:creator>
      <pubDate>Sun, 25 Oct 2026 19:38:29 +0000</pubDate>
      <link>https://dev.to/bruin-data/what-is-cdc-in-data-engineering-change-data-capture-explained-cmd</link>
      <guid>https://dev.to/bruin-data/what-is-cdc-in-data-engineering-change-data-capture-explained-cmd</guid>
      <description>&lt;p&gt;&lt;em&gt;This article was first published on the &lt;a href="https://getbruin.com/blog/what-is-cdc-change-data-capture/" rel="noopener noreferrer"&gt;Bruin blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quick answer:&lt;/strong&gt; CDC stands for &lt;strong&gt;change data capture&lt;/strong&gt;. It is a way to track inserts, updates, and deletes in a source system, then move those changes into another system without reloading the whole table every time.&lt;/p&gt;

&lt;p&gt;The shortest version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;source database changes -&amp;gt; CDC process -&amp;gt; warehouse, lake, stream, or another database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If an order is inserted in Postgres, CDC can send that new order to BigQuery. If a customer row is updated in MySQL, CDC can send the updated version to Snowflake. If an account is deleted in SQL Server, CDC can preserve that delete as an event so the destination does not quietly drift away from the source.&lt;/p&gt;

&lt;p&gt;That's the useful bit. CDC is not just "faster ETL". It changes the shape of the ingestion problem: instead of asking "what does the full table look like now?", you ask "what changed since the last point I safely processed?"&lt;/p&gt;

&lt;p&gt;This article covers the broader capture method. For the continuous, low-latency delivery model, see &lt;a href="https://getbruin.com/blog/what-is-cdc-streaming/" rel="noopener noreferrer"&gt;CDC streaming&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For data teams, this matters because most company data does not sit still. Product events arrive, payments settle, subscriptions change status, tickets get merged, rows get corrected, and someone eventually deletes the thing everyone assumed would never be deleted. The classic nightly full refresh works for small tables. It gets painful once tables are large, freshness matters, or downstream systems need a proper history of changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What CDC means
&lt;/h2&gt;

&lt;p&gt;Change data capture is the process of detecting row-level changes in a source system and making those changes available to another system.&lt;/p&gt;

&lt;p&gt;A CDC event usually answers a few practical questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which table changed?&lt;/li&gt;
&lt;li&gt;Which row changed?&lt;/li&gt;
&lt;li&gt;Was it an insert, update, or delete?&lt;/li&gt;
&lt;li&gt;What did the row look like before?&lt;/li&gt;
&lt;li&gt;What does the row look like after?&lt;/li&gt;
&lt;li&gt;When did the change happen?&lt;/li&gt;
&lt;li&gt;Where in the source log did this change come from?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A simplified CDC event might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"table"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"public.orders"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"operation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"update"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"primary_key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"order_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1042&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"before"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pending"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"updated_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-07-13T08:14:10Z"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"after"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"paid"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"updated_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-07-13T08:16:03Z"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source_position"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"log-sequence-number-or-binlog-offset"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;source_position&lt;/code&gt; part is easy to ignore when you are learning CDC, but it is one of the most important pieces. A CDC pipeline needs to know where it stopped so it can resume without skipping changes or processing the same change incorrectly. In PostgreSQL, this is tied to logical decoding and replication slots. In MySQL, it is commonly tied to the binary log. In SQL Server, CDC reads from the transaction log into change tables.&lt;/p&gt;

&lt;p&gt;The names vary by database, but the pattern is similar: the database already records changes so it can commit transactions, recover from failure, and replicate data. CDC uses that record as the source of truth for downstream movement.&lt;/p&gt;

&lt;h2&gt;
  
  
  How CDC works
&lt;/h2&gt;

&lt;p&gt;There are a few ways to capture changes, but log-based CDC is the one most engineers mean when they say CDC.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The database writes a change
&lt;/h3&gt;

&lt;p&gt;An application inserts, updates, or deletes a row. The database commits the transaction and records it in its internal log.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PostgreSQL exposes changes through &lt;a href="https://www.postgresql.org/docs/current/logicaldecoding.html" rel="noopener noreferrer"&gt;logical decoding&lt;/a&gt;, which can stream SQL-level modifications to external consumers.&lt;/li&gt;
&lt;li&gt;MySQL records changes in the &lt;a href="https://dev.mysql.com/doc/refman/8.4/en/binary-log.html" rel="noopener noreferrer"&gt;binary log&lt;/a&gt;, often called the binlog.&lt;/li&gt;
&lt;li&gt;SQL Server CDC reads from the &lt;a href="https://learn.microsoft.com/en-us/sql/relational-databases/track-changes/about-change-data-capture-sql-server?view=sql-server-ver17" rel="noopener noreferrer"&gt;transaction log&lt;/a&gt; and stores changes in relational change tables.&lt;/li&gt;
&lt;li&gt;Debezium connectors commonly capture MySQL changes from the binlog and PostgreSQL changes from a logical replication stream, as described in the &lt;a href="https://debezium.io/documentation/reference/stable/architecture.html" rel="noopener noreferrer"&gt;Debezium architecture docs&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. A CDC reader consumes the log
&lt;/h3&gt;

&lt;p&gt;The CDC reader connects to the source database and reads committed changes in order. It does not need to run &lt;code&gt;select * from orders&lt;/code&gt; every five minutes. It reads the database's change stream.&lt;/p&gt;

&lt;p&gt;This usually reduces source load compared with full-table extraction, especially when a table is large but only a small percentage of rows change during each sync.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The pipeline stores an offset
&lt;/h3&gt;

&lt;p&gt;The pipeline records the last safe position it processed. That offset might be a log sequence number, binlog file and position, timestamp plus version, or another source-specific cursor.&lt;/p&gt;

&lt;p&gt;This is what lets the pipeline restart after a deploy, network failure, worker crash, or source maintenance window.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. The destination applies the changes
&lt;/h3&gt;

&lt;p&gt;The destination might receive the changes as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an append-only raw change table&lt;/li&gt;
&lt;li&gt;a current-state replica table&lt;/li&gt;
&lt;li&gt;a stream topic&lt;/li&gt;
&lt;li&gt;a slowly changing dimension table&lt;/li&gt;
&lt;li&gt;a queue for downstream applications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Analytics teams often keep both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;raw CDC events&lt;/strong&gt; for auditability and replay&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;current tables&lt;/strong&gt; for normal BI, AI analysis, dashboards, and modelling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That split is boring, but it saves you later. Raw events are useful when something goes wrong and you need to replay or explain history. Current tables are useful because most business users want to query "orders" rather than reconstruct a table from every historical mutation.&lt;/p&gt;

&lt;h2&gt;
  
  
  CDC vs ETL vs ELT
&lt;/h2&gt;

&lt;p&gt;CDC is not a replacement for ETL or ELT. It is a method of extraction.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concept&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;th&gt;Where CDC fits&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ETL&lt;/td&gt;
&lt;td&gt;Extract, transform, load&lt;/td&gt;
&lt;td&gt;CDC can be the extract step&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ELT&lt;/td&gt;
&lt;td&gt;Extract, load, transform&lt;/td&gt;
&lt;td&gt;CDC can load raw changes first, then transform in the warehouse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data ingestion&lt;/td&gt;
&lt;td&gt;Moving data from source to destination&lt;/td&gt;
&lt;td&gt;CDC is one ingestion strategy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Replication&lt;/td&gt;
&lt;td&gt;Keeping another system close to the source&lt;/td&gt;
&lt;td&gt;CDC may power replication, but it is not limited to source-shaped copies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Streaming&lt;/td&gt;
&lt;td&gt;Moving events continuously&lt;/td&gt;
&lt;td&gt;CDC can produce a stream, but CDC can also be consumed in batches&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The difference sounds pedantic until you design the pipeline.&lt;/p&gt;

&lt;p&gt;If you say "we need ETL", you still have to decide whether extraction is full refresh, timestamp incremental, API pagination, event export, or CDC. If you say "we need CDC", you still have to decide what happens after the changes land: raw storage, merges, transformations, checks, lineage, documentation, and access control.&lt;/p&gt;

&lt;p&gt;This is why CDC is usually one part of a broader data platform, not the whole platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  CDC vs incremental loading
&lt;/h2&gt;

&lt;p&gt;CDC and incremental loading overlap, but they are not the same thing.&lt;/p&gt;

&lt;p&gt;Incremental loading means "only load new or changed data since the last run." CDC is one way to do that.&lt;/p&gt;

&lt;p&gt;The common incremental strategies look like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Strategy&lt;/th&gt;
&lt;th&gt;How it finds changes&lt;/th&gt;
&lt;th&gt;Good for&lt;/th&gt;
&lt;th&gt;Main risk&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Timestamp cursor&lt;/td&gt;
&lt;td&gt;Reads rows where &lt;code&gt;updated_at &amp;gt; last_run_time&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Simple tables with reliable timestamps&lt;/td&gt;
&lt;td&gt;Missed deletes, clock issues, late updates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Increasing ID&lt;/td&gt;
&lt;td&gt;Reads rows where &lt;code&gt;id &amp;gt; last_seen_id&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Append-only tables&lt;/td&gt;
&lt;td&gt;Cannot detect updates or deletes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Full diff&lt;/td&gt;
&lt;td&gt;Compares source and destination snapshots&lt;/td&gt;
&lt;td&gt;Smaller tables where correctness matters&lt;/td&gt;
&lt;td&gt;Expensive at scale&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Log-based CDC&lt;/td&gt;
&lt;td&gt;Reads transaction logs or change streams&lt;/td&gt;
&lt;td&gt;Large mutable tables, low-latency syncs&lt;/td&gt;
&lt;td&gt;More setup, permissions, and operational detail&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trigger-based CDC&lt;/td&gt;
&lt;td&gt;Writes changes through database triggers&lt;/td&gt;
&lt;td&gt;Databases without usable logs&lt;/td&gt;
&lt;td&gt;Adds write-path overhead and schema complexity&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If a table is append-only, an increasing ID cursor might be enough. If rows are updated and deleted all day, CDC becomes much more attractive.&lt;/p&gt;

&lt;p&gt;The mistake is treating every table the same. A small reference table can be full-refreshed. A huge orders table probably should not be. An event table may only need append mode. A customer table with merges, deletes, and compliance requirements may need CDC or SCD Type 2.&lt;/p&gt;

&lt;h2&gt;
  
  
  Types of CDC
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Log-based CDC
&lt;/h3&gt;

&lt;p&gt;This is the usual preferred option for production databases. The CDC tool reads the transaction log, binlog, write-ahead log, redo log, or logical replication stream.&lt;/p&gt;

&lt;p&gt;Why teams like it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;lower source query load than repeated full scans&lt;/li&gt;
&lt;li&gt;captures inserts, updates, and deletes&lt;/li&gt;
&lt;li&gt;preserves ordering better than timestamp scans&lt;/li&gt;
&lt;li&gt;can support lower-latency syncs&lt;/li&gt;
&lt;li&gt;usually does not require changing application code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tradeoff: you need source-specific setup. Permissions, retention, replication slots, binlog settings, replica identity, networking, and failover behaviour matter.&lt;/p&gt;

&lt;h3&gt;
  
  
  Trigger-based CDC
&lt;/h3&gt;

&lt;p&gt;Trigger-based CDC adds database triggers that write changes to audit or shadow tables. A pipeline then reads those tables.&lt;/p&gt;

&lt;p&gt;This can work, especially when the database does not expose a useful log. But it adds logic to the write path. That means more performance risk, more migration work, and more things to review when schema changes happen.&lt;/p&gt;

&lt;h3&gt;
  
  
  Query-based CDC
&lt;/h3&gt;

&lt;p&gt;Query-based CDC polls the source with a cursor, usually &lt;code&gt;updated_at&lt;/code&gt;, &lt;code&gt;created_at&lt;/code&gt;, or an incrementing ID.&lt;/p&gt;

&lt;p&gt;This is the easiest model to understand and often the first one teams implement. It is also the easiest one to get subtly wrong.&lt;/p&gt;

&lt;p&gt;The usual problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;hard deletes disappear unless you soft-delete rows&lt;/li&gt;
&lt;li&gt;updates can be missed if timestamps are unreliable&lt;/li&gt;
&lt;li&gt;backfilled rows can fall outside the cursor window&lt;/li&gt;
&lt;li&gt;bulk updates can overload the next sync&lt;/li&gt;
&lt;li&gt;clock and timezone issues create awkward edge cases&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Query-based incremental loading is still useful. It just should not be called "proper CDC" unless it really captures the mutations you care about.&lt;/p&gt;

&lt;h2&gt;
  
  
  When CDC is worth it
&lt;/h2&gt;

&lt;p&gt;CDC is worth considering when at least one of these is true:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;tables are too large for regular full refreshes&lt;/li&gt;
&lt;li&gt;downstream data needs to be fresh within minutes&lt;/li&gt;
&lt;li&gt;updates and deletes matter&lt;/li&gt;
&lt;li&gt;you need an audit trail of row-level changes&lt;/li&gt;
&lt;li&gt;you want to replicate operational database changes into a warehouse or lakehouse&lt;/li&gt;
&lt;li&gt;source systems are sensitive and you want to reduce repeated heavy reads&lt;/li&gt;
&lt;li&gt;downstream consumers need to replay events from a known point&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Good CDC use cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;syncing Postgres, MySQL, or SQL Server into Snowflake, BigQuery, Redshift, Databricks, or DuckDB&lt;/li&gt;
&lt;li&gt;keeping customer, subscription, payment, order, ticket, or inventory tables fresh&lt;/li&gt;
&lt;li&gt;feeding event-driven systems from database mutations&lt;/li&gt;
&lt;li&gt;maintaining current-state and historical tables side by side&lt;/li&gt;
&lt;li&gt;reducing batch windows for large mutable datasets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CDC is especially useful when the business keeps asking why yesterday's dashboard is not fresh enough, but the data team knows that full-refreshing the source every 15 minutes would be irresponsible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Popular CDC tools
&lt;/h2&gt;

&lt;p&gt;If you search for "CDC tools", you will usually see the same names come up: Debezium, Fivetran, Airbyte, Estuary Flow, AWS Database Migration Service, Google Datastream, Oracle GoldenGate, Qlik Replicate, and now Bruin with ingestr for code-first CDC inside a governed data pipeline.&lt;/p&gt;

&lt;p&gt;They are not interchangeable. Some are CDC engines. Some are managed ELT platforms. Some are cloud migration services that happen to support ongoing replication. Some are better when Kafka is already part of your stack. Some are better when your team wants a YAML asset in Git and a scheduler around it.&lt;/p&gt;

&lt;p&gt;Here is the practical split.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Best fit&lt;/th&gt;
&lt;th&gt;What to watch&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://debezium.io/documentation/reference/stable/features.html" rel="noopener noreferrer"&gt;Debezium&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Open-source log-based CDC into Kafka Connect, Debezium Server, or streaming infrastructure&lt;/td&gt;
&lt;td&gt;You operate the runtime, connectors, offsets, schema changes, and downstream sinks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://fivetran.com/docs/connectors/databases/troubleshooting/optimize-cdc-performance" rel="noopener noreferrer"&gt;Fivetran&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Managed database replication into warehouses with less infrastructure work&lt;/td&gt;
&lt;td&gt;Cost, vendor control, sync frequency, and connector-specific behaviour&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://docs.airbyte.com/platform/understanding-airbyte/cdc" rel="noopener noreferrer"&gt;Airbyte&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Open-source or managed connector platform with CDC support for common databases&lt;/td&gt;
&lt;td&gt;Airbyte's CDC model is scheduled sync, not an infinite stream, so log retention and sync cadence matter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://docs.estuary.dev/" rel="noopener noreferrer"&gt;Estuary Flow&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Streaming-first CDC and materialization for low-latency data movement&lt;/td&gt;
&lt;td&gt;Another streaming platform to operate, govern, and connect to the rest of your stack&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://docs.aws.amazon.com/dms/latest/userguide/CHAP_Task.CDC.html" rel="noopener noreferrer"&gt;AWS DMS&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Database migration and ongoing replication inside AWS&lt;/td&gt;
&lt;td&gt;AWS explicitly notes that DMS CDC is not real-time replication and latency depends on workload, network, replication instance, and target capacity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://docs.cloud.google.com/datastream/docs/overview" rel="noopener noreferrer"&gt;Google Datastream&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Serverless CDC on Google Cloud, especially into BigQuery and Cloud Storage&lt;/td&gt;
&lt;td&gt;Strong fit for GCP-centred stacks, less portable if your CDC architecture needs to stay cloud-neutral&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://docs.oracle.com/en/middleware/goldengate/index.html" rel="noopener noreferrer"&gt;Oracle GoldenGate&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Enterprise replication, Oracle-heavy estates, and heterogeneous database environments&lt;/td&gt;
&lt;td&gt;Powerful, but usually more enterprise-ops heavy than a simple warehouse ingestion job&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.qlik.com/us/change-data-capture/data-capture" rel="noopener noreferrer"&gt;Qlik Replicate&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Enterprise CDC and replication across many source and target systems&lt;/td&gt;
&lt;td&gt;Similar tradeoff: broad enterprise capability, but with platform cost and operational ownership&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://getbruin.com/docs/bruin/assets/ingestr.html" rel="noopener noreferrer"&gt;Bruin + ingestr&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Code-first ingestion assets, CDC, transformations, checks, lineage, and governance in one workflow&lt;/td&gt;
&lt;td&gt;Best when CDC should be part of a reviewed data pipeline, not a disconnected sync job&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Honestly, the right choice is less about the CDC acronym and more about the operating model.&lt;/p&gt;

&lt;p&gt;If Kafka is already the centre of the architecture, Debezium is a natural fit. If the team wants a managed warehouse connector and does not want to run capture infrastructure, Fivetran or Airbyte may be the simpler path. If the project is a cloud migration, AWS DMS or Datastream can be the right boring answer. If the estate is Oracle-heavy or enterprise replication is the main job, GoldenGate or Qlik Replicate deserve a look.&lt;/p&gt;

&lt;p&gt;And if the question is "how do we put CDC inside the same repo where we define transformations, tests, owners, metadata, and lineage?", that is where Bruin fits.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Bruin supports CDC with ingestr
&lt;/h2&gt;

&lt;p&gt;Bruin's CDC support lives in the &lt;a href="https://getbruin.com/docs/bruin/assets/ingestr.html" rel="noopener noreferrer"&gt;&lt;code&gt;ingestr&lt;/code&gt; asset type&lt;/a&gt;. In other words, CDC is not configured as a one-off side process that nobody can find later. It is an asset in the pipeline, alongside SQL assets, Python assets, checks, ownership metadata, and downstream lineage.&lt;/p&gt;

&lt;p&gt;A simple PostgreSQL CDC asset looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;raw.orders&lt;/span&gt;
&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ingestr&lt;/span&gt;
&lt;span class="na"&gt;connection&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bigquery&lt;/span&gt;

&lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;source_connection&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pg_prod&lt;/span&gt;
  &lt;span class="na"&gt;source_table&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;public.orders&lt;/span&gt;
  &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bigquery&lt;/span&gt;
  &lt;span class="na"&gt;cdc&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
  &lt;span class="na"&gt;cdc_mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;stream&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Bruin docs describe CDC support for PostgreSQL, MySQL/MariaDB, Vitess, and PlanetScale sources through &lt;code&gt;ingestr&lt;/code&gt; assets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://getbruin.com/docs/bruin/platforms/postgres.html" rel="noopener noreferrer"&gt;PostgreSQL CDC in Bruin&lt;/a&gt; uses PostgreSQL logical replication. The source needs &lt;code&gt;wal_level=logical&lt;/code&gt;, a publication, and a replication slot.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://getbruin.com/docs/bruin/platforms/mysql.html" rel="noopener noreferrer"&gt;MySQL CDC in Bruin&lt;/a&gt; uses binary-log replication. MySQL needs settings like &lt;code&gt;log_bin=ON&lt;/code&gt;, &lt;code&gt;binlog_format=ROW&lt;/code&gt;, and &lt;code&gt;binlog_row_image=FULL&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://getbruin.com/docs/bruin/ingestion/vitess.html" rel="noopener noreferrer"&gt;Vitess CDC in Bruin&lt;/a&gt; uses Vitess VStream through vtgate's gRPC API.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://getbruin.com/docs/bruin/ingestion/planetscale.html" rel="noopener noreferrer"&gt;PlanetScale CDC in Bruin&lt;/a&gt; uses PlanetScale's hosted &lt;code&gt;psdbconnect&lt;/code&gt; API over TLS.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Under the hood, &lt;a href="https://getbruin.com/docs/ingestr/commands/ingest.html" rel="noopener noreferrer"&gt;ingestr's CDC docs&lt;/a&gt; expose the lower-level source schemes and runtime controls. For example, PostgreSQL CDC can use &lt;code&gt;postgres+cdc://&lt;/code&gt;, MySQL CDC can use &lt;code&gt;mysql+cdc://&lt;/code&gt;, and PlanetScale CDC can use &lt;code&gt;ps_mysql+cdc://&lt;/code&gt;. ingestr also has streaming flush controls like &lt;code&gt;--flush-interval&lt;/code&gt; and &lt;code&gt;--flush-records&lt;/code&gt;, and stream monitoring metrics for replication lag and durable row counts.&lt;/p&gt;

&lt;p&gt;Bruin adds the pipeline layer around that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CDC assets are defined in Git.&lt;/li&gt;
&lt;li&gt;Destination application uses &lt;code&gt;merge&lt;/code&gt; semantics for CDC, so updates and deletes can be applied by primary key.&lt;/li&gt;
&lt;li&gt;Primary keys are determined from the source for CDC assets where the source exposes them.&lt;/li&gt;
&lt;li&gt;Pipeline runs can include CDC ingestion, transformations, checks, and downstream assets in the same graph.&lt;/li&gt;
&lt;li&gt;Bruin Cloud can add scheduling, catalog, lineage, access controls, SSO, audit logs, and observability around those assets.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's the main difference. A CDC tool can move changes. A governed CDC pipeline also tells you who owns the data, which downstream models depend on it, what checks ran, what changed in Git, and whether the tables are trustworthy enough for dashboards or an AI data analyst.&lt;/p&gt;

&lt;h2&gt;
  
  
  When CDC is not worth it
&lt;/h2&gt;

&lt;p&gt;CDC has a cost. Not always a vendor cost, but definitely an operational cost.&lt;/p&gt;

&lt;p&gt;You may not need CDC when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the table is tiny&lt;/li&gt;
&lt;li&gt;the table is append-only&lt;/li&gt;
&lt;li&gt;daily freshness is enough&lt;/li&gt;
&lt;li&gt;deletes do not matter&lt;/li&gt;
&lt;li&gt;a source API already gives reliable incremental exports&lt;/li&gt;
&lt;li&gt;the source database team will not grant replication or log access&lt;/li&gt;
&lt;li&gt;your downstream models are not ready to handle late-arriving changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CDC can make a messy pipeline faster, but it will not make it governed.&lt;/p&gt;

&lt;p&gt;If there is no ownership, no tests, no lineage, no clear schema-change policy, and no destination contract, CDC just moves the chaos more quickly. This is where teams get disappointed: they add a powerful capture layer, then discover their warehouse models still cannot explain what "active customer" means.&lt;/p&gt;

&lt;h2&gt;
  
  
  What can go wrong with CDC
&lt;/h2&gt;

&lt;p&gt;The failure modes are not exotic. They are normal data engineering problems with sharper edges.&lt;/p&gt;

&lt;h3&gt;
  
  
  Schema changes
&lt;/h3&gt;

&lt;p&gt;Source tables change. Columns are added, renamed, removed, widened, narrowed, or repurposed. CDC pipelines need a schema-change policy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Should new columns appear automatically?&lt;/li&gt;
&lt;li&gt;Should type changes be blocked?&lt;/li&gt;
&lt;li&gt;Should the raw event stream preserve unknown fields?&lt;/li&gt;
&lt;li&gt;Who reviews downstream model impact?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without this, one application migration can break ingestion, transformations, dashboards, and AI answers at the same time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deletes
&lt;/h3&gt;

&lt;p&gt;Deletes are the reason many timestamp-based incremental jobs lie.&lt;/p&gt;

&lt;p&gt;If a row is physically deleted from the source, a normal &lt;code&gt;updated_at&lt;/code&gt; query will not see it. CDC can capture delete events, but the destination still needs to decide what to do with them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;delete the row from the current table&lt;/li&gt;
&lt;li&gt;mark it as deleted&lt;/li&gt;
&lt;li&gt;keep it in raw history only&lt;/li&gt;
&lt;li&gt;apply a retention policy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That choice is a business and governance decision, not just an engineering one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Backfills and snapshots
&lt;/h3&gt;

&lt;p&gt;Most CDC pipelines start with an initial snapshot, then switch to ongoing changes.&lt;/p&gt;

&lt;p&gt;That handoff is critical. If the snapshot and log stream are not coordinated, you can miss changes that happen during the snapshot or apply duplicates after it.&lt;/p&gt;

&lt;p&gt;Good CDC tools handle this carefully. Homegrown scripts often do not, mostly because the happy path looks deceptively simple.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ordering and idempotency
&lt;/h3&gt;

&lt;p&gt;CDC events need to be applied in a way that produces a correct destination state even if a worker retries. That usually means designing around primary keys, source positions, transaction boundaries, and idempotent merges.&lt;/p&gt;

&lt;p&gt;If "run it again" creates duplicates, the pipeline is not finished.&lt;/p&gt;

&lt;h3&gt;
  
  
  Source retention
&lt;/h3&gt;

&lt;p&gt;Database logs are not kept forever. If a CDC process stops for too long and the source removes the old log segment, the pipeline may no longer be able to resume from its saved offset. Then you are looking at a re-snapshot or some careful repair work.&lt;/p&gt;

&lt;p&gt;This is why monitoring matters. CDC needs lag alerts, not just job-success alerts.&lt;/p&gt;

&lt;h2&gt;
  
  
  How CDC fits into a governed data pipeline
&lt;/h2&gt;

&lt;p&gt;A practical CDC pipeline usually has these layers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;source database
  -&amp;gt; CDC capture
  -&amp;gt; raw change events
  -&amp;gt; current-state tables
  -&amp;gt; transformations
  -&amp;gt; checks
  -&amp;gt; catalog, lineage, dashboards, AI analyst
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The capture layer is only the beginning.&lt;/p&gt;

&lt;p&gt;For analytics and AI workflows, the downstream context is just as important:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;catalog&lt;/strong&gt; so people know what tables mean&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;lineage&lt;/strong&gt; so changes can be traced from source to dashboard&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;quality checks&lt;/strong&gt; so broken loads do not become trusted answers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;asset tiers&lt;/strong&gt; so important tables are handled differently from scratch work&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;audit trails&lt;/strong&gt; so production changes have a review history&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;access controls&lt;/strong&gt; so raw operational data is not exposed casually&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the part Bruin cares a lot about. The open-source side gives you local, code-first pipeline work with Bruin CLI and ingestion with &lt;a href="https://github.com/bruin-data/ingestr" rel="noopener noreferrer"&gt;ingestr&lt;/a&gt;. Bruin Cloud adds orchestration, catalog, lineage, checks, access controls, SSO, audit logs, and observability around those workflows.&lt;/p&gt;

&lt;p&gt;CDC is strongest when it is not a disconnected sync job. It should be visible inside the same workflow where transformations, checks, and downstream data products are reviewed.&lt;/p&gt;

&lt;h2&gt;
  
  
  A simple CDC decision checklist
&lt;/h2&gt;

&lt;p&gt;Before choosing CDC, ask:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Does the source expose a reliable change log or CDC feature?&lt;/li&gt;
&lt;li&gt;Can we get the permissions needed to read it?&lt;/li&gt;
&lt;li&gt;Do we need deletes, or only inserts and updates?&lt;/li&gt;
&lt;li&gt;Do we need raw history, current-state tables, or both?&lt;/li&gt;
&lt;li&gt;How fresh does the destination actually need to be?&lt;/li&gt;
&lt;li&gt;What happens when the schema changes?&lt;/li&gt;
&lt;li&gt;What happens when the pipeline is down for two hours?&lt;/li&gt;
&lt;li&gt;How do we monitor lag?&lt;/li&gt;
&lt;li&gt;Can the destination apply events idempotently?&lt;/li&gt;
&lt;li&gt;Who owns the downstream model if the source semantics change?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If those questions feel annoying, that's the point. CDC is not hard because the acronym is hard. It is hard because it touches database internals, pipeline reliability, schema contracts, and business meaning at the same time.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is CDC in data engineering?
&lt;/h3&gt;

&lt;p&gt;CDC means change data capture. It is the process of identifying inserts, updates, and deletes in a source system and moving those changes to another system, usually a warehouse, lakehouse, stream, or operational database.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between CDC and ETL?
&lt;/h3&gt;

&lt;p&gt;ETL is the broader process of extracting, transforming, and loading data. CDC is a capture strategy inside that process. Instead of extracting a whole table every time, CDC captures only the rows that changed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is CDC the same as replication?
&lt;/h3&gt;

&lt;p&gt;No. Replication usually tries to keep another database copy close to the source shape. CDC emits a stream or table of change events that downstream systems can transform, audit, replay, or merge into different models.&lt;/p&gt;

&lt;h3&gt;
  
  
  When should a team use CDC?
&lt;/h3&gt;

&lt;p&gt;Use CDC when full refreshes are too slow, source tables are large, downstream data needs to stay fresh, or deletes and updates matter. It is especially useful for database-to-warehouse ingestion and event-driven data pipelines.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is log-based CDC?
&lt;/h3&gt;

&lt;p&gt;Log-based CDC reads the database transaction log, such as the PostgreSQL logical replication stream, MySQL binary log, or SQL Server transaction log, instead of repeatedly querying source tables for changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  What are the most popular CDC tools?
&lt;/h3&gt;

&lt;p&gt;Popular CDC tools include Debezium, Fivetran, Airbyte, Estuary Flow, AWS Database Migration Service, Google Datastream, Oracle GoldenGate, Qlik Replicate, and Bruin with ingestr. The right choice depends on whether you want open-source streaming CDC, managed ELT, cloud migration replication, enterprise database replication, or CDC as part of a governed pipeline.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Bruin support CDC?
&lt;/h3&gt;

&lt;p&gt;Yes. Bruin supports CDC through ingestr assets. Bruin docs describe CDC support for PostgreSQL, MySQL/MariaDB, Vitess, and PlanetScale sources using &lt;code&gt;cdc: "true"&lt;/code&gt;. Bruin keeps CDC in the same asset graph as transformations, checks, ownership metadata, catalog, lineage, and Cloud observability.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does CDC require Kafka?
&lt;/h3&gt;

&lt;p&gt;No. Kafka is common in CDC architectures, especially with Debezium, but it is not mandatory. CDC changes can flow into warehouses, object storage, queues, streams, or another database depending on the tool and destination.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can CDC capture deletes?
&lt;/h3&gt;

&lt;p&gt;Yes, proper CDC can capture deletes if the source and capture method support them. The destination still needs a clear policy for applying those deletes, preserving them in history, or marking rows as deleted.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is CDC always real-time?
&lt;/h3&gt;

&lt;p&gt;No. CDC can be near real-time, but it can also be consumed in micro-batches or scheduled jobs. The important part is that the pipeline reads changes from a reliable change source instead of repeatedly extracting the whole table.&lt;/p&gt;

&lt;h2&gt;
  
  
  The useful way to think about CDC
&lt;/h2&gt;

&lt;p&gt;CDC is a better question to ask of operational data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;What changed, in what order, and how do we safely apply it downstream?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you think about it that way, the rest of the design gets clearer. You need a source of changes, a stored position, an idempotent destination write, a schema policy, monitoring, and enough governance that people can trust the tables built from those changes.&lt;/p&gt;

&lt;p&gt;The capture part is technical. The trust part is the real work.&lt;/p&gt;

</description>
      <category>dataengineering</category>
      <category>database</category>
      <category>postgres</category>
      <category>etl</category>
    </item>
    <item>
      <title>What Is a Data Quality Check? Types, Where to Run Them, and Examples</title>
      <dc:creator>Kateryna Kozachenko</dc:creator>
      <pubDate>Sun, 25 Oct 2026 19:38:12 +0000</pubDate>
      <link>https://dev.to/bruin-data/what-is-a-data-quality-check-types-where-to-run-them-and-examples-39a9</link>
      <guid>https://dev.to/bruin-data/what-is-a-data-quality-check-types-where-to-run-them-and-examples-39a9</guid>
      <description>&lt;p&gt;&lt;em&gt;This article was first published on the &lt;a href="https://getbruin.com/blog/what-is-a-data-quality-check/" rel="noopener noreferrer"&gt;Bruin blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A data quality check is a rule that runs against a table, or a column in it, and fails when the data breaks the rule.&lt;/strong&gt; "No nulls in &lt;code&gt;order_id&lt;/code&gt;", "every &lt;code&gt;status&lt;/code&gt; is one of four values", "the newest row is less than six hours old". The check either passes or it fails, and what happens on failure is the design decision that separates a useful check from a dashboard nobody reads: a blocking check stops the pipeline, so bad data never reaches the tables downstream. In &lt;a href="https://getbruin.com/platform" rel="noopener noreferrer"&gt;Bruin&lt;/a&gt;, checks are declared on the column inside the asset that produces it and block by default; dbt tests, Great Expectations, and Soda run the same kinds of rules as a separate step.&lt;/p&gt;

&lt;h2&gt;
  
  
  The types of data quality check
&lt;/h2&gt;

&lt;p&gt;Most checks fall into six families. The names below are Bruin's built-in check names; every framework has an equivalent.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Family&lt;/th&gt;
&lt;th&gt;What it asserts&lt;/th&gt;
&lt;th&gt;Built-in check&lt;/th&gt;
&lt;th&gt;Catches&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Completeness&lt;/td&gt;
&lt;td&gt;The column has a value&lt;/td&gt;
&lt;td&gt;&lt;code&gt;not_null&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A source that started sending empty fields&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Uniqueness&lt;/td&gt;
&lt;td&gt;No duplicates&lt;/td&gt;
&lt;td&gt;&lt;code&gt;unique&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A load that ran twice, a join that fanned out&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Validity&lt;/td&gt;
&lt;td&gt;Values are from an allowed set or shape&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;accepted_values&lt;/code&gt;, &lt;code&gt;pattern&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Upstream schema drift, a new status nobody mapped&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Range&lt;/td&gt;
&lt;td&gt;Numbers and dates sit inside bounds&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;positive&lt;/code&gt;, &lt;code&gt;non_negative&lt;/code&gt;, &lt;code&gt;min&lt;/code&gt;, &lt;code&gt;max&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Unit errors, currency mix-ups, future dates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Referential integrity&lt;/td&gt;
&lt;td&gt;Every foreign key points at a real row&lt;/td&gt;
&lt;td&gt;&lt;code&gt;relationships&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Orphaned facts after a dimension reload&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Freshness and volume&lt;/td&gt;
&lt;td&gt;The table was updated recently and has the expected rows&lt;/td&gt;
&lt;td&gt;custom SQL check&lt;/td&gt;
&lt;td&gt;The pipeline that silently stopped&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The first two families on a primary key, plus a freshness check on the load timestamp, catch most of the incidents a data team ever gets paged for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the check runs matters more than which checks you write
&lt;/h2&gt;

&lt;p&gt;There are two places a check can live. It can sit beside the pipeline, as a separate suite of expectations or a YAML scan file, run by a step the orchestrator triggers between load and transform. Or it can sit inside the pipeline, declared on the asset that produces the table and executed as part of building it.&lt;/p&gt;

&lt;p&gt;Beside the pipeline is how Great Expectations and Soda Core work, and it suits a platform team that wants one central library of rules over many producers. The cost is drift: rename a column in the transformation and the expectation that references it is now wrong in a different repository, and nobody finds out until it fails for the wrong reason.&lt;/p&gt;

&lt;p&gt;Inside the pipeline is how Bruin and dbt tests work. The check is in the same file as the SQL that produces the column, so rename the column and the check moves with it, delete the asset and the check goes with it. There is nothing to keep in sync.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a check looks like in practice
&lt;/h2&gt;

&lt;p&gt;In Bruin the checks are part of the asset header, in the SQL or Python file that builds the table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* @bruin
name: mart.orders
type: sf.sql
depends: [raw.orders]
materialization:
  type: table
columns:
  - name: order_id
    type: integer
    primary_key: true
    checks:
      - name: not_null
      - name: unique
  - name: status
    type: string
    checks:
      - name: accepted_values
        value: [placed, paid, shipped, refunded]
  - name: order_total
    type: float
    checks:
      - name: non_negative
custom_checks:
  - name: loaded in the last 6 hours
    query: SELECT max(updated_at) &amp;gt; current_timestamp - interval '6 hours' FROM mart.orders
    value: 1
@bruin */&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;order_total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;updated_at&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;orders&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;bruin run&lt;/code&gt; builds the table and runs the checks in the same step. Every check is blocking by default, so a failure stops the downstream assets; a check you only want to observe gets &lt;code&gt;blocking: false&lt;/code&gt;. The &lt;code&gt;custom_checks&lt;/code&gt; block takes any SQL that returns a value to compare, which is how freshness, row counts, and business rules such as "no order total above a limit without a manager flag" are expressed.&lt;/p&gt;

&lt;p&gt;The dbt equivalent is a &lt;code&gt;tests:&lt;/code&gt; list under the column in the model's YAML, run by &lt;code&gt;dbt test&lt;/code&gt; or &lt;code&gt;dbt build&lt;/code&gt;. Great Expectations expresses the same rules as expectations in a suite and runs them from a checkpoint. Soda writes them as &lt;code&gt;checks for orders:&lt;/code&gt; with lines like &lt;code&gt;missing_count(order_id) = 0&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  When a check fails
&lt;/h2&gt;

&lt;p&gt;A failed check should do three things: stop the assets that depend on the table, tell a person in the channel they already watch, and leave enough context to diagnose the cause. Bruin does the first two from the pipeline definition, with notifications routed to Slack or Microsoft Teams, and the third comes from lineage, which shows which upstream asset fed the bad rows. The alternative, a check that logs a warning and lets the pipeline continue, is how a wrong number ends up in a board deck.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checks are the start of data quality, not the whole of it
&lt;/h2&gt;

&lt;p&gt;Checks are a gate: they catch what you thought to write a rule for. They do not notice that a table's row count is 40% below its usual Tuesday, because no rule said what Tuesday usually looks like. That is the job of an observability tool such as Elementary, Monte Carlo, or Anomalo, and most teams end up with both a gate and a monitor once they have more tables than rules. For the tool-by-tool view see &lt;a href="https://getbruin.com/blog/best-data-quality-tools-2026/" rel="noopener noreferrer"&gt;the best data quality tools in 2026&lt;/a&gt;, and for the full strategy including data contracts, &lt;a href="https://getbruin.com/blog/data-quality-testing-strategies-data-pipelines/" rel="noopener noreferrer"&gt;data quality and testing strategies for modern pipelines&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is the best way to add data quality checks to a pipeline?
&lt;/h3&gt;

&lt;p&gt;Declare the check on the asset that produces the table, so it runs every time the table is built and stops the pipeline when it fails. Start with not_null and unique on every primary key, accepted_values on every status column, and a freshness check on every scheduled load. In Bruin the checks sit in the columns block of the SQL or Python asset and run with bruin run; dbt tests, Great Expectations suites, and Soda scans are the equivalents that run as a separate step.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I validate data as part of a pipeline?
&lt;/h3&gt;

&lt;p&gt;Make validation a stage of the pipeline rather than a job beside it. A blocking check fails the asset and stops its downstream assets, so bad rows never reach a dashboard. Bruin does this by default for every check declared on an asset; in dbt you get the same by running dbt build, which stops on a failing test; with Great Expectations or Soda you wire a checkpoint or scan into the orchestrator between load and transform.&lt;/p&gt;

&lt;h3&gt;
  
  
  What are the most important data quality checks?
&lt;/h3&gt;

&lt;p&gt;Four cover most incidents: not_null and unique on primary keys, which keep joins safe; accepted_values on categorical columns, which catch upstream schema drift; a range check such as non_negative or max on amounts, which catches unit and currency errors; and a freshness check on the load timestamp, which catches the pipeline that silently stopped. Add referential integrity between fact and dimension tables once those four are in place.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between a data quality check and data observability?
&lt;/h3&gt;

&lt;p&gt;A check is a gate: a rule you wrote, run at build time, that blocks bad data. Observability is a monitor: it watches tables after the fact and flags anomalies you did not write a rule for. Checks are cheap and prevent incidents; observability costs a licence and reports them. Most teams need a gate first and add a monitor when the number of tables outgrows the rules they can write.&lt;/p&gt;

</description>
      <category>dataengineering</category>
      <category>dataquality</category>
      <category>sql</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
