From e9d611fb3f45cdd9641fe6e73bb4fa7f7f08aad2 Mon Sep 17 00:00:00 2001 From: Pavel Borisov Date: Tue, 10 Nov 2020 13:39:00 +0400 Subject: [PATCH v2] Free disk space for dropped relations on commit. When committing a transaction that dropped a relation, we previously truncated only the first segment file to free up disk space, because it won't be unlinked until the next checkpoint. Truncate later segments too, even though we unlink them on commit. This frees the disk space immediately, even if other backends have open file descriptors and might take a long time to get around to handling shared invalidation events and closing them. Also extend the same behavior to the first segment in recovery. Author: Thomas Munro Bug: #16663 Reported-by: Denis Patron Discussion: https://postgr.es/m/16663-fe97ccf9932fc800%40postgresql.org --- src/backend/storage/smgr/md.c | 74 +++++++++++++++++++++++------------ 1 file changed, 48 insertions(+), 26 deletions(-) diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c index 1d4aa482cc..785ee7ef1e 100644 --- a/src/backend/storage/smgr/md.c +++ b/src/backend/storage/smgr/md.c @@ -286,11 +286,39 @@ mdunlink(RelFileNodeBackend rnode, ForkNumber forkNum, bool isRedo) mdunlinkfork(rnode, forkNum, isRedo); } +/* + * Reduce the size of a file to zero, to free up disk space. + */ +static void +do_truncate(const char *path) +{ + int ret; + int fd; + + /* truncate(2) would be easier here, but Windows hasn't got it */ + fd = OpenTransientFile(path, O_RDWR | PG_BINARY); + if (fd >= 0) + { + int save_errno; + + ret = ftruncate(fd, 0); + save_errno = errno; + CloseTransientFile(fd); + errno = save_errno; + } + else + ret = -1; + if (ret < 0 && errno != ENOENT) + ereport(WARNING, + (errcode_for_file_access(), + errmsg("could not truncate file \"%s\": %m", path))); +} + static void mdunlinkfork(RelFileNodeBackend rnode, ForkNumber forkNum, bool isRedo) { char *path; - int ret; + int ret = 0; path = relpath(rnode, forkNum); @@ -299,10 +327,15 @@ mdunlinkfork(RelFileNodeBackend rnode, ForkNumber forkNum, bool isRedo) */ if (isRedo || forkNum != MAIN_FORKNUM || RelFileNodeBackendIsTemp(rnode)) { - /* First, forget any pending sync requests for the first segment */ if (!RelFileNodeBackendIsTemp(rnode)) + { + /* First, forget any pending sync requests for the first segment */ register_forget_request(rnode, forkNum, 0 /* first seg */ ); + /* Prevent other backends' fds from holding on to the disk space */ + do_truncate(path); + } + /* Next unlink the file */ ret = unlink(path); if (ret < 0 && errno != ENOENT) @@ -312,25 +345,8 @@ mdunlinkfork(RelFileNodeBackend rnode, ForkNumber forkNum, bool isRedo) } else { - /* truncate(2) would be easier here, but Windows hasn't got it */ - int fd; - - fd = OpenTransientFile(path, O_RDWR | PG_BINARY); - if (fd >= 0) - { - int save_errno; - - ret = ftruncate(fd, 0); - save_errno = errno; - CloseTransientFile(fd); - errno = save_errno; - } - else - ret = -1; - if (ret < 0 && errno != ENOENT) - ereport(WARNING, - (errcode_for_file_access(), - errmsg("could not truncate file \"%s\": %m", path))); + /* Prevent other backends' fds from holding on to the disk space */ + do_truncate(path); /* Register request to unlink first segment later */ register_unlink_segment(rnode, forkNum, 0 /* first seg */ ); @@ -350,14 +366,20 @@ mdunlinkfork(RelFileNodeBackend rnode, ForkNumber forkNum, bool isRedo) */ for (segno = 1;; segno++) { - /* - * Forget any pending sync requests for this segment before we try - * to unlink. - */ + sprintf(segpath, "%s.%u", path, segno); + if (!RelFileNodeBackendIsTemp(rnode)) + { + /* + * Forget any pending sync requests for this segment before we try + * to unlink. + */ register_forget_request(rnode, forkNum, segno); - sprintf(segpath, "%s.%u", path, segno); + /* Prevent other backends' fds from holding on to the disk space */ + do_truncate(segpath); + } + if (unlink(segpath) < 0) { /* ENOENT is expected after the last segment... */ -- 2.28.0