Skip to content

[6.12] Track btrfs patches#36

Closed
kakra wants to merge 36 commits into
base-6.12from
rebase-6.12/btrfs-patches
Closed

[6.12] Track btrfs patches#36
kakra wants to merge 36 commits into
base-6.12from
rebase-6.12/btrfs-patches

Conversation

@kakra

@kakra kakra commented Nov 23, 2024

Copy link
Copy Markdown
Owner

Export patch series: https://github.com/kakra/linux/pull/36.patch

Here's a good guide by @Forza-tng: https://wiki.tnonline.net/w/Btrfs/Allocator_Hints. Please leave them a nice comment. Thanks. :-)

  • Allocator hint patches: Allows to prefer SSDs for meta-data allocations while excluding HDDs from meta-data allocation, greatly improves btrfs responsiveness, file system remains compatible with non-patched systems but won't honor allocation preferences then (re-balance needed to fix that after going back to a patched kernel)
  • RAID1 read balance patches: Allows to round-robin RAID1 read requests across multiple disks, or prefer the device with the lowest latency. In some non-scientific tests, this can easily cut loading times in some games in half.

Allocator hints

To make use of the allocator hints, add these to your kernel. Then run btrfs device usage /path/to/btrfs and take note of which device IDs are SSDs and which are HDDs.

Go to /sys/fs/btrfs/BTRFS-UUID/devinfo and run:

  • echo 0 | sudo tee HDD-ID/type to prefer writing data to this device (btrfs will then prefer allocating data chunks from this device before considering other devices) - recommended for HDDs, set by default
  • echo 1 | sudo tee SSD-ID/type to prefer writing meta-data to this device (btrfs will then prefer allocating meta-data chunks from this device before considering other devices) - recommended for SSDs
  • There's also type 2 and 3 which write meta-data only (2) or data only (3) to the specified device - not recommended, can result in early no-space situations
  • Added 2024-06-27: Type 4 can be used to avoid allocating new chunks from a device, useful if you plan on removing the device from the pool in the future: echo 4 | sudo tee LEGACY-ID/type
  • Added 2024-12-06: Type 5 can be used to prevent allocating any chunks from a device, useful if you plan on removing multiple devices from the pool in parallel: echo 5 | sudo tee LEGACY-ID/type
  • NEVER EVER use type 2 or 3 if you only have one type of device unless you know what you do and why you are doing this
  • The default "preferred" heuristics (0 and 1) are good enough because btrfs will always allocate from devices with most space first (respecting the "preferred" type with this patch)
  • After changing the values, a one-time meta-data and/or data balance (optionally filtered to the affected device IDs) is needed

Important note: This recommends to use at least two independent SSDs so btrfs meta-data raid1 requirement is still satisfied. You can, however, create two partitions on the same SSD but then it's no longer protected against hardware faults, it's essentially dup-quality meta-data then, not raid1. Before sizing the partitions, look at btrfs device usage to find the amount of meta-data, at least double that size to size your meta-data partitions.

This can be combined with bcache by directly using meta-data partitions as a native SSD partition for btrfs, and only using data partitions routed through bcache. This also takes a lot of meta-data pressure from bcache, making it more efficient and less write-wearing as a result.

Real-world example

In this example, sde is a 1 TB SSD having two meta-data partitions (2x 128 GB) with the remaining space dedicated to a single bcache partition attached to my btrfs pool devices:

# btrfs device usage /
/dev/bcache2, ID: 1
   Device size:             3.63TiB
   Device slack:            3.50KiB
   Data,single:             1.66TiB
   Unallocated:             1.97TiB

/dev/bcache0, ID: 2
   Device size:             3.63TiB
   Device slack:            3.50KiB
   Data,single:             1.66TiB
   Unallocated:             1.97TiB

/dev/bcache1, ID: 3
   Device size:             2.70TiB
   Device slack:            3.50KiB
   Data,single:           752.00GiB
   Unallocated:             1.96TiB

/dev/sde4, ID: 4
   Device size:           128.00GiB
   Device slack:              0.00B
   Metadata,RAID1:         27.00GiB
   System,RAID1:           32.00MiB
   Unallocated:           100.97GiB

