How a Routine Linux fstrim Job Took Down MySQL: An InnoDB Crash Analysis

Vinoth Kanna
Oct 13, 2026
7
Mins to Read
All
How a Routine Linux fstrim Job Took Down MySQL
How a Routine Linux fstrim Job Took Down MySQL

When a production MySQL instance abruptly terminates, the standard response is to search inside the database: did a runaway analytical query consume all available memory? Did an unindexed schema migration trigger table lock contention? Did a client application flood the connection pool?

Recently, one of our managed production clusters monitored by our DBA Managed Services team, faced an unexpected outage where the database was entirely blameless. Queries were running normally, traffic was standard, and CPU and memory usage showed no spikes. Yet, InnoDB’s internal watchdog decided the server was hung and intentionally forced an abort.

The actual culprit was one layer down in the operating system: fstrim, a routine Linux disk maintenance job. A burst of block discard requests saturated the disk controller long enough to trip InnoDB's safety limits, as outlined in the MySQL 8.0 Reference Manual: InnoDB Troubleshooting.

Here is the step-by-step investigation of how this happened, why thick-provisioned storage made it worse, and how to prevent it in your environment.

The Incident: A Fatal Semaphore Abort

Early on a Monday morning, a production MySQL server running Percona Server 8.0.33-25 on VirtIO block storage stopped serving traffic. The error log did not point to an Out-Of-Memory (OOM) killer event or a host-level hypervisor crash. As documented in MySQL Error Handling and Fatal Aborts, InnoDB had chosen to terminate itself:

[ERROR] [MY-012872] [InnoDB] [FATAL] Semaphore wait has lasted > 600 seconds.
We intentionally crash the server because it appears to be hung.
mysqld got signal 6 ;

The stack trace confirmed that srv_error_monitor_thread called ib::fatal.

I/O Contention: Shared Virtual Block Path

Both database transactions and batch discards converge onto the same VirtIO queue

MySQL Server (mysqld) • Data Reads & Writes • Redo Log / Binlog fsync() fstrim.timer (Systemd) • Weekly Discard (TRIM/UNMAP) • Multi-TiB Batch Queue Flush VIRTIO BLOCK REQUEST QUEUE Saturated Queue • Elevated Latency /data_mysql01 VirtIO Device (/dev/vdb) 3.2 TiB Discarded /data_mysql02 VirtIO Device (/dev/vdc) 1.6 TiB Discarded /logs VirtIO Device (/dev/vdd) 457 GiB Discarded

MySQL has an internal watchdog thread that periodically inspects sync primitives (mutexes and read-write locks). If a thread waits on an internal semaphore for longer than 600 seconds (10 minutes), InnoDB assumes that code execution is deadlocked or broken. Rather than risking data corruption or hanging indefinitely in an undefined state, it raises SIGABRT to trigger a crash recovery on restart.

The question was: What kept an InnoDB thread blocked for 10 full minutes?

The Investigation: Working Down the Stack

Diagnostic Flow: Isolating the Outage Layer

Step 01
Error Log Verification

InnoDB semaphore wait > 600s triggered SIGABRT. Stack pointed to watchdog thread.

Excluded OOM / Kernel Crash
Step 02
Engine Metrics

Checked connection count, running threads, locks, buffer pool activity, and active DDL.

Database Internals Clean
Step 03
Host OS Metrics

Examined scheduled background systemd services and cron timers around crash time.

Found fstrim.service Active
Step 04
Timeline Correlation

Aligned the 65m discard run with InnoDB's 600s counter. Exact 1:1 timeline match.

Root Cause: I/O Saturation

Step 1: Rule Out Database-Level Issues

We started by reviewing activity immediately preceding the crash:

  • Running queries showed normal execution plans and execution times.
  • Connection counts were well within configured thresholds.
  • InnoDB row lock waits, transaction lists, and throughput metrics were clean.
  • No Data Definition Language (ALTER TABLE) statements were running.
