MySQL Change Stream Applier (CSA): How MySQL 26.7 Fixes Replication Lag

Vinoth Kanna
Oct 22, 2026
8
Mins to Read
All
MySQL Change Stream Applier (CSA): How MySQL 26.7 Fixes Replication Lag
MySQL Change Stream Applier (CSA): How MySQL 26.7 Fixes Replication Lag

Replication lag remains one of the most persistent bottlenecks in high-throughput MySQL environments. As write workloads surge on a primary server, the replica must continuously read, schedule, apply, and commit those transactions. When a replica cannot process incoming transactions as fast as the source generates them, replication lag accumulates, leading to stale reads, delayed analytical reporting, and compromised readiness during automated failover scenarios.

MySQL 26.7 introduces the Change Stream Applier (CSA), a next-generation implementation of the replica SQL applier designed to fundamentally change how worker threads process binary log events. Enabled via APPLIER_VERSION = 2, CSA shifts replication scheduling from traditional thread-per-transaction lock-in to an asynchronous, worker-pool architecture.

Below, we examine the mechanics of CSA, walk through a reproduction benchmark against the legacy Multi-Threaded Applier (MTA), and review the configuration parameters required to run it in production.

What Is the Change Stream Applier (CSA)?

In traditional Multi-Threaded Appliers (APPLIER_VERSION = 1), a single worker thread remains bound to a transaction throughout its entire lifecycle:

Read⟶Apply⟶Wait⟶Commit

Workers often sit idle while waiting for preceding transactions to commit, holding thread resources captive.

CSA fundamentally re-architects this model using three core concepts:

  1. Channel-Specific Worker Pools: Instead of rigid worker-to-transaction mappings, worker sessions are managed in a dynamic pool per replication channel.
  2. Dependency-Driven Parallelism: Uses last_committed and sequence_number GTID metadata to schedule non-conflicting transactions concurrently.
  3. Phase Separation (APPLY vs. COMMIT): When commit-order preservation is enabled, CSA splits the transaction lifecycle into separate phases. A worker completing the APPLY phase can detach and return to the pool while the transaction waits for its turn to COMMIT.
Traditional MTA Worker (APPLIER_VERSION = 1):
[ Worker Thread 1 ] ----> [ APPLY Phase ] ----> [ WAITING FOR COMMIT ORDER (Thread blocked, idle)] ----> [ COMMIT Phase ]
                                               

CSA Worker Pool (APPLIER_VERSION = 2):
[ Worker Thread 1 ] ----> [ APPLY Phase ] ----> Detaches & returns to pool!(Available for next transaction)

[ Transaction State ] ------------------------> [ Waiting in queue ] -------------> [ Committed by pool ]

The 4-Layer CSA Pipeline

Unlike MTA, which ties a single SQL coordinator thread directly to a rigid set of execution threads, CSA is built as a decoupled 4-layer engine:

  1. Storage / Transport Layer: Reads relay log events closer to their execution time and immediately releases memory upon event consumption.
  2. Dependency Adapter: Extracts last_committed and sequence_number GTID metadata from binlogs using the same logical clock mechanism as MTA, but schedules work asynchronously.
  3. Channel-Specific Scheduler: Schedules tasks to worker sessions independently per channel based on dynamic dependency checks.
  4. Worker Session Pool: A pool of 1 to 1024 worker threads that claim tasks dynamically.

CSA Decoupled Execution Pipeline

Dynamic event stream and the non-blocking worker recycling loop

LAYER 1 • TRANSPORT
Relay Log Stream
• APPLIER_EVENT_MEMORY_LIMIT
• On-demand chunk streaming
• Zero pre-allocation lock
LAYER 2 • PARSER
Dependency Adapter
• Evaluates last_committed
• Resolves sequence_number
• Builds conflict DAG in RAM
LAYER 3 • ROUTING
Channel Scheduler
• Per-channel queues
• Bypasses slow primary locks
• Dispatches to pool instantly

Layer 4: Worker Session Detachment & Commit Ring

Continuous Cycle
WORKER
RECYCLER
A
Worker Picks Task from Pool Thread claims runnable event batch from channel queue.
B
Executes APPLY Phase Mutates rows into storage engine buffers concurrently.
C
Session Detaches & Thread Recycled Session detached from worker thread — Worker returns to pool immediately for the next task.
D
Asynchronous Commit Preservation Detached transaction commits in order without blocking any physical worker threads.

Configuration & Prerequisites

To run CSA, the replication topology must satisfy row-based logging and GTID consistency constraints:

my.cnf Settings

gtid_mode = ON
enforce_gtid_consistency = ON
replica_preserve_commit_order = ON

Channel Prerequisites

Before configuring CSA, the target channel must meet these requirements:

  • SOURCE_AUTO_POSITION = 1
  • GTID_ONLY = 1
  • REQUIRE_ROW_FORMAT = 1
  • SOURCE_DELAY = 0

Core CSA Options

MySQL 26.7 CSA Channel Configuration Parameters

Dynamic Configuration
APPLIER_VERSION = 2
Activates the Change Stream Applier (CSA) engine on the target channel, migrating the replication scheduling logic from thread-per-transaction lock-in to dynamic session detachment.
APPLIER_WORKER_COUNT
Defines the pool of asynchronous worker threads (range: 1 to 1024) dedicated exclusively to servicing tasks within the channel's task queue.
APPLIER_EVENT_MEMORY_LIMIT
Allocates bounded per-channel memory buffers for staging binlog events. Effective limit evaluates as max(replica_max_allowed_packet, APPLIER_EVENT_MEMORY_LIMIT) to prevent replica Out-of-Memory (OOM) scenarios.

Enabling CSA on a Channel

STOP REPLICA FOR CHANNEL 'channel_name';

CHANGE REPLICATION SOURCE TO 
    APPLIER_VERSION = 2, 
    APPLIER_WORKER_COUNT = 6 
FOR CHANNEL 'channel_name';

START REPLICA FOR CHANNEL 'channel_name';

Benchmark Setup: MTA vs. CSA Under Contention

To observe how both appliers handle write contention and serialization blocks, consider a simple table with four concurrent transactions running on the primary database:

CREATE DATABASE IF NOT EXISTS csa_test;
USE csa_test;

CREATE TABLE test_orders (
    id INT PRIMARY KEY,
    status VARCHAR(50),
    amount INT
);


root@warehouse-b2b-db-playground-none-9216947:/tmp# cat t1.sql 
USE csa_test;
START TRANSACTION;
SELECT SLEEP(5);
UPDATE test_orders SET status = 'T1' WHERE id = 1;
COMMIT;
root@warehouse-b2b-db-playground-none-9216947:/tmp# cat t2.sql 
USE csa_test;
START TRANSACTION;
SELECT SLEEP(10);
UPDATE test_orders SET status = 'T2' WHERE id = 2;
COMMIT;
root@warehouse-b2b-db-playground-none-9216947:/tmp# cat t3.sql 
USE csa_test;
START TRANSACTION;
SELECT SLEEP(5);
UPDATE test_orders SET status = 'T3' WHERE id = 1;
COMMIT;
root@warehouse-b2b-db-playground-none-9216947:/tmp# cat t4.sql 
USE csa_test;
START TRANSACTION;
SELECT SLEEP(5);
UPDATE test_orders SET status = 'T4' WHERE id = 3;
COMMIT;
  • T1 ( id = 1): Heavy update batch modifying ~500,010 rows (SET status = 'T1').
  • T2 ( id = 2): Light independent update modifying 5 rows (SET status = 'T2').
  • T3 (id = 1): Heavy dependent update modifying ~500,010 rows (SET status = 'T3').
  • T4 ( id = 3): Light independent update modifying 5 rows (SET status = 'T4').
Dependency Graph:
  T1 (id = 1)
      |
      v
  T3 (id = 1) [Dependent on T1]

  T2 (id = 2) [Independent]
  T4 (id = 3) [Independent]

Benchmark Log Analysis

Part 1: Legacy Multi-Threaded Applier (APPLIER_VERSION = 1)

Examining the replica under MTA reveals classic thread starvation and coordinator serialization.

1. Worker Starvation

Even though 6 worker threads are allocated, inspecting the worker status table shows every thread blocked:

+-----------+-----------+---------------+------+---------------------------------------+
| WORKER_ID | THREAD_ID | SERVICE_STATE | GTID | Worker_State                          |
+-----------+-----------+---------------+------+---------------------------------------+
|         1 |        58 | ON            |      | Waiting for an event from Coordinator |
|         2 |        59 | ON            |      | Waiting for an event from Coordinator |
|         3 |        60 | ON            |      | Waiting for an event from Coordinator |
|         4 |        61 | ON            |      | Waiting for an event from Coordinator |
|         5 |        62 | ON            |      | Waiting for an event from Coordinator |
|         6 |        63 | ON            |      | Waiting for an event from Coordinator |
+-----------+-----------+---------------+------+---------------------------------------+

