MDEV-14992 BACKUP SERVER - #4817
Conversation
|
|
2723322 to
1703796
Compare
9a529de to
857edeb
Compare
8149b3d to
c08d121
Compare
e5a9d2d to
b625be2
Compare
Thirunarayanan
left a comment
There was a problem hiding this comment.
First round of review.
| auto p= buf_flush_space(space_id); | ||
| space= p.first; | ||
| last_space_id= space_id; | ||
| auto p= buf_flush_space(space_id, &backup_page_end); |
There was a problem hiding this comment.
Something like this:
- page cleaner acquires space S and caches backup_page_end = 0 (no
backup active yet) - backup: backup_start(1064) → window [1000,1064) published
- backup: batch_wait scans 1063..1001; page P=1020 is CLEAN and skipped
- backup: backup::copy(node->handle, dst, 100016k, 106416k) : 1 MB read in flight
- user thread: mtr_t::commit() modifies P, inserted into
flush_list, oldest_modification = LSN - page cleaner reaches P: cached backup_page_end is 0
-page < backup_page_endis1020 < 0= false- bpage->flush(space), real write fix, 16 KB pwrite at offset 1020*16k.
Likebuf_flush_list_space(), we shouldn't rely on cached backup_page_end.
- bpage->flush(space), real write fix, 16 KB pwrite at offset 1020*16k.
we should change in buf_do_flush_list_batch() and same for the callers of buf_flush_try_neighbors()
There was a problem hiding this comment.
First, let me correct the page number range in your scenario to 960‥1023, because we are dealing with integer multiples of fil_space_t::BACKUP_BATCH_SIZE (64 pages).
I’d like to observe that mtr_t::commit() must hold buf_pool.flush_list_mutex in order to be able to add a first-time-modified page to buf_pool.flush_list.
This race condition scenario will be avoided by reading the fil_space_t::backup_end after acquiring buf_pool.flush_list_mutex. I think that this is all that is needed. The store-release fil_space_t::backup_start() and the load-acquire of backup_page_end (moved to be within the critical section of buf_pool.flush_list_mutex) should guarantee the correct operation. The store should be sufficient outside any mutex.
The logic is complicated, because innodb_backup_batch_wait() must avoid dirtying a large number of cache lines in order to avoid a significant performance impact when most of the buffer pool is clean.
There was a problem hiding this comment.
As far as I can tell, the existing logic in buf_flush_list_space() and buf_do_flush_list_batch() should be safe with respect to the presented scenario: We acquired a U-latch on a dirty block while holding buf_pool.flush_list_mutex. The page is already dirty, and we only consume blocks via the buf_pool.flush_list. It should not matter at which exact point we read the backup_page_end; the main thing is that we were holding buf_pool.flush_list_mutex when acquiring the U-latch on the dirty page and before reading the backup_page_end.
It turns out that in buf_flush_LRU_list_batch() we are not holding buf_pool.flush_list_mutex at all. We are traversing buf_pool.LRU under the protection of buf_pool.mutex instead.
At some point I had a relaxed store of fil_space_t::backup_end inside a critical section of buf_pool.mutex. That (as well as a relaxed load of that field within buf_pool.mutex) should prevent the problem. But, it would also introduce significant additional contention on an already busy mutex.
The write/backup race scenario with buf_flush_LRU_list_batch() should be rare, because typically any pages that are about to be dirtied would first be moved to the most recently used end of buf_pool.LRU, which the scan is not expected to access.
There was a problem hiding this comment.
I reviewed the case of buf_flush_LRU_list_batch() once more. If the mtr_t::commit() thread had not yet released the page latch, the bpage->lock.u_lock_try(true) in buf_flush_LRU_list_batch() would have failed and we would have moved on to the next page, without initiating any write.
Also here, we load-acquire fil_space_t::backup_end while holding a page U-latch that blocks any writes to the page frame or the file system. In backup_batch_start() we first store-release that field before checking if any blocks are dirty and have to be fake-write-fixed.
Let me rephrase your scenario in more exact terms. It applies to each of the functions in buf0flu.cc:
buf0flu.ccholdsbpage->lock.u_lock(), does load-acquire offil_space_t::backup_end(as 0 or as a smaller value that belonged to an earlier backup batch, not matchingbpage->id())backup_batch_start()does a store-release offil_space_t::backup_endthat covers thebpage->id()innodb_backup_batch_wait()noticesbpage->oldest_modification()and doesbpage->lock.u_lock()to wait for thebuf_page_t::write_complete()buf0flu.ccinvokesbuf_page_t::flush()to submit the writebuf_page_t::write_complete()in another thread will release thebpage->lock.u_unlock()upon write completioninnodb_backup_batch_wait()acquires the page latch and immediately releases it withbpage->lock.u_unlock()without any further action
I do not see a problem here. The page latch wait is doing the right thing. There is no need to move the load-acquire to another critical section. It already is protected by bpage->lock.u_lock(), which matters.
The fake write-fix takes care of a slightly different scenario where buf_page_t::flush() will be invoked in case the load of fil_space_t::backup_end occurred shortly before backup_batch_start() was invoked. In this case, buf_page_t::flush() will yield to the fake write-fix that would be set before backup_batch_start() completes.
Can you please double-check your claim and my reasoning?
Thirunarayanan
left a comment
There was a problem hiding this comment.
I do have only these comments as of now
| mysql_mutex_lock(&fil_system.mutex); | ||
| for (fil_space_t &space : fil_system.space_list) | ||
| if (space.id < SRV_SPACE_ID_UPPER_BOUND && | ||
| !space.is_being_imported() && !space.is_stopping() && |
There was a problem hiding this comment.
what if the imported tablespace started before init() and ends up before NO_DDL? will it be copied or reconstructable ?
There was a problem hiding this comment.
Because ALTER TABLE…IMPORT TABLESPACE is currently bypassing the write-ahead-logging protocol that both BACKUP SERVER and mariadb-backup will depend on, the effects of any such statements executed during the backup could be lost in the backup. For mariadb-backup --backup there already is an existing report MDEV-29208 about this. A possible fix might be to make ALTER TABLE…IMPORT TABLESPACE mutually exclusive with backup locks.
Thirunarayanan
left a comment
There was a problem hiding this comment.
- FLUSH TABLES FOR EXPORT and encryption_rotate_key_age key rotation concurrent with backup server
- Run 2 concurrent backup server statement
- log rotation during backup
- system tablespace larger than dblwr
These test case exist already?
|
@Thirunarayanan, thank you for your review comments. I am linking to additional comments to individual source files so that each open topic can be followed up in separate threads and marked as resolved individually.
This will be followed up here and here.
Please refer to That scenario confirms the mutual exclusion of
"Log rotation" is a term that I associate with If you are referring to diff --git a/mysql-test/suite/backup/backup_innodb.test b/mysql-test/suite/backup/backup_innodb.test
index 87b56fb7614..bdc75c8bacd 100644
--- a/mysql-test/suite/backup/backup_innodb.test
+++ b/mysql-test/suite/backup/backup_innodb.test
@@ -84,7 +84,7 @@ if (!$MARIADB_UPGRADE_EXE) {
--exec cat $MYSQLTEST_VARDIR/my.cnf >> $target_directory/backup.cnf
}
--let $restart_parameters=--defaults-file=$target_directory/backup.cnf --datadir=$target_directory
---source include/restart_mysqld.inc
+--source include/shutdown_mysqld.inc
SELECT * FROM t;
# A nonzero innodb_log_recovery_target makes InnoDB read-only.you will find two log files
|
5d8ce9b to
8b2ec58
Compare
| if (snprintf(cmd, sizeof cmd, "%s %d", command, t) >= int(sizeof cmd)) | ||
| goto oor; | ||
| FILE *f= my_popen(cmd, "w"); |
There was a problem hiding this comment.
@vuvova suggested that the BACKUP SERVER WITH command be prefixed with a directory path that is defaults to opt_basedir. When I asked for a clarification how to handle environments where any customization is kept separately (for example, /usr in Debian is supposed to be read-only), he suggested that there be a parameter that specifies a number of alternatives. That would allow DBAs to place their custom backup scripts anywhere.
There was a problem hiding this comment.
215c4e7 simply prepends the user-specified command with mariabd-backup- and quotes it. Path separators and quotes ('/ or "\/ depending on platform) are disallowed. It is up to the user to ensure that such a script exists in the $PATH of the mariadbd process (or in the case of Microsoft Windows, in datadir).
For some reason, invoking mysql-test/mtr --suite=backup will not add client to the $PATH. This will cause the test backup_stream to fail when invoked in such a way. If no --suite parameter is passed, the mariadb-backup-cat will be found.
For reference, I checked what the wsp::process proc in sst_donor_thread() does. The constructor initializes a popen(3) like argument list for posix_spawnp(3) that starts with sh -c. The safety of this appears to be guaranteed by sst_donate_other() in sql/wsrep_sst.cc, which guarantees that the name of the script starts with wsrep_sst_. The name is passed in via wsrep_sst_donate(), which validates it via wsrep_check_request_str() and wsrep_filename_char(). That is, each character of the name of the script must be one of the characters -, _, or ., or satisfy isalnum(3) in the current locale. There is no check for IFS or PATH and no quoting around the name of the script.
In /bin/sh, commands must be either built-in commands or searched via PATH. So, a script like wsrep_sst_mariabackup can be trivially overloaded by writing a modified copy of the script somewhere and specifying the directory at the start of the PATH in the environment of the mariadbd process.
The macro my_popen expands to either the standard popen or to my_win_popen(). These should prepend the specified command line with /bin/sh -c and cmd.exe /c, respectively. So far, I only ran some interactive tests from the shell on Microsoft Windows [Version 10.0.26100.33296]; we’ll see soon if my interpretation of https://learn.microsoft.com/en-us/cpp/c-language/parsing-c-command-line-arguments and my invocation of my_popen() works as desired.
There was a problem hiding this comment.
@vuvova, this change is making the test backup.backup_stream fail on all autobake targets, because an executable mariadb-backup-cat is missing. How can we install a script in the $PATH of the mariadbd process
for test purposes, without distributing it? To add some extra challenge, mysql-test/mtr --mem may be using a file system mounted with noexec. That is why the test used to invoke the script as follows:
--remove_files_wildcard $MYSQL_TMP_DIR/. stream.bat
--write_file $MYSQL_TMP_DIR/stream.bat
#!/bin/sh
exec cat > $MYSQL_TMP_DIR/$1.tar
EOF
--let $script=$MYSQL_TMP_DIR/stream.bat
if (!$MARIADB_UPGRADE_EXE) {
# Because we may run on Linux /dev/shm which may be mounted as noexec,
# we cannot rely on chmod +x, but must explicitly invoke a shell on the script.
--let $script=/bin/sh $script
}
There was a problem hiding this comment.
if you're taking the approach from wsrep scripts, it must be all solved there, I suppose. Let's just do the same, it's a proven working solution
There was a problem hiding this comment.
In the rebase b387a4a I made mysql-test/suite/backup/suite.pm adjust the $PATH and check for the availability of mariadb-backup-cat. So far, it has gotten past one builder where the script is missing: https://buildbot.mariadb.org/#/builders/889/builds/12954/steps/14/logs/stdio
backup.backup_stream [ skipped ] needs cat,tar,mariadb-backup-cat
The test is running (and passing) on both CMAKE_BUILD_TYPE=Debug and CMAKE_BUILD_TYPE=RelWithDebInfo targets, where the script client/mariadb-backup-cat will be available. On Microsoft Windows, a mariadb-backup-cat.bat will be created in the server @@datadir by the test backup.backup_stream.
| const lsn_t next_lsn{lsn + log_sys.capacity()}; | ||
| if (next_lsn < last_lsn) | ||
| queue.emplace_back(lsn= next_lsn); | ||
| ctx.max_first_lsn= lsn; |
There was a problem hiding this comment.
Currently, for the streaming BACKUP SERVER WITH deferring the copying to this point seems to be an acceptable and competitive choice. (It would also be hard to change that, because our stream format of choice (GNU tar --format=oldgnu) requires the size to be declared upfront.)
However, for the BACKUP SERVER TO … CONCURRENT variant we can and should start copying the log right from the beginning. Otherwise mariadb-backup --backup may finish faster and produce less log, thanks to its dedicated log_copying_thread() that starts eagerly.
There was a problem hiding this comment.
a1c90c4 implements more eager log copying in the HAVE_INNODB_PMEM code path. I must extend it for the pwrite based log writing path before marking this as resolved.
There was a problem hiding this comment.
I filed MDEV-41040 for implementing the BACKUP SERVER TO … CONCURRENT performance improvement separately and reverted the current prototype (a1c90c4 and df3d431).
df3d431 to
b0944c9
Compare
| queue.emplace_back | ||
| (uint64_t{space.id} | | ||
| uint64_t{std::min(space.size, space.free_limit)} << 32); |
There was a problem hiding this comment.
We actually must remember the file name as it was at this point of time. @mleich1 shared an rr replay trace where InnoDB_backup::backup() invokes the following:
fail:
my_error(ER_CANT_CREATE_FILE, MYF(0), node->name, errno);
return -1;The reason is that the backup included two files, space ID 28 and 52, the latter one with a space.create_lsn that is slightly before start, by ALTER TABLE t8 FORCE.
The original space ID 28 was copied as test/t8.ibd. Subsequently, fil_space_t::drop() was invoked on it. After that, tablespace ID 52 was copied with the its current name test/t8.ibd.
I believe that the easiest way to fix this is to add a field fil_node_t::backup_name that will store the current fil_node_t::name at the start of backup. fil_space_t::rename() must preserve the old name (so that backup_name will remain intact) and InnoDB_backup::init() as well as InnoDB_backup::step() must collect the garbage.
| struct Aria_backup | ||
| { | ||
| #ifndef _WIN32 | ||
| /** directory stream */ | ||
| DIR *dir; | ||
| /** the readdir(dir) result for which subdir was opened */ | ||
| const struct dirent *d; | ||
| /** subdirectory stream, or NULL if iterating to next entry in dir */ | ||
| DIR *subdir; | ||
| #else | ||
| /** directory iterator */ | ||
| HANDLE dir; | ||
| /** subdirectory iterator, or INVALID_HANDLE_VALUE */ | ||
| HANDLE subdir; | ||
| #endif |
There was a problem hiding this comment.
@montywi does not want to see any _WIN32 in storage/maria (other than the 38 that exist there before these changes).
I think that his request can be accommodated by moving some definitions and code to sql/sql_backup_interface.h and sql/sql_backup.cc. What can’t be easily accommodated is to avoid any new _WIN32 in the sql subdirectory. There already are well over a hundred references to _WIN32 in sql without any of these changes.
There was a problem hiding this comment.
69dad73 refactors the way how non-ACID files are backed up. The directory scan is now being driven by backup_context in sql/sql_backup.cc. maria_backup_file() gets to decide which files should be backed up. Aria log files are being copied sequentially in aria_backup_step(). This is work in progress.
| else if (error_if_data_home_dir(target, "BACKUP SERVER TO")) | ||
| return true; | ||
| else if (!is_secure_file_path(target)) | ||
| { | ||
| my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--secure-file-priv"); | ||
| return true; | ||
| } |
There was a problem hiding this comment.
@vuvova wrote the following in a private email:
Secure_file_priv is definitely not appropriate. You don't want any user with FILE privilege to be able to read your whole backup.
Therefore, I will remove the is_secure_file_path check and assume that the file system permissions for the service user that runs the mariadbd server process have been configured appropriately. We will only check that the requested BACKUP SERVER TO target is outside the @@datadir, by invoking error_if_data_home_dir().
There was a problem hiding this comment.
b387a4a removed the secure_file_priv related changes.
file_name_t::page0_lsn: Keep track of the last applied recv_sys_t::parse_page0() so that a multi-batch recovery will not reset the file to a smaller size. Reviewed by: Thirunarayanan Balathandayuthapani (cherry picked from commit 8f00e6c)
The following SQL statements will be introduced: BACKUP SERVER TO '/path/to/directory' [ 1 CONCURRENT ]; BACKUP SERVER WITH [ 1 CONCURRENT ] 'command'; In place of the 1, any positive number of threads may be specified. For the first variant, '/path/to' must exist and '/path/to/directory' must not exist; that is where the backup will be written to. For BACKUP SERVER WITH, the string mariadb-backup- will be prepended to the specified command and quoted. Path separators or quotes are not allowed in command. We expect the mariadb-backup-command to be in the search PATH of the mariadbd process. For now, we distribute no such script or program; the user has to write one, something like this: exec zstdmt|ssh backup@remote.example.com "exec cat > $1.tar.zstd" The standard input of that command will be in a format compatible with GNU tar --format=oldgnu (and also BSD tar variants that are also part of Microsoft Windows and Apple macOS). The command is expected to optionally compress and encrypt the stream and redirect it to a file on a local or a remote server. The BACKUP SERVER WITH will append an additional argument, a positive base-ten number in ASCII, starting with 1, to identify the current thread. In this way, there can be multiple concurrent streams. The InnoDB write-ahead log will be streamed near the end, because the stream format requires the file sizes to be declared upfront. TODO: implement the following: In BACKUP SERVER TO ... 2 CONCURRENT or more, one thread will be responsible for copying the InnoDB write-ahead log from the beginning of the backup. Other files will be copied by other threads. Note: In single-threaded BACKUP SERVER TO, the parameter innodb_log_recovery_start that is written out to backup.cnf is STRICTLY NECESSARY TO AVOID CORRUPTION during recovery! By default, InnoDB crash recovery starts from the latest available log checkpoint. However, for restoring a backup, recovery must start from the checkpoint that was the latest when the backup was started. Starting recovery from a possible later checkpoint will result in a corrupted database! The backup or the first stream will contain a file backup.cnf, which includes parameters needed for restoring the backup. Currently, these are innodb_log_recovery_start and innodb_log_recovery_target. If innodb_log_recovery_target>0, InnoDB will be in read-only mode, not allowing any writes to persistent files other than via the log application. To restore a streaming backup made with BACKUP SERVER WITH, an empty directory needs to be created and all streams be extracted there using the standard tar utility of the operating system, optionally after undoing any encryption or compression that had been added by the backup command. Then, the backup is prepared or MariaDB server started up on the extracted directory, similar to as if the BACKUP SERVER TO statement had been used. The following will be implemented separately: MDEV-39061 mariadb-backup compatible wrapper script for BACKUP SERVER MDEV-40163 Partial backup and restore MDEV-39091 Back up ENGINE=RocksDB MDEV-40333 Concurrent DDL for Aria tables in BACKUP SERVER The implementation introduces a basic multi-threaded driver Sql_cmd_backup, storage engine interfaces, and basic copying of the storage engines InnoDB, Aria, MyISAM, MERGE (MyISAM), Archive, CSV. backup_target: A structured data type to represent a target directory. On Microsoft Windows, we must use directory paths because there is no variant of CopyFileEx() that would work on file handles. backup_sink: Wraps a per-thread output stream as well as storage engine specific context. handlerton::backup_start(), handlerton::backup_end(): Invoked at the start or end of a backup phase, in the thread that executes a BACKUP SERVER statement. handlerton::backup_step(): A backup step that can be invoked from multiple threads concurrently, between the execution of the corresponding handlerton::backup_start() and handlerton::backup_end() of the same phase. copy_entire_file(): A file copying service for POSIX systems. copy_mmap(): A zero-copy alternative to backup::copy(), to copy from a memory-mapped buffer. copy_file_range_try(): A wrapper for Linux copy_file_range(2), which may fail with EOPNOTSUPP or EXDEV and thus require a fallback to copy_mmap() or backup::copy(). backup::copy(): A partial or sparse file-copying service. On other platforms than FreeBSD or Microsoft Windows, there are shortcut alternatives to this. Note: On Linux we never invoke sendfile(2) for copying between files, because can be much slower than the alternatives. backup_stream_append_plain(): A wrapper of backup::append(), which is the streaming equivalent of backup::copy(). backup_stream_zeropad(): Zero-pad the last tar block if needed. backup_stream_append_async(): A variant of backup_stream_append_plain() where the source file region is guaranteed to be immutable after the call returns. Zero-copy mmap(2) or Linux sendfile(2) are inherently risky for copying data files that may be modified in place, because it could introduce a race condition between a page write that runs concurrently with a child process that is reading the data from the pipe. backup::append(): On systems where we can determine the size of the pipe buffer, invoke backup_stream_append_async() for the initial write, and pread_write() for the last part, to guarantee that the data written by the zero-copy shortcut will have been consumed before the call returns and the caller is able to resume writes to the source region. pread_write(): On 64-bit systems, allocate a buffer of up to 1 MiB. This is the "slow path" of copying or streaming files. struct Aria_backup: Context for multi-threaded backup, comprising directory handles, a mutex and enum Aria_backup_status. aria_backup_start(): Prepare the context for aria_backup_step(). Most files are copied in BACKUP_PHASE_NO_DDL after flush_tables(thd, FLUSH_NON_TRANS_TABLES) has been invoked. All ENGINE=Aria files (including TRANSACTIONAL=0) are copied in BACKUP_PHASE_NO_COMMIT. Thanks to Andrzej Jarząbek for writing test cases and suggesting this logic. aria_backup_step(): Copy one non-ACID file. Acquires Aria_backup::mutex, traverses directories to construct one file name, releases the mutex, and copies the file if one was found. aria_backup_data(): Copy one data file. On Microsoft Windows, this assumes that the current directory is the datadir. This assumption would not hold in the Embedded Server library, which is not supported on Microsoft Windows. aria_backup_log(): Copy one ENGINE=Aria log file. aria_backup_end(): Finish a copying phase and clean up the context. InnoDB_backup::init(): Wait for a possible previous BACKUP SERVER operation to reach the very end of InnoDB_backup::context::cleanup() so that the context can be safely reused. InnoDB_backup::queue: Collection of tablespace IDs and payload sizes at the start of the backup, and the log_sys.first_lsn of log files that have to be included in the backup. If any data file is created or extended while the backup is executing, we must have the corresponding write-ahead-log entries that we are copying since the latest checkpoint that was completed when the backup started. If any tablespaces are deleted during the backup, we may or may not copy them, and the application of a FILE_DELETE record will remove them. Similarly, applying FILE_RENAME or FILE_CREATE records will rename or create files during recovery as needed. log_sys.backup: Whether BACKUP SERVER is in progress. The purpose of this is to make BACKUP SERVER prevent the concurrent execution of SET GLOBAL innodb_log_archive=OFF or SET GLOBAL innodb_log_file_size when innodb_log_archive=OFF. log_sys.archived_checkpoint: Keep track of the earliest available checkpoint, corresponding to log_sys.archived_lsn. This reflects SET GLOBAL innodb_log_recovery_start (which is settable now), for incremental backup. fil_system.have_all_spaces: Whether all tablespace metadata is guaranteed to be known. To speed up startup, InnoDB does not normally open all tablespace files. fil_space_t::create_lsn: Change to Atomic_relaxed and use this to indicate tablespace creation LSN, in addition to indicate undo tablespace rebuild LSN. fil_space_t::backup_end: The first page number that is not being backed up (by default 0, to indicate that no backup is in progress). fil_space_t::BACKUP_BATCH_SIZE: The number of preceding pages that will be covered by fil_space_t::backup_end. This is the unit of "page range locking" during InnoDB backup. buf_page_t::write_fix_try(), buf_page_t::write_unfix_try(): Try to set or unset a fake "write fix" on a page, to prevent concurrent flush() during a backup batch. The atomic operations may run concurrently with set_reinit() and set_freed(). The fake "write fix" does not prevent any concurrent read or write of the page data in the buffer pool; it only blocks writes to the underlying data file. buf_page_t::flush(): Atomically test and set write fix, and skip the operation if the fake "write fix" was set. buf_page_t::set_freed(), buf_page_t::set_reinit(): Employ a compare-and-exchange loop to accommodate for the "write fix". innodb_backup_batch_wait(): Look up any pages that we are about to back up. For any dirty pages, invoke buf_page_t::write_fix_try() to try to set a fake "write fix" lock-free. If the page is currently write-fixed between buf_page_t::flush() and buf_page_t::write_complete(), acquire and release a page U-latch to wait for the conflicting write to complete. InnoDB_backup::backup_batch_start(), InnoDB_backup::backup_batch_stop(): Adjust fil_space_t::backup_end and fake "write fix" of dirty pages to protect the copying of a range of pages from the underlying file. InnoDB_backup::commit(): Enqueue the remaining log to be copied. InnoDB_backup::checkpoint_complete(): If backup is running and commit() has not been called, add each completed innodb_archive_log=ON file to the queue. Else, skip or delete, as appropriate. log_t::backup_start(): If we were running with innodb_log_archive=ON, ensure that the latest file is a valid recovery starting point. That is, wait for the latest log checkpoint to be within the file. buf_flush_list_space(): Check for concurrent backup before writing each page. This is inefficient, but this function may be invoked from multiple threads concurrently, and it cannot be changed easily, especially for fil_crypt_thread(). fil_ibd_create(): Set fil_space_t::create_lsn after the file has been created. dict_load_tablespaces(): Determine the size of each file if upgrade==true. Backup depends on that. buf_dblwr_t::begin(), buf_dblwr_t::end(), buf_dblwr_t::size(): Accessors to allow BACKUP SERVER to skip the contents of the doublewrite buffer in the system tablespace. It is only useful for crash recovery in case a data page had been incompletely written by the time the server was killed. If the server is killed during a backup, the backup will be incomplete and unusable anyway. Furthermore, the page range locking makes page writes and backup mutually exclusive.
handlerton::backup_file: Check if a file should be included in the backup. Implemented for ENGINE=Aria in maria_backup_file(). aria_backup_start(): Copy the Aria log files (FIXME: currently, single-threaded) backup::copy_or_stream(): Copy or stream a file. backup_context: Process-wide BACKUP SERVER context. Handles the directory traversal and copying of files for built-in storage engines that do not implement this backup interface. backup_target_phase. Wrap backup_context. backup_target_phase::step(), backup_context::step(): Process a file from a directory scan, or by invoking handlerton::backup_step().
The following SQL statements will be introduced:
In place of the
1, any positive number of threads may be specified. For the first variant,'/path/to'must exist and'/path/to/directory'must not exist; that is where the backup will be written to.For the second variant,
'command'must be the name of a script or command that will be executed in a child process. The standard input of that command will be in a format that is compatible with GNUtar --format=oldgnu(and also BSDtarvariants that are also part of Microsoft Windows and Apple macOS). The command is expected to optionally compress and encrypt the stream and redirect it to a file on a local or a remote server. TheBACKUP SERVER WITH willappend an additional argument, a positive base-ten number in ASCII, starting with1, to identify the current thread. In this way, each concurrent stream can write a separate file.The backup or the first stream will contain a file
backup.cnf, which includes parameters needed for restoring the backup. Currently, these areinnodb_log_recovery_startandinnodb_log_recovery_target. Ifinnodb_log_recovery_target>0, InnoDB will be in read-only mode, not allowing any writes to persistent files other than via the log application.To restore a streaming backup made with
BACKUP SERVER WITH, an empty directory needs to be created and all streams be extracted there using the standardtarutility of the operating system, optionally after undoing any encryption or compression that had been added by the backup command. Then, the backup is prepared or MariaDB server started up on the extracted directory, similar to as if theBACKUP SERVER TOstatement had been used.Note: The parameter
innodb_log_recovery_startinbackup.cnfis STRICTLY NECESSARY TO AVOID CORRUPTION! By default, InnoDB crash recovery starts from the latest available log checkpoint. However, for restoring a backup, recovery must start from the checkpoint that was the latest when the backup was started. Starting recovery from a possible later checkpoint will result in a corrupted database!The following will be implemented separately:
MDEV-39061
mariadb-backupcompatible wrapper script forBACKUP SERVERMDEV-40163 Partial backup and restore
MDEV-39091 Back up
ENGINE=RocksDBMDEV-39092 Less blocking backup of
ENGINE=AriaThe implementation introduces a basic driver
Sql_cmd_backup, storage engine interfaces, and basic copying of the storage engines InnoDB, Aria, MyISAM, MERGE (MyISAM), Archive, CSV.backup_target: A structured data type to represent a target directory. On Microsoft Windows, we must use directory paths because there is no variant ofCopyFileEx()that would work on file handles.backup_sink: Wraps a per-thread output stream as well as storage engine specific context.handlerton::backup_start(),handlerton::backup_end(): Invoked at the start or end of a backup phase, in the thread that executes aBACKUP SERVERstatement.handlerton::backup_step(): A backup step that can be invoked from multiple threads concurrently, between the execution of the correspondinghandlerton::backup_start()andhandlerton::backup_end()of the same phase.copy_entire_file(): A file copying service for POSIX systems.copy_file(): A partial or sparse file-copying service for all systems.backup_stream_append(): Equivalent tocopy_file(), but appending to a stream. On Linux, this usessendfile(2), which assumes that the source data will not be changed before the data has been consumed from the pipe.backup_stream_append_async(): A variant ofbackup_stream_append()where the source file region is guaranteed to be immutable after the call returns. We must not use Linuxsendfile(2)for copying data files that may be modified in place, because it could introduce a race condition between a page write that runs concurrently with a child process that is reading the data from the pipe.InnoDB_backup::context: Backup context, attached tobackup_sinkso that context can continue to exist between the time aBACKUP SERVERreleases all locks and anotherBACKUP SERVERstarts executing, withinnodb_backuppointing to the new backup, while the old backup is still being finished.fil_space_t::write_or_backup: Keep track of in-flight page writes and pending backup operation. We must not allow them concurrently, because that could lead into torn pages in the backup.fil_space_t::backup_end: The first page number that is not being backed up (by default 0, to indicate that no backup is in progress).fil_space_t::BACKUP_BATCH_SIZE: The number of preceding pages that will be covered byfil_space_t::backup_end. This is the unit of "page range locking" during InnoDB backup.log_sys.backup: WhetherBACKUP SERVERis in progress. The purpose of this is to makeBACKUP SERVERprevent the concurrent execution ofSET GLOBAL innodb_log_archive=OFForSET GLOBAL innodb_log_file_sizewheninnodb_log_archive=OFF.log_sys.archived_checkpoint: Keep track of the earliest available checkpoint, corresponding tolog_sys.archived_lsn. This reflectsSET GLOBAL innodb_log_recovery_start(which is settable now), for incremental backup.buf_flush_list_space(): Check for concurrent backup before writing each page. This is inefficient, but this function may be invoked from multiple threads concurrently, and it cannot be changed easily, especially forfil_crypt_thread().fil_system.have_all_spaces: Whether all tablespace metadata is guaranteed to be known. To speed up startup, InnoDB does not normally open all tablespace files.