Re: POSIX violation by writeback error

From: Dave Chinner
Date: Wed Sep 05 2018 - 22:57:17 EST


On Wed, Sep 05, 2018 at 02:07:46PM +0200, Rogier Wolff wrote:
> On Wed, Sep 05, 2018 at 06:55:15AM -0400, Jeff Layton wrote:
> > There is no requirement for a filesystem to flush data on close().
>
> And you can't start doing things like that.

Of course we can. And we do.

We've been doing targetted flush-on-close for years in some
filesystems because applications don't use fsync where they should
and users blame the filesystems for losing their data.

i.e. we do what the applications should have done but don't because
"fsync is slow". Another common phrase I hear is "I don't need
fsync because I don't see any problems in my testing". They don't
see problems because appliation developers typically don't do power
fail testing of their applications and hence never trigger the
conditions needed to expose the bugs in their applications.

> In some weird cases, you
> might have an application open-write-close files at a much higher rate
> than what a harddisk can handle.

It's not a weird case - the kernel NFSD does this for every write
request it receives.

> And this has worked for years because
> the kernel caches stuff from inodes and data-blocks. If you suddenly
> write stuff to harddisk at 10ms for each seek between inode area and
> data-area..

You're assuming an awful lot about filesystem implementation here.
Neither ext4, btrfs or XFS issue physical IO like this when flushing
data.

> You end up limited to about 50 of these open-write-close
> cycles per second.

You're also conflating "flushing data" with "synchronous".

fsync() is a synchronous data flush because we have defined that way
- it has to wait for IO completion *after* flushing. However, we can
use other methods of flushing data that don't need to wait for
completion, or we can issue synchronous IOs concurrently (e.g. via
AIO or threads) so flushes don't block applications from doing
real work while waiting for IO. Examples are below.

> My home system is now able make/write/close about 100000 files per
> second.
>
> assurancetourix:~/testfiles> time ../a.out 100000 000
> 0.103u 0.999s 0:01.10 99.0% 0+0k 0+800000io 0pf+0w

Now you've written 100k files in a second into the cache, how long
do it take the system to flush them out to stable storage? If the
data is truly so ephemeral it's going to be deleted before it's
written back, then why the hell write it to the filesystem in the
first place?

I use fsmark for open-write-close testing to drive through the
cache phase into sustained IO-at-resource-exhaustion behavour. I
also use multiple threads to drive the system to being IO bound
before it runs out of CPU.

>From one of my test scripts for creating 10 million 4k files to test
background writeback behaviour:

# ./fs_mark -D 10000 -S0 -n 10000 -s 4096 -L 120 -d /mnt/scratch/0 -d /mnt/scratch/1 -d /mnt/scratch/2 -d /mnt/scratch/3 -d /mnt/scratch/4 -d /mnt/scratch/5 -d /mnt/scratch/6 -d /mnt/scratch/7
# Version 3.3, 8 thread(s) starting at Thu Sep 6 09:37:52 2018
# Sync method: NO SYNC: Test does not issue sync() or fsync() calls.
# Directories: Time based hash between directories across 10000 subdirectories with 1800 seconds per subdirectory.
# File names: 40 bytes long, (16 initial bytes of time stamp with 24 random bytes at end of name)
# Files info: size 4096 bytes, written with an IO size of 16384 bytes per write
# App overhead is time in microseconds spent in the test not doing file writing related system calls.

FSUse% Count Size Files/sec App Overhead
0 80000 4096 99066.0 545242
0 160000 4096 100528.7 579256
0 240000 4096 95789.6 600522
0 320000 4096 102129.8 532474
0 400000 4096 89551.3 581729

[skip rest of clean cache phase, dirty cache throttling begins an
we enter the sustained IO performance phase]

0 1360000 4096 32222.3 685659
0 1440000 4096 35473.6 693983
0 1520000 4096 34753.0 693478
0 1600000 4096 35540.4 690363
....

So, I see 100k files/s on an idle page cache, and 35k files/s
(170MB/s at ~2000 IOPS) when background writeback kicks in
and it's essentially in a sustained IO bound state.

Turning that into open-write-fsync-close:

-S Sync Method (0:No Sync, 1:fsyncBeforeClose,
....

Yeah, that sucks - it's about 1000 files/s (~5MB/s and 2000 IOPS),
because it's *synchronous* writeback and I'm only driving 8 threads.
Note that the number of IOPS is almost identical to the "no fsync"
case above - the disk utilisation is almost identical for the two
workloads.

HOwever, let's turn that into a open-write-flush-close operation by
using AIO and not waiting for the fsync to complete before closing
the file. I have this in fsmark, because lots of people with IO
intensive apps have been asking for it over the past 10 years.

-A <use aio_fsync>

.....
FSUse% Count Size Files/sec App Overhead
0 80000 4096 28770.5 1569090
0 160000 4096 31340.7 1356595
0 240000 4096 30803.0 1423583
0 320000 4096 30404.5 1510099
0 400000 4096 30961.2 1500736

Yup, it's pretty much the same throughput as background async
writeback. It's a little slower - about 160MB/s and 2,500 IOPS -
due to the increase in overall journal writes caused by the fsync
calls.

What's clear, however, is that we're retiring 10 userspace fsync
operations for every physical disk IO here, as opposed to 2 IOs per
fsync in the above case. Put simply, the assumption that
applications can't do more flush/fsync operations than disk IOs is
not valid, and that performance of open-write-flush-close workloads
on modern filesystems isn't anywhere near as bad as you think it is.

To mangle a common saying into storage speak:

"Caches are for show, IO is for go"

Cheers,

Dave.
--
Dave Chinner
david@xxxxxxxxxxxxx