Result: nothing stood out. No runaway query, no unusual load, no obvious lock contention. The database had no visible reason to be stuck, so the cause had to be outside it.

Step 2: Check Host and Scheduled Jobs

With the database ruled out, we moved to the OS-level metrics and to anything else that ran on the host around thesame time. The systemd journal showed a job that lined up exactly with the incident: fstrim.service, started bythe weekly fstrim.timer. It was sending discard requests for several TiB of free blocks to the very volumes InnoDB reads from and writes to:

00:54:17 systemd: Starting Discard unused blocks on filesystems from /etc/fstab...
01:59:25 fstrim: /logs: 457.6 GiB trimmed on /dev/vdd
01:59:25 fstrim: /data_mysql02: 1.6 TiB trimmed on /dev/vdc
01:59:25 fstrim: /data_mysql01: 3.2 TiB trimmed on /dev/vdb
01:59:25 systemd: fstrim.service: Deactivated successfully. (UTC, abridged)

Step 3: Line up the timeline

fstrim started at 06:24 IST (00:54 UTC). InnoDB's 600 s counter shows the stall began no later than 01:31:34 UTC,about 37 minutes into the job, and the abort came 47 minutes after it started, squarely inside fstrim's active window. In other words, InnoDB threads were waiting on disk I/O that fstrim's discard burst had stalled. The job finished at 01:59:25, while the server was still starting back up.

Timeline Reconstruction: Discard Burst vs Watchdog Abort

Proportional UTC timeline (00:54 to 02:00) showing disk stall overlap

mysqld auto-restart 01:57:38 00:54:17 fstrim starts (5.2 TiB) 01:31:34 Latch stalled by disk I/O 01:41:34 >600s FATAL abort (SIGABRT) 02:00:22 Recovery ready (2m 38s)
fstrim.service Execution (00:54:17 – 01:59:25 / 65 min)
InnoDB Semaphore Wait Stall (01:31:34 – 01:41:34 / 600 s)
Crash Recovery Window (01:57:38 – 02:00:22 / 2m 38s)

That was the lead. The next question was what fstrim actually does, and how it could stall disk I/O enough to hurt adatabase.

Digging Deeper: What fstrim Actually Does

fstrim is a Linux utility that tells the underlying storage which filesystem blocks are no longer in use. When a file is deleted, the filesystem only marks its blocks free; the storage below has no idea. fstrim sends discard (TRIM/UNMAP)requests for those free ranges so the backend can reclaim them.

sudo fstrim -v /data

The Discard Lifecycle: From File Deletion to Hardware

How free-space metadata propagates from the database down to storage blocks

STEP 01 Userland
File Deleted

rm, purge, or DROP TABLE removes metadata

STEP 02 Filesystem
Filesystem Free

OS marks block extents free in inode bitmap

STEP 03 Block Layer
fstrim Dispatched

Sends batch FITRIM ioctl requests down I/O queue

STEP 04 Hardware
Storage Backend

Reclaims space only if storage pool is thin-provisioned

Operational Reality: df -h usage does not decrease, database files are not touched, and the disk volume is not shrunk. fstrim only informs the backing controller which block addresses are unreferenced.

When it is useful: Only when the storage can act on it: thin-provisioned volumes, thin LVM pools and flash media. On thin storage, physical capacity is allocated on demand, so reclaiming deleted blocks frees real pool space. On thick(fixed) provisioning the capacity is already allocated, so there is nothing to give back.

Thin vs. Thick Provisioning: Discard Behavior

Why fstrim reclaims physical storage on dynamic pools but creates pure overhead on thick disks

Thin Provisioned Volume Physical Space Reclaimable
physically allocated (500 GB)
free logical extents (500 GB)

Logical disk capacity: 1 TB. Space is only consumed on write; deleted extents are handed back to the shared storage pool by fstrim.

Real capacity reclaimed
Thick / Fixed Provisioned (Our MySQL Volumes) Zero Capacity Benefit
fully allocated up front (1 TB) — 100% reserved

Nothing to give back. Physical backing is permanently reserved up front. Discard commands generate massive I/O traffic with zero pool capacity benefit.

0 bytes reclaimed • Pure I/O overhead

fstrim does not delete files, shrink the filesystem, reduce df -h usage, or reduce the provisioned disk size.

How it works: DISC-GRAN and DISC-MAX

You can verify whether a Linux system accepts discard requests by inspecting the parameters detailed in the Linux fstrim(8) Manual.

fstrim asks the filesystem for its free extents, and the block layer cuts them into discard requests using the limits thedevice advertises. lsblk -D shows those limits:

The Discard Pipeline: Kernel to Storage Backend

How a single userland trim request multiplies into thousands of I/O operations

01 / Trigger
fstrim

(weekly timer)

02 / System Call
FITRIM ioctl

per mount point

03 / Filesystem
Filesystem

lists free extents

04 / Fragmentation
Block Layer

splits by DISC-MAX, aligns to DISC-GRAN

05 / Hardware
virtio-blk

hypervisor → storage backend

Key Insight: A single fstrim execution explodes into thousands of individual discard requests at the block layer—all saturating the exact same virtual I/O queue that InnoDB relies on for transaction commits and fsync().

lsblk -D /dev/vdb
NAME DISC-ALN DISC-GRAN DISC-MAX DISC-ZERO
vdb 0 4K 2G 0

Parameter Dictionary: lsblk -D Discard Metrics

How Linux translates block geometry into batch discard behavior

Column Meaning Why It Matters to MySQL
DISC-GRAN
Discard Granularity
The smallest contiguous unit of storage that the underlying device or virtual controller can discard at a time. All filesystem free extents must align to this value.
0 means no discard support (which safely prevents fstrim from submitting I/O).
Guard Check: 0 = Safe
DISC-MAX
Max Discard Size
The maximum size of a single discard request allowed by the block driver (e.g., 2G on VirtIO). Determines queue burst density: A large value causes a few massive requests; a small value spawns hundreds of thousands of small requests. A multi-TiB trim generates a severe I/O storm either way.
Queue Saturation Vector
DISC-ALN / DISC-ZERO
Alignment & Zeroing
Alignment offset in bytes, and a flag indicating whether discarded blocks read back deterministically as zeroes. Informational on virtual and cloud disks. Has minimal performance impact on guest storage queue contention.
Informational Flag

Verification Matrix: Is fstrim Actually Reclaiming Blocks?

Differentiating between advertised interface support, queued commands, and true backend reclamation

Verification Check What You Observe Underlying Meaning
lsblk -D Device layer query DISC-GRAN & DISC-MAX = 0 Device does not accept discards. fstrim immediately fails with 'operation not supported' and terminates without sending I/O.
No Discard Support (Safe)
lsblk -D Device layer query DISC-GRAN & DISC-MAX > 0 Discard is advertised by the driver/hypervisor. fstrim considers the target eligible and will submit discard commands down the I/O path.
Discard Enabled in OS
sudo fstrim -v /mount journalctl -u fstrim Filesystem runtime check 'X GiB (N bytes) trimmed' fstrim submitted discard requests for that address range. This only proves requests entered the block queue (what we observed in the logs).
I/O Burst Dispatched
Storage Backend Stats Thin pool usage / SAN metrics Pool allocation drops The only real proof of reclamation. Only here do you verify whether storage was actually freed. On thick-provisioned storage, it never drops.
True Capacity Verification

Crucial Takeaway: Discard support is not the same as discard being useful. On fixed-provisioned VirtIO disks, DISC-GRAN and DISC-MAX are non-zero and fstrim reports terabytes "trimmed", yet zero capacity is reclaimed because the backing storage was never thin to begin with.

