Commit 30edd554 authored by Marko Mäkelä's avatar Marko Mäkelä

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.
parent b11aa0df
/* 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;
......
......@@ -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
......
......@@ -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);
......
......@@ -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)
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++)
{
......
......@@ -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 @@ fil_rename_tablespace(
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 @@ fil_ibd_create(
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 @@ fil_ibd_create(
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;
......
......@@ -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,
......
......@@ -424,13 +424,6 @@ struct fil_space_t final
/** 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 @@ struct fil_space_t final
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 @@ struct fil_space_t final
/** @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.
......@@ -1067,18 +1058,27 @@ struct fil_space_t final
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) */
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 actually is a raw device or disk partition */
bool is_raw_disk;
/** whether the file is on non-rotational media (SSD) */
bool on_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;
/** size of the file in database pages (0 if not known yet);
the possible last incomplete megabyte may be ignored
if space->id == 0 */
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 */
......@@ -1090,24 +1090,11 @@ struct fil_node_t final
/** link to other files in this tablespace */
UT_LIST_NODE_T(fil_node_t) chain;
/** whether this file could use atomic write (data file) */
bool atomic_write;
/** 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;
/** FIL_NODE_MAGIC_N */
ulint magic_n;
/** @return whether this file is open */
bool is_open() const
{
return(handle != OS_FILE_CLOSED);
}
bool is_open() const { return handle != OS_FILE_CLOSED; }
/** Read the first page of a data file.
@return whether the page was found valid */
......@@ -1115,9 +1102,9 @@ struct fil_node_t final
/** 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
void find_metadata(os_file_t file= OS_FILE_CLOSED
#ifndef _WIN32
, struct stat* statbuf = NULL
, bool create= false, struct stat *statbuf= nullptr
#endif
);
......@@ -1138,8 +1125,11 @@ struct fil_node_t final
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()
{
......
......@@ -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;
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;
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);
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;
}
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
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 @@ bool fil_node_t::read_page0()
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;
......
......@@ -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()) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment