Uploaded image for project: 'MariaDB Server'
  1. MariaDB Server
  2. MDEV-40579

Master Can Purge a Binary Log Requested by Connecting Slave Despite slave_connections_needed_for_purge=1

    XMLWordPrintable

Details

    • Can result in data loss

    Description

      With slave_connections_needed_for_purge=1 (the default), the master
      retains binary logs until at least one connected slave has processed
      them. Despite that, a slave that connects and requests binary logs
      which still exist on the master can have exactly those logs purged
      during the connect. The slave IO thread stops with a fatal error:

      Got fatal error 1236 from master when reading data from binary log: 'Could not open log file'
      

      In GTID mode the purged file held transactions the slave had not yet
      received, so the slave cannot resume from its gtid_slave_pos and must
      be reprovisioned.

      As a disclaimer: with slave_connections_needed_for_purge=0 the server
      is explicitly configured to purge binary logs without regard to
      slaves, so a slave losing events to purge is expected behavior there.
      This report is about slave_connections_needed_for_purge=1, where the
      logs are supposed to be retained for the slave.

      The scenario a user would see: a slave falls behind or disconnects
      (a network hiccup, a restart, a failover), binary logs accumulate on
      the master, and the slave reconnects at the same moment a purge runs.
      The purge can be a manual PURGE BINARY LOGS statement, or automatic
      purge (binlog_expire_logs_seconds, max_binlog_total_size) when another
      connected slave satisfies the required count of 1. The reconnecting
      slave requested files the master still had, yet the connection fails
      with error 1236 as if the logs were long gone.

      The cause is the order of operations while a slave connects. The
      master normally protects a file a slave is reading: each dump thread
      registers the file it is currently sending, and every purge path
      checks the registrations (MYSQL_BIN_LOG::can_purge_log() calling
      log_in_use()) and refuses to remove a file any slave is on. During a
      connect, however, init_binlog_sender() (sql/sql_repl.cc) does this:

      1. Resolve the slave's request to a file name. A GTID connect resolves
      through gtid_find_binlog_pos(), which reads through binlog files to
      find the right starting point, so this step spans file IO.

      2. Register the resolved file as in use (THD::set_current_linfo()).

      3. Open the file and start sending.

      Between steps 1 and 2 nothing marks the resolved file as in use, so a
      purge running at that moment sees no reader and deletes it. At step 3
      the dump thread fails to open the file and sends the slave error 1236.

      slave_connections_needed_for_purge=1 does not close this window. PURGE
      BINARY LOGS is exempt from the count entirely and honors only the in
      use check, which cannot see the connecting slave yet. Automatic purge
      does refuse to run while the connecting slave is the only binlog
      reader, but only because an unregistered slave is not counted; the
      file the slave was promised is not itself protected, and any other
      connected slave satisfies the count and lets automatic purge proceed.

      How to reproduce:
      Hitting the window in the wild is timing dependent, so the patch below
      adds a DEBUG_SYNC pause inside it (debug builds only, compiled under
      ENABLED_DEBUG_SYNC). The test sets
      slave_connections_needed_for_purge=1 and uses the pause to run PURGE
      BINARY LOGS while a connecting dump thread is parked between resolving
      and registering its file.

      diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc
      index 546988000d1..dbf17239e0b 100644
      --- a/sql/sql_repl.cc
      +++ b/sql/sql_repl.cc
      @@ -2458,6 +2458,24 @@ static int init_binlog_sender(binlog_send_info *info,
       
         // set current pos too
         linfo->pos= *pos;
      +
      +#ifdef ENABLED_DEBUG_SYNC
      +  DBUG_EXECUTE_IF("dump_thread_wait_before_register_linfo",
      +                  {
      +                    const char act[]=
      +                      "now SIGNAL dump_thread_before_register_linfo "
      +                      "WAIT_FOR dump_thread_continue_register_linfo";
      +                    DBUG_ASSERT(!debug_sync_set_action(thd,
      +                                                       STRING_WITH_LEN(act)));
      +                  };);
      +#endif
      +
         // note: publish that we use file, before we open it
         thd->set_current_linfo(linfo);
      
      

      The test:

      --source include/have_innodb.inc
      --source include/have_debug.inc
      --source include/have_debug_sync.inc
      # Test is binlog format independent
      --source include/have_binlog_format_row.inc
      --source include/master-slave.inc
       
      --connection slave
      --source include/stop_slave.inc
      --echo # GTID mode is the mode under test: the slave requests its
      --echo # position as a GTID state and the master resolves it to a binary
      --echo # log file
      CHANGE MASTER TO master_use_gtid=slave_pos;
       
      --connection master
      set @old_dbug= @@global.debug_dbug;
      set @old_slave_connections_needed_for_purge=
          @@global.slave_connections_needed_for_purge;
      --echo # Binary logs must be retained until a connected slave has
      --echo # processed them
      set @@global.slave_connections_needed_for_purge= 1;
      --echo # Wait for the old dump thread to go away, so it cannot mark a
      --echo # file as in use
      --let $wait_condition= SELECT count(*)= 0 FROM information_schema.processlist WHERE command LIKE 'Binlog Dump%'
      --source include/wait_condition.inc
       
      --echo # Write the transactions the slave will request into
      --echo # master-bin.000001, and rotate so it is not the active file
      create table t1 (a int) engine=innodb;
      insert into t1 values (1);
      flush binary logs;
      --echo # Ensure the binlog checkpoint has moved to the active file, so a
      --echo # purge refusal below can only mean a file is in use, never that
      --echo # it is needed for crash recovery (XID)
      --source include/wait_for_binlog_checkpoint.inc
      set @@global.debug_dbug= "+d,dump_thread_wait_before_register_linfo";
       
      --connection slave
      --echo # Start the slave; on the master, its dump thread pauses after
      --echo # resolving the requested file and before registering it as in
      --echo # use
      START SLAVE;
       
      --connection master
      set debug_sync= "now WAIT_FOR dump_thread_before_register_linfo";
      --echo # The requested master-bin.000001 must survive this purge
      --let $purge_to_binlog= query_get_value(SHOW MASTER STATUS, File, 1)
      --eval PURGE BINARY LOGS TO '$purge_to_binlog'
      set @@global.debug_dbug= @old_dbug;
      --echo # Release the paused dump thread
      set debug_sync= "now SIGNAL dump_thread_continue_register_linfo";
      --source include/save_master_gtid.inc
       
      --connection slave
      --echo # A connected slave must receive every transaction from its
      --echo # requested GTID position onward. If the following wait fails
      --echo # with Last_IO_Errno = 1236, the master purged the binary log
      --echo # file it had already resolved for this slave's dump request,
      --echo # and the transactions in it are lost
      --let $slave_param= Gtid_IO_Pos
      --let $slave_param_value= $master_pos
      --let $slave_error_param= Last_IO_Errno
      --source include/wait_for_slave_param.inc
      --source include/sync_with_master_gtid.inc
      select * from t1;
      

      Attachments

        Issue Links

          Activity

            People

              Unassigned Unassigned
              bnestere Brandon Nesterenko
              Votes:
              0 Vote for this issue
              Watchers:
              3 Start watching this issue

              Dates

                Created:
                Updated:

                Time Tracking

                  Estimated:
                  Original Estimate - Not Specified
                  Not Specified
                  Remaining:
                  Remaining Estimate - 0d
                  0d
                  Logged:
                  Time Spent - 1h
                  1h

                  Git Integration

                    Error rendering 'com.xiplink.jira.git.jira_git_plugin:git-issue-webpanel'. Please contact your Jira administrators.