Root Cause: Why a Disk Cleanup Can Crash MySQL

Think of MySQL's internals as a busy single-lane bridge. Many workers (InnoDB threads) need to cross it to readtable information, update bookkeeping or fetch data pages. To avoid chaos only one worker is allowed on the bridge ata time. That one-at-a-time turnstile is what a mutex or latch is.

Normally each worker crosses in a split second. But if the road surface under the bridge, the disk, suddenly gets busywith a completely different task such as a huge cleanup sending thousands of 'these blocks are free' instructions, the worker on the bridge gets stuck mid-crossing. Everyone else queues behind it.

The Single-Lane Bridge: Latch Contention Model

How disk I/O saturation blocks critical mutual exclusion locks (latches)

THE BRIDGE = ONE INNODB LATCH, ONE THREAD AT A TIME (DICT_SYS / FIL_SHARD mutex, buffer-pool page latch, redo log fsync) Other InnoDB threads queue up behind it Stuck mid-crossing: waiting on a stalled disk ! SEMAPHORE LIMIT > 600 s InnoDB watchdog detects hung thread → ib::fatal (Signal 6) THE STORAGE LAYER UNDER THE BRIDGE: VIRTIO DISK CONTROLLER Saturated with fstrim discard I/O • Thousands of 2 GB batch requests blocking redo log fsync()

MySQL watches the clock. If a worker is stuck for more than 600 seconds, InnoDB's internal monitor assumes the server is hung and deliberately aborts instead of continuing in an unknown state.

Inside InnoDB, threads share a few core structures, each guarded by a latch that is meant to be held formicroseconds:

  • DICT_SYS: the data dictionary (table and index metadata)• FIL_SHARD: tablespace file bookkeeping (opening, extending, flushing files)
  • Buffer-pool latches: guard pages while they are read from or flushed to disk
  • Redo log and binlog writes: must be fsynced before a commit can finish

The danger is a thread that holds one of these latches while it waits for disk. When disk latency goes from millisecondsto seconds, that thread cannot release the latch, every other thread needing it must wait on a semaphore, and the queue grows for as long as the disk stays busy. Warnings about long semaphore waits typically start appearing afterabout four minutes; srv_error_monitor_thread pulls the plug at 600 seconds (ib::fatal, signal 6).

Note: the log excerpt we captured shows the abort but not which latch was involved. The names above are the typical candidates for this pattern; the SEMAPHORES section of SHOW ENGINE INNODB STATUS or the long-wait warnings just before a crash would name the exact one.

The trigger: how fstrim stalls disk I/O

In that single run fstrim walked the data and log volumes in turn and discarded about 5.2 TiB of free blocks in 65 minutes, most of it on the MySQL data volumes:

TiB Discarded in the 07 Sep Run

Total volume trimmed by fstrim across all mounted VirtIO block devices (5.2 TiB total)

3 TiB 2 TiB 1 TiB 0 3.2 TiB /data_mysql01 /dev/vdb 61.5% of total 1.6 TiB /data_mysql02 /dev/vdc 30.7% of total 457.6 GiB /logs /dev/vdd 8.6% of total 4.8 GiB / (root) /dev/vda 0.1% of total
5.2 TiB Total Discard Burst Volume
65 Minutes Job Duration on VirtIO Path
0 Bytes Physical Pool Reclaimed

Discards are real I/O. They travel through the same virtual block queue and storage backend as InnoDB's redo-log writes, page flushes and binlog fsyncs, and depending on the filesystem (ext4 or XFS) the trim also works through free-space structures that file extension and log rotation may need.

Even when a discard ends up as a no-op on thick storage, the backend still has to process every request. A multi-TiB burst therefore stalls disk I/O: reads, writes and fsyncs queue behind the discards, and fsync latency goes from milliseconds to seconds. That I/O stall is exactly what jams the bridge

Putting It Together: The Cascading Chain of Failure