/dev/sde5, ID: 5
   Device size:           128.01GiB
   Device slack:              0.00B
   Metadata,RAID1:         27.00GiB
   System,RAID1:           32.00MiB
   Unallocated:           100.98GiB

# bcache show
Name            Type            State                   Bname           AttachToDev
/dev/sdd2       1 (data)        dirty(running)          bcache1         /dev/sde2
/dev/sdb2       1 (data)        dirty(running)          bcache2         /dev/sde2
/dev/sde2       3 (cache)       active                  N/A             N/A
/dev/sdc2       1 (data)        clean(running)          bcache3         /dev/sde2
/dev/sda2       1 (data)        dirty(running)          bcache0         /dev/sde2

A curious reader may find that sde1 and sde3 are missing, which is my EFI boot partition (sde1) and swap space (sde3).

Read Policies aka "RAID1 read balancer"

To use the balancer, CONFIG_BTRFS_EXPERIMENTAL=y is needed while building the kernel. The balancer offers six modes:

  • pid provides the old PID-based balancer and is the default.
  • round-robin provides the best performance if all member disks are of the same type. Performance may be unstable with bcache. Preferred for parallel workloads like servers.
  • latency provides best performance if using asymmetric RAID configurations with mixed device types (e.g. SSD paired with HDD). Performance may be unstable with bcache but may increase bcache hit rates over time. Preferred for latency-sensitive workloads like desktop.
  • latency-rr tries to combine round-robin and latency into one hybrid approach by using round-robin across a set of stripes within a 125% margin of the best latency. I am currently testing this and have not yet discovered the benefits or downsides but in theory it should prefer the fastest stripes for small requests while it switches over to using all stripes for large continuous requests. Technically, this works either as latency or round-robin with just two mirrors, should work better with more mirrors.
  • queue diverts each request to a stripe with the shortest IO queue (in-flight requests) and works exceptionally (and unexpected) well with all workloads I tested: It massively outperforms all other policies in each benchmark scenario and in virtualization workloads.
  • devid prefers reading from a specified device ID. Similar to latency, it provides best performance if you know one drive is faster than another. This could stabilize performance with bcache as it will prefer caching data only of specified the disk. Defaults to the latest disk added to the pool.

Combined with bcache, performance is unstable with both round-robin and latency because latency and throughput depend on whether data is cached or not - but still it is overall better than the old PID balancer.

Unexpectedly, queue performs exceptionally well on my mixed device setup. YMMV with identical member devices. It outperforms all other policies in each discipline and benchmark.

To use the balancer, use btrfs.read_policy=<pid,round-robin,latency,latency-rr,queue,devid:#> on the kernel cmdline. There's also a sysfs interface at /sys/fs/btrfs/<UUID>/read_policy to switch balancers on demand (e.g., for benchmarks). See modinfo btrfs for more information.

Benchmark results: https://gist.github.com/kakra/ce99896e5915f9b26d13c5637f56ff37

Note 1: The latency calculation currently uses an average of the full history of requests only - which is bad because it will cancel out changing variations over time. A better approach would be to use an EMA (exponential moving average) with an alpha of 1/8 or 1/16. This requires to sample individual bio latency and thus requires to change structs and code in other parts of btrfs. I'm not very familiar with all the internal structures yet, and the feature is still guarded by CONFIG_BTRFS_EXPERIMENTAL, making that approach more complex. OTOH, having a permanent EMA right in the bio structures of btrfs could prove useful in other areas of btrfs.

Note 2: In theory, both latency modes should automatically prefer faster zones of HDDs and properly switch stripes automatically. In practice, this is probably overruled by note 1 except most of your data is in specific zones by coincidence, in which case the average would properly hold some sort of "zone performance".

Note 3: With high CPU core counts, queue might have a measurable CPU overhead due to queue length calculation (per-core counters have to be summed per each request).

Real-world example

Some simple tests have shown that both the round-robin and latency balancers can increase throughput while loading games from 200-300 MB/s to 500-700 MB/s when combined with bcache.

