From 30edd5549dd764dbc8171ee764380dba4eb94cc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Mon, 28 Jun 2021 13:42:43 +0300 Subject: [PATCH] MDEV-26029: Sparse files are inefficient on thinly provisioned storage The MariaDB implementation of page_compressed tables for InnoDB used sparse files. In the worst case, in the data file, every data page will consist of some data followed by a hole. This may be extremely inefficient in some file systems. If the underlying storage device is thinly provisioned (can compress data on the fly), it would be good to write regular files (with sequences of NUL bytes at the end of each page_compressed block) and let the storage device take care of compressing the data. For reads, sparse file regions and regions containing NUL bytes will be indistinguishable. my_test_if_disable_punch_hole(): A new predicate for detecting thinly provisioned storage. (Not implemented yet.) innodb_atomic_writes: Correct the comment. buf_flush_page(): Support all values of fil_node_t::punch_hole. On a thinly provisioned storage device, we will always write NUL-padded innodb_page_size bytes also for page_compressed tables. buf_flush_freed_pages(): Remove a redundant condition. fil_space_t::atomic_write_supported: Remove. (This was duplicating fil_node_t::atomic_write.) fil_space_t::punch_hole: Remove. (Duplicated fil_node_t::punch_hole.) fil_node_t: Remove magic_n, and consolidate flags into bitfields. For punch_hole we introduce a third value that indicates a thinly provisioned storage device. fil_node_t::find_metadata(): Detect all attributes of the file. --- include/my_sys.h | 5 +- .../suite/sys_vars/r/sysvars_innodb.result | 2 +- storage/innobase/buf/buf0dblwr.cc | 1 + storage/innobase/buf/buf0flu.cc | 20 ++- storage/innobase/fil/fil0fil.cc | 25 +--- storage/innobase/handler/ha_innodb.cc | 4 +- storage/innobase/include/fil0fil.h | 112 +++++++-------- storage/innobase/os/os0file.cc | 136 +++++++----------- storage/innobase/row/row0import.cc | 2 +- 9 files changed, 126 insertions(+), 181 deletions(-) diff --git a/include/my_sys.h b/include/my_sys.h index f3515b7726c..fccff539fa9 100644 --- a/include/my_sys.h +++ b/include/my_sys.h @@ -1,5 +1,5 @@ /* Copyright (c) 2000, 2013, Oracle and/or its affiliates. - Copyright (c) 2010, 2020, MariaDB Corporation. + Copyright (c) 2010, 2021, MariaDB Corporation. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -183,10 +183,11 @@ extern BOOL my_obtain_privilege(LPCSTR lpPrivilege); #endif void my_init_atomic_write(void); +#define my_test_if_thinly_provisioned(A) 0 #ifdef __linux__ my_bool my_test_if_atomic_write(File handle, int pagesize); #else -#define my_test_if_atomic_write(A, B) 0 +# define my_test_if_atomic_write(A, B) 0 #endif /* __linux__ */ extern my_bool my_may_have_atomic_write; diff --git a/mysql-test/suite/sys_vars/r/sysvars_innodb.result b/mysql-test/suite/sys_vars/r/sysvars_innodb.result index 1a88710de94..bba60290863 100644 --- a/mysql-test/suite/sys_vars/r/sysvars_innodb.result +++ b/mysql-test/suite/sys_vars/r/sysvars_innodb.result @@ -1754,7 +1754,7 @@ SESSION_VALUE NULL DEFAULT_VALUE ON VARIABLE_SCOPE GLOBAL VARIABLE_TYPE BOOLEAN -VARIABLE_COMMENT Enable atomic writes, instead of using the doublewrite buffer, for files on devices that supports atomic writes. This option only works on Linux with either FusionIO cards using the directFS filesystem or with Shannon cards using any file system. +VARIABLE_COMMENT Enable atomic writes, instead of using the doublewrite buffer, for files on devices that supports atomic writes. NUMERIC_MIN_VALUE NULL NUMERIC_MAX_VALUE NULL NUMERIC_BLOCK_SIZE NULL diff --git a/storage/innobase/buf/buf0dblwr.cc b/storage/innobase/buf/buf0dblwr.cc index 1b7a2d9608a..44db9986df6 100644 --- a/storage/innobase/buf/buf0dblwr.cc +++ b/storage/innobase/buf/buf0dblwr.cc @@ -712,6 +712,7 @@ void buf_dblwr_t::add_to_batch(const IORequest &request, size_t size) ut_ad(request.bpage); ut_ad(request.bpage->in_file()); ut_ad(request.node); + ut_ad(request.node->space->purpose == FIL_TYPE_TABLESPACE); ut_ad(request.node->space->id == request.bpage->id().space()); ut_ad(request.node->space->referenced()); ut_ad(!srv_read_only_mode); diff --git a/storage/innobase/buf/buf0flu.cc b/storage/innobase/buf/buf0flu.cc index 4adf5931281..0717558eba6 100644 --- a/storage/innobase/buf/buf0flu.cc +++ b/storage/innobase/buf/buf0flu.cc @@ -804,8 +804,6 @@ static bool buf_flush_page(buf_page_t *bpage, bool lru, fil_space_t *space) ut_ad(bpage->ready_for_flush()); ut_ad((space->purpose == FIL_TYPE_TEMPORARY) == (space == fil_system.temp_space)); - ut_ad(space->purpose == FIL_TYPE_TABLESPACE || - space->atomic_write_supported); ut_ad(space->referenced()); ut_ad(lru || space != fil_system.temp_space); @@ -912,8 +910,16 @@ static bool buf_flush_page(buf_page_t *bpage, bool lru, fil_space_t *space) } #if defined HAVE_FALLOC_PUNCH_HOLE_AND_KEEP_SIZE || defined _WIN32 - if (size != orig_size && space->punch_hole) - type= lru ? IORequest::PUNCH_LRU : IORequest::PUNCH; + if (size != orig_size) + { + switch (space->chain.start->punch_hole) { + case 1: + type= lru ? IORequest::PUNCH_LRU : IORequest::PUNCH; + break; + case 2: + size= orig_size; + } + } #endif frame=page; } @@ -1036,8 +1042,8 @@ innodb_immediate_scrub_data_uncompressed from the freed ranges. @param space tablespace which may contain ranges of freed pages */ static void buf_flush_freed_pages(fil_space_t *space) { - const bool punch_hole= space->punch_hole; - if (!srv_immediate_scrub_data_uncompressed && !punch_hole) + const bool punch_hole= space->chain.start->punch_hole == 1; + if (!punch_hole && !srv_immediate_scrub_data_uncompressed) return; lsn_t flush_to_disk_lsn= log_sys.get_flushed_lsn(); @@ -1064,7 +1070,7 @@ static void buf_flush_freed_pages(fil_space_t *space) (range.last - range.first + 1) * physical_size, nullptr); } - else if (srv_immediate_scrub_data_uncompressed) + else { for (os_offset_t i= range.first; i <= range.last; i++) { diff --git a/storage/innobase/fil/fil0fil.cc b/storage/innobase/fil/fil0fil.cc index 6997978ea0a..c841eb79497 100644 --- a/storage/innobase/fil/fil0fil.cc +++ b/storage/innobase/fil/fil0fil.cc @@ -317,8 +317,6 @@ fil_node_t* fil_space_t::add(const char* name, pfs_os_file_t handle, node->size = size; - node->magic_n = FIL_NODE_MAGIC_N; - node->init_size = size; node->max_size = max_pages; @@ -718,7 +716,6 @@ bool fil_space_extend(fil_space_t *space, uint32_t size) inline pfs_os_file_t fil_node_t::close_to_free(bool detach_handle) { mysql_mutex_assert_owner(&fil_system.mutex); - ut_a(magic_n == FIL_NODE_MAGIC_N); ut_a(!being_extended); if (is_open() && @@ -941,16 +938,6 @@ fil_space_t *fil_space_t::create(ulint id, ulint flags, space->latch.SRW_LOCK_INIT(fil_space_latch_key); - if (space->purpose == FIL_TYPE_TEMPORARY) { - /* SysTablespace::open_or_create() would pass - size!=0 to fil_space_t::add(), so first_time_open - would not hold in fil_node_open_file(), and we - must assign this manually. We do not care about - the durability or atomicity of writes to the - temporary tablespace files. */ - space->atomic_write_supported = true; - } - mysql_mutex_lock(&fil_system.mutex); if (const fil_space_t *old_space = fil_space_get_by_id(id)) { @@ -1951,9 +1938,6 @@ skip_second_rename: return(success); } -/* FIXME: remove this! */ -IF_WIN(, bool os_is_sparse_file_supported(os_file_t fh)); - /** Create a tablespace file. @param[in] space_id Tablespace ID @param[in] name Tablespace name in dbname/tablename format. @@ -2041,7 +2025,6 @@ fil_ibd_create( } const bool is_compressed = fil_space_t::is_compressed(flags); - bool punch_hole = is_compressed; fil_space_crypt_t* crypt_data = nullptr; #ifdef _WIN32 if (is_compressed) { @@ -2060,9 +2043,6 @@ err_exit: return NULL; } - /* FIXME: remove this */ - IF_WIN(, punch_hole = punch_hole && os_is_sparse_file_supported(file)); - /* We have to write the space id to the file immediately and flush the file to disk. This is because in crash recovery we must be aware what tablespaces exist and what are their space id's, so that we can apply @@ -2115,9 +2095,8 @@ err_exit: if (fil_space_t* space = fil_space_t::create(space_id, flags, FIL_TYPE_TABLESPACE, crypt_data, mode)) { - space->punch_hole = punch_hole; fil_node_t* node = space->add(path, file, size, false, true); - node->find_metadata(file); + IF_WIN(node->find_metadata(), node->find_metadata(file, true)); mtr.start(); mtr.set_named_space(space); fsp_header_init(space, size, &mtr); @@ -2878,7 +2857,7 @@ fil_io_t fil_space_t::io(const IORequest &type, os_offset_t offset, size_t len, /* Punch hole is not supported, make space not to support punch hole */ if (UNIV_UNLIKELY(err == DB_IO_NO_PUNCH_HOLE)) { - punch_hole = false; + node->punch_hole = false; err = DB_SUCCESS; } goto release_sync_write; diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index ab94684e982..8b97bf07c33 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -18510,9 +18510,7 @@ static MYSQL_SYSVAR_BOOL(doublewrite, srv_use_doublewrite_buf, static MYSQL_SYSVAR_BOOL(use_atomic_writes, srv_use_atomic_writes, PLUGIN_VAR_NOCMDARG | PLUGIN_VAR_READONLY, "Enable atomic writes, instead of using the doublewrite buffer, for files " - "on devices that supports atomic writes. " - "This option only works on Linux with either FusionIO cards using " - "the directFS filesystem or with Shannon cards using any file system.", + "on devices that supports atomic writes.", NULL, NULL, TRUE); static MYSQL_SYSVAR_BOOL(stats_include_delete_marked, diff --git a/storage/innobase/include/fil0fil.h b/storage/innobase/include/fil0fil.h index 58d83770daa..9fd0b076520 100644 --- a/storage/innobase/include/fil0fil.h +++ b/storage/innobase/include/fil0fil.h @@ -424,13 +424,6 @@ public: /** Checks that this tablespace needs key rotation. */ bool is_in_default_encrypt; - /** True if the device this filespace is on supports atomic writes */ - bool atomic_write_supported; - - /** True if file system storing this tablespace supports - punch hole */ - bool punch_hole; - /** mutex to protect freed ranges */ std::mutex freed_range_mutex; @@ -444,11 +437,7 @@ public: ulint magic_n;/*!< FIL_SPACE_MAGIC_N */ /** @return whether doublewrite buffering is needed */ - bool use_doublewrite() const - { - return !atomic_write_supported && srv_use_doublewrite_buf && - buf_dblwr.is_initialised(); - } + inline bool use_doublewrite() const; /** Append a file to the chain of files of a space. @param[in] name file name of a file that is not open @@ -509,6 +498,8 @@ public: /** @return whether the storage device is rotational (HDD, not SSD) */ inline bool is_rotational() const; + /** whether the tablespace discovery is being deferred during crash + recovery due to incompletely written page 0 */ inline bool is_deferred() const; /** Open each file. Never invoked on .ibd files. @@ -1066,60 +1057,56 @@ private: /** File node of a tablespace or the log data space */ struct fil_node_t final { - /** tablespace containing this file */ - fil_space_t* space; - /** file name; protected by fil_system.mutex and log_sys.mutex. */ - char* name; - /** file handle (valid if is_open) */ - pfs_os_file_t handle; - /** whether the file actually is a raw device or disk partition */ - bool is_raw_disk; - /** whether the file is on non-rotational media (SSD) */ - bool on_ssd; - /** size of the file in database pages (0 if not known yet); - the possible last incomplete megabyte may be ignored - if space->id == 0 */ - uint32_t size; - /** initial size of the file in database pages; - FIL_IBD_FILE_INITIAL_SIZE by default */ - uint32_t init_size; - /** maximum size of the file in database pages (0 if unlimited) */ - uint32_t max_size; - /** whether the file is currently being extended */ - Atomic_relaxed being_extended; - /** link to other files in this tablespace */ - UT_LIST_NODE_T(fil_node_t) chain; + /** tablespace containing this file */ + fil_space_t *space; + /** file name; protected by fil_system.mutex and log_sys.mutex */ + char *name; + /** file handle */ + pfs_os_file_t handle; + /** whether the file is on non-rotational media (SSD) */ + unsigned on_ssd:1; + /** how to write page_compressed tables + (0=do not punch holes but write minimal amount of data, 1=punch holes, + 2=always write the same amount; thinly provisioned storage will compress) */ + unsigned punch_hole:2; + /** whether this file could use atomic write */ + unsigned atomic_write:1; + /** whether the file actually is a raw device or disk partition */ + unsigned is_raw_disk:1; + /** whether the tablespace discovery is being deferred during crash + recovery due to incompletely written page 0 */ + unsigned deferred:1; - /** whether this file could use atomic write (data file) */ - bool atomic_write; + /** size of the file in database pages (0 if not known yet); + the possible last incomplete megabyte may be ignored if space->id == 0 */ + uint32_t size; + /** initial size of the file in database pages; + FIL_IBD_FILE_INITIAL_SIZE by default */ + uint32_t init_size; + /** maximum size of the file in database pages (0 if unlimited) */ + uint32_t max_size; + /** whether the file is currently being extended */ + Atomic_relaxed being_extended; + /** link to other files in this tablespace */ + UT_LIST_NODE_T(fil_node_t) chain; - /** Filesystem block size */ - ulint block_size; + /** Filesystem block size */ + ulint block_size; - /** Deferring the tablespace during recovery and it - can be used to skip the validation of page0 */ - bool deferred=false; + /** @return whether this file is open */ + bool is_open() const { return handle != OS_FILE_CLOSED; } - /** FIL_NODE_MAGIC_N */ - ulint magic_n; + /** Read the first page of a data file. + @return whether the page was found valid */ + bool read_page0(); - /** @return whether this file is open */ - bool is_open() const - { - return(handle != OS_FILE_CLOSED); - } - - /** Read the first page of a data file. - @return whether the page was found valid */ - bool read_page0(); - - /** Determine some file metadata when creating or reading the file. - @param file the file that is being created, or OS_FILE_CLOSED */ - void find_metadata(os_file_t file = OS_FILE_CLOSED + /** Determine some file metadata when creating or reading the file. + @param file the file that is being created, or OS_FILE_CLOSED */ + void find_metadata(os_file_t file= OS_FILE_CLOSED #ifndef _WIN32 - , struct stat* statbuf = NULL + , bool create= false, struct stat *statbuf= nullptr #endif - ); + ); /** Close the file handle. */ void close(); @@ -1138,8 +1125,11 @@ private: void prepare_to_close_or_detach(); }; -/** Value of fil_node_t::magic_n */ -#define FIL_NODE_MAGIC_N 89389 +inline bool fil_space_t::use_doublewrite() const +{ + return !UT_LIST_GET_FIRST(chain)->atomic_write && srv_use_doublewrite_buf && + buf_dblwr.is_initialised(); +} inline void fil_space_t::set_imported() { diff --git a/storage/innobase/os/os0file.cc b/storage/innobase/os/os0file.cc index 53c0c07eda4..efc92a384c5 100644 --- a/storage/innobase/os/os0file.cc +++ b/storage/innobase/os/os0file.cc @@ -3233,7 +3233,7 @@ os_file_set_nocache( /** Check if the file system supports sparse files. @param fh file handle @return true if the file system supports sparse files */ -IF_WIN(static,) bool os_is_sparse_file_supported(os_file_t fh) +static bool os_is_sparse_file_supported(os_file_t fh) { #ifdef _WIN32 FILE_ATTRIBUTE_TAG_INFO info; @@ -3495,24 +3495,23 @@ dberr_t IORequest::punch_hole(os_offset_t off, ulint len) const /* Check does file system support punching holes for this tablespace. */ - if (!node->space->punch_hole) { + if (!node->punch_hole) { return DB_IO_NO_PUNCH_HOLE; } dberr_t err = os_file_punch_hole(node->handle, off, trim_len); - if (err == DB_SUCCESS) { + switch (err) { + case DB_SUCCESS: srv_stats.page_compressed_trim_op.inc(); - } else { - /* If punch hole is not supported, - set space so that it is not used. */ - if (err == DB_IO_NO_PUNCH_HOLE) { - node->space->punch_hole = false; - err = DB_SUCCESS; - } + return err; + case DB_IO_NO_PUNCH_HOLE: + node->punch_hole = false; + err = DB_SUCCESS; + /* fall through */ + default: + return err; } - - return (err); } /** This function returns information about the specified file @@ -4101,81 +4100,56 @@ static bool is_file_on_ssd(char *file_path) #endif -/** Determine some file metadata when creating or reading the file. -@param file the file that is being created, or OS_FILE_CLOSED */ void fil_node_t::find_metadata(os_file_t file #ifndef _WIN32 - , struct stat* statbuf + , bool create, struct stat *statbuf #endif - ) + ) { - if (file == OS_FILE_CLOSED) { - file = handle; - ut_ad(is_open()); - } + if (!is_open()) + { + handle= file; + ut_ad(is_open()); + } -#ifdef _WIN32 /* FIXME: make this unconditional */ - if (space->punch_hole) { - space->punch_hole = os_is_sparse_file_supported(file); - } -#endif + if (!space->is_compressed()) + punch_hole= 0; + else if (my_test_if_thinly_provisioned(file)) + punch_hole= 2; + else + punch_hole= IF_WIN(, !create ||) os_is_sparse_file_supported(file); - /* - For the temporary tablespace and during the - non-redo-logged adjustments in - IMPORT TABLESPACE, we do not care about - the atomicity of writes. - - Atomic writes is supported if the file can be used - with atomic_writes (not log file), O_DIRECT is - used (tested in ha_innodb.cc) and the file is - device and file system that supports atomic writes - for the given block size. - */ - space->atomic_write_supported = space->purpose == FIL_TYPE_TEMPORARY - || space->purpose == FIL_TYPE_IMPORT; #ifdef _WIN32 - on_ssd = is_file_on_ssd(name); - FILE_STORAGE_INFO info; - if (GetFileInformationByHandleEx( - file, FileStorageInfo, &info, sizeof(info))) { - block_size = info.PhysicalBytesPerSectorForAtomicity; - } else { - block_size = 512; - } + on_ssd= is_file_on_ssd(name); + FILE_STORAGE_INFO info; + if (GetFileInformationByHandleEx(file, FileStorageInfo, &info, sizeof info)) + block_size= info.PhysicalBytesPerSectorForAtomicity; + else + block_size= 512; #else - struct stat sbuf; - if (!statbuf && !fstat(file, &sbuf)) { - statbuf = &sbuf; - } - if (statbuf) { - block_size = statbuf->st_blksize; - } - on_ssd = space->atomic_write_supported + struct stat sbuf; + if (!statbuf && !fstat(file, &sbuf)) + statbuf= &sbuf; + if (statbuf) + block_size= statbuf->st_blksize; # ifdef UNIV_LINUX - || (statbuf && fil_system.is_ssd(statbuf->st_dev)) + on_ssd= statbuf && fil_system.is_ssd(statbuf->st_dev); # endif - ; #endif - if (!space->atomic_write_supported) { - space->atomic_write_supported = atomic_write - && srv_use_atomic_writes -#ifndef _WIN32 - && my_test_if_atomic_write(file, - space->physical_size()) -#else - /* On Windows, all single sector writes are atomic, - as per WriteFile() documentation on MSDN. - We also require SSD for atomic writes, eventhough - technically it is not necessary- the reason is that - on hard disks, we still want the benefit from - (non-atomic) neighbor page flushing in the buffer - pool code. */ - && srv_page_size == block_size - && on_ssd -#endif - ; - } + + if (space->purpose != FIL_TYPE_TABLESPACE) + { + /* For temporary tablespace or during IMPORT TABLESPACE, we + disable neighbour flushing and do not care about atomicity. */ + on_ssd= true; + atomic_write= true; + } + else + /* On Windows, all single sector writes are atomic, as per + WriteFile() documentation on MSDN. */ + atomic_write= srv_use_atomic_writes && + IF_WIN(srv_page_size == block_size, + my_test_if_atomic_write(file, space->physical_size())); } /** Read the first page of a data file. @@ -4270,20 +4244,16 @@ invalid: space->free_len= free_len; } -#ifdef UNIV_LINUX - find_metadata(handle, &statbuf); -#else - find_metadata(); -#endif + IF_WIN(find_metadata(), find_metadata(handle, false, &statbuf)); /* Truncate the size to a multiple of extent size. */ ulint mask= psize * FSP_EXTENT_SIZE - 1; if (size_bytes <= mask); /* .ibd files start smaller than an extent size. Do not truncate valid data. */ - else size_bytes &= ~os_offset_t(mask); + else + size_bytes&= ~os_offset_t(mask); - space->punch_hole= space->is_compressed(); this->size= uint32_t(size_bytes / psize); space->set_sizes(this->size); return true; diff --git a/storage/innobase/row/row0import.cc b/storage/innobase/row/row0import.cc index 00016761c91..b2638c6fea2 100644 --- a/storage/innobase/row/row0import.cc +++ b/storage/innobase/row/row0import.cc @@ -3436,7 +3436,7 @@ fil_iterate( required by buf_zip_decompress() */ dberr_t err = DB_SUCCESS; bool page_compressed = false; - bool punch_hole = true; + bool punch_hole = !my_test_if_thinly_provisioned(iter.file); for (offset = iter.start; offset < iter.end; offset += n_bytes) { if (callback.is_interrupted()) {