How a background systemd timer escalated into a production database crash

01
fstrim.timer fires (weekly, Monday)
Ubuntu default scheduled job; run triggered at 00:54 UTC
02
fstrim -av walks every mounted volume
Targets /logs, /data_mysql01, /data_mysql02, and /
03
Millions of discard requests, TBs of ranges
~5.2 TiB of free block extents queued across 3 disks in 65 minutes
04
Discards compete with real reads/writes
Shared VirtIO block queue + hypervisor; filesystem locks while trimming
05
I/O stall on data and log volumes
Redo log, undo, and binlog fsync() times spike from ms to minutes
06
InnoDB threads block while holding latches
DICT_SYS, FIL_SHARD, and buffer pool page latches held waiting on disk
07
Queries and connections pile up
Application threads block on held latches; connection pool saturates
08
One wait crosses 600 seconds
srv_error_monitor_thread watchdog declares engine hung
09
ib::fatal → signal 6 → mysqld aborts
Automated crash restart; instance ready after crash recovery (~19m total)

Even when running resilient high-availability setups, such as those detailed in How to Set Up InnoDB Cluster Using MySQL Shell and our guide to Deploying and Managing MySQL InnoDB Cluster, an underlying OS storage freeze will take down a primary node if the maintenance jobs run concurrently.

The Fix: Disable fstrim on Thick-Provisioned Storage

The question was simple: what does fstrim give us on this storage? Our MySQL volumes are thick (fixed)provisioned: the full capacity is allocated up front. Deleting data returns space to the filesystem, but there is no thin pool underneath to hand it back to. The cost-benefit looked like this:

Cost-Benefit Matrix: fstrim on Fixed Storage

Evaluating normal thin-storage expectations against thick-provisioned database reality

Evaluation Dimension What fstrim Normally Delivers (Thin) On Our Thick-Provisioned MySQL Disks
Benefit Reclaims real physical capacity in a shared dynamic pool or thin LVM volume.
Frees Host Storage
None. Capacity is already fully reserved up front. No pool space is reclaimed.
0 GB Reclaimed
Cost A modest amount of background discard I/O during scheduled maintenance.
Minimal I/O Impact
Several TiB of discards per week competing across /logs, /data_mysql01, and /data_mysql02.
5.2 TiB Burst Flood
Risk Usually negligible on properly throttled storage controllers.
Low Risk Profile
VirtIO block queue saturation, severe fsync stalls, InnoDB latch waits, and a 600s watchdog crash.
Fatal Self-Abort (SIGABRT)
Net Result Net positive on thin storage (essential for preventing physical out-of-disk pool conditions).
Positive ROI
All cost, zero benefit. Pure operational downside with no possible gain.
Pure Overhead • Must Disable

Final Architectural Verdict: Because thick-provisioned disks cannot reclaim space, throttling fstrim is not worth the operational effort. Even a throttled run represents unneeded I/O overhead—disabling the job entirely is the optimal fix.

Architectural Decision: When to Retain or Disable fstrim

Determine your strategy based on underlying hypervisor block reclamation

DECISION GATEWAY
Does the backend actually reclaim space on discard (thin pool)?
YES • Thin / Flash Dynamic Pools Only
Keep It, But Control It
  • Reschedule execution to low-traffic maintenance windows
  • Throttle requests: fstrim -m 128M to skip fragmented chunks
  • Lower discard_max_bytes and alert on disk await latency

Engineering Rule of Thumb: On fixed-capacity disks (pre-allocated VMDKs, raw block LUNs, thick VirtIO), discard throttling is pure overhead. Completely disabling the systemd timer is both safer and more efficient.

Is it on by default? It depends on the distribution:

Distribution Defaults: fstrim.timer Behavior

Is automatic block discard enabled by default on your operating system?

Ubuntu Linux Enabled by Default
Timer Schedule

Active weekly timer (since version 18.04 LTS). Fires automatically early on Monday morning.

