Re: [PATCH 1/5] xfs: rework log recovery to submit buffers on LSN boundaries

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Mon, Aug 29, 2016 at 11:16:31AM +1000, Dave Chinner wrote:
> On Thu, Aug 11, 2016 at 01:11:03PM -0400, Brian Foster wrote:
> > The fix to log recovery to update the metadata LSN in recovered buffers
> > introduces the requirement that a buffer is submitted only once per
> > current LSN. Log recovery currently submits buffers on transaction
> > boundaries. This is not sufficient as the abstraction between log
> > records and transactions allows for various scenarios where multiple
> > transactions can share the same current LSN. If independent transactions
> > share an LSN and both modify the same buffer, log recovery can
> > incorrectly skip updates and leave the filesystem in an inconsisent
> > state.
> > 
> > In preparation for proper metadata LSN updates during log recovery,
> > update log recovery to submit buffers for write on LSN change boundaries
> > rather than transaction boundaries. Explicitly track the current LSN in
> > a new struct xlog field to handle the various corner cases of when the
> > current LSN may or may not change.
> 
> .....
> 
> > @@ -4221,8 +4223,39 @@ xlog_recover_process_ophdr(
> >  		return 0;
> >  	}
> >  
> > +	/*
> > +	 * Recovered buffers are submitted for I/O on current LSN change
> > +	 * boundaries. This is necessary to accommodate metadata LSN ordering
> > +	 * rules of v5 superblock filesystems.
> > +	 *
> > +	 * Store the new current LSN in l_recovery_lsn as we cannot rely on
> > +	 * either record boundaries or transaction boundaries alone to track LSN
> > +	 * changes. This has several contributing factors:
> > +	 *
> > +	 * - Metadata LSNs are updated at buffer submission time. Thus, buffers
> > +	 *   can only be submitted safely once per current LSN value.
> > +	 * - The current LSN is defined as the start cycle/block of the first
> > +	 *   record in which a transaction appears.
> > +	 * - A record can hold multiple transactions. Thus, a transaction change
> > +	 *   does not guarantee a change in current LSN.
> > +	 * - A transaction can span multiple records. Thus, a record change does
> > +	 *   not guarantee a change in current LSN. Consider the case where a
> > +	 *   record holds one small transaction and a subsequent that carries
> > +	 *   over to the next record. Both transactions share the same LSN as
> > +	 *   per the definition of the current LSN.
> > +	 *
> > +	 * In summary, this means we must track the current LSN independently
> > +	 * and submit buffers for the previous LSN only when it has changed.
> > +	 */
> > +	if (log->l_recovery_lsn != trans->r_lsn) {
> > +		error = xfs_buf_delwri_submit(buffer_list);
> > +		if (error)
> > +			return error;
> > +		log->l_recovery_lsn = trans->r_lsn;
> > +	}
> 
> I'm not sure this is the right place to be submitting buffers. We
> can have multiple transactions open at once because the writing of
> the transaction to the log is split into two parts: xlog_write()
> which writes the changes to the log, and xfs_log_done() which writes
> the commit record (via xlog_commit_record()) to close the
> transaction.
> 
> Hence we can get the situation where we have multiple open
> transactions such as:
> 
>                               CA CB                  CC CD
>  +---------+--------+--------+--+--+--------+-------+--+--+
>    trans A   trans B  trans C       trans C  trans D
> 

Ok, thanks for drawing this out. I hadn't thought about it from this
angle...

> where the changes in multiple transactions are written before the
> ophdr that contains the commit record ("CA", "CB", ....) is written.
> With the above code, we'd be doing writeback of A when we see B, not
> when we see the commit record for A. Like wise B when we see C. And
> worse, partial writeback of C when we see the commit record for A...
> 

... but I don't think that accurately describes the behavior here. At
least, I'm not sure there is enough information presented to describe
when buffers are going to be submitted because we don't have the mapping
of transactions to records.

That aside for a moment, the current recovery code makes a couple passes
over the entire dirty log. The first pass is for management of cancelled
items and thus not really relevant to this problem. The second pass
constructs all of the transactions in memory and on XLOG_COMMIT_TRANS,
we iterate all of the items in said transaction, perform recovery on the
actual filesystem metadata buffers and submit the buffers for I/O. As
you've outlined above, I believe this corresponds to the commit record
as this flag is written out by xlog_commit_record().