Because the centralized coordinator cannot safely dispatch dependent transactions out of order, it stops assigning new tasks altogether. Independent rows sit in an unapplied state:

+------+--------+
| id   | status |
+------+--------+
|    1 | New    |
|    2 | New    |
|    3 | New    |
+------+--------+

2. Processlist Blocking

Under sustained execution, SHOW PROCESSLIST highlights the thread-lock issue:

|  9 | system user | csa_test | Query   | 14 | Applying batch of row changes (update)      | NULL |
| 12 | system user | NULL     | Query   |  4 | Waiting for preceding transaction to commit | NULL |
| 13 | system user | NULL     | Connect | 72 | Waiting for an event from Coordinator       | NULL |
  • Worker 1 (Thread 9): Applying batch of row changes (update) — Actively processing row updates.
  • Worker 2 (Thread 12): Waiting for preceding transaction to commit — This thread is associated with a transaction that cannot complete its commit order. Because MTA ties physical threads directly to transactions, Thread 12 remains locked in this wait state instead of taking another ready task off the queue.

3. Single-Thread Pinning

Looking at transaction history metrics confirms that tasks were serialized onto a single thread:

Txn  | GTID                                        | WORKER_ID (THREAD_ID) | Duration
+----+---------------------------------------------+-----------------------+----------+
| T1 | 00000000-0000-0000-0000-000000000000:778804 | Worker 1 (44)         | 0.0000s  |
| T2 | 3f359584-9df0-11f1-9e86-02010a323a76:778806 | Worker 1 (44)         | 0.0029s  |
| T3 | 3f359584-9df0-11f1-9e86-02010a323a76:778807 | Worker 1 (44)         | 5.5076s  |
| T4 | 3f359584-9df0-11f1-9e86-02010a323a76:778808 | Worker 1 (44)         | 4.4446s  |
+----+---------------------------------------------+-----------------------+----------+
+---------------------------------------------+-----------+----------+-----------+---------+--------------+-----------+
| GTID                                        | THREAD_ID | EVENT_ID | start_sec | end_sec | duration_sec | STATE     |
+---------------------------------------------+-----------+----------+-----------+---------+--------------+-----------+
| 3f359584-9df0-11f1-9e86-02010a323a76:778808 |        44 |       15 |   49.9310 | 54.3756 |       4.4446 | COMMITTED |
| 3f359584-9df0-11f1-9e86-02010a323a76:778807 |        44 |       13 |   44.4231 | 49.9307 |       5.5076 | COMMITTED |
| 3f359584-9df0-11f1-9e86-02010a323a76:778806 |        44 |       11 |   37.4776 | 37.4805 |       0.0029 | COMMITTED |
| 00000000-0000-0000-0000-000000000000:778804 |        44 |        9 |    0.9970 |  0.9971 |       0.0000 | COMMITTED |
| 00000000-0000-0000-0000-000000000000:778802 |        44 |        7 |    0.9627 |  0.9969 |       0.0342 | COMMITTED |
+---------------------------------------------+-----------+----------+-----------+---------+--------------+-----------+

Key MTA Failure: All primary tasks (T1 through T4) were forced onto Worker 1 (Thread 44) serially. Transaction :778807 ran for 5.5076 seconds, and only after it hit its COMMITTED boundary tick did the next transaction (:778808) begin execution on the exact same thread for another 4.4446 seconds.

Part 2: Change Stream Applier Behavior (APPLIER_VERSION = 2)

Running the exact same four-transaction workload under CSA produces an entirely different execution pattern.

1. Worker Pool Status

+-----------+-----------+---------------+---------------------------------------------+----------------------------------------+
| WORKER_ID | THREAD_ID | SERVICE_STATE | GTID                                        | Worker_State                           |
+-----------+-----------+---------------+---------------------------------------------+----------------------------------------+
|         1 |        42 | ON            | 3f359584-9df0-11f1-9e86-02010a323a76:778815 | Applying batch of row changes (update) |
|         2 |        43 | ON            |                                             | Thread pool worker: wait for task.     |
|         3 |        44 | ON            |                                             | Thread pool worker: wait for task.     |
|         4 |        45 | ON            |                                             | Thread pool worker: wait for task.     |
|         5 |        46 | ON            |                                             | Thread pool worker: wait for task.     |
|         6 |        47 | ON            | 3f359584-9df0-11f1-9e86-02010a323a76:778816 | Thread pool worker: wait for task.     |
+-----------+-----------+---------------+---------------------------------------------+----------------------------------------+
  • CSA Worker 1 (Thread 42): Actively applying a transaction (performing row updates).
  • Dynamic Pool Worker Availability: Unassigned workers remain in Thread pool worker: wait for task., managed dynamically to pull ready work off the channel queue.

