4300: Polish slide by changing <pre> to <table>.
[libguestfs-talks.git] / 2019-kvm-forum / notes-04-fast-zero
1 Case study: Adding fast zero to NBD
2 [About 5-6 mins]
3
4 - based heavily on https://www.redhat.com/archives/libguestfs/2019-August/msg00322.html
5
6 * Heading: Case study baseline
7 - 4000- shell to pre-create source file
8 - baseline is about 8.5s
9
10 As Rich mentioned, qemu-img convert is a great tool for copying guest
11 images, with support for NBD on both source and destination.  However,
12 most guest images are sparse, and we want to avoid naively reading
13 lots of zeroes on the source then writing lots of zeroes on the
14 destination.  Here's a case study of our last three years in
15 optimizing that, starting with a baseline of straightline copying,
16 which matches qemu 2.7 behavior (Sep 2016).  Let's convert a 100M
17 image, which alternates between data and holes at each megabyte.
18
19 The ./convert command show here is rather long; if you're interested
20 in its origins, my patch submission in Aug 2019 goes into more
21 details.  But for now, just think of it as a fancy way to run
22 'qemu-img convert' against a server where I can tweak server behavior
23 to control which zeroing-related features are advertised or
24 implemented.
25
26 * Heading: Writing zeroes: much ado about nothing
27 - 4200- .term
28   - ./convert zeromode=plugin fastzeromode=none for server A
29   - ./convert zeromode=emulate fastzeromode=none for server B
30
31 In qemu 2.8 (Dec 2016), we implemented the NBD extension of
32 WRITE_ZEROES, with the initial goal of reducing network traffic (no
33 need to send an explicit payload of all zero bytes over the network).
34 However, the spec was intentionally loose on implementation, with two
35 common scenarios.  With server A, the act of writing zeroes is heavily
36 optimized - a simple constant-time metadata notation and the operation
37 is done regardless of the size of the zero, and we see an immediate
38 benefit in execution time, even though the amount of I/O transactions
39 did not drop.  With server B, writing zeroes populates a buffer in the
40 server, then goes through the same path as a normal WRITE command, for
41 no real difference from the baseline.  But can we do better?
42
43 * Heading: What's the status?
44 - 4300- slide: why qemu-img convert wants at most one block status
45
46 Do we even have to worry whether WRITE_ZEROES will be fast or slow?
47 If we know that the destination already contains all zeroes, we could
48 entirely skip destination I/O for each hole in the source.  qemu 2.12
49 (Apr 2018) added support for NBD_CMD_BLOCK_STATUS to quickly learn
50 whether a portion of a disk is a hole.  But experiments with qemu-img
51 convert showed that using BLOCK_STATUS as a way to avoid WRITE_ZEROES
52 didn't really help, for a couple of reasons.  If writing zeroes is
53 fast, checking the destination first is either a mere tradeoff in
54 commands (BLOCK_STATUS replacing WRITE_ZEROES when the destination is
55 already zero) or a pessimization (BLOCK_STATUS still has to be
56 followed by WRITE_ZEROES).  Conversely, if writing zeroes is slow, the
57 penalty of the extra check when the destination is not a hole could be
58 in the noise, but whether or not we have a definite speedup for
59 avoiding WRITE_ZEROES depends on whether BLOCK_STATUS itself is fast -
60 yet we encountered situations such as tmpfs that has a linear rather
61 than constant-time lseek(SEEK_HOLE) implementation, where we ended up
62 with quadratic behavior all due to BLOCK_STATUS calls.  Thus, for now,
63 qemu-img convert does not use BLOCK_STATUS.
64
65 * Heading: Pre-zeroing: a tale of two servers
66 - 4400- .term
67   - ./convert zeromode=plugin fastzeromode=ignore for server A
68   - ./convert zeromode=emulate fastzeromode=ignore for server B
69
70 But do we really even need to use BLOCK_STATUS?  What if, instead, we
71 just guarantee that the destination image starts life with all zeroes?
72 After all, since WRITE_ZEROES has no network payload, we can just bulk
73 pre-zero the image, and then skip I/O for source holes without having
74 to do any further checks of destination status.  qemu 3.1 took this
75 approach, but quickly ran into a surprise.  For server A, we have a
76 speedup: fewer overall I/O transactions makes us slightly faster than
77 one WRITE_ZEROES per hole.  But for server B, we actually have a
78 dramatic pessimization!  It turns out that when writing zeroes falls
79 back to a normal write path, pre-zeroing the image now forces twice
80 the I/O for any data portion of the image.
81
82 * Heading: qemu's solution
83 - 4500- slide: graph of the scenarios
84
85 With the root cause for the pessimation understood, qemu folks
86 addressed the situation by adding a flag BDRV_REQ_NO_FALLBACK in qemu
87 4.0 (Apr 2019): when performing a pre-zeroing pass, we want zeroing to
88 be fast: if it cannot succeed quickly, then it must fail rather than
89 fall back to writes.  For server A, the pre-zero request succeeds, and
90 we've avoided all further hole I/O; while for server B, the pre-zero
91 request fails but we didn't lose any time to doubled-up I/O to data
92 segments.  This sounds fine on paper, but has one problem: it requires
93 server cooperation, and without that, the only sane default is to
94 assume that zeroes are not fast, so while we avoided hurting server B,
95 we ended up pessimizing server A back to one zero request per hole.
96
97 * Heading: Protocol extension: time to pull a fast one
98 - 4600- .term with
99   - ./convert zeromode=plugin fastzeromode=default for server A
100   - ./convert zeromode=emulate fastzeromode=default for server B
101
102 So the solution is obvious: let's make nbdkit as server perform the
103 necessary cooperation for qemu to request a fast zero.  The NBD
104 protocol added the extension NBD_CMD_FLAG_FAST_ZERO, taking care that
105 both server and client must support the extension before it can be
106 used, but that if it is not supported, we merely lose performance but
107 do not corrupt image contents.  qemu 4.2 will be the first release
108 that now supports ideal performance for both server A and server B out
109 of the box.
110
111 * Heading: Reference implementation
112 - 4700- nbdkit filters/plugins that were adjusted
113
114 The qemu implementation was quite trivial (map the new NBD flag to the
115 existing BDRV_REQ_NO_FALLBACK flag, in both client and server, due out
116 in qemu 4.2).  But to actually get the NBD extension into the
117 protocol, it's better to prove that the extension will be
118 interoperable with other NBD implementations.  So, the obvious second
119 implementation is libnbd for a client (adding a new flag to nbd_zero,
120 and support for mapping a new errno value, due out in 1.2), and nbdkit
121 for a server (adding a new .can_fast_zero callback for plugins and
122 filters, then methodically patching all in-tree files where it can be
123 reasonably implemented, due out in 1.16).  Among other things, the
124 nbdkit changes to the nozero filter added the parameters I used in my
125 demo for controlling whether to advertise and/or honor the semantics
126 of the new flag.
127
128 [if time:] Note that the file plugin was not touched in the initial
129 patches. This is because accurate support is harder than it looks:
130 both fallocate(FALLOC_FL_ZERO_RANGE) and ioctl(BLKZEROOUT) can trigger
131 fallbacks to slow writes, so we would need kernel support for new
132 interfaces that guarantee fast failure.
133
134 * segue: XXX
135 slide 4800-? Or just sentence leading into Rich's demos?
136
137 I just showed a case study of how nbdkit helped address a real-life
138 optimization issue.  Now let's see some of the more esoteric things
139 that the NBD protocol makes possible.