Skip to content

dm-crypt Causing System Freezes

For the past year, I had been having frequent system freezes on my NixOS desktop and laptop. The system would become completely unresponsive, to the point where only SysRq commands would work.

The freezes occured particularly often, and repeatably, during the following events:

  • nixos-rebuild
  • Downloading large files e.g. a 33GB Immich album

Initially I thought the issue was due to the IO scheduler being used (mq-deadline on my desktop with a SATA SSD, none on my laptop with an NVME SSD). However, even after changing the scheduler, there was no improvement.

The IO scheduler

You can check the scheduler being used for a particular device with:

 cat /sys/block/nvme0n1/queue/scheduler
none mq-deadline kyber [bfq]

To change the scheduler:

 echo none | sudo tee /sys/block/nvme0n1/queue/scheduler
none

Generally, bfq is recommended for interactive systems as it guarantees disk latencies similar to idle, at the expense of throughput.

You can see a comparison of schedulers here, as well as on the Arch Linux Wiki.

The Culprit

As part of exploration I ran sar -d 1, and noticed that the average disk queue size (aqu-sz) became excessively large during the above operations.

I found that by simulating large writes (e.g. with openssl rand -out bigfile.bin $((1024 * 1024 * 30000))), I was able to increase the queue size, to the point where disk utilization hovered at 100% and thereafter the system would lockup.

Finally, I found an article on Reddit that suggested dm-crypt was the culprit. Turns out, Cloudflare also did a writeup on the issue.

Essentially, dm-crypt maintains a separate workqueue for IO, which is actually unnecessary on fast drives like SSDs. For reasons I do not fully understand yet, this can severely degrade performance under load. I suspect this is due to running the IO scheduler over a workqueue (in this case, a sorted binary tree), instead of allowing it to access block IO directly.

Fixing

You can check whether dm-crypt uses the workqueues:

 sudo dmsetup table
luks-primary: 0 487315456 crypt aes-xts-plain64 :64:logon:cryptsetup:7b61f149-1dc8-499b-b5b6-078e05031c1b-d0 0 8:2 32768 2 no_read_workqueue no_write_workqueue
vg-nixos: 0 487309312 linear 254:0 2048

In the example above, the workqueues have already been disabled (no_read_workqueue and no_write_workqueue respectively).

To change them:

# Temporary
# To make persistent, add --persistent
cryptsetup --perf-no_read_workqueue --perf-no_write_workqueue refresh DEVICE

Comments