2. The Defining State: Session Detachment

During execution, SHOW PROCESSLIST records:

73 system user |             | Query | 11 | Session detached from worker thread.
74 system user | csa_test    | Query | 11 | Applying batch of row changes (update)
75 system user |             | Query |  6 | Waiting for preceding transaction to commit

Key CSA Innovation: "Session detached from worker thread."

This state confirms that the session is no longer occupying the physical worker thread for that phase of the transaction's lifecycle. The session completed its APPLY work and detached, allowing the physical worker thread to return to the pool immediately to process other unblocked transactions.

3. Concurrent Execution Traces

Checking the trace logs shows work balanced across multiple worker threads simultaneously:

+---------------------------------------------+-----------+----------+-----------+---------+--------------+-----------+
| GTID                                        | THREAD_ID | EVENT_ID | start_sec | end_sec | duration_sec | STATE     |
+---------------------------------------------+-----------+----------+-----------+---------+--------------+-----------+
| 3f359584-9df0-11f1-9e86-02010a323a76:778815 |        42 |        2 |   55.7403 | 61.1282 |       5.3879 | COMMITTED |
| 3f359584-9df0-11f1-9e86-02010a323a76:778813 |        44 |        2 |   43.2463 | 43.2498 |       0.0035 | COMMITTED |
| 3f359584-9df0-11f1-9e86-02010a323a76:778814 |        46 |        2 |   50.0296 | 55.7400 |       5.7104 | COMMITTED |
| 3f359584-9df0-11f1-9e86-02010a323a76:778816 |        47 |        2 |   55.2758 | 61.1282 |       5.8524 | COMMITTED |
+---------------------------------------------+-----------+----------+-----------+---------+--------------+-----------+

Key CSA Advantage: Work execution was balanced concurrently across Threads 42, 44, 46, and 47. Thread 46 executed from 50.0296 to 55.7400, while Thread 47 ran concurrently right in the middle of that window (55.2758 to 61.1282).

Visualizing the Difference

MTA vs CSA Architectural Execution Flow

1. Traditional MTA (APPLIER_VERSION = 1) — Serial Thread Lock-In
Relay Log Queue:
T1 (id=1: Heavy)
T2 (id=2: Independent)
T3 (id=1: Dependent)
T4 (id=3: Independent)
Worker 1 (Thread 44) Applying batch of row changes (T1) serially
Worker 2 (Thread 12) — FROZEN Waiting for preceding transaction to commit
Workers 3-6 (Threads 58-63) STARVED: Waiting for event from Coordinator
Replica Table Read Visibility Over Time (MTA):
Time 0.0s – 5.5s
id=1: 'New' | id=2: 'New' | id=3: 'New'
READS STALE FOR >5 SECONDS
Time 5.5s (T1 Commits)
id=1: 'T1' | id=2: 'T2' | id=3: 'New'
id=2 Updated Late on W1
Time 10.0s (Final)
id=1: 'T3' | id=2: 'T2' | id=3: 'T4'
Sequential Catch-Up Done
2. Change Stream Applier (APPLIER_VERSION = 2) — Phase Separation & Parallel Execution
Change Stream Buffer:
T1 (id=1: Heavy)
T2 (id=2: Independent)
T3 (id=1: Dependent)
T4 (id=3: Independent)
Worker 3 (Thread 44) Session Detached in 0.003s!
Worker 5 (Thread 46) Applies & Commits T2 in 0.3s!
Worker 1 (Thread 42) Applies T3 concurrently
Worker 6 (Thread 47) Applies & Commits T4 concurrently
Replica Table Read Visibility Progression (CSA):
Poll 1 (t = 0.3s)
id=1: 'New' | id=2: 'T2' | id=3: 'New'
FRESH INSTANTLY!
Poll 2 (t = 0.7s)
id=1: 'T1' (Interm.) | id=2: 'T2' | id=3: 'New'
T2 Fresh & Available
Poll 4 (Final)
id=1: 'T3' | id=2: 'T2' | id=3: 'T4'
All Updated Concurrently

