Details
-
Bug
-
Status: Open (View Workflow)
-
Major
-
Resolution: Unresolved
-
None
-
None
-
None
-
Unexpected results
-
Description
Summary
In a MariaDB primary -> replica1 -> replica2 chain, an intermediate replica
configured with slave_run_triggers_for_rbr=YES writes rows from its
replica-local trigger into its binary log when log-slave-updates is enabled.
Those local trigger rows are then replayed by replica2, even though replica1 is
configured with YES rather than LOGGING. The same schedule with
slave_run_triggers_for_rbr=NO produces no trigger rows, and the
LOGGING configuration produces the expected downstream rows.
Environment
- Product: MariaDB Server
- Observed versions:
- MariaDB 12.2.2, three-node chain
- MariaDB 10.6, three-node chain
- Topology: primary -> replica1 -> replica2
- Replication: asynchronous file/position replication
- Binary logging: binlog_format=ROW
- Intermediate replica: log-slave-updates=ON
- Storage engine: InnoDB
Steps to reproduce
Set up a three-node MariaDB chain:
primary -> replica1 -> replica2
|
Use row-based binary logging on all three servers. Enable
log-slave-updates on replica1 and replica2.
On the primary, create only the base table:
CREATE TABLE base_t( |
id INT PRIMARY KEY, |
v INT NOT NULL, |
g INT GENERATED ALWAYS AS (v * 2) STORED, |
note VARCHAR(32) NOT NULL |
) ENGINE=InnoDB;
|
On replica1 and replica2, create the same base table plus a local audit table
and a local AFTER INSERT trigger. This DDL is local to each replica; it is not
created by primary replication.
CREATE TABLE base_t( |
id INT PRIMARY KEY, |
v INT NOT NULL, |
g INT GENERATED ALWAYS AS (v * 2) STORED, |
note VARCHAR(32) NOT NULL |
) ENGINE=InnoDB;
|
|
|
CREATE TABLE side_audit( |
seq INT AUTO_INCREMENT PRIMARY KEY, |
origin VARCHAR(128) NOT NULL, |
row_id INT NOT NULL, |
seen_v INT NOT NULL, |
seen_g INT NOT NULL, |
note VARCHAR(32) NOT NULL, |
UNIQUE KEY uq_origin_row(origin,row_id) |
) ENGINE=InnoDB;
|
|
|
CREATE TRIGGER base_t_ai AFTER INSERT ON base_t |
FOR EACH ROW |
INSERT INTO side_audit(origin,row_id,seen_v,seen_g,note) |
VALUES(@@hostname,NEW.id,NEW.v,NEW.g,NEW.note); |
Configure replica1 to run local triggers for row events, but not to use
LOGGING:
SET GLOBAL slave_run_triggers_for_rbr='YES'; |
Configure replica2 not to run local triggers for row events:
SET GLOBAL slave_run_triggers_for_rbr='NO'; |
Then run this INSERT on the primary and wait until both replication links have
reached the corresponding binary log positions:
INSERT INTO base_t(id,v,note) |
VALUES (1,10,'first'),(2,20,'second'); |
Inspect the base and audit tables on all three servers:
SELECT id,v,g,note FROM base_t ORDER BY id; |
SELECT origin,row_id,seen_v,seen_g,note FROM side_audit ORDER BY seq; |
SHOW SLAVE STATUS\G
|
Expected result
With slave_run_triggers_for_rbr=YES on replica1, replica1 should run its
local trigger for the two row events and store two local audit rows. Since the
mode is YES rather than LOGGING, those local trigger rows should not be
written into replica1's binary log for downstream replay.
Replica2 is configured with slave_run_triggers_for_rbr=NO, so replica2
should not create its own local audit rows from the incoming base_t row
events. The expected downstream audit table is therefore empty.
Actual result
The base table converges on all three servers:
primary.base_t:
|
(1, 10, 20, 'first')
|
(2, 20, 40, 'second')
|
|
|
replica1.base_t:
|
(1, 10, 20, 'first')
|
(2, 20, 40, 'second')
|
|
|
replica2.base_t:
|
(1, 10, 20, 'first')
|
(2, 20, 40, 'second')
|
Replica1 runs its local trigger and stores two audit rows, as expected:
replica1.side_audit:
|
(replica1_hostname, 1, 10, 20, 'first')
|
(replica1_hostname, 2, 20, 40, 'second')
|
Replica2 also receives those two audit rows:
replica2.side_audit:
|
(replica1_hostname, 1, 10, 20, 'first')
|
(replica1_hostname, 2, 20, 40, 'second')
|
The origin value is replica1's hostname, not replica2's hostname. This
shows that the rows stored on replica2 came from replica1's binary log rather
than from replica2's own local trigger. Both replica SQL threads remain running
with no SQL error.
Relevant controls
The same chain was run with slave_run_triggers_for_rbr=NO on replica1 and
NO on replica2. In that configuration, neither replica stored audit rows:
replica1.side_audit row count: 0
|
replica2.side_audit row count: 0
|
The same chain was run with slave_run_triggers_for_rbr=LOGGING on replica1
and NO on replica2. In that configuration, replica1 stored two local audit
rows and replica2 received those two rows downstream:
replica1.side_audit row count: 2
|
replica2.side_audit row count: 2
|
The YES result therefore matches the LOGGING result for the downstream
audit rows, not the NO result.
Repetition and scope
The schedule reproduced on both tested MariaDB versions:
MariaDB 12.2.2, replica1=YES, replica2=NO: 3/3 executions
|
MariaDB 10.6, replica1=YES, replica2=NO: 3/3 executions
|
|
|
MariaDB 12.2.2, replica1=NO, replica2=NO: 3/3 controls, no audit rows
|
MariaDB 10.6, replica1=NO, replica2=NO: 3/3 controls, no audit rows
|
|
|
MariaDB 12.2.2, replica1=LOGGING, replica2=NO: 3/3 controls, downstream audit rows
|
MariaDB 10.6, replica1=LOGGING, replica2=NO: 3/3 controls, downstream audit rows
|
Every execution reached the expected primary log position on replica1 and the
expected replica1 log position on replica2. Both replica SQL threads remained
healthy, and the base_t rows matched on all three servers.
Nearby issues checked
This is not a case where replica2 creates its own local trigger rows. Replica2
is configured with slave_run_triggers_for_rbr=NO, and the origin column
records replica1's hostname in the rows observed on replica2.
This is also different from the documented LOGGING mode: the intermediate
replica is configured with YES, but the downstream result matches the
LOGGING control.