From: Denis V. Lunev <[email protected]> The balloon holds file system space that never carries data, but its blocks are allocated, so the free space walk of xfs_ioc_trim() never discards them, and inflating the balloon is a plain fallocate() that does not discard either. Once the balloon is inflated over blocks that held data, e.g. on an online shrink after files were removed, the device below keeps them allocated: a ploop or qcow2 image keeps host disk space the container can never use, and compaction, which is FITRIM, does not give it back.
Walk the balloon after the free space of the data device and discard its unwritten extents that fall into the requested range, merging physically adjacent ones into one discard. The walk goes to the end of the file offset space, as fallocate() with FALLOC_FL_KEEP_SIZE can leave extents past i_size. A merged run is discarded in chunks, with a check for fatal signals and freezing between them. The walk holds the IOLOCK and the MMAPLOCK exclusively, after waiting for direct I/O and flushing the page cache of the balloon. No block of the balloon can be freed and reused under the discard, and nothing is written to it meanwhile, so an extent that is still unwritten reads as zeroes before and after the discard. The balloon is hidden from lookup and opened only through XFS_IOC_OPEN_BALLOON, which takes CAP_SYS_ADMIN, so the locks only wait for ploop inflating or shrinking it. An empty balloon is skipped before taking the locks. Any other one is walked whole on every FITRIM, whatever range it asks for, as its extent map is indexed by file offset; FITRIM is rare enough for that. Discarding the balloon again each time is cheap: dm-ploop completes the discard of an unmapped cluster at once. A discard error or a failure to read the balloon inode fails FITRIM; a balloon that is no longer a regular mutable file is skipped. A balloon inflated before this change is released by the next FITRIM. https://virtuozzo.atlassian.net/browse/VSTOR-153590 Feature: fs/xfs: fast online shrink support Signed-off-by: Denis V. Lunev <[email protected]> --- Notes: - The balloon is locked, flushed and walked whole on every FITRIM, whatever the range. This is deliberate: ploop runs FITRIM on compaction, about once a day. Measured on a 4-CPU VM with a balloon of 84-99G over all the free space of a 100G image: a FITRIM after the first one spends 0.3-0.7s in the balloon, on ploop and qcow2, ext4 and xfs, for a contiguous balloon and for one in up to about 10000 pieces. The time goes mostly into discarding clusters that are already unmapped and grows with the size of the balloon and the number of its pieces. The locks held meanwhile only keep ploop from resizing the balloon. Narrowing the walk to the range would need a reverse map from blocks to file offsets. An empty balloon, the common case, is skipped before the locks are taken. - XFS_FSB_TO_BB(mp, minlen) could not wrap in 32 bits: xfs_ioc_trim() rejects a minlen above the AG size, and an AG is at most 1 TiB, 2^31 sectors. The u64 cast keeps the check from depending on that. fs/xfs/xfs_discard.c | 144 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) diff --git a/fs/xfs/xfs_discard.c b/fs/xfs/xfs_discard.c index a6c43e4fdec3..72c6d1bb3fba 100644 --- a/fs/xfs/xfs_discard.c +++ b/fs/xfs/xfs_discard.c @@ -22,6 +22,10 @@ #include "xfs_health.h" #include "xfs_rtbitmap.h" #include "xfs_rtgroup.h" +#include "xfs_bit.h" +#include "xfs_inode.h" +#include "xfs_bmap.h" +#include "xfs_super.h" /* * Notes on an efficient, low latency fstrim algorithm @@ -843,6 +847,140 @@ xfs_trim_rtdev_extents( # define xfs_trim_rtdev_extents(...) (-EOPNOTSUPP) #endif /* CONFIG_XFS_RT */ +#define XFS_BALLOON_DISCARD_CHUNK_BB ((128U << 20) >> BBSHIFT) + +static int +xfs_trim_balloon_run( + struct xfs_mount *mp, + xfs_daddr_t daddr, + uint64_t len, + xfs_daddr_t start, + xfs_daddr_t end, + xfs_extlen_t minlen) +{ + struct block_device *bdev = mp->m_ddev_targp->bt_bdev; + unsigned int gran = max(1U, + bdev_discard_granularity(bdev) >> BBSHIFT); + unsigned int chunk = roundup(XFS_BALLOON_DISCARD_CHUNK_BB, gran); + xfs_daddr_t last = daddr + len - 1, next; + int error; + + if (!len) + return 0; + if (daddr < start) + daddr = start; + if (last > end) + last = end; + if (last < daddr || last - daddr + 1 < XFS_FSB_TO_BB(mp, (u64)minlen)) + return 0; + + /* + * Chunks keep a long run interruptible. They end on multiples of the + * discard granularity, so no cluster below is split between two. + */ + while (daddr <= last) { + u64 rem = daddr; + + next = daddr + chunk - do_div(rem, chunk); + if (next > last + 1) + next = last + 1; + error = blkdev_issue_discard(bdev, daddr, next - daddr, + GFP_KERNEL); + if (error) + return error; + daddr = next; + if (xfs_trim_should_stop()) + break; + cond_resched(); + } + + return 0; +} + +/* + * Blocks of the balloon never hold data, but they are allocated, so the + * free space btree walk never discards them. Discard its unwritten + * extents, merging physically adjacent ones, so that the device below + * can release the space as it does for free blocks. + */ +static int +xfs_trim_balloon( + struct xfs_mount *mp, + xfs_daddr_t start, + xfs_daddr_t end, + xfs_extlen_t minlen) +{ + u64 balloon_ino = READ_ONCE(mp->m_balloon_ino); + xfs_daddr_t run_daddr = 0; + uint64_t run_len = 0; + struct xfs_bmbt_irec map; + struct xfs_inode *ip; + xfs_fileoff_t off = 0; + unsigned int lock_mode; + bool unwritten; + int nmaps, error; + + if (!balloon_ino) + return 0; + ip = xfs_balloon_get(mp, balloon_ino, 0); + if (IS_ERR(ip)) + return PTR_ERR(ip) == -EINVAL ? 0 : PTR_ERR(ip); + error = 0; + if (XFS_IS_REALTIME_INODE(ip) || !ip->i_nblocks) + goto out_rele; + + /* + * Nobody may write the balloon meanwhile: data written into an + * unwritten extent would be discarded with it. + */ + xfs_ilock(ip, XFS_IOLOCK_EXCL | XFS_MMAPLOCK_EXCL); + inode_dio_wait(VFS_I(ip)); + error = filemap_write_and_wait(VFS_I(ip)->i_mapping); + if (error) + goto out_unlock; + + while (off < XFS_MAX_FILEOFF) { + if (xfs_trim_should_stop()) + break; + cond_resched(); + + nmaps = 1; + lock_mode = xfs_ilock_data_map_shared(ip); + error = xfs_bmapi_read(ip, off, XFS_MAX_FILEOFF - off, &map, + &nmaps, 0); + xfs_iunlock(ip, lock_mode); + if (error || !nmaps || !map.br_blockcount) + break; + off = map.br_startoff + map.br_blockcount; + + unwritten = xfs_bmap_is_real_extent(&map) && + map.br_state == XFS_EXT_UNWRITTEN; + if (unwritten && run_len && run_daddr + run_len == + XFS_FSB_TO_DADDR(mp, map.br_startblock)) { + run_len += XFS_FSB_TO_BB(mp, map.br_blockcount); + continue; + } + + error = xfs_trim_balloon_run(mp, run_daddr, run_len, start, + end, minlen); + if (error) + break; + run_len = 0; + if (unwritten) { + run_daddr = XFS_FSB_TO_DADDR(mp, map.br_startblock); + run_len = XFS_FSB_TO_BB(mp, map.br_blockcount); + } + } + if (!error && !xfs_trim_should_stop()) + error = xfs_trim_balloon_run(mp, run_daddr, run_len, start, + end, minlen); +out_unlock: + xfs_iunlock(ip, XFS_IOLOCK_EXCL | XFS_MMAPLOCK_EXCL); +out_rele: + xfs_irele(ip); + return error; +} + /* * trim a range of the filesystem. * @@ -915,6 +1053,12 @@ xfs_ioc_trim( error = xfs_trim_datadev_extents(mp, start, end, minlen); if (error) last_error = error; + + if (!xfs_trim_should_stop()) { + error = xfs_trim_balloon(mp, start, end, minlen); + if (error) + last_error = error; + } } if (rt_bdev && !xfs_trim_should_stop()) { -- 2.53.0 _______________________________________________ Devel mailing list [email protected] https://lists.openvz.org/mailman/listinfo/devel