Given that, this change can't cause writeback of A when we see B because
the buffers for A still aren't read, recovered and queued for I/O until
we see the commit record for A. Instead, what this change does is to
defer the final I/O submission from commit record time until we see a
metadata LSN change. This effectively means that the current "per
transaction" buffer delwri queue becomes a "per metadata LSN" queue and
ensures that a subsequent transaction that happens to use the same LSN
and modify the same block(s) does not submit the buffer for write
multiple times from contexts that would stamp the same metadata LSN.

In other words, this change can be viewed as batching or "plugging"
buffer submissions from commit records to subsequent metadata LSN
updates. It doesn't actually reorder how transactions are recovered in
any ways. Getting back to the example above, what should happen (in pass
2) is:

- Allocate trans A and start populating ->r_itemq.
- Allocate trans B and start populating ->r_itemq.
- Allocate trans C and start populating ->r_itemq.
[Note: buffer_list is still empty so any current LSN updates cause no
actions to this point.]
- Hit CR A, recover all of the items in trans A and queue the buffers
  onto buffer_list. Now that buffer_list is populated, the buffers will
  be submitted on the next metadata LSN change.
- Hit CR B. We don't know what's going to happen to buffer_list here
  because the example doesn't define record context. We'll look up
  transaction B and if transaction B started in the same record as A,
  for example, we won't actually drain buffer_list. If they started in
  different records (e.g., different LSNs), we drain buffer_list and
  proceed. In either case, we perform recovery of the items in trans B
  and queue B's buffers to buffer_list.
- We revisit trans C. Again, the starting LSN of trans C and B determine
  whether we drain buffer_list. Note that buffer_list still only
  contains buffers up through transaction B (i.e., possibly A as well)
  as we have not yet recovered or queued anything from transaction C.
- Allocate trans D and start populating ->r_itemq.
...

... and so on until the end and we drain buffer_list from the last LSN
in xlog_do_recovery_pass().

> i.e. We are very careful to write commit records in the correct
> order because that is what determines recovery order, but we don't
> care what order we write the actual contents of the checkpoints or
> whether they interleave with other checkpoints.  As such, ophdrs
> change transactions and LSNs without having actually completed
> recovery of a checkpoint. 
> 
> I think writeback should occur when all the transactions with a
> given lsn have been committed. I'm not sure there's a simple way to
> track and detect this, but using the ophdrs to detect a change of
> lsn to trigger buffer writeback does not look correct to me at this
> point in time.
> 

That is precisely the intent of this patch. What I think could be a
problem is something like the following, if possible:

                    CA         CB                  CC CD
+---------+--------+--+-------+--+--------+-------+--+--+
  trans A   trans B    trans C    trans C  trans D

Assume that trans A and trans B are within the same record and trans C
is in a separate record. In that case, we commit trans A which populates
buffer_list. We lookup trans C, note a new LSN and drain buffer_list.
Then we ultimately commit trans B, which has the same metadata LSN as
trans A and thus is a path to the original problem if trans B happened
to modify any of the same blocks as trans A.

Do note however that this is just an occurrence of the problem with log
recovery as implemented today (once we update metadata LSNs, and is
likely rare as I haven't been able to reproduce corruption in many
tries). If that analysis is correct, I think a straightforward solution
might be to defer submission to the lookup of a transaction with a new
LSN that _also_ corresponds with processing of a commit record based on
where we are in the on-disk log. E.g.:

	if (log->l_recovery_lsn != trans->r_lsn &&
	    oh_flags & XLOG_COMMIT_TRANS) {
		error = xfs_buf_delwri_submit(buffer_list);
		...
	}

So in the above, we'd submit buffers for A and B once we visit the
commit record for trans C. Thoughts?

Brian

> Cheers,
> 
> Dave.
> -- 
> Dave Chinner
> david@xxxxxxxxxxxxx

_______________________________________________
xfs mailing list
xfs@xxxxxxxxxxx
http://oss.sgi.com/mailman/listinfo/xfs



[Index of Archives]     [Linux XFS Devel]     [Linux Filesystem Development]     [Filesystem Testing]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux