Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 92821e2b authored by David Chinner's avatar David Chinner Committed by Tim Shimmin
Browse files

[XFS] Lazy Superblock Counters



When we have a couple of hundred transactions on the fly at once, they all
typically modify the on disk superblock in some way.
create/unclink/mkdir/rmdir modify inode counts, allocation/freeing modify
free block counts.

When these counts are modified in a transaction, they must eventually lock
the superblock buffer and apply the mods. The buffer then remains locked
until the transaction is committed into the incore log buffer. The result
of this is that with enough transactions on the fly the incore superblock
buffer becomes a bottleneck.

The result of contention on the incore superblock buffer is that
transaction rates fall - the more pressure that is put on the superblock
buffer, the slower things go.

The key to removing the contention is to not require the superblock fields
in question to be locked. We do that by not marking the superblock dirty
in the transaction. IOWs, we modify the incore superblock but do not
modify the cached superblock buffer. In short, we do not log superblock
modifications to critical fields in the superblock on every transaction.
In fact we only do it just before we write the superblock to disk every
sync period or just before unmount.

This creates an interesting problem - if we don't log or write out the
fields in every transaction, then how do the values get recovered after a
crash? the answer is simple - we keep enough duplicate, logged information
in other structures that we can reconstruct the correct count after log
recovery has been performed.

It is the AGF and AGI structures that contain the duplicate information;
after recovery, we walk every AGI and AGF and sum their individual
counters to get the correct value, and we do a transaction into the log to
correct them. An optimisation of this is that if we have a clean unmount
record, we know the value in the superblock is correct, so we can avoid
the summation walk under normal conditions and so mount/recovery times do
not change under normal operation.

One wrinkle that was discovered during development was that the blocks
used in the freespace btrees are never accounted for in the AGF counters.
This was once a valid optimisation to make; when the filesystem is full,
the free space btrees are empty and consume no space. Hence when it
matters, the "accounting" is correct. But that means the when we do the
AGF summations, we would not have a correct count and xfs_check would
complain. Hence a new counter was added to track the number of blocks used
by the free space btrees. This is an *on-disk format change*.

As a result of this, lazy superblock counters are a mkfs option and at the
moment on linux there is no way to convert an old filesystem. This is
possible - xfs_db can be used to twiddle the right bits and then
xfs_repair will do the format conversion for you. Similarly, you can
convert backwards as well. At some point we'll add functionality to
xfs_admin to do the bit twiddling easily....

SGI-PV: 964999
SGI-Modid: xfs-linux-melb:xfs-kern:28652a