Important note: This will be officially available with kernel 6.15 or later, excluding the latency balancer. I've included it because I think it can provide better performance in edge cases, e.g. asymmetric RAID or bcache. It may also provide better performance on the desktop because on the desktop, latency is more important than throughput. The latency balancer is thus an experiment and may go away. But I will keep it until at least the next LTS kernel.


Description / instructions for balancing

(AI generated after training with some stats, observations and incremental development steps)

Interpreting the Btrfs read_stats Sysfs Output

The /sys/fs/btrfs/<UUID>/devinfo/<DEVID>/read_stats file, enhanced by these patches, offers valuable insights into the dynamic read balancing behavior and performance of individual devices within a Btrfs RAID1/10/1C3/4 setup. Here's a breakdown of the fields:

cumulative ios %lu wait %llu avg %llu checkpoint ios %ld wait %lld avg %llu age %lld count %llu ignored %lld
  • cumulative ios %lu: The total count of read I/O operations completed on this specific device since the filesystem was mounted.

  • cumulative wait %llu: The total time (in nanoseconds) accumulated waiting for all cumulative read IOs on this device.

  • cumulative avg %llu: The long-term average read latency (cumulative wait / cumulative ios) in nanoseconds. This represents the device's average performance over its entire operational history within the current mount. It changes very slowly and can be heavily influenced by caching layers (like bcache or the page cache) if present.

  • checkpoint ios %ld: The number of read IOs completed since the last checkpoint. A checkpoint is established when a device undergoes "rehabilitation" – meaning its age counter reached the BTRFS_DEVICE_LATENCY_CHECKPOINT_AGE threshold, triggering a read probe and a reset of these checkpoint statistics. For devices that have never been rehabilitated, this value will equal cumulative ios.

  • checkpoint wait %lld: The total time (in nanoseconds) accumulated waiting for reads since the last checkpoint.

  • checkpoint avg %llu: The average read latency (checkpoint wait / checkpoint ios) calculated only using the IOs since the last checkpoint. This is a key metric reflecting recent performance. It's much more responsive to current conditions than the cumulative average, especially after a period of being ignored.

  • age %lld: This counter tracks how "stale" the device is in terms of read selection. It increments each time a read balancing decision is made for a stripe group containing this device, but another device from that group is chosen.

    • 0: The device was selected for a read very recently (in the last relevant balancing decision).
    • > 0: The device has been ignored for this many consecutive selection events where it was a candidate. A high value indicates it's consistently considered slower or less preferred than its peers.
    • < 0: The device has just been rehabilitated (hit the age threshold). It is now in a "burst IO" probation period (e.g., starting at -100 and incrementing towards 0). During this negative age phase, its reported latency is forced to 0 to guarantee it receives reads.
  • count %llu: The number of times this device has triggered the rehabilitation mechanism by reaching the age threshold. A high count suggests the device is frequently deemed too slow by the latency policy or is subject to other selection biases (like non-balancing metadata reads).

  • ignored %lld: A counter incremented every time this device was a candidate for a read, but the balancing policy ultimately selected a different device from the same stripe group. This provides insight into how often the policy actively chooses a peer over this device, indicating relative preference or "fairness" of the algorithm.

How to Use These Stats:

  • Identify Slow Devices: Devices consistently showing a high checkpoint avg (compared to peers) and a high, frequently reset age are likely performance bottlenecks under the current load.
  • Assess Policy Effectiveness: Compare cumulative avg and checkpoint avg. A large difference after rehabilitation (count > 0) shows the policy is adapting to performance changes more quickly than the cumulative average would suggest.
  • Detect Selection Bias: A device with a good checkpoint avg but a persistently high age and ignored count (like NVMe metadata mirrors sometimes exhibit) points towards a selection bias not based purely on latency.
  • Tune Rehabilitation: The age and count values help evaluate the AGE_THRESHOLD and IO_BURST parameters. If age hits the threshold very frequently, it might be too low. If checkpoint ios barely increases after a reset, the IO_BURST might be too short (or the device becomes slow again immediately).

These enhanced statistics provide a powerful diagnostic tool for understanding and fine-tuning Btrfs's read balancing behavior in complex, real-world storage environments.

Loading
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

done To be superseded by next LTS

Projects

None yet

Development

Successfully merging this pull request may close these issues.