Add setup slide for fastzero demo.
[libguestfs-talks.git] / 2019-kvm-forum / notes-04-fast-zero
1 Case study: Adding fast zero to NBD
2 [About 5 mins]
3
4 * Heading: Case study baseline
5 - link to https://www.redhat.com/archives/libguestfs/2019-August/msg00322.html
6 - shell to pre-create source file
7
8 As Rich mentioned, qemu-img convert is a great tool for copying guest
9 images, with support for NBD on both source and destination.  However,
10 most guest images are sparse, and we want to avoid naively reading
11 lots of zeroes on the source then writing lots of zeroes on the
12 destination.  Here's a case study of optimizing that, starting with a
13 baseline of straightline copying.
14
15 * Heading: Nothing to see here
16
17 The NBD extension of WRITE_ZEROES made it faster to write large blocks
18 of zeroes to the destination (less network traffic).  And to some
19 extent, BLOCK_STATUS on the source made it easy to learn where blocks
20 of zeroes are in the source, for then knowing when to use
21 WRITE_ZEROES.  But for an image that is rather fragmented (frequent
22 alternation of holes and data), that's still a lot of individual
23 commands sent over the wire, which can slow performance especially in
24 scenarios when each command is serialized.  Can we do better?
25
26 * Heading: What's the status?
27
28 We could check before using WRITE_ZEROES whether the destination is
29 already zero.  If we get lucky, we can even learn from a single
30 BLOCK_STATUS that the entire image already started life as all zeroes,
31 so that there is no further work needed for any of the source holes.
32 But luck isn't always on our side: BLOCK_STATUS itself is an extension
33 and not present on all servers; and worse, at least tmpfs has an issue
34 where lseek(SEEK_HOLE) is O(n) rather than O(1), so querying status
35 for every hole turns our linear walk into an O(n^2) ordeal, so we
36 don't want to use it more than once.  So for the rest of my case
37 study, I investigated what happens when BLOCK_STATUS is unavailable
38 (which is in fact the case with qemu 3.0).
39
40 * Heading: Tale of two servers
41
42 What happens if we start by pre-zeroing the entire destination (either
43 because BLOCK_STATUS proved the image did not start as zero, or was
44 unavailable)?  Then the remainder of the copy only has to worry about
45 source data portions, and not revisit the holes; fewer commands over
46 the wire should result in better performance, right?  But in practice,
47 we discovered an odd effect - some servers were indeed faster this
48 way, but others were actually slower than the baseline of just writing
49 the entire image in a single pass.  This pessimization appeared in
50 qemu 3.1.
51
52 * Heading: The problem
53
54 Even though WRITE_ZEROES results in less network traffic, the
55 implementation on the server varies widely: in some servers, it really
56 is an O(1) request to bulk-zero a large portion of the disk, but in
57 others, it is merely sugar for an O(n) write of actual zeroes.  When
58 pre-zeroing an image, if you have an O(1) server, you save time, but
59 if you have an O(n) server, then you are actually taking the time to
60 do two full writes to every portion of the disk containing data.
61
62 * Heading: qemu's solution
63
64 The pre-zeroing pass is supposed to be an optimization: what if we can
65 guarantee that the server will only attempt pre-zeroing with an O(1)
66 implementation, and return an error to allow a fallback to piecewise
67 linear writes otherwise?  Qemu added this with BDRV_REQ_NO_FALLBACK
68 for qemu 4.0, but has to guess pessimistically: any server not known
69 to be O(1) is assumed to be O(n), even if that assumption is wrong.
70 And since NBD did not have a way to tell the client which
71 implementation is in use, we lost out on the speedup for server A, but
72 at least no longer have the pessimisation for server B.
73
74 * Heading: Time for a protocol extension
75
76 Since qemu-img convert can benefit from knowing if a server's zero
77 operation is fast, it follows that NBD should offer that information
78 as a server. The next step was to propose an extension to the protocol
79 that preserves backwards compatibility (both client and server must
80 understand the extension to utilize it, and either side lacking the
81 extension should result in at most a lack of performance, but not
82 compromise contents).  The proposal was NBD_CMD_FLAG_FAST_ZERO.
83
84 * Heading: Reference implementation
85
86 No good protocol will add extensions without a reference
87 implementation. And for qemu, the implementation for both server and
88 client is quite simple, map a new flag in NBD to the existing qemu
89 flag of BDRV_REQ_NO_FALLBACK, for both server and client; this will be
90 landing in qemu 4.2.  But at the same time, a single implementation,
91 and unreleased at that, is hardly portable, so the NBD specification
92 is reluctant to codify things without some interoperability testing.
93
94 * Heading: Second implementation
95
96 So, the obvious second implementation is libnbd for a client (adding a
97 new flag to nbd_zero, and support for mapping a new errno value), and
98 nbdkit for a server (adding a new .can_fast_zero callback for plugins
99 and filters, then methodically patching all in-tree files where it can
100 be reasonably implemented).  Here, the power of filters stands out: by
101 adding a second parameter to the existing 'nozero' filter, I could
102 quickly change the behavior of any plugin on whether to advertise
103 and/or honor the semantics of the new flag.
104
105 * Heading: Show me the numbers
106
107 When submitting the patches, a concrete example was easiest to prove
108 the patch matters.  So I set up a test-bed on a 100M image with every
109 other megabyte being a hole:
110
111 .sh file with setup, core function
112
113 and with filters in place to artificially slow the image down (data
114 writes slower than zeroes, and no data write larger than 256k, block
115 status disabled) and observe behavior (log to see what the client
116 requests based on handshake results, stats to get timing numbers for
117 overall performance).  Then by tweaking the 'nozero' filter
118 parameters, I was able to recreate qemu 3.0 behavior (baseline
119 straight copy), qemu 3.1 behavior (blind pre-zeroing pass with speedup
120 or slowdown based on zeroing implementation), qemu 4.0 behavior (no
121 speedups without detection of fast zero support, but at least nothing
122 worse than baseline), and qemu 4.2 behavior (take advantage of
123 pre-zeroing when it helps, while still nothing worse than baseline).