Verification Command: systemctl is-enabled fstrim.timer
RHEL (8 / 9) • Rocky • Alma Dormant by Default
Timer Schedule

Not enabled. The util-linux package supplies the systemd unit, but administrators must explicitly start and enable it.

Verification Command: systemctl is-enabled fstrim.timer

What we did. We disabled the timer on the MySQL host; no restart is needed:

sudo systemctl disable --now fstrim.timer # stop and disable the weekly job
sudo systemctl mask fstrim.timer fstrim.service # optional: prevent re-enabling
systemctl list-timers --all | grep fstrim # verify nothing is scheduled
grep -r fstrim /etc/cron* 2>/dev/null # make sure no cron job runs it

Disk capacity is now managed through filesystem housekeeping, log and backup cleanup, utilization monitoring, andstorage expansion when required.

When to keep fstrim: on thin-provisioned or flash-backed storage where reclaim matters, run it in an off-peakwindow, test on a staging server first, consider throttling (fstrim -m / -l, a lower discard_max_bytes), andalert on I/O latency while it runs.

The Result

The Result: Operational State Before vs. After

Production measurements recorded across VirtIO storage and MySQL watchdog threads

Metric Before Resolution After Disabling fstrim
Weekly fstrim Run
Yes (Monday early morning) Permanently Disabled
Discard I/O on MySQL Volumes
~5.2 TiB batch bursts across 3 disks None (0 Bytes) • Eliminated
Disk I/O During Monday Window
Stalled by discard burst No stall • Consistent <2ms
InnoDB Semaphore-Wait Abort
Triggered at 600s timeout (SIGABRT) Zero aborts since resolution
0 B Discard Queue Load
< 2 ms Redo Log fsync Latency
0 Semaphore Watchdog Timeouts
0 B Physical Capacity Lost

Since the timer was disabled we have not seen another InnoDB semaphore-wait abort. Nothing was lost by turning it off, because on fixed-provisioned storage fstrim was never reclaiming anything. Throttling it instead was not worth theeffort: with nothing to reclaim, even a throttled run is pure overhead.

Key Takeaways

  • Don't stop at the database: when queries and database metrics look clean, move to OS-level metrics andscheduled jobs. The cause may be one layer down.
  • Check the schedule: an event that lines up with an early-morning window deserves a look at systemctllist-timers and the systemd journal.
  • Maintenance jobs can stall I/O: a multi-TiB discard burst from fstrim competes with InnoDB's own reads andwrites. Watch disk I/O latency during scheduled maintenance windows.
  • Discard support is not usefulness: non-zero DISC-GRAN / DISC-MAX means fstrim can run, not that it should. Only thin or flash-backed storage benefits.
  • Match maintenance to storage type: on thick-provisioned database volumes, disable the weekly fstrim and manage capacity directly.

Final Thoughts

When a database shuts itself down, the instinct is to look inside the database. Here the answer was one layer down: a housekeeping job that is enabled by default on many Linux distributions and is perfectly sensible on thin storage, but only stalled disk I/O on ours. Rule out the workload first, then check what else is scheduled on the host and whether it actually benefits your storage.

Prevent Infrastructure-Level Outages with Mydbops

Unplanned database downtime often stems from the interaction between Linux kernel defaults and storage architecture. Mydbops provides full-stack database consulting, infrastructure audits, and 24/7 managed services to ensure your production MySQL clusters remain resilient under any workload.

No items found.

About the Author

Founding Partner and Database Architect at Mydbops, with 14+ years running MySQL, MariaDB and MongoDB systems. Before co-founding Mydbops in 2016, he worked as a remote DBA at Percona. He holds AWS Solutions Architect Professional and Oracle MySQL 8.0 certifications.

Subscribe Now!

Subscribe here to get exclusive updates on upcoming webinars, meetups, and to receive instant updates on new database technologies.

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.