From: Andres Freund Date: Mon, 27 Feb 2023 17:36:37 -0800 Subject: [PATCH v5 04/15] Add smgrzeroextend(), FileZero(), FileFallocate() diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c index 9fd8444ed4..c34ed41d52 100644 --- a/src/backend/storage/file/fd.c +++ b/src/backend/storage/file/fd.c @@ -2206,6 +2206,92 @@ FileSync(File file, uint32 wait_event_info) return returnCode; } +/* + * Zero a region of the file. + * + * Returns 0 on success, -1 otherwise. In the latter case errno is set to the + * appropriate error. + */ +int +FileZero(File file, off_t offset, off_t amount, uint32 wait_event_info) +{ + int returnCode; + ssize_t written; + + Assert(FileIsValid(file)); + returnCode = FileAccess(file); + if (returnCode < 0) + return returnCode; + + pgstat_report_wait_start(wait_event_info); + written = pg_pwrite_zeros(VfdCache[file].fd, amount, offset); + pgstat_report_wait_end(); + + if (written < 0) + return -1; + else if (written != amount) this doesn't need to be an else if + { + /* if errno is unset, assume problem is no disk space */ + if (errno == 0) + errno = ENOSPC; + return -1; + } +int +FileFallocate(File file, off_t offset, off_t amount, uint32 wait_event_info) +{ +#ifdef HAVE_POSIX_FALLOCATE + int returnCode; + + Assert(FileIsValid(file)); + returnCode = FileAccess(file); + if (returnCode < 0) + return returnCode; + + pgstat_report_wait_start(wait_event_info); + returnCode = posix_fallocate(VfdCache[file].fd, offset, amount); + pgstat_report_wait_end(); + + if (returnCode == 0) + return 0; + + /* for compatibility with %m printing etc */ + errno = returnCode; + + /* + * Return in cases of a "real" failure, if fallocate is not supported, + * fall through to the FileZero() backed implementation. + */ + if (returnCode != EINVAL && returnCode != EOPNOTSUPP) + return returnCode; I'm pretty sure you can just delete the below if statement + if (returnCode == 0 || + (returnCode != EINVAL && returnCode != EINVAL)) + return returnCode; diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c index 352958e1fe..59a65a8305 100644 --- a/src/backend/storage/smgr/md.c +++ b/src/backend/storage/smgr/md.c @@ -28,6 +28,7 @@ #include "access/xlog.h" #include "access/xlogutils.h" #include "commands/tablespace.h" +#include "common/file_utils.h" #include "miscadmin.h" #include "pg_trace.h" #include "pgstat.h" @@ -500,6 +501,116 @@ mdextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, Assert(_mdnblocks(reln, forknum, v) <= ((BlockNumber) RELSEG_SIZE)); } +/* + * mdzeroextend() -- Add ew zeroed out blocks to the specified relation. not sure what ew is + * + * Similar to mdrextend(), except the relation can be extended by mdrextend->mdextend + * multiple blocks at once, and that the added blocks will be filled with I would lose the comma and just say "and the added blocks will be filled..." +void +mdzeroextend(SMgrRelation reln, ForkNumber forknum, + BlockNumber blocknum, int nblocks, bool skipFsync) So, I think there are a few too many local variables in here, and it actually makes it more confusing. Assuming you would like to keep the input parameters blocknum and nblocks unmodified for debugging/other reasons, here is a suggested refactor of this function Also, I think you can combine the two error cases (I don't know if the user cares what you were trying to extend the file with). I've done this below also. void mdzeroextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, int nblocks, bool skipFsync) { MdfdVec *v; BlockNumber curblocknum = blocknum; int remblocks = nblocks; Assert(nblocks > 0); /* This assert is too expensive to have on normally ... */ #ifdef CHECK_WRITE_VS_EXTEND Assert(blocknum >= mdnblocks(reln, forknum)); #endif /* * If a relation manages to grow to 2^32-1 blocks, refuse to extend it any * more --- we mustn't create a block whose number actually is * InvalidBlockNumber or larger. */ if ((uint64) blocknum + nblocks >= (uint64) InvalidBlockNumber) ereport(ERROR, (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), errmsg("cannot extend file \"%s\" beyond %u blocks", relpath(reln->smgr_rlocator, forknum), InvalidBlockNumber))); while (remblocks > 0) { int segstartblock = curblocknum % ((BlockNumber) RELSEG_SIZE); int numblocks = remblocks; off_t seekpos = (off_t) BLCKSZ * segstartblock; int ret; if (segstartblock + remblocks > RELSEG_SIZE) numblocks = RELSEG_SIZE - segstartblock; v = _mdfd_getseg(reln, forknum, curblocknum, skipFsync, EXTENSION_CREATE); /* * If available and useful, use posix_fallocate() (via FileAllocate()) * to extend the relation. That's often more efficient than using * write(), as it commonly won't cause the kernel to allocate page * cache space for the extended pages. * * However, we don't use FileAllocate() for small extensions, as it * defeats delayed allocation on some filesystems. Not clear where * that decision should be made though? For now just use a cutoff of * 8, anything between 4 and 8 worked OK in some local testing. */ if (numblocks > 8) ret = FileFallocate(v->mdfd_vfd, seekpos, (off_t) BLCKSZ * numblocks, WAIT_EVENT_DATA_FILE_EXTEND); else /* * Even if we don't want to use fallocate, we can still extend a * bit more efficiently than writing each 8kB block individually. * pg_pwrite_zeroes() (via FileZero()) uses * pg_pwritev_with_retry() to avoid multiple writes or needing a * zeroed buffer for the whole length of the extension. */ ret = FileZero(v->mdfd_vfd, seekpos, (off_t) BLCKSZ * numblocks, WAIT_EVENT_DATA_FILE_EXTEND); if (ret != 0) ereport(ERROR, errcode_for_file_access(), errmsg("could not extend file \"%s\": %m", FilePathName(v->mdfd_vfd)), errhint("Check free disk space.")); if (!skipFsync && !SmgrIsTemp(reln)) register_dirty_segment(reln, forknum, v); Assert(_mdnblocks(reln, forknum, v) <= ((BlockNumber) RELSEG_SIZE)); remblocks -= numblocks; curblocknum += numblocks; } } diff --git a/src/backend/storage/smgr/smgr.c b/src/backend/storage/smgr/smgr.c index dc466e5414..5224ca5259 100644 --- a/src/backend/storage/smgr/smgr.c +++ b/src/backend/storage/smgr/smgr.c @@ -50,6 +50,8 @@ typedef struct f_smgr +/* + * smgrzeroextend() -- Add new zeroed out blocks to a file. + * + * Similar to smgrextend(), except the relation can be extended by + * multiple blocks at once, and that the added blocks will be filled with + * zeroes. + */ Similar grammatical feedback as mdzeroextend.