<div dir="ltr">Here's my take:<br><br>I don't see why the filesystem cares if thinp is doing a reservation or<br>provisioning under the hood.  All that matters is that a future write<br>to that region will be honoured (barring device failure etc.).<br><br>I agree that the reservation/force mapped status needs to be inherited<br>by snapshots.<br><br><br>One of the few strengths of thinp is the performance of taking a snapshot.<br>Most snapshots created are never activated.  Many other snapshots are<br>only alive for a brief period, and used read-only.  eg, blk-archive<br>(<a href="https://github.com/jthornber/blk-archive">https://github.com/jthornber/blk-archive</a>) uses snapshots to do very<br>fast incremental backups.  As such I'm strongly against any scheme that<br>requires provisioning as part of the snapshot operation.<br><br>Hank and I are in the middle of the range tree work which requires a metadata<br>change.  So now is a convenient time to piggyback other metadata changes to<br>support reservations.<br><br><br>Given the above this is what I suggest:<br><br>1) We have an api (ioctl, bio flag, whatever) that lets you reserve/guarantee a region:<br><br>  int reserve_region(dev, sector_t begin, sector_t end);<br><br>  This api should be used minimally, eg, critical FS metadata only.<br><br>2) Each thinp device will gain two extra fields in the metadata:<br><br>  - Total reserved (TR)<br>  - reserved actually provisioned (RAP)<br><br>  The difference between these two is the amount of space we need to guarantee<br>  for this device.<br><br>3) Each individual mapping for a device will gain a flag indicating if it's 'reserved'.<br>   We will need to support 'reserved but unmapped' mappings.  There are<br>   two provisioning events:<br><br>  - unmapped but reserved -> mapped reserved.  Initial provision, RAP incremented.<br>  - mapped and reserved -> ie. break sharing after snapshot.  Again RAP incremented.<br><br>4) When a snapshot is taken:<br>  - Reset RAP to zero for origin<br>  - New snap has Total reserved set to that of origin, RAP <- 0<br>  - All mappings are shared, so the reserved flags for each mapping is preserved<br>  - Only allow snapshot if there is enough free space for the new reserve pool.<br><div><br></div><div><br>5) Reserve for the whole pool is the sum of (TR - RAP) for all devices.  This pool<br>   can only be touched if we're provisioning for a reserved mapping.<br><br>One drawback with this scheme is the double accounting due to RAP being<br>reset to zero for the origin device.  This comes about because after the<br>snapshot operation the two devices are equal, no sense of which device<br>is the origin is preserved or needed.  So a write to a given block<br>will trigger provisioning on whichever device it is written to first.<br>A later write to the other device will not trigger a provision.  I think<br>this problem can be solved without too much complexity.<br><br><br>Now this is a lot of work.  As well as the kernel changes we'll need to<br>update the userland tools: thin_check, thin_ls, thin_metadata_unpack,<br>thin_rmap, thin_delta, thin_metadata_pack, thin_repair, thin_trim,<br>thin_dump, thin_metadata_size, thin_restore.  Are we confident that we<br>have buy in from the FS teams that this will be widely adopted?  Are users<br>asking for this?  I really don't want to do 6 months of work for nothing.<br><br>- Joe<br></div></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Fri, May 26, 2023 at 10:37 AM Dave Chinner <<a href="mailto:david@fromorbit.com">david@fromorbit.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">On Thu, May 25, 2023 at 12:19:47PM -0400, Brian Foster wrote:<br>
> On Wed, May 24, 2023 at 10:40:34AM +1000, Dave Chinner wrote:<br>
> > On Tue, May 23, 2023 at 11:26:18AM -0400, Mike Snitzer wrote:<br>
> > > On Tue, May 23 2023 at 10:05P -0400, Brian Foster <<a href="mailto:bfoster@redhat.com" target="_blank">bfoster@redhat.com</a>> wrote:<br>
> > > > On Mon, May 22, 2023 at 02:27:57PM -0400, Mike Snitzer wrote:<br>
> > > > ... since I also happen to think there is a potentially interesting<br>
> > > > development path to make this sort of reserve pool configurable in terms<br>
> > > > of size and active/inactive state, which would allow the fs to use an<br>
> > > > emergency pool scheme for managing metadata provisioning and not have to<br>
> > > > track and provision individual metadata buffers at all (dealing with<br>
> > > > user data is much easier to provision explicitly). So the space<br>
> > > > inefficiency thing is potentially just a tradeoff for simplicity, and<br>
> > > > filesystems that want more granularity for better behavior could achieve<br>
> > > > that with more work. Filesystems that don't would be free to rely on the<br>
> > > > simple/basic mechanism provided by dm-thin and still have basic -ENOSPC<br>
> > > > protection with very minimal changes.<br>
> > > > <br>
> > > > That's getting too far into the weeds on the future bits, though. This<br>
> > > > is essentially 99% a dm-thin approach, so I'm mainly curious if there's<br>
> > > > sufficient interest in this sort of "reserve mode" approach to try and<br>
> > > > clean it up further and have dm guys look at it, or if you guys see any<br>
> > > > obvious issues in what it does that makes it potentially problematic, or<br>
> > > > if you would just prefer to go down the path described above...<br>
> > > <br>
> > > The model that Dave detailed, which builds on REQ_PROVISION and is<br>
> > > sticky (by provisioning same blocks for snapshot) seems more useful to<br>
> > > me because it is quite precise.  That said, it doesn't account for<br>
> > > hard requirements that _all_ blocks will always succeed.<br>
> > <br>
> > Hmmm. Maybe I'm misunderstanding the "reserve pool" context here,<br>
> > but I don't think we'd ever need a hard guarantee from the block<br>
> > device that every write bio issued from the filesystem will succeed<br>
> > without ENOSPC.<br>
> > <br>
> <br>
> The bigger picture goal that I didn't get into in my previous mail is<br>
> the "full device" reservation model is intended to be a simple, crude<br>
> reference implementation that can be enabled for any arbitrary thin<br>
> volume consumer (filesystem or application). The idea is to build that<br>
> on a simple enough reservation mechanism that any such consumer could<br>
> override it based on its own operational model. The goal is to guarantee<br>
> that a particular filesystem never receives -ENOSPC from dm-thin on<br>
> writes, but the first phase of implementing that is to simply guarantee<br>
> every block is writeable.<br>
> <br>
> As a specific filesystem is able to more explicitly provision its own<br>
> allocations in a way that it can guarantee to return -ENOSPC from<br>
> dm-thin up front (rather than at write bio time), it can reduce the need<br>
> for the amount of reservation required, ultimately to zero if that<br>
> filesystem provides the ability to pre-provision all of its writes to<br>
> storage in some way or another.<br>
> <br>
> I think for filesystems with complex metadata management like XFS, it's<br>
> not very realistic to expect explicit 1-1 provisioning for all metadata<br>
> changes on a per-transaction basis in the same way that can (fairly<br>
> easily) be done for data, which means a pool mechanism is probably still<br>
> needed for the metadata class of writes.<br>
<br>
I'm trying to avoid need for 1-1 provisioning and the need for a<br>
accounting based reservation pool approach. I've tried the<br>
reservation pool thing several times, and they've all collapsed<br>
under the complexity of behaviour guarantees under worst case write<br>
amplification situations.<br>
<br>
The whole point of the LBA provisioning approach is that it<br>
completely avoids the need to care about write amplification because<br>
the underlying device guarantees any write to a LBA that is<br>
provisioned will succeed. It takes care of the write amplification<br>
problem for us, and we can make it even easier for the backing<br>
device by aligning LBA range provision requests to device region<br>
sizes.<br>
<br>
> > If the block device can provide a guarantee that a provisioned LBA<br>
> > range is always writable, then everything else is a filesystem level<br>
> > optimisation problem and we don't have to involve the block device<br>
> > in any way. All we need is a flag we can ready out of the bdev at<br>
> > mount time to determine if the filesystem should be operating with<br>
> > LBA provisioning enabled...<br>
> > <br>
> > e.g. If we need to "pre-provision" a chunk of the LBA space for<br>
> > filesystem metadata, we can do that ahead of time and track the<br>
> > pre-provisioned range(s) in the filesystem itself.<br>
> > <br>
> > In XFS, That could be as simple as having small chunks of each AG<br>
> > reserved to metadata (e.g. start with the first 100MB) and limiting<br>
> > all metadata allocation free space searches to that specific block<br>
> > range. When we run low on that space, we pre-provision another 100MB<br>
> > chunk and then allocate all metadata out of that new range. If we<br>
> > start getting ENOSPC to pre-provisioning, then we reduce the size of<br>
> > the regions and log low space warnings to userspace. If we can't<br>
> > pre-provision any space at all and we've completely run out, we<br>
> > simply declare ENOSPC for all incoming operations that require<br>
> > metadata allocation until pre-provisioning succeeds again.<br>
> > <br>
> <br>
> The more interesting aspect of this is not so much how space is<br>
> provisioned and allocated, but how the filesystem is going to consume<br>
> that space in a way that guarantees -ENOSPC is provided up front before<br>
> userspace is allowed to make modifications.<br>
<br>
Yeah, that's trivial with REQ_PROVISION.<br>
<br>
If, at transaction reservation time, we don't have enough<br>
provisioned metadata space available for the potential allocations<br>
we'll need to make, we kick provisioning work off wait for more to<br>
come available. If that fails and none is available, we'll get an<br>
enospc error right there, same as if the filesystem itself has no<br>
blocks available for allocation.<br>
<br>
This is no different to, say, having xfs_create() fail reservation<br>
because ENOSPC, then calling xfs_flush_inodes() to kick off an inode<br>
cache walk to trim away all the unused post-eof allocations in<br>
memory to free up some space we can use. When that completes,<br>
we try the reservation again.<br>
<br>
There's no new behaviours we need to introduce here - it's just<br>
replication of existing behaviours and infrastructure.<br>
<br>
> You didn't really touch on<br>
> that here, so I'm going to assume we'd have something like a perag<br>
> counter of how many free blocks currently live in preprovisioned ranges,<br>
> and then an fs-wide total somewhere so a transaction has the ability to<br>
> consume these blocks at trans reservation time, the fs knows when to<br>
> preprovision more space (or go into -ENOSPC mode), etc.<br>
<br>
Sure, something like that. Those are all implementation details, and<br>
not really that complex to implement and is largely replication of<br>
reservation infrastructure we already have.<br>
<br>
> Some accounting of that nature is necessary here in order to prevent the<br>
> filesystem from ever writing to unprovisioned space. So what I was<br>
> envisioning is rather than explicitly preprovision a physical range of<br>
> each AG and tracking all that, just reserve that number of arbitrarily<br>
> located blocks from dm for each AG.<br>
> <br>
> The initial perag reservations can be populated at mount time,<br>
> replenished as needed in a very similar way as what you describe, and<br>
> 100% released back to the thin pool at unmount time. On top of that,<br>
> there's no need to track physical preprovisioned ranges at all. Not just<br>
> for allocation purposes, but also to avoid things like having to protect<br>
> background trims from preprovisioned ranges of free space dedicated for<br>
> metadata, etc. <br>
<br>
That's all well and good, but reading further down the email the<br>
breadth and depth of changes to filesystem and block device<br>
behaviour to enable this are ... significant.<br>
<br>
> > Further, managing shared pool exhaustion doesn't require a<br>
> > reservation pool in the backing device and for the filesystems to<br>
> > request space from it. Filesystems already have their own reserve<br>
> > pools via pre-provisioning. If we want the filesystems to be able to<br>
> > release that space back to the shared pool (e.g. because the shared<br>
> > backing pool is critically short on space) then all we need is an<br>
> > extension to FITRIM to tell the filesystem to also release internal<br>
> > pre-provisioned reserves.<br>
> > <br>
> > Then the backing pool admin (person or automated daemon!) can simply<br>
> > issue a trim on all the filesystems in the pool and spce will be<br>
> > returned. Then filesystems will ask for new pre-provisioned space<br>
> > when they next need to ingest modifications, and the backing pool<br>
> > can manage the new pre-provisioning space requests directly....<br>
> > <br>
> <br>
> This is written as to imply that the reservation pool is some big<br>
> complex thing, which makes me think there is some<br>
> confusion/miscommunication.<br>
<br>
No confusion, I'm just sceptical that it will work given my<br>
experience trying to implement reservation based solutions multiple<br>
different ways over the past decade. They've all failed because<br>
they collapse under either the complexity explosion or space<br>
overhead required to handle the worst case behavioural scenarios.<br>
<br>
At one point I calculated the worst case reservation needed ensure<br>
log recovery will always succeeded, ignoring write amplification,<br>
was about 16x the size of the log. If I took write amplification for<br>
dm-thinp having 64kB blocks and each inode hitting a different<br>
cluster in it's own dm thinp block, that write amplication hit 64x.<br>
<br>
So for recovering a 2GB log, if dm-thinp doesn't have a reserve of<br>
well over 100GB of pool space, there is no guarantee that log<br>
recovery will -always- succeed.<br>
<br>
It's worst case numbers like this which made me conclude that<br>
reservation based approaches cannot provide guarantees that ENOSPC<br>
will never occur. The numbers are just too large when you start<br>
considering journals that can hold a million dirty objects,<br>
intent chains that might require modifying hundreds of metadata<br>
blocks across a dozen transactions before they complete, etc.<br>
<br>
OTOH, REQ_PROVISION makes this "log recovery needs new space to be<br>
allocated" problem go away entirely. It provides a mechanism that<br>
ensures log recovery does not consume any new space in the backing<br>
pool as all the overwrites it performs are to previously provisioned<br>
metadata.....<br>
<br>
This is just one of the many reasons why I think the REQ_PROVISION<br>
method is far better than reservations - it solves problems that<br>
pure runtime reservations can't.<br>
<br>
> It's basically just an in memory counter of<br>
> space that is allocated out of a shared thin pool and is held in a<br>
> specific thin volume while it is currently in use. The counter on the<br>
> volume is managed indirectly by filesystem requests and/or direct<br>
> operations on the volume (like dm snapshots).<br>
><br>
> Sure, you could replace the counter and reservation interface with<br>
> explicitly provisioned/trimmed LBA ranges that the fs can manage to<br>
> provide -ENOSPC guarantees, but then the fs has to do those various<br>
> things you've mentioned:<br>
> <br>
> - Provision those ranges in the fs and change allocation behavior<br>
>   accordingly.<br>
<br>
This is relatively simple - most of the allocator functionality is<br>
already there.<br>
<br>
> - Do the background post-crash fitrim preprovision clean up thing.<br>
<br>
We've already decided this is not needed.<br>
<br>
> - Distinguish between trims that are intended to return preprovisioned<br>
>   space vs. those that come from userspace.<br>
<br>
It's about ten lines of code in xfs_trim_extents() to do this.  i.e.<br>
the free space tree walk simply skips over free extents in the<br>
metadata provisioned region based on a flag value.<br>
<br>
> - Have some daemon or whatever (?) responsible for communicating the<br>
>   need for trims in the fs to return space back to the pool.<br>
<br>
Systems are already configured to run a periodic fstrim passes to do<br>
this via systemd units. And I'm pretty sure dm-thinp has a low space<br>
notification to userspace (via dbus?) that is already used by<br>
userspace agents to handle "near ENOSPC" events automatically.<br>
<br>
> Then this still depends on changing how dm thin snapshots work and needs<br>
> a way to deal with delayed allocation to actually guarantee -ENOSPC<br>
> protection..?<br>
<br>
I think you misunderstand: I'm not proposing to use REQ_PROVISION<br>
for writes the filesystem does not guarantee will succeed. Never<br>
have, I think it makes no sense at all.  If the filesystem<br>
can return ENOSPC for an unprovisioned user data write, then the<br>
block device can too.<br>
<br>
> > Hence I think if we get the basic REQ_PROVISION overwrite-in-place<br>
> > guarantees defined and implemented as previously outlined, then we<br>
> > don't need any special coordination between the fs and block devices<br>
> > to avoid fatal ENOSPC issues with sparse and/or snapshot capable<br>
> > block devices...<br>
> > <br>
> <br>
> This all sounds like a good amount of coordination and unnecessary<br>
> complexity to me. What I was thinking as a next phase (i.e. after<br>
> initial phase full device reservation) approach for a filesystem like<br>
> XFS would be something like this.<br>
> <br>
> - Support a mount option for a configurable size metadata reservation<br>
>   pool (with sane/conservative default).<br>
<br>
I want this to all to work without the user having be aware that<br>
there filesystem is running on a sparse device.<br>
<br>
> - The pool is populated at mount time, else the fs goes right into<br>
>   simulated -ENOSPC mode.<br>
<br>
What are the rules of this mode?<br>
<br>
Hmmmm.<br>
<br>
Log recovery needs to be able to allocate new metadata (i.e. in<br>
intent replay), so I'm guessing reservation is needed before log<br>
recovery? But if pool reservation fails, how do we then safely<br>
perform log recovery given the filesystem is in ENOSPC mode?<br>
<br>
> - Thin pool reservation consumption is controlled by a flag on write<br>
>   bios that is managed by the fs (flag polarity TBD).<br>
<br>
So we still need a bio flag to communicate "this IO consumes<br>
reservation".<br>
<br>
What are the semantics of this flag?  What happens on submission<br>
error? e.g. the bio is failed before it gets to the layer that<br>
consumes it - how does the filesystem know that reservation was<br>
consumed or not at completion?<br>
<br>
How do we know when to set it for user data writes?<br>
<br>
What happens if the device recieves a bio with this flag but there<br>
is no reservation remaining? e.g. the filesystem or device<br>
accounting have got out of whack?<br>
<br>
Hmmm. On that note, what about write amplification? Or should I call<br>
it "reservation amplification". i.e. a 4kB bio with a "consume<br>
reservation" flag might trigger a dm-region COW or allocation and<br>
require 512kB of dm-thinp pool space to be allocated. How much<br>
reservation actually gets consumed, and how do we reconcile the<br>
differences in physical consumption vs reservation consumption?<br>
<br>
> - All fs data writes are explicitly reserved up front in the write path.<br>
>   Delalloc maps to explicit reservation, overwrites are easy and just<br>
>   involve an explicit provision.<br>
<br>
This is the first you've mentioned an "explicit provision"<br>
operation. Is this like REQ_PROVISION, or something else?<br>
<br>
This seems to imply that the ->iomap_begin method has to do<br>
explicit provisioning callouts when we get a write that lands in an<br>
IOMAP_MAPPED extent? Or something else?<br>
<br>
Can you describe this mechanism in more detail?<br>
<br>
> - Metadata writes are not reserved or provisioned at all. They allocate<br>
>   out of the thin pool on write (if needed), just as they do today. On<br>
>   an -ENOSPC metadata write error, the fs goes into simulated -ENOSPC mode<br>
>   and allows outstanding metadata writes to now use the bio flag to<br>
>   consume emergency reservation.<br>
<br>
Okay. We need two pools in the backing device? The normal free space<br>
pool, and an emergency reservation pool?<br>
<br>
Without reading further, this implies that the filesystem is<br>
reliant on the emergency reservation pool being large enough that<br>
it can write any dirty metadata it has outstanding without ENOSPC<br>
occuring. How does the size of this emergency pool get configured?<br>
<br>
> So this means that metadata -ENOSPC protection is only as reliable as<br>
> the size of the specified pool. This is by design, so the filesystem<br>
> still does not have to track provisioning, allocation or overwrites of<br>
> its own metadata usage. Users with metadata heavy workloads or who<br>
> happen to be sensitive to -ENOSPC errors can be more aggressive with<br>
> pool size, while other users might be able to get away with a smaller<br>
> pool. Users who are super paranoid and want perfection can continue to<br>
> reserve the entire device and pay for the extra storage.<br>
<br>
Oh. Hand tuning. :(<br>
<br>
> Users who are not sure can test their workload in an appropriate<br>
> environment, collect some data/metrics on maximum outstanding dirty<br>
> metadata, and then use that as a baseline/minimum pool size for reliable<br>
> behavior going forward.  This is also where something like Stratis can<br>
> come in to generate this sort of information, make recommendations or<br>
> implement heuristics (based on things like fs size, amount of RAM, for<br>
> e.g.) to provide sane defaults based on use case. I.e., this is<br>
> initially exposed as a userspace/tuning issue instead of a<br>
> filesystem/dm-thin hard guarantee.<br>
<br>
Which are the same things people have been complaining about for years.<br>
<br>
> Finally, if you really want to get to that last step of maximally<br>
> efficient and safe provisioning in the fs, implement a<br>
> 'thinreserve=adaptive' mode in the fs that alters the acquisition and<br>
> consumption of dm-thin reserved blocks to be adaptive in nature and<br>
> promises to do it's own usage throttling against outstanding<br>
> reservation. I think this is the mode that most closely resembles your<br>
> preprovisioned range mechanism.<br>
><br>
> For example, adaptive mode could add the logic/complexity where you do<br>
> the per-ag provision thing (just using reservation instead of physical<br>
> ranges), change the transaction path to attempt to increase the<br>
> reservation pool or go into -ENOSPC mode, and flag all writes to be<br>
> satisfied from the reserve pool (because you've done the<br>
> provision/reservation up front).<br>
<br>
Ok, so why not just go straight to this model using REQ_PROVISION?<br>
<br>
If we then want to move to a different "accounting only" model for<br>
provisioning, we just change REQ_PROVISION?<br>
<br>
But I still see the problem of write amplification accounting being<br>
unsolved by the "filesystem accounting only" approach advocated<br>
here.  We have no idea when the backing device has snapshots taken,<br>
we have no idea when a filesystem write IO actually consumes more<br>
thinp blocks than filesystem blocks, etc.  How does the filesystem<br>
level reservation pool address these problems?<br>
<br>
> Thoughts on any of the above?<br>
<br>
I'd say it went wrong at the requirements stage, resulting in an<br>
overly complex, over-engineered solution.<br>
<br>
> One general tradeoff with using reservations vs. preprovisioning is the<br>
> the latter can just use the provision/trim primitives to alloc/free LBA<br>
> ranges. My thought on that is those primitives could possibly be<br>
> modified to do the same sort of things with reservation as for physical<br>
> allocations. That seems fairly easy to do with bio op flags/modifiers,<br>
> though one thing I'm not sure about is how to submit a provision bio to<br>
> request a certain amount location agnostic blocks. I'd have to<br>
> investigate that more.<br>
<br>
Sure, if the constrained LBA space aspect of the REQ_PROVISION<br>
implementation causes issues, then we see if we can optimise away<br>
the fixed LBA space requirement.<br>
<br>
<br>
-- <br>
Dave Chinner<br>
<a href="mailto:david@fromorbit.com" target="_blank">david@fromorbit.com</a><br>
<br>
</blockquote></div>