agora inbox for [email protected]help / color / mirror / Atom feed
[PATCH 2/8] Pass all scan keys to BRIN consistent function at once 8+ messages / 2 participants [nested] [flat]
* [PATCH 2/8] Pass all scan keys to BRIN consistent function at once @ 2020-09-12 13:07 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 8+ messages in thread From: Tomas Vondra @ 2020-09-12 13:07 UTC (permalink / raw) Passing all scan keys to the BRIN consistent function at once may allow elimination of additional ranges, which would be impossible when only passing individual scan keys. The code continues to support both the original (one scan key at a time) and new (all scan keys at once) approaches, depending on whether the consistent function accepts three or four arguments. Author: Tomas Vondra <[email protected]> Reviewed-by: Alvaro Herrera <[email protected]> Reviewed-by: Mark Dilger <[email protected]> Reviewed-by: Alexander Korotkov <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/backend/access/brin/brin.c | 158 +++++++++++++++++++----- src/backend/access/brin/brin_validate.c | 4 +- 2 files changed, 126 insertions(+), 36 deletions(-) diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c index 27ba596c6e..f9a0476024 100644 --- a/src/backend/access/brin/brin.c +++ b/src/backend/access/brin/brin.c @@ -390,6 +390,9 @@ bringetbitmap(IndexScanDesc scan, TIDBitmap *tbm) BrinMemTuple *dtup; BrinTuple *btup = NULL; Size btupsz = 0; + ScanKey **keys; + int *nkeys; + int keyno; opaque = (BrinOpaque *) scan->opaque; bdesc = opaque->bo_bdesc; @@ -411,6 +414,66 @@ bringetbitmap(IndexScanDesc scan, TIDBitmap *tbm) */ consistentFn = palloc0(sizeof(FmgrInfo) * bdesc->bd_tupdesc->natts); + /* + * Make room for per-attribute lists of scan keys that we'll pass to the + * consistent support procedure. We allocate space for all attributes, so + * that we don't have to bother determining which attributes are used. + * + * XXX The widest table can have ~1600 attributes, so this may allocate a + * couple kilobytes of memory). We could invent a more compact approach + * (with just space for used attributes) but that would make the matching + * more complicated, so it may not be a win. + */ + keys = palloc0(sizeof(ScanKey *) * bdesc->bd_tupdesc->natts); + nkeys = palloc0(sizeof(int) * bdesc->bd_tupdesc->natts); + + /* + * Preprocess the scan keys - split them into per-attribute arrays. + */ + for (keyno = 0; keyno < scan->numberOfKeys; keyno++) + { + ScanKey key = &scan->keyData[keyno]; + AttrNumber keyattno = key->sk_attno; + + /* + * The collation of the scan key must match the collation used in the + * index column (but only if the search is not IS NULL/ IS NOT NULL). + * Otherwise we shouldn't be using this index ... + */ + Assert((key->sk_flags & SK_ISNULL) || + (key->sk_collation == + TupleDescAttr(bdesc->bd_tupdesc, + keyattno - 1)->attcollation)); + + /* First time we see this index attribute, so init as needed. */ + if (!keys[keyattno - 1]) + { + FmgrInfo *tmp; + + /* + * This is a bit of an overkill - we don't know how many scan keys + * are there for this attribute, so we simply allocate the largest + * number possible. This may waste a bit of memory, but we only + * expect small number of scan keys in general, so this should be + * negligible, and it's cheaper than having to repalloc + * repeatedly. + */ + keys[keyattno - 1] = palloc0(sizeof(ScanKey) * scan->numberOfKeys); + + /* First time this column, so look up consistent function */ + Assert(consistentFn[keyattno - 1].fn_oid == InvalidOid); + + tmp = index_getprocinfo(idxRel, keyattno, + BRIN_PROCNUM_CONSISTENT); + fmgr_info_copy(&consistentFn[keyattno - 1], tmp, + CurrentMemoryContext); + } + + /* Add key to the per-attribute array. */ + keys[keyattno - 1][nkeys[keyattno - 1]] = key; + nkeys[keyattno - 1]++; + } + /* allocate an initial in-memory tuple, out of the per-range memcxt */ dtup = brin_new_memtuple(bdesc); @@ -471,7 +534,7 @@ bringetbitmap(IndexScanDesc scan, TIDBitmap *tbm) } else { - int keyno; + int attno; /* * Compare scan keys with summary values stored for the range. @@ -481,51 +544,78 @@ bringetbitmap(IndexScanDesc scan, TIDBitmap *tbm) * no keys. */ addrange = true; - for (keyno = 0; keyno < scan->numberOfKeys; keyno++) + for (attno = 1; attno <= bdesc->bd_tupdesc->natts; attno++) { - ScanKey key = &scan->keyData[keyno]; - AttrNumber keyattno = key->sk_attno; - BrinValues *bval = &dtup->bt_columns[keyattno - 1]; + BrinValues *bval; Datum add; - /* - * The collation of the scan key must match the collation - * used in the index column (but only if the search is not - * IS NULL/ IS NOT NULL). Otherwise we shouldn't be using - * this index ... - */ - Assert((key->sk_flags & SK_ISNULL) || - (key->sk_collation == - TupleDescAttr(bdesc->bd_tupdesc, - keyattno - 1)->attcollation)); + /* skip attributes without any scan keys */ + if (nkeys[attno - 1] == 0) + continue; - /* First time this column? look up consistent function */ - if (consistentFn[keyattno - 1].fn_oid == InvalidOid) - { - FmgrInfo *tmp; + bval = &dtup->bt_columns[attno - 1]; - tmp = index_getprocinfo(idxRel, keyattno, - BRIN_PROCNUM_CONSISTENT); - fmgr_info_copy(&consistentFn[keyattno - 1], tmp, - CurrentMemoryContext); - } + Assert((nkeys[attno - 1] > 0) && + (nkeys[attno - 1] <= scan->numberOfKeys)); /* * Check whether the scan key is consistent with the page * range values; if so, have the pages in the range added * to the output bitmap. * - * When there are multiple scan keys, failure to meet the - * criteria for a single one of them is enough to discard - * the range as a whole, so break out of the loop as soon - * as a false return value is obtained. + * The opclass may or may not support processing of + * multiple scan keys. We can determine that based on the + * number of arguments - functions with extra parameter + * (number of scan keys) do support this, otherwise we + * have to simply pass the scan keys one by one, as + * before. */ - add = FunctionCall3Coll(&consistentFn[keyattno - 1], - key->sk_collation, - PointerGetDatum(bdesc), - PointerGetDatum(bval), - PointerGetDatum(key)); - addrange = DatumGetBool(add); + if (consistentFn[attno - 1].fn_nargs >= 4) + { + Oid collation; + + /* + * Collation from the first key (has to be the same + * for all keys for the same attribue). + */ + collation = keys[attno - 1][0]->sk_collation; + + /* Check all keys at once */ + add = FunctionCall4Coll(&consistentFn[attno - 1], + collation, + PointerGetDatum(bdesc), + PointerGetDatum(bval), + PointerGetDatum(keys[attno - 1]), + Int32GetDatum(nkeys[attno - 1])); + addrange = DatumGetBool(add); + } + else + { + /* + * Check keys one by one + * + * When there are multiple scan keys, failure to meet + * the criteria for a single one of them is enough to + * discard the range as a whole, so break out of the + * loop as soon as a false return value is obtained. + */ + int keyno; + + for (keyno = 0; keyno < nkeys[attno - 1]; keyno++) + { + add = FunctionCall3Coll(&consistentFn[attno - 1], + keys[attno - 1][keyno]->sk_collation, + PointerGetDatum(bdesc), + PointerGetDatum(bval), + PointerGetDatum(keys[attno - 1][keyno])); + addrange = DatumGetBool(add); + + /* mismatching key, no need to look further */ + if (!addrange) + break; + } + } + if (!addrange) break; } diff --git a/src/backend/access/brin/brin_validate.c b/src/backend/access/brin/brin_validate.c index 6d4253c05e..11835d85cd 100644 --- a/src/backend/access/brin/brin_validate.c +++ b/src/backend/access/brin/brin_validate.c @@ -97,8 +97,8 @@ brinvalidate(Oid opclassoid) break; case BRIN_PROCNUM_CONSISTENT: ok = check_amproc_signature(procform->amproc, BOOLOID, true, - 3, 3, INTERNALOID, INTERNALOID, - INTERNALOID); + 3, 4, INTERNALOID, INTERNALOID, + INTERNALOID, INT4OID); break; case BRIN_PROCNUM_UNION: ok = check_amproc_signature(procform->amproc, BOOLOID, true, -- 2.26.2 --------------82D677E9F94AC7574E460205 Content-Type: text/x-patch; charset=UTF-8; name="0003-Process-all-scan-keys-in-existing-BRIN-opcl-20210308.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0003-Process-all-scan-keys-in-existing-BRIN-opcl-20210308.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 8+ messages in thread
* [PATCH v2 03/14] Add smgrzeroextend(), FileZero(), FileFallocate() @ 2022-10-23 21:25 Andres Freund <[email protected]> 0 siblings, 0 replies; 8+ messages in thread From: Andres Freund @ 2022-10-23 21:25 UTC (permalink / raw) smgrzeroextend() uses FileFallocate() to efficiently extend files by multiple blocks. When extending by a small number of blocks, use FileZero() instead, as using posix_fallocate() for small numbers of blocks is inefficient for some file systems / operating systems. FileZero() is also used as the fallback for FileFallocate() on platforms / filesystems that don't support fallocate. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/include/storage/fd.h | 3 + src/include/storage/md.h | 2 + src/include/storage/smgr.h | 2 + src/backend/storage/file/fd.c | 105 ++++++++++++++++++++++++++++++++ src/backend/storage/smgr/md.c | 103 +++++++++++++++++++++++++++++++ src/backend/storage/smgr/smgr.c | 21 +++++++ 6 files changed, 236 insertions(+) diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h index f85de97d083..2c9453aa3f0 100644 --- a/src/include/storage/fd.h +++ b/src/include/storage/fd.h @@ -106,6 +106,9 @@ extern int FilePrefetch(File file, off_t offset, off_t amount, uint32 wait_event extern int FileRead(File file, void *buffer, size_t amount, off_t offset, uint32 wait_event_info); extern int FileWrite(File file, const void *buffer, size_t amount, off_t offset, uint32 wait_event_info); extern int FileSync(File file, uint32 wait_event_info); +extern int FileZero(File file, off_t offset, off_t len, uint32 wait_event_info); +extern int FileFallocate(File file, off_t offset, off_t len, uint32 wait_event_info); + extern off_t FileSize(File file); extern int FileTruncate(File file, off_t offset, uint32 wait_event_info); extern void FileWriteback(File file, off_t offset, off_t nbytes, uint32 wait_event_info); diff --git a/src/include/storage/md.h b/src/include/storage/md.h index bcada9ff221..67afd14d7b0 100644 --- a/src/include/storage/md.h +++ b/src/include/storage/md.h @@ -28,6 +28,8 @@ extern bool mdexists(SMgrRelation reln, ForkNumber forknum); extern void mdunlink(RelFileLocatorBackend rlocator, ForkNumber forknum, bool isRedo); extern void mdextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, char *buffer, bool skipFsync); +extern void mdzeroextend(SMgrRelation reln, ForkNumber forknum, + BlockNumber blocknum, int nblocks, bool skipFsync); extern bool mdprefetch(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum); extern void mdread(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, diff --git a/src/include/storage/smgr.h b/src/include/storage/smgr.h index 56233c4d216..a5806029ce1 100644 --- a/src/include/storage/smgr.h +++ b/src/include/storage/smgr.h @@ -92,6 +92,8 @@ extern void smgrdosyncall(SMgrRelation *rels, int nrels); extern void smgrdounlinkall(SMgrRelation *rels, int nrels, bool isRedo); extern void smgrextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, char *buffer, bool skipFsync); +extern void smgrzeroextend(SMgrRelation reln, ForkNumber forknum, + BlockNumber blocknum, int nblocks, bool skipFsync); extern bool smgrprefetch(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum); extern void smgrread(SMgrRelation reln, ForkNumber forknum, diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c index 926d000f2ea..afd05e48cc0 100644 --- a/src/backend/storage/file/fd.c +++ b/src/backend/storage/file/fd.c @@ -93,6 +93,7 @@ #include "common/pg_prng.h" #include "miscadmin.h" #include "pgstat.h" +#include "port/pg_iovec.h" #include "portability/mem.h" #include "postmaster/startup.h" #include "storage/fd.h" @@ -2205,6 +2206,105 @@ FileSync(File file, uint32 wait_event_info) return returnCode; } +/* So that FileZero() doesn't have to re-zero a block on every call */ +static const PGAlignedBlock zerobuf = {0}; + +int +FileZero(File file, off_t offset, off_t len, uint32 wait_event_info) +{ + int returnCode; + int numblocks; + struct iovec iov[PG_IOV_MAX]; + + /* + * FIXME: Quick-and-dirty implementation, to be replaced by + * pg_pwrite_zeros() from + * https://postgr.es/m/Y1oc%2BFjiyVjNZa%2BL%40paquier.xyz + * + * Otherwise it'd not at all be ok to rely on len being a multiple of + * BLCKSZ. + */ + Assert((len % BLCKSZ) == 0); + + Assert(FileIsValid(file)); + returnCode = FileAccess(file); + if (returnCode < 0) + return returnCode; + + numblocks = len / BLCKSZ; + + for (int i = 0; i < Min(numblocks, lengthof(iov)); ++i) + { + iov[i].iov_base = (char *) zerobuf.data; + iov[i].iov_len = BLCKSZ; + } + + while (numblocks > 0) + { + int iovcnt = Min(numblocks, lengthof(iov)); + off_t seekpos_l = offset; + ssize_t ret; + + pgstat_report_wait_start(wait_event_info); + ret = pg_pwritev_with_retry(VfdCache[file].fd, iov, iovcnt, seekpos_l); + pgstat_report_wait_end(); + + if (ret < 0) + return -1; + + Assert(ret == iovcnt * BLCKSZ); + offset += iovcnt * BLCKSZ; + numblocks -= iovcnt; + } + + return 0; +} + +/* + * Try to reserve file space with posix_fallocate(). If posix_fallocate() is + * not implemented on the operating system or fails with EINVAL / EOPNOTSUPP, + * use FileZero() instead. + * + * Note that at least glibc() implements posix_fallocate() in userspace if not + * implemented by the filesystem. That's not the case for all environments + * though. + */ +int +FileFallocate(File file, off_t offset, off_t len, uint32 wait_event_info) +{ + int returnCode; + + Assert(FileIsValid(file)); + returnCode = FileAccess(file); + if (returnCode < 0) + return returnCode; + +#ifdef HAVE_POSIX_FALLOCATE + pgstat_report_wait_start(wait_event_info); + returnCode = posix_fallocate(VfdCache[file].fd, offset, len); + 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; + + if (returnCode == 0 || + (returnCode != EINVAL && returnCode != EINVAL)) + return returnCode; +#endif + + return FileZero(file, offset, len, wait_event_info); +} + off_t FileSize(File file) { @@ -2277,6 +2377,11 @@ int FileGetRawDesc(File file) { Assert(FileIsValid(file)); + + if (FileAccess(file) < 0) + return -1; + + FileAccess(file); return VfdCache[file].fd; } diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c index 60c9905eff9..2197670f4b0 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,108 @@ mdextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, Assert(_mdnblocks(reln, forknum, v) <= ((BlockNumber) RELSEG_SIZE)); } +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 segendblock = (curblocknum % ((BlockNumber) RELSEG_SIZE)) + remblocks; + off_t seekpos = (off_t) BLCKSZ * segstartblock; + int numblocks; + + if (segendblock > RELSEG_SIZE) + segendblock = RELSEG_SIZE; + + numblocks = segendblock - segstartblock; + + v = _mdfd_getseg(reln, forknum, curblocknum, skipFsync, EXTENSION_CREATE); + + Assert(segstartblock < RELSEG_SIZE); + Assert(segendblock <= RELSEG_SIZE); + + /* + * If available use posix_fallocate() 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 shouldn't use fallocate() for small extensions, 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) + { + int ret; + + ret = FileFallocate(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\" with posix_fallocate(): %m", + FilePathName(v->mdfd_vfd)), + errhint("Check free disk space."))); + } + } + else + { + int ret; + + /* + * Even if we don't have fallocate, we can still extend a bit more + * efficiently than writing each 8kB block individually. + * FileZero() uses pg_writev[with_retry] with a single zeroed + * buffer to avoid 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 -= segendblock - segstartblock; + curblocknum += segendblock - segstartblock; + } +} + /* * mdopenfork() -- Open one fork of the specified relation. * diff --git a/src/backend/storage/smgr/smgr.c b/src/backend/storage/smgr/smgr.c index 80eb6311e74..2c0d26eabe0 100644 --- a/src/backend/storage/smgr/smgr.c +++ b/src/backend/storage/smgr/smgr.c @@ -50,6 +50,8 @@ typedef struct f_smgr bool isRedo); void (*smgr_extend) (SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, char *buffer, bool skipFsync); + void (*smgr_zeroextend) (SMgrRelation reln, ForkNumber forknum, + BlockNumber blocknum, int nblocks, bool skipFsync); bool (*smgr_prefetch) (SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum); void (*smgr_read) (SMgrRelation reln, ForkNumber forknum, @@ -75,6 +77,7 @@ static const f_smgr smgrsw[] = { .smgr_exists = mdexists, .smgr_unlink = mdunlink, .smgr_extend = mdextend, + .smgr_zeroextend = mdzeroextend, .smgr_prefetch = mdprefetch, .smgr_read = mdread, .smgr_write = mdwrite, @@ -507,6 +510,24 @@ smgrextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, reln->smgr_cached_nblocks[forknum] = InvalidBlockNumber; } +void +smgrzeroextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, + int nblocks, bool skipFsync) +{ + smgrsw[reln->smgr_which].smgr_zeroextend(reln, forknum, blocknum, + nblocks, skipFsync); + + /* + * Normally we expect this to increase nblocks by nblocks, but if the + * cached value isn't as expected, just invalidate it so the next call + * asks the kernel. + */ + if (reln->smgr_cached_nblocks[forknum] == blocknum) + reln->smgr_cached_nblocks[forknum] = blocknum + nblocks; + else + reln->smgr_cached_nblocks[forknum] = InvalidBlockNumber; +} + /* * smgrprefetch() -- Initiate asynchronous read of the specified block of a relation. * -- 2.38.0 --wsjhb5zouqhvcv7q Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v2-0004-bufmgr-Add-Pin-UnpinLocalBuffer.patch" ^ permalink raw reply [nested|flat] 8+ messages in thread
* [PATCH v1 04/12] Add smgrzeroextend(), FileZero(), FileFallocate() @ 2022-10-23 21:25 Andres Freund <[email protected]> 0 siblings, 0 replies; 8+ messages in thread From: Andres Freund @ 2022-10-23 21:25 UTC (permalink / raw) smgrzeroextend() uses FileFallocate() to efficiently extend files by multiple blocks. When extending by a small number of blocks, use FileZero() instead, as using posix_fallocate() for small numbers of blocks is inefficient for some file systems / operating systems. FileZero() is also used as the fallback for FileFallocate() on platforms / filesystems that don't support fallocate. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/include/storage/fd.h | 3 + src/include/storage/md.h | 2 + src/include/storage/smgr.h | 2 + src/backend/storage/file/fd.c | 105 ++++++++++++++++++++++++++++++++ src/backend/storage/smgr/md.c | 103 +++++++++++++++++++++++++++++++ src/backend/storage/smgr/smgr.c | 21 +++++++ 6 files changed, 236 insertions(+) diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h index c0a212487d9..3d309edb152 100644 --- a/src/include/storage/fd.h +++ b/src/include/storage/fd.h @@ -106,6 +106,9 @@ extern int FilePrefetch(File file, off_t offset, int amount, uint32 wait_event_i extern int FileRead(File file, char *buffer, int amount, off_t offset, uint32 wait_event_info); extern int FileWrite(File file, char *buffer, int amount, off_t offset, uint32 wait_event_info); extern int FileSync(File file, uint32 wait_event_info); +extern int FileZero(File file, off_t offset, off_t len, uint32 wait_event_info); +extern int FileFallocate(File file, off_t offset, off_t len, uint32 wait_event_info); + extern off_t FileSize(File file); extern int FileTruncate(File file, off_t offset, uint32 wait_event_info); extern void FileWriteback(File file, off_t offset, off_t nbytes, uint32 wait_event_info); diff --git a/src/include/storage/md.h b/src/include/storage/md.h index 10aa1b0109b..d0597f8a575 100644 --- a/src/include/storage/md.h +++ b/src/include/storage/md.h @@ -28,6 +28,8 @@ extern bool mdexists(SMgrRelation reln, ForkNumber forknum); extern void mdunlink(RelFileLocatorBackend rlocator, ForkNumber forknum, bool isRedo); extern void mdextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, char *buffer, bool skipFsync); +extern void mdzeroextend(SMgrRelation reln, ForkNumber forknum, + BlockNumber blocknum, int nblocks, bool skipFsync); extern bool mdprefetch(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum); extern void mdread(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, diff --git a/src/include/storage/smgr.h b/src/include/storage/smgr.h index a07715356ba..503310e82ba 100644 --- a/src/include/storage/smgr.h +++ b/src/include/storage/smgr.h @@ -92,6 +92,8 @@ extern void smgrdosyncall(SMgrRelation *rels, int nrels); extern void smgrdounlinkall(SMgrRelation *rels, int nrels, bool isRedo); extern void smgrextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, char *buffer, bool skipFsync); +extern void smgrzeroextend(SMgrRelation reln, ForkNumber forknum, + BlockNumber blocknum, int nblocks, bool skipFsync); extern bool smgrprefetch(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum); extern void smgrread(SMgrRelation reln, ForkNumber forknum, diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c index 4151cafec54..47b620649aa 100644 --- a/src/backend/storage/file/fd.c +++ b/src/backend/storage/file/fd.c @@ -93,6 +93,7 @@ #include "common/pg_prng.h" #include "miscadmin.h" #include "pgstat.h" +#include "port/pg_iovec.h" #include "portability/mem.h" #include "postmaster/startup.h" #include "storage/fd.h" @@ -2205,6 +2206,105 @@ FileSync(File file, uint32 wait_event_info) return returnCode; } +/* So that FileZero() doesn't have to re-zero a block on every call */ +static const PGAlignedBlock zerobuf = {0}; + +int +FileZero(File file, off_t offset, off_t len, uint32 wait_event_info) +{ + int returnCode; + int numblocks; + struct iovec iov[PG_IOV_MAX]; + + /* + * FIXME: Quick-and-dirty implementation, to be replaced by + * pg_pwrite_zeros() from + * https://postgr.es/m/Y1oc%2BFjiyVjNZa%2BL%40paquier.xyz + * + * Otherwise it'd not at all be ok to rely on len being a multiple of + * BLCKSZ. + */ + Assert((len % BLCKSZ) == 0); + + Assert(FileIsValid(file)); + returnCode = FileAccess(file); + if (returnCode < 0) + return returnCode; + + numblocks = len / BLCKSZ; + + for (int i = 0; i < Min(numblocks, lengthof(iov)); ++i) + { + iov[i].iov_base = (char *) zerobuf.data; + iov[i].iov_len = BLCKSZ; + } + + while (numblocks > 0) + { + int iovcnt = Min(numblocks, lengthof(iov)); + off_t seekpos_l = offset; + ssize_t ret; + + pgstat_report_wait_start(wait_event_info); + ret = pg_pwritev_with_retry(VfdCache[file].fd, iov, iovcnt, seekpos_l); + pgstat_report_wait_end(); + + if (ret < 0) + return -1; + + Assert(ret == iovcnt * BLCKSZ); + offset += iovcnt * BLCKSZ; + numblocks -= iovcnt; + } + + return 0; +} + +/* + * Try to reserve file space with posix_fallocate(). If posix_fallocate() is + * not implemented on the operating system or fails with EINVAL / EOPNOTSUPP, + * use FileZero() instead. + * + * Note that at least glibc() implements posix_fallocate() in userspace if not + * implemented by the filesystem. That's not the case for all environments + * though. + */ +int +FileFallocate(File file, off_t offset, off_t len, uint32 wait_event_info) +{ + int returnCode; + + Assert(FileIsValid(file)); + returnCode = FileAccess(file); + if (returnCode < 0) + return returnCode; + +#ifdef HAVE_POSIX_FALLOCATE + pgstat_report_wait_start(wait_event_info); + returnCode = posix_fallocate(VfdCache[file].fd, offset, len); + 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; + + if (returnCode == 0 || + (returnCode != EINVAL && returnCode != EINVAL)) + return returnCode; +#endif + + return FileZero(file, offset, len, wait_event_info); +} + off_t FileSize(File file) { @@ -2277,6 +2377,11 @@ int FileGetRawDesc(File file) { Assert(FileIsValid(file)); + + if (FileAccess(file) < 0) + return -1; + + FileAccess(file); return VfdCache[file].fd; } diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c index a515bb36ac1..eee3cae7c4e 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" @@ -486,6 +487,108 @@ mdextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, Assert(_mdnblocks(reln, forknum, v) <= ((BlockNumber) RELSEG_SIZE)); } +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 segendblock = (curblocknum % ((BlockNumber) RELSEG_SIZE)) + remblocks; + off_t seekpos = (off_t) BLCKSZ * segstartblock; + int numblocks; + + if (segendblock > RELSEG_SIZE) + segendblock = RELSEG_SIZE; + + numblocks = segendblock - segstartblock; + + v = _mdfd_getseg(reln, forknum, curblocknum, skipFsync, EXTENSION_CREATE); + + Assert(segstartblock < RELSEG_SIZE); + Assert(segendblock <= RELSEG_SIZE); + + /* + * If available use posix_fallocate() 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 shouldn't use fallocate() for small extensions, 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) + { + int ret; + + ret = FileFallocate(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\" with posix_fallocate(): %m", + FilePathName(v->mdfd_vfd)), + errhint("Check free disk space."))); + } + } + else + { + int ret; + + /* + * Even if we don't have fallocate, we can still extend a bit more + * efficiently than writing each 8kB block individually. + * FileZero() uses pg_writev[with_retry] with a single zeroed + * buffer to avoid 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 -= segendblock - segstartblock; + curblocknum += segendblock - segstartblock; + } +} + /* * mdopenfork() -- Open one fork of the specified relation. * diff --git a/src/backend/storage/smgr/smgr.c b/src/backend/storage/smgr/smgr.c index c1a5febcbfd..6fb693cb062 100644 --- a/src/backend/storage/smgr/smgr.c +++ b/src/backend/storage/smgr/smgr.c @@ -50,6 +50,8 @@ typedef struct f_smgr bool isRedo); void (*smgr_extend) (SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, char *buffer, bool skipFsync); + void (*smgr_zeroextend) (SMgrRelation reln, ForkNumber forknum, + BlockNumber blocknum, int nblocks, bool skipFsync); bool (*smgr_prefetch) (SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum); void (*smgr_read) (SMgrRelation reln, ForkNumber forknum, @@ -75,6 +77,7 @@ static const f_smgr smgrsw[] = { .smgr_exists = mdexists, .smgr_unlink = mdunlink, .smgr_extend = mdextend, + .smgr_zeroextend = mdzeroextend, .smgr_prefetch = mdprefetch, .smgr_read = mdread, .smgr_write = mdwrite, @@ -507,6 +510,24 @@ smgrextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, reln->smgr_cached_nblocks[forknum] = InvalidBlockNumber; } +void +smgrzeroextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, + int nblocks, bool skipFsync) +{ + smgrsw[reln->smgr_which].smgr_zeroextend(reln, forknum, blocknum, + nblocks, skipFsync); + + /* + * Normally we expect this to increase nblocks by nblocks, but if the + * cached value isn't as expected, just invalidate it so the next call + * asks the kernel. + */ + if (reln->smgr_cached_nblocks[forknum] == blocknum) + reln->smgr_cached_nblocks[forknum] = blocknum + nblocks; + else + reln->smgr_cached_nblocks[forknum] = InvalidBlockNumber; +} + /* * smgrprefetch() -- Initiate asynchronous read of the specified block of a relation. * -- 2.38.0 --pdbwq5asyrs2p56f Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v1-0005-bufmgr-Add-Pin-UnpinLocalBuffer.patch" ^ permalink raw reply [nested|flat] 8+ messages in thread
* [PATCH v5 04/15] Add smgrzeroextend(), FileZero(), FileFallocate() @ 2023-02-28 01:36 Andres Freund <[email protected]> 0 siblings, 0 replies; 8+ messages in thread From: Andres Freund @ 2023-02-28 01:36 UTC (permalink / raw) 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. ^ permalink raw reply [nested|flat] 8+ messages in thread
* [PATCH v6 06/17] Add smgrzeroextend(), FileZero(), FileFallocate() @ 2023-02-28 01:36 Andres Freund <[email protected]> 0 siblings, 0 replies; 8+ messages in thread From: Andres Freund @ 2023-02-28 01:36 UTC (permalink / raw) smgrzeroextend() uses FileFallocate() to efficiently extend files by multiple blocks. When extending by a small number of blocks, use FileZero() instead, as using posix_fallocate() for small numbers of blocks is inefficient for some file systems / operating systems. FileZero() is also used as the fallback for FileFallocate() on platforms / filesystems that don't support fallocate. Reviewed-by: Melanie Plageman <[email protected]> Reviewed-by: Heikki Linnakangas <[email protected]> Reviewed-by: Kyotaro Horiguchi <[email protected]> Reviewed-by: David Rowley <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/storage/fd.h | 3 + src/include/storage/md.h | 2 + src/include/storage/smgr.h | 2 + src/backend/storage/file/fd.c | 89 ++++++++++++++++++++++++++ src/backend/storage/smgr/md.c | 108 ++++++++++++++++++++++++++++++++ src/backend/storage/smgr/smgr.c | 28 +++++++++ 6 files changed, 232 insertions(+) diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h index f85de97d083..daceafd4732 100644 --- a/src/include/storage/fd.h +++ b/src/include/storage/fd.h @@ -106,6 +106,9 @@ extern int FilePrefetch(File file, off_t offset, off_t amount, uint32 wait_event extern int FileRead(File file, void *buffer, size_t amount, off_t offset, uint32 wait_event_info); extern int FileWrite(File file, const void *buffer, size_t amount, off_t offset, uint32 wait_event_info); extern int FileSync(File file, uint32 wait_event_info); +extern int FileZero(File file, off_t offset, off_t amount, uint32 wait_event_info); +extern int FileFallocate(File file, off_t offset, off_t amount, uint32 wait_event_info); + extern off_t FileSize(File file); extern int FileTruncate(File file, off_t offset, uint32 wait_event_info); extern void FileWriteback(File file, off_t offset, off_t nbytes, uint32 wait_event_info); diff --git a/src/include/storage/md.h b/src/include/storage/md.h index 8f32af9ef3d..941879ee6a8 100644 --- a/src/include/storage/md.h +++ b/src/include/storage/md.h @@ -28,6 +28,8 @@ extern bool mdexists(SMgrRelation reln, ForkNumber forknum); extern void mdunlink(RelFileLocatorBackend rlocator, ForkNumber forknum, bool isRedo); extern void mdextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, const void *buffer, bool skipFsync); +extern void mdzeroextend(SMgrRelation reln, ForkNumber forknum, + BlockNumber blocknum, int nblocks, bool skipFsync); extern bool mdprefetch(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum); extern void mdread(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, diff --git a/src/include/storage/smgr.h b/src/include/storage/smgr.h index 0935144f425..a9a179aabac 100644 --- a/src/include/storage/smgr.h +++ b/src/include/storage/smgr.h @@ -92,6 +92,8 @@ extern void smgrdosyncall(SMgrRelation *rels, int nrels); extern void smgrdounlinkall(SMgrRelation *rels, int nrels, bool isRedo); extern void smgrextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, const void *buffer, bool skipFsync); +extern void smgrzeroextend(SMgrRelation reln, ForkNumber forknum, + BlockNumber blocknum, int nblocks, bool skipFsync); extern bool smgrprefetch(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum); extern void smgrread(SMgrRelation reln, ForkNumber forknum, diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c index 2ac365e97cc..9eabdbc589e 100644 --- a/src/backend/storage/file/fd.c +++ b/src/backend/storage/file/fd.c @@ -2206,6 +2206,94 @@ 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)); + + DO_DB(elog(LOG, "FileZero: %d (%s) " INT64_FORMAT " " INT64_FORMAT, + file, VfdCache[file].fileName, + (int64) offset, (int64) amount)); + + 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) + { + /* if errno is unset, assume problem is no disk space */ + if (errno == 0) + errno = ENOSPC; + return -1; + } + + return 0; +} + +/* + * Try to reserve file space with posix_fallocate(). If posix_fallocate() is + * not implemented on the operating system or fails with EINVAL / EOPNOTSUPP, + * use FileZero() instead. + * + * Note that at least glibc() implements posix_fallocate() in userspace if not + * implemented by the filesystem. That's not the case for all environments + * though. + * + * Returns 0 on success, -1 otherwise. In the latter case errno is set to the + * appropriate error. + */ +int +FileFallocate(File file, off_t offset, off_t amount, uint32 wait_event_info) +{ +#ifdef HAVE_POSIX_FALLOCATE + int returnCode; + + Assert(FileIsValid(file)); + + DO_DB(elog(LOG, "FileFallocate: %d (%s) " INT64_FORMAT " " INT64_FORMAT, + file, VfdCache[file].fileName, + (int64) offset, (int64) amount)); + + returnCode = FileAccess(file); + if (returnCode < 0) + return -1; + + 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 -1; +#endif + + return FileZero(file, offset, amount, wait_event_info); +} + off_t FileSize(File file) { @@ -2278,6 +2366,7 @@ int FileGetRawDesc(File file) { Assert(FileIsValid(file)); + return VfdCache[file].fd; } diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c index 352958e1feb..51e1599dcf4 100644 --- a/src/backend/storage/smgr/md.c +++ b/src/backend/storage/smgr/md.c @@ -500,6 +500,114 @@ mdextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, Assert(_mdnblocks(reln, forknum, v) <= ((BlockNumber) RELSEG_SIZE)); } +/* + * mdzeroextend() -- Add new zeroed out blocks to the specified relation. + * + * Similar to mdextend(), except the relation can be extended by multiple + * blocks at once and the added blocks will be filled with zeroes. + */ +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) + { + BlockNumber segstartblock = curblocknum % ((BlockNumber) RELSEG_SIZE); + off_t seekpos = (off_t) BLCKSZ * segstartblock; + int numblocks; + + if (segstartblock + remblocks > RELSEG_SIZE) + numblocks = RELSEG_SIZE - segstartblock; + else + numblocks = remblocks; + + v = _mdfd_getseg(reln, forknum, curblocknum, skipFsync, EXTENSION_CREATE); + + Assert(segstartblock < RELSEG_SIZE); + Assert(segstartblock + numblocks <= RELSEG_SIZE); + + /* + * 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) + { + int ret; + + ret = FileFallocate(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\" with posix_fallocate(): %m", + FilePathName(v->mdfd_vfd)), + errhint("Check free disk space.")); + } + } + else + { + int ret; + + /* + * 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; + } +} + /* * mdopenfork() -- Open one fork of the specified relation. * diff --git a/src/backend/storage/smgr/smgr.c b/src/backend/storage/smgr/smgr.c index dc466e54145..c37c246b77f 100644 --- a/src/backend/storage/smgr/smgr.c +++ b/src/backend/storage/smgr/smgr.c @@ -50,6 +50,8 @@ typedef struct f_smgr bool isRedo); void (*smgr_extend) (SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, const void *buffer, bool skipFsync); + void (*smgr_zeroextend) (SMgrRelation reln, ForkNumber forknum, + BlockNumber blocknum, int nblocks, bool skipFsync); bool (*smgr_prefetch) (SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum); void (*smgr_read) (SMgrRelation reln, ForkNumber forknum, @@ -75,6 +77,7 @@ static const f_smgr smgrsw[] = { .smgr_exists = mdexists, .smgr_unlink = mdunlink, .smgr_extend = mdextend, + .smgr_zeroextend = mdzeroextend, .smgr_prefetch = mdprefetch, .smgr_read = mdread, .smgr_write = mdwrite, @@ -507,6 +510,31 @@ smgrextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, reln->smgr_cached_nblocks[forknum] = InvalidBlockNumber; } +/* + * smgrzeroextend() -- Add new zeroed out blocks to a file. + * + * Similar to smgrextend(), except the relation can be extended by + * multiple blocks at once and the added blocks will be filled with + * zeroes. + */ +void +smgrzeroextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, + int nblocks, bool skipFsync) +{ + smgrsw[reln->smgr_which].smgr_zeroextend(reln, forknum, blocknum, + nblocks, skipFsync); + + /* + * Normally we expect this to increase the fork size by nblocks, but if + * the cached value isn't as expected, just invalidate it so the next call + * asks the kernel. + */ + if (reln->smgr_cached_nblocks[forknum] == blocknum) + reln->smgr_cached_nblocks[forknum] = blocknum + nblocks; + else + reln->smgr_cached_nblocks[forknum] = InvalidBlockNumber; +} + /* * smgrprefetch() -- Initiate asynchronous read of the specified block of a relation. * -- 2.38.0 --cfn72vqnlbycypta Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v6-0007-bufmgr-Add-Pin-UnpinLocalBuffer.patch" ^ permalink raw reply [nested|flat] 8+ messages in thread
* [PATCH v5 03/14] Add smgrzeroextend(), FileZero(), FileFallocate() @ 2023-02-28 01:36 Andres Freund <[email protected]> 0 siblings, 0 replies; 8+ messages in thread From: Andres Freund @ 2023-02-28 01:36 UTC (permalink / raw) smgrzeroextend() uses FileFallocate() to efficiently extend files by multiple blocks. When extending by a small number of blocks, use FileZero() instead, as using posix_fallocate() for small numbers of blocks is inefficient for some file systems / operating systems. FileZero() is also used as the fallback for FileFallocate() on platforms / filesystems that don't support fallocate. Author: Reviewed-by: Discussion: https://postgr.es/m/[email protected] Backpatch: --- src/include/storage/fd.h | 3 + src/include/storage/md.h | 2 + src/include/storage/smgr.h | 2 + src/backend/storage/file/fd.c | 90 ++++++++++++++++++++++++++ src/backend/storage/smgr/md.c | 111 ++++++++++++++++++++++++++++++++ src/backend/storage/smgr/smgr.c | 28 ++++++++ 6 files changed, 236 insertions(+) diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h index f85de97d083..daceafd4732 100644 --- a/src/include/storage/fd.h +++ b/src/include/storage/fd.h @@ -106,6 +106,9 @@ extern int FilePrefetch(File file, off_t offset, off_t amount, uint32 wait_event extern int FileRead(File file, void *buffer, size_t amount, off_t offset, uint32 wait_event_info); extern int FileWrite(File file, const void *buffer, size_t amount, off_t offset, uint32 wait_event_info); extern int FileSync(File file, uint32 wait_event_info); +extern int FileZero(File file, off_t offset, off_t amount, uint32 wait_event_info); +extern int FileFallocate(File file, off_t offset, off_t amount, uint32 wait_event_info); + extern off_t FileSize(File file); extern int FileTruncate(File file, off_t offset, uint32 wait_event_info); extern void FileWriteback(File file, off_t offset, off_t nbytes, uint32 wait_event_info); diff --git a/src/include/storage/md.h b/src/include/storage/md.h index 8f32af9ef3d..941879ee6a8 100644 --- a/src/include/storage/md.h +++ b/src/include/storage/md.h @@ -28,6 +28,8 @@ extern bool mdexists(SMgrRelation reln, ForkNumber forknum); extern void mdunlink(RelFileLocatorBackend rlocator, ForkNumber forknum, bool isRedo); extern void mdextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, const void *buffer, bool skipFsync); +extern void mdzeroextend(SMgrRelation reln, ForkNumber forknum, + BlockNumber blocknum, int nblocks, bool skipFsync); extern bool mdprefetch(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum); extern void mdread(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, diff --git a/src/include/storage/smgr.h b/src/include/storage/smgr.h index 0935144f425..a9a179aabac 100644 --- a/src/include/storage/smgr.h +++ b/src/include/storage/smgr.h @@ -92,6 +92,8 @@ extern void smgrdosyncall(SMgrRelation *rels, int nrels); extern void smgrdounlinkall(SMgrRelation *rels, int nrels, bool isRedo); extern void smgrextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, const void *buffer, bool skipFsync); +extern void smgrzeroextend(SMgrRelation reln, ForkNumber forknum, + BlockNumber blocknum, int nblocks, bool skipFsync); extern bool smgrprefetch(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum); extern void smgrread(SMgrRelation reln, ForkNumber forknum, diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c index 9fd8444ed4d..c34ed41d52e 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) + { + /* if errno is unset, assume problem is no disk space */ + if (errno == 0) + errno = ENOSPC; + return -1; + } + + return 0; +} + +/* + * Try to reserve file space with posix_fallocate(). If posix_fallocate() is + * not implemented on the operating system or fails with EINVAL / EOPNOTSUPP, + * use FileZero() instead. + * + * Note that at least glibc() implements posix_fallocate() in userspace if not + * implemented by the filesystem. That's not the case for all environments + * though. + * + * Returns 0 on success, -1 otherwise. In the latter case errno is set to the + * appropriate error. + * + * Even though posix_fallocate has the offset before the length argument, it + * seems better to keep the argument order consistent with most of the other + * functions in this file. + */ +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; + + if (returnCode == 0 || + (returnCode != EINVAL && returnCode != EINVAL)) + return returnCode; +#endif + + return FileZero(file, offset, amount, wait_event_info); +} + off_t FileSize(File file) { @@ -2278,6 +2364,10 @@ int FileGetRawDesc(File file) { Assert(FileIsValid(file)); + + if (FileAccess(file) < 0) + return -1; + return VfdCache[file].fd; } diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c index 352958e1feb..59a65a8305c 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. + * + * Similar to mdrextend(), except the relation can be extended by + * multiple blocks at once, and that the added blocks will be filled with + * zeroes. + */ +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 segendblock = (curblocknum % ((BlockNumber) RELSEG_SIZE)) + remblocks; + off_t seekpos = (off_t) BLCKSZ * segstartblock; + int numblocks; + + if (segendblock > RELSEG_SIZE) + segendblock = RELSEG_SIZE; + + numblocks = segendblock - segstartblock; + + v = _mdfd_getseg(reln, forknum, curblocknum, skipFsync, EXTENSION_CREATE); + + Assert(segstartblock < RELSEG_SIZE); + Assert(segendblock <= RELSEG_SIZE); + + /* + * 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) + { + int ret; + + ret = FileFallocate(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\" with posix_fallocate(): %m", + FilePathName(v->mdfd_vfd)), + errhint("Check free disk space.")); + } + } + else + { + int ret; + + /* + * 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 -= segendblock - segstartblock; + curblocknum += segendblock - segstartblock; + } +} + /* * mdopenfork() -- Open one fork of the specified relation. * diff --git a/src/backend/storage/smgr/smgr.c b/src/backend/storage/smgr/smgr.c index dc466e54145..5224ca52592 100644 --- a/src/backend/storage/smgr/smgr.c +++ b/src/backend/storage/smgr/smgr.c @@ -50,6 +50,8 @@ typedef struct f_smgr bool isRedo); void (*smgr_extend) (SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, const void *buffer, bool skipFsync); + void (*smgr_zeroextend) (SMgrRelation reln, ForkNumber forknum, + BlockNumber blocknum, int nblocks, bool skipFsync); bool (*smgr_prefetch) (SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum); void (*smgr_read) (SMgrRelation reln, ForkNumber forknum, @@ -75,6 +77,7 @@ static const f_smgr smgrsw[] = { .smgr_exists = mdexists, .smgr_unlink = mdunlink, .smgr_extend = mdextend, + .smgr_zeroextend = mdzeroextend, .smgr_prefetch = mdprefetch, .smgr_read = mdread, .smgr_write = mdwrite, @@ -507,6 +510,31 @@ smgrextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, reln->smgr_cached_nblocks[forknum] = InvalidBlockNumber; } +/* + * 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. + */ +void +smgrzeroextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, + int nblocks, bool skipFsync) +{ + smgrsw[reln->smgr_which].smgr_zeroextend(reln, forknum, blocknum, + nblocks, skipFsync); + + /* + * Normally we expect this to increase the fork size by nblocks, but if + * the cached value isn't as expected, just invalidate it so the next call + * asks the kernel. + */ + if (reln->smgr_cached_nblocks[forknum] == blocknum) + reln->smgr_cached_nblocks[forknum] = blocknum + nblocks; + else + reln->smgr_cached_nblocks[forknum] = InvalidBlockNumber; +} + /* * smgrprefetch() -- Initiate asynchronous read of the specified block of a relation. * -- 2.38.0 --wwosng5vofi3dmcs Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v5-0004-bufmgr-Add-Pin-UnpinLocalBuffer.patch" ^ permalink raw reply [nested|flat] 8+ messages in thread
* [PATCH v4 04/15] Add smgrzeroextend(), FileZero(), FileFallocate() @ 2023-02-28 01:36 Andres Freund <[email protected]> 0 siblings, 0 replies; 8+ messages in thread From: Andres Freund @ 2023-02-28 01:36 UTC (permalink / raw) smgrzeroextend() uses FileFallocate() to efficiently extend files by multiple blocks. When extending by a small number of blocks, use FileZero() instead, as using posix_fallocate() for small numbers of blocks is inefficient for some file systems / operating systems. FileZero() is also used as the fallback for FileFallocate() on platforms / filesystems that don't support fallocate. Author: Reviewed-by: Discussion: https://postgr.es/m/[email protected] Backpatch: --- src/include/storage/fd.h | 3 + src/include/storage/md.h | 2 + src/include/storage/smgr.h | 2 + src/backend/storage/file/fd.c | 90 ++++++++++++++++++++++++++ src/backend/storage/smgr/md.c | 111 ++++++++++++++++++++++++++++++++ src/backend/storage/smgr/smgr.c | 28 ++++++++ 6 files changed, 236 insertions(+) diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h index f85de97d083..5f300a89f62 100644 --- a/src/include/storage/fd.h +++ b/src/include/storage/fd.h @@ -106,6 +106,9 @@ extern int FilePrefetch(File file, off_t offset, off_t amount, uint32 wait_event extern int FileRead(File file, void *buffer, size_t amount, off_t offset, uint32 wait_event_info); extern int FileWrite(File file, const void *buffer, size_t amount, off_t offset, uint32 wait_event_info); extern int FileSync(File file, uint32 wait_event_info); +extern int FileZero(File file, off_t amount, off_t offset, uint32 wait_event_info); +extern int FileFallocate(File file, off_t amount, off_t offset, uint32 wait_event_info); + extern off_t FileSize(File file); extern int FileTruncate(File file, off_t offset, uint32 wait_event_info); extern void FileWriteback(File file, off_t offset, off_t nbytes, uint32 wait_event_info); diff --git a/src/include/storage/md.h b/src/include/storage/md.h index 8f32af9ef3d..941879ee6a8 100644 --- a/src/include/storage/md.h +++ b/src/include/storage/md.h @@ -28,6 +28,8 @@ extern bool mdexists(SMgrRelation reln, ForkNumber forknum); extern void mdunlink(RelFileLocatorBackend rlocator, ForkNumber forknum, bool isRedo); extern void mdextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, const void *buffer, bool skipFsync); +extern void mdzeroextend(SMgrRelation reln, ForkNumber forknum, + BlockNumber blocknum, int nblocks, bool skipFsync); extern bool mdprefetch(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum); extern void mdread(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, diff --git a/src/include/storage/smgr.h b/src/include/storage/smgr.h index 0935144f425..a9a179aabac 100644 --- a/src/include/storage/smgr.h +++ b/src/include/storage/smgr.h @@ -92,6 +92,8 @@ extern void smgrdosyncall(SMgrRelation *rels, int nrels); extern void smgrdounlinkall(SMgrRelation *rels, int nrels, bool isRedo); extern void smgrextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, const void *buffer, bool skipFsync); +extern void smgrzeroextend(SMgrRelation reln, ForkNumber forknum, + BlockNumber blocknum, int nblocks, bool skipFsync); extern bool smgrprefetch(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum); extern void smgrread(SMgrRelation reln, ForkNumber forknum, diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c index ea690f05c69..77d00296a3a 100644 --- a/src/backend/storage/file/fd.c +++ b/src/backend/storage/file/fd.c @@ -2198,6 +2198,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) + { + /* if errno is unset, assume problem is no disk space */ + if (errno == 0) + errno = ENOSPC; + return -1; + } + + return 0; +} + +/* + * Try to reserve file space with posix_fallocate(). If posix_fallocate() is + * not implemented on the operating system or fails with EINVAL / EOPNOTSUPP, + * use FileZero() instead. + * + * Note that at least glibc() implements posix_fallocate() in userspace if not + * implemented by the filesystem. That's not the case for all environments + * though. + * + * Returns 0 on success, -1 otherwise. In the latter case errno is set to the + * appropriate error. + * + * Even though posix_fallocate has the offset before the length argument, it + * seems better to keep the argument order consistent with most of the other + * functions in this file. + */ +int +FileFallocate(File file, off_t amount, off_t offset, 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; + + if (returnCode == 0 || + (returnCode != EINVAL && returnCode != EINVAL)) + return returnCode; +#endif + + return FileZero(file, amount, offset, wait_event_info); +} + off_t FileSize(File file) { @@ -2270,6 +2356,10 @@ int FileGetRawDesc(File file) { Assert(FileIsValid(file)); + + if (FileAccess(file) < 0) + return -1; + return VfdCache[file].fd; } diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c index 352958e1feb..59a65a8305c 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. + * + * Similar to mdrextend(), except the relation can be extended by + * multiple blocks at once, and that the added blocks will be filled with + * zeroes. + */ +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 segendblock = (curblocknum % ((BlockNumber) RELSEG_SIZE)) + remblocks; + off_t seekpos = (off_t) BLCKSZ * segstartblock; + int numblocks; + + if (segendblock > RELSEG_SIZE) + segendblock = RELSEG_SIZE; + + numblocks = segendblock - segstartblock; + + v = _mdfd_getseg(reln, forknum, curblocknum, skipFsync, EXTENSION_CREATE); + + Assert(segstartblock < RELSEG_SIZE); + Assert(segendblock <= RELSEG_SIZE); + + /* + * 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) + { + int ret; + + ret = FileFallocate(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\" with posix_fallocate(): %m", + FilePathName(v->mdfd_vfd)), + errhint("Check free disk space.")); + } + } + else + { + int ret; + + /* + * 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 -= segendblock - segstartblock; + curblocknum += segendblock - segstartblock; + } +} + /* * mdopenfork() -- Open one fork of the specified relation. * diff --git a/src/backend/storage/smgr/smgr.c b/src/backend/storage/smgr/smgr.c index dc466e54145..5224ca52592 100644 --- a/src/backend/storage/smgr/smgr.c +++ b/src/backend/storage/smgr/smgr.c @@ -50,6 +50,8 @@ typedef struct f_smgr bool isRedo); void (*smgr_extend) (SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, const void *buffer, bool skipFsync); + void (*smgr_zeroextend) (SMgrRelation reln, ForkNumber forknum, + BlockNumber blocknum, int nblocks, bool skipFsync); bool (*smgr_prefetch) (SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum); void (*smgr_read) (SMgrRelation reln, ForkNumber forknum, @@ -75,6 +77,7 @@ static const f_smgr smgrsw[] = { .smgr_exists = mdexists, .smgr_unlink = mdunlink, .smgr_extend = mdextend, + .smgr_zeroextend = mdzeroextend, .smgr_prefetch = mdprefetch, .smgr_read = mdread, .smgr_write = mdwrite, @@ -507,6 +510,31 @@ smgrextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, reln->smgr_cached_nblocks[forknum] = InvalidBlockNumber; } +/* + * 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. + */ +void +smgrzeroextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, + int nblocks, bool skipFsync) +{ + smgrsw[reln->smgr_which].smgr_zeroextend(reln, forknum, blocknum, + nblocks, skipFsync); + + /* + * Normally we expect this to increase the fork size by nblocks, but if + * the cached value isn't as expected, just invalidate it so the next call + * asks the kernel. + */ + if (reln->smgr_cached_nblocks[forknum] == blocknum) + reln->smgr_cached_nblocks[forknum] = blocknum + nblocks; + else + reln->smgr_cached_nblocks[forknum] = InvalidBlockNumber; +} + /* * smgrprefetch() -- Initiate asynchronous read of the specified block of a relation. * -- 2.38.0 --njltjzibte523gwd Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v4-0005-bufmgr-Add-Pin-UnpinLocalBuffer.patch" ^ permalink raw reply [nested|flat] 8+ messages in thread
* [PATCH v7 02/14] Add smgrzeroextend(), FileZero(), FileFallocate() @ 2023-02-28 01:36 Andres Freund <[email protected]> 0 siblings, 0 replies; 8+ messages in thread From: Andres Freund @ 2023-02-28 01:36 UTC (permalink / raw) smgrzeroextend() uses FileFallocate() to efficiently extend files by multiple blocks. When extending by a small number of blocks, use FileZero() instead, as using posix_fallocate() for small numbers of blocks is inefficient for some file systems / operating systems. FileZero() is also used as the fallback for FileFallocate() on platforms / filesystems that don't support fallocate. A big advantage of using posix_fallocate() is that it typically won't cause dirty buffers in the kernel pagecache. So far the most common pattern in our code is that we smgrextend() a page full of zeroes and put the corresponding page into shared buffers, from where we later write out the actual contents of the page. If the kernel, e.g. due to memory pressure or elapsed time, already wrote back the all-zeroes page, this can lead to doubling the amount of writes reaching storage. There are no users of the facility as of this commit. That will follow in future commit. Reviewed-by: Melanie Plageman <[email protected]> Reviewed-by: Heikki Linnakangas <[email protected]> Reviewed-by: Kyotaro Horiguchi <[email protected]> Reviewed-by: David Rowley <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/include/storage/fd.h | 3 + src/include/storage/md.h | 2 + src/include/storage/smgr.h | 2 + src/backend/storage/file/fd.c | 89 ++++++++++++++++++++++++++ src/backend/storage/smgr/md.c | 108 ++++++++++++++++++++++++++++++++ src/backend/storage/smgr/smgr.c | 28 +++++++++ 6 files changed, 232 insertions(+) diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h index f85de97d083..daceafd4732 100644 --- a/src/include/storage/fd.h +++ b/src/include/storage/fd.h @@ -106,6 +106,9 @@ extern int FilePrefetch(File file, off_t offset, off_t amount, uint32 wait_event extern int FileRead(File file, void *buffer, size_t amount, off_t offset, uint32 wait_event_info); extern int FileWrite(File file, const void *buffer, size_t amount, off_t offset, uint32 wait_event_info); extern int FileSync(File file, uint32 wait_event_info); +extern int FileZero(File file, off_t offset, off_t amount, uint32 wait_event_info); +extern int FileFallocate(File file, off_t offset, off_t amount, uint32 wait_event_info); + extern off_t FileSize(File file); extern int FileTruncate(File file, off_t offset, uint32 wait_event_info); extern void FileWriteback(File file, off_t offset, off_t nbytes, uint32 wait_event_info); diff --git a/src/include/storage/md.h b/src/include/storage/md.h index 8f32af9ef3d..941879ee6a8 100644 --- a/src/include/storage/md.h +++ b/src/include/storage/md.h @@ -28,6 +28,8 @@ extern bool mdexists(SMgrRelation reln, ForkNumber forknum); extern void mdunlink(RelFileLocatorBackend rlocator, ForkNumber forknum, bool isRedo); extern void mdextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, const void *buffer, bool skipFsync); +extern void mdzeroextend(SMgrRelation reln, ForkNumber forknum, + BlockNumber blocknum, int nblocks, bool skipFsync); extern bool mdprefetch(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum); extern void mdread(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, diff --git a/src/include/storage/smgr.h b/src/include/storage/smgr.h index 0935144f425..a9a179aabac 100644 --- a/src/include/storage/smgr.h +++ b/src/include/storage/smgr.h @@ -92,6 +92,8 @@ extern void smgrdosyncall(SMgrRelation *rels, int nrels); extern void smgrdounlinkall(SMgrRelation *rels, int nrels, bool isRedo); extern void smgrextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, const void *buffer, bool skipFsync); +extern void smgrzeroextend(SMgrRelation reln, ForkNumber forknum, + BlockNumber blocknum, int nblocks, bool skipFsync); extern bool smgrprefetch(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum); extern void smgrread(SMgrRelation reln, ForkNumber forknum, diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c index 2ac365e97cc..9eabdbc589e 100644 --- a/src/backend/storage/file/fd.c +++ b/src/backend/storage/file/fd.c @@ -2206,6 +2206,94 @@ 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)); + + DO_DB(elog(LOG, "FileZero: %d (%s) " INT64_FORMAT " " INT64_FORMAT, + file, VfdCache[file].fileName, + (int64) offset, (int64) amount)); + + 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) + { + /* if errno is unset, assume problem is no disk space */ + if (errno == 0) + errno = ENOSPC; + return -1; + } + + return 0; +} + +/* + * Try to reserve file space with posix_fallocate(). If posix_fallocate() is + * not implemented on the operating system or fails with EINVAL / EOPNOTSUPP, + * use FileZero() instead. + * + * Note that at least glibc() implements posix_fallocate() in userspace if not + * implemented by the filesystem. That's not the case for all environments + * though. + * + * Returns 0 on success, -1 otherwise. In the latter case errno is set to the + * appropriate error. + */ +int +FileFallocate(File file, off_t offset, off_t amount, uint32 wait_event_info) +{ +#ifdef HAVE_POSIX_FALLOCATE + int returnCode; + + Assert(FileIsValid(file)); + + DO_DB(elog(LOG, "FileFallocate: %d (%s) " INT64_FORMAT " " INT64_FORMAT, + file, VfdCache[file].fileName, + (int64) offset, (int64) amount)); + + returnCode = FileAccess(file); + if (returnCode < 0) + return -1; + + 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 -1; +#endif + + return FileZero(file, offset, amount, wait_event_info); +} + off_t FileSize(File file) { @@ -2278,6 +2366,7 @@ int FileGetRawDesc(File file) { Assert(FileIsValid(file)); + return VfdCache[file].fd; } diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c index 352958e1feb..1c2d1405f86 100644 --- a/src/backend/storage/smgr/md.c +++ b/src/backend/storage/smgr/md.c @@ -500,6 +500,114 @@ mdextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, Assert(_mdnblocks(reln, forknum, v) <= ((BlockNumber) RELSEG_SIZE)); } +/* + * mdzeroextend() -- Add new zeroed out blocks to the specified relation. + * + * Similar to mdextend(), except the relation can be extended by multiple + * blocks at once and the added blocks will be filled with zeroes. + */ +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) + { + BlockNumber segstartblock = curblocknum % ((BlockNumber) RELSEG_SIZE); + off_t seekpos = (off_t) BLCKSZ * segstartblock; + int numblocks; + + if (segstartblock + remblocks > RELSEG_SIZE) + numblocks = RELSEG_SIZE - segstartblock; + else + numblocks = remblocks; + + v = _mdfd_getseg(reln, forknum, curblocknum, skipFsync, EXTENSION_CREATE); + + Assert(segstartblock < RELSEG_SIZE); + Assert(segstartblock + numblocks <= RELSEG_SIZE); + + /* + * 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) + { + int ret; + + ret = FileFallocate(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\" with FileFallocate(): %m", + FilePathName(v->mdfd_vfd)), + errhint("Check free disk space.")); + } + } + else + { + int ret; + + /* + * 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; + } +} + /* * mdopenfork() -- Open one fork of the specified relation. * diff --git a/src/backend/storage/smgr/smgr.c b/src/backend/storage/smgr/smgr.c index dc466e54145..c37c246b77f 100644 --- a/src/backend/storage/smgr/smgr.c +++ b/src/backend/storage/smgr/smgr.c @@ -50,6 +50,8 @@ typedef struct f_smgr bool isRedo); void (*smgr_extend) (SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, const void *buffer, bool skipFsync); + void (*smgr_zeroextend) (SMgrRelation reln, ForkNumber forknum, + BlockNumber blocknum, int nblocks, bool skipFsync); bool (*smgr_prefetch) (SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum); void (*smgr_read) (SMgrRelation reln, ForkNumber forknum, @@ -75,6 +77,7 @@ static const f_smgr smgrsw[] = { .smgr_exists = mdexists, .smgr_unlink = mdunlink, .smgr_extend = mdextend, + .smgr_zeroextend = mdzeroextend, .smgr_prefetch = mdprefetch, .smgr_read = mdread, .smgr_write = mdwrite, @@ -507,6 +510,31 @@ smgrextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, reln->smgr_cached_nblocks[forknum] = InvalidBlockNumber; } +/* + * smgrzeroextend() -- Add new zeroed out blocks to a file. + * + * Similar to smgrextend(), except the relation can be extended by + * multiple blocks at once and the added blocks will be filled with + * zeroes. + */ +void +smgrzeroextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, + int nblocks, bool skipFsync) +{ + smgrsw[reln->smgr_which].smgr_zeroextend(reln, forknum, blocknum, + nblocks, skipFsync); + + /* + * Normally we expect this to increase the fork size by nblocks, but if + * the cached value isn't as expected, just invalidate it so the next call + * asks the kernel. + */ + if (reln->smgr_cached_nblocks[forknum] == blocknum) + reln->smgr_cached_nblocks[forknum] = blocknum + nblocks; + else + reln->smgr_cached_nblocks[forknum] = InvalidBlockNumber; +} + /* * smgrprefetch() -- Initiate asynchronous read of the specified block of a relation. * -- 2.38.0 --ju7ntqqtbf66a3ug Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v7-0003-bufmgr-Add-some-more-error-checking-infrastructur.patch" ^ permalink raw reply [nested|flat] 8+ messages in thread
end of thread, other threads:[~2023-02-28 01:36 UTC | newest] Thread overview: 8+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2020-09-12 13:07 [PATCH 2/8] Pass all scan keys to BRIN consistent function at once Tomas Vondra <[email protected]> 2022-10-23 21:25 [PATCH v2 03/14] Add smgrzeroextend(), FileZero(), FileFallocate() Andres Freund <[email protected]> 2022-10-23 21:25 [PATCH v1 04/12] Add smgrzeroextend(), FileZero(), FileFallocate() Andres Freund <[email protected]> 2023-02-28 01:36 [PATCH v5 04/15] Add smgrzeroextend(), FileZero(), FileFallocate() Andres Freund <[email protected]> 2023-02-28 01:36 [PATCH v6 06/17] Add smgrzeroextend(), FileZero(), FileFallocate() Andres Freund <[email protected]> 2023-02-28 01:36 [PATCH v5 03/14] Add smgrzeroextend(), FileZero(), FileFallocate() Andres Freund <[email protected]> 2023-02-28 01:36 [PATCH v4 04/15] Add smgrzeroextend(), FileZero(), FileFallocate() Andres Freund <[email protected]> 2023-02-28 01:36 [PATCH v7 02/14] Add smgrzeroextend(), FileZero(), FileFallocate() Andres Freund <[email protected]>
This inbox is served by agora; see mirroring instructions for how to clone and mirror all data and code used for this inbox