Under MTA, a single thread executes all dependent and independent tasks sequentially, stalling replica read consistency for several seconds. Under CSA, worker threads apply independent transactions immediately, detaching to let secondary tasks catch up within milliseconds.

Architectural Comparison Matrix

Architectural Metric Comparison

System Metric Change Stream Applier (CSA) Multi-Threaded Applier (MTA)
Active OS Threads 4 Concurrent Threads
Work balanced across Threads 42, 44, 46, and 47
Single Thread Pinning
Forced onto Thread 44; others wait
Worker Allocation Workers 1, 3, 5, and 6 active together Worker 1 executes all transactions; remaining workers sit starved
Execution Mechanics Asynchronous & Overlapping:
Subsequent tasks bypass slow writes
Serialized:
Hard blocked waiting for earlier transactions to reach commit order
Thread State During Commit Wait Session detached from worker thread.
(Thread immediately returned to pool)
Waiting for preceding transaction to commit
(OS thread blocked and unusable)
Idle Worker State Thread pool worker: wait for task. Waiting for an event from Coordinator

Beyond thread pooling, CSA introduces critical operational upgrades documented in Oracle Worklogs:

Operational Upgrades in CSA

Beyond thread scheduling, CSA introduces three notable stability improvements:

1. Bounded Event Memory Management

Under MTA, high backlog catch-up frequently causes the coordinator to buffer massive numbers of binlog events into worker queues, increasing the risk of replica Out-of-Memory (OOM) crashes.

CSA establishes a strict cap on memory usage:

Effective Limit = max(replica_max_allowed_packet, APPLIER_EVENT_MEMORY_LIMIT)

Events load on demand, are applied, and release their memory buffers immediately. This keeps memory predictable even when catching up across gigabytes of relay logs.

2. Subscriber-Driven Relay Log Purging

In MTA, relay log files are cleared strictly sequentially by the coordinator thread. CSA implements a subscriber pattern: once all events within a relay log segment have been applied by the worker pool, that relay log file is immediately marked for purge, keeping replica disk consumption lower.

3. Explicit Stop Orchestration

When running STOP REPLICA on a heavily lagged MTA replica, threads often hang while waiting for earlier transactions to reach commit points. CSA decouples session execution from worker threads, so stop signals are handled immediately. Workers clean up active buffers and stop in milliseconds rather than minutes.

Monitoring CSA with Performance Schema

MySQL provides native Performance Schema tables to inspect CSA configuration and worker thread execution:

1. Verify Applier Configuration

SELECT
    CHANNEL_NAME,
    APPLIER_VERSION,
    APPLIER_WORKER_COUNT,
    APPLIER_EVENT_MEMORY_LIMIT
FROM performance_schema.replication_applier_configuration;

2. Monitor Live Worker Sessions

SELECT
    CHANNEL_NAME,
    WORKER_ID,
    THREAD_ID,
    SERVICE_STATE,
    APPLYING_TRANSACTION,
    LAST_APPLIED_TRANSACTION,
    LAST_ERROR_NUMBER,
    LAST_ERROR_MESSAGE
FROM performance_schema.replication_applier_status_by_worker;

Summary

The Change Stream Applier in MySQL 26.7 addresses the structural bottlenecks that have limited the Multi-Threaded Applier since MySQL 5.7 and 8.0. By separating transaction application from commit-order preservation and switching to an asynchronous worker-pool model, CSA keeps worker threads utilized, prevents thread starvation, and shortens catch-up time on write-heavy replicas.

If your workloads frequently suffer from replication lag during bulk updates or back-office batch jobs, testing APPLIER_VERSION = 2 on your MySQL 26.7 staging clusters is a worthwhile operational evaluation.

Struggling with replication lag, thread contention, or high-availability failover issues in your MySQL environments? The database engineers at Mydbops help enterprises design high-throughput replication architectures, audit MySQL performance bottlenecks, and execute seamless database upgrades.

Talk to a MySQL replication specialist today to assess your database architecture.

No items found.

About the Author

Founding Partner and Database Architect at Mydbops, with 14+ years running MySQL, MariaDB and MongoDB systems. Before co-founding Mydbops in 2016, he worked as a remote DBA at Percona. He holds AWS Solutions Architect Professional and Oracle MySQL 8.0 certifications.

Subscribe Now!

Subscribe here to get exclusive updates on upcoming webinars, meetups, and to receive instant updates on new database technologies.

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.