Signed-off-by: default avatarDavid Chinner <dgc@sgi.com>
Signed-off-by: default avatarChristoph Hellwig <hch@infradead.org>
Signed-off-by: default avatarTim Shimmin <tes@sgi.com>
parent 3260f78a
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -547,7 +547,8 @@ vfs_sync_worker(

	if (!(vfsp->vfs_flag & VFS_RDONLY))
		error = bhv_vfs_sync(vfsp, SYNC_FSDATA | SYNC_BDFLUSH | \
					SYNC_ATTR | SYNC_REFCACHE, NULL);
					SYNC_ATTR | SYNC_REFCACHE | SYNC_SUPER,
					NULL);
	vfsp->vfs_sync_seq++;
	wake_up(&vfsp->vfs_wait_single_sync_task);
}
+1 −0
Original line number Diff line number Diff line
@@ -92,6 +92,7 @@ typedef enum {
#define SYNC_REFCACHE		0x0040  /* prune some of the nfs ref cache */
#define SYNC_REMOUNT		0x0080  /* remount readonly, no dummy LRs */
#define SYNC_IOWAIT		0x0100  /* wait for all I/O to complete */
#define SYNC_SUPER		0x0200  /* flush superblock to disk */

#define SHUTDOWN_META_IO_ERROR	0x0001	/* write attempt to metadata failed */
#define SHUTDOWN_LOG_IO_ERROR	0x0002	/* write attempt to the log failed */
+6 −2
Original line number Diff line number Diff line
@@ -68,6 +68,7 @@ typedef struct xfs_agf {
	__be32		agf_flcount;	/* count of blocks in freelist */
	__be32		agf_freeblks;	/* total free blocks */
	__be32		agf_longest;	/* longest free space */
	__be32		agf_btreeblks;	/* # of blocks held in AGF btrees */
} xfs_agf_t;

#define	XFS_AGF_MAGICNUM	0x00000001
@@ -81,7 +82,8 @@ typedef struct xfs_agf {
#define	XFS_AGF_FLCOUNT		0x00000100
#define	XFS_AGF_FREEBLKS	0x00000200
#define	XFS_AGF_LONGEST		0x00000400
#define	XFS_AGF_NUM_BITS	11
#define	XFS_AGF_BTREEBLKS	0x00000800
#define	XFS_AGF_NUM_BITS	12
#define	XFS_AGF_ALL_BITS	((1 << XFS_AGF_NUM_BITS) - 1)

/* disk block (xfs_daddr_t) in the AG */
@@ -186,11 +188,13 @@ typedef struct xfs_perag
	__uint32_t	pagf_flcount;	/* count of blocks in freelist */
	xfs_extlen_t	pagf_freeblks;	/* total free blocks */
	xfs_extlen_t	pagf_longest;	/* longest free space */
	__uint32_t	pagf_btreeblks;	/* # of blocks held in AGF btrees */
	xfs_agino_t	pagi_freecount;	/* number of free inodes */
	xfs_agino_t	pagi_count;	/* number of allocated inodes */
	int		pagb_count;	/* pagb slots in use */
#ifdef __KERNEL__
	lock_t		pagb_lock;	/* lock for pagb_list */
#endif
	int		pagb_count;	/* pagb slots in use */
	xfs_perag_busy_t *pagb_list;	/* unstable blocks */
} xfs_perag_t;

+38 −10
Original line number Diff line number Diff line
@@ -1447,7 +1447,8 @@ xfs_alloc_ag_vextent_small(
	else if (args->minlen == 1 && args->alignment == 1 && !args->isfl &&
		 (be32_to_cpu(XFS_BUF_TO_AGF(args->agbp)->agf_flcount)
		  > args->minleft)) {
		if ((error = xfs_alloc_get_freelist(args->tp, args->agbp, &fbno)))
		error = xfs_alloc_get_freelist(args->tp, args->agbp, &fbno, 0);
		if (error)
			goto error0;
		if (fbno != NULLAGBLOCK) {
			if (args->userdata) {
@@ -1923,7 +1924,8 @@ xfs_alloc_fix_freelist(
	while (be32_to_cpu(agf->agf_flcount) > need) {
		xfs_buf_t	*bp;

		if ((error = xfs_alloc_get_freelist(tp, agbp, &bno)))
		error = xfs_alloc_get_freelist(tp, agbp, &bno, 0);
		if (error)
			return error;
		if ((error = xfs_free_ag_extent(tp, agbp, args->agno, bno, 1, 1)))
			return error;
@@ -1973,8 +1975,9 @@ xfs_alloc_fix_freelist(
		 * Put each allocated block on the list.
		 */
		for (bno = targs.agbno; bno < targs.agbno + targs.len; bno++) {
			if ((error = xfs_alloc_put_freelist(tp, agbp, agflbp,
					bno)))
			error = xfs_alloc_put_freelist(tp, agbp,
							agflbp, bno, 0);
			if (error)
				return error;
		}
	}
@@ -1991,13 +1994,15 @@ int /* error */
xfs_alloc_get_freelist(
	xfs_trans_t	*tp,	/* transaction pointer */
	xfs_buf_t	*agbp,	/* buffer containing the agf structure */
	xfs_agblock_t	*bnop)	/* block address retrieved from freelist */
	xfs_agblock_t	*bnop,	/* block address retrieved from freelist */
	int		btreeblk) /* destination is a AGF btree */
{
	xfs_agf_t	*agf;	/* a.g. freespace structure */
	xfs_agfl_t	*agfl;	/* a.g. freelist structure */
	xfs_buf_t	*agflbp;/* buffer for a.g. freelist structure */
	xfs_agblock_t	bno;	/* block number returned */
	int		error;
	int		logflags;
#ifdef XFS_ALLOC_TRACE
	static char	fname[] = "xfs_alloc_get_freelist";
#endif
@@ -2032,8 +2037,16 @@ xfs_alloc_get_freelist(
	be32_add(&agf->agf_flcount, -1);
	xfs_trans_agflist_delta(tp, -1);
	pag->pagf_flcount--;
	TRACE_MODAGF(NULL, agf, XFS_AGF_FLFIRST | XFS_AGF_FLCOUNT);
	xfs_alloc_log_agf(tp, agbp, XFS_AGF_FLFIRST | XFS_AGF_FLCOUNT);

	logflags = XFS_AGF_FLFIRST | XFS_AGF_FLCOUNT;
	if (btreeblk) {
		be32_add(&agf->agf_btreeblks, 1);
		pag->pagf_btreeblks++;
		logflags |= XFS_AGF_BTREEBLKS;
	}

	TRACE_MODAGF(NULL, agf, logflags);
	xfs_alloc_log_agf(tp, agbp, logflags);
	*bnop = bno;

	/*
@@ -2071,6 +2084,7 @@ xfs_alloc_log_agf(
		offsetof(xfs_agf_t, agf_flcount),
		offsetof(xfs_agf_t, agf_freeblks),
		offsetof(xfs_agf_t, agf_longest),
		offsetof(xfs_agf_t, agf_btreeblks),
		sizeof(xfs_agf_t)
	};

@@ -2106,12 +2120,14 @@ xfs_alloc_put_freelist(
	xfs_trans_t		*tp,	/* transaction pointer */
	xfs_buf_t		*agbp,	/* buffer for a.g. freelist header */
	xfs_buf_t		*agflbp,/* buffer for a.g. free block array */
	xfs_agblock_t		bno)	/* block being freed */
	xfs_agblock_t		bno,	/* block being freed */
	int			btreeblk) /* block came from a AGF btree */
{
	xfs_agf_t		*agf;	/* a.g. freespace structure */
	xfs_agfl_t		*agfl;	/* a.g. free block array */
	__be32			*blockp;/* pointer to array entry */
	int			error;
	int			logflags;
#ifdef XFS_ALLOC_TRACE
	static char		fname[] = "xfs_alloc_put_freelist";
#endif
@@ -2132,11 +2148,22 @@ xfs_alloc_put_freelist(
	be32_add(&agf->agf_flcount, 1);
	xfs_trans_agflist_delta(tp, 1);
	pag->pagf_flcount++;

	logflags = XFS_AGF_FLLAST | XFS_AGF_FLCOUNT;
	if (btreeblk) {
		be32_add(&agf->agf_btreeblks, -1);
		pag->pagf_btreeblks--;
		logflags |= XFS_AGF_BTREEBLKS;
	}

	TRACE_MODAGF(NULL, agf, logflags);
	xfs_alloc_log_agf(tp, agbp, logflags);

	ASSERT(be32_to_cpu(agf->agf_flcount) <= XFS_AGFL_SIZE(mp));
	blockp = &agfl->agfl_bno[be32_to_cpu(agf->agf_fllast)];
	*blockp = cpu_to_be32(bno);
	TRACE_MODAGF(NULL, agf, XFS_AGF_FLLAST | XFS_AGF_FLCOUNT);
	xfs_alloc_log_agf(tp, agbp, XFS_AGF_FLLAST | XFS_AGF_FLCOUNT);
	TRACE_MODAGF(NULL, agf, logflags);
	xfs_alloc_log_agf(tp, agbp, logflags);
	xfs_trans_log_buf(tp, agflbp,
		(int)((xfs_caddr_t)blockp - (xfs_caddr_t)agfl),
		(int)((xfs_caddr_t)blockp - (xfs_caddr_t)agfl +
@@ -2196,6 +2223,7 @@ xfs_alloc_read_agf(
	pag = &mp->m_perag[agno];
	if (!pag->pagf_init) {
		pag->pagf_freeblks = be32_to_cpu(agf->agf_freeblks);
		pag->pagf_btreeblks = be32_to_cpu(agf->agf_btreeblks);
		pag->pagf_flcount = be32_to_cpu(agf->agf_flcount);
		pag->pagf_longest = be32_to_cpu(agf->agf_longest);
		pag->pagf_levels[XFS_BTNUM_BNOi] =
+4 −2
Original line number Diff line number Diff line
@@ -136,7 +136,8 @@ int /* error */
xfs_alloc_get_freelist(
	struct xfs_trans *tp,	/* transaction pointer */
	struct xfs_buf	*agbp,	/* buffer containing the agf structure */
	xfs_agblock_t	*bnop);	/* block address retrieved from freelist */
	xfs_agblock_t	*bnop,	/* block address retrieved from freelist */
	int		btreeblk); /* destination is a AGF btree */

/*
 * Log the given fields from the agf structure.
@@ -165,7 +166,8 @@ xfs_alloc_put_freelist(
	struct xfs_trans *tp,	/* transaction pointer */
	struct xfs_buf	*agbp,	/* buffer for a.g. freelist header */
	struct xfs_buf	*agflbp,/* buffer for a.g. free block array */
	xfs_agblock_t	bno);	/* block being freed */
	xfs_agblock_t	bno,	/* block being freed */
	int		btreeblk); /* owner was a AGF btree */

/*
 * Read in the allocation group header (free/alloc section).
Loading