SQL SERVER – Timeout Occurred While Waiting for Latch: Class FGCB_ADD_REMOVE

A client saw a stream of latch timeouts a few minutes after restarting SQL Server, all naming FGCB_ADD_REMOVE. It sounds like a deep engine problem. It is almost always a file taking too long to grow. I measured how long a file growth really takes on SQL Server 2025, and the difference between the data file and the log file is the whole story.

An amber warning lamp standing alone on wet concrete in a dark plant room

The Message

Timeout occurred while waiting for latch: class 'FGCB_ADD_REMOVE',
id 00000000807D59B8, type 2, Task 0x00000000C896FDC8 : 0, waittime 300,
flags 0x1a, owning task 0x00000000820BE608. Continuing to wait.

Three parts of this matter. FGCB is the filegroup control block. It tracks the data files in a filegroup. ADD_REMOVE means something is changing those files: a growth, a shrink, a new file. And waittime 300 means a task has waited 300 seconds.

Then look at the owning task. In my client’s log, every waiting session named the same owner. One task held the latch. Everyone who needed that filegroup queued behind it. The line ends with “Continuing to wait”. That is SQL Server being honest: nothing has failed yet, it is just very slow.

What Was Holding It

We captured an Extended Events session and the owner turned out to be an autogrowth of a data file. The file needed more room, the growth started, and it took so long that everything else queued up behind it.

Why right after a restart? I cannot prove it for their server, but it is the usual pattern. A restart is when every application reconnects at once and the work that queued up during the outage arrives together. Whatever was going to fill the file fills it quickly.

What a Growth Costs, Measured

I created a fresh database and grew each file from 8 MB to 4 GB, timing each one.

ALTER DATABASE GrowLab MODIFY FILE (NAME = GrowLab,     SIZE = 4096MB);
ALTER DATABASE GrowLab MODIFY FILE (NAME = GrowLab_log, SIZE = 4096MB);
data file +4 GB      98 ms
log file  +4 GB    2287 ms

Same size, same disk, same second. The log took twenty-three times longer.

The difference is zeroing. Before SQL Server uses new space, it normally writes zeros over every byte. The data file skipped that, because this instance has instant file initialization switched on. The log file cannot skip it. SQL Server zeroes a large log growth whatever the setting, because it must read the log back safely. That is documented behaviour. My 2.3 seconds is what it cost on my own test machine.

So the log growth is a fair picture of what the data file growth would cost with instant file initialization switched off. On a slow SAN, or a disk that is busy with the rush after a restart, multiply accordingly. That is how you reach 300 seconds.

Check Instant File Initialization

You no longer need to dig through the ERRORLOG or a trace flag to find out. One query:

SELECT servicename, instant_file_initialization_enabled
FROM   sys.dm_server_services
WHERE  servicename LIKE 'SQL Server (%';
SQL Server (SQLDEV)    Y

If it says N, the service account does not have the Windows right Perform volume maintenance tasks. Grant it in the local security policy, or tick the box that setup offers during installation, and restart the service. My client had it switched off. That was half of their problem.

Check the Growth Setting

The other half was the growth size. Percentage growth looks harmless on a small file and becomes enormous on a big one. Ten percent of a 2 TB file is 200 GB, in one go, while everybody waits.

SELECT DB_NAME(database_id) AS database_name,
       name AS file_name,
       type_desc,
       size * 8 / 1024 AS size_mb,
       CASE WHEN is_percent_growth = 1
            THEN CAST(growth AS varchar(10)) + ' percent'
            ELSE CAST(growth * 8 / 1024 AS varchar(10)) + ' MB' END AS growth
FROM   sys.master_files
ORDER BY is_percent_growth DESC, size DESC;

A new database on this instance came out with 64 MB growth for both files, which is today’s default and a sensible one. Older databases, or ones restored from older servers, often still carry 10 percent. Those float to the top of that query.

I set growth in fixed megabytes. For data files with instant file initialization on, 512 MB or 1024 MB is comfortable, because the growth is nearly free. Keep log growth smaller. The log is not in a filegroup, so it never takes this latch. But every megabyte of it is zeroed, and writes to the database wait while that happens. Different wait, same pain.

The Real Fix Is Not to Grow at All

Autogrowth is a safety net, not a plan. If you know a file is going to need 200 GB next month, grow it now, deliberately, at a quiet hour. Then autogrowth only ever fires for the surprise, and nobody queues behind it in the middle of the morning.

You can see how often the latch has been a problem since the last restart:

SELECT latch_class, waiting_requests_count, wait_time_ms, max_wait_time_ms
FROM   sys.dm_os_latch_stats
WHERE  latch_class = 'FGCB_ADD_REMOVE';

Mine stayed at zero through both of my growths, because nothing else was waiting on that filegroup at the time. On a busy server during a slow growth, that is the number that climbs.

FGCB_ADD_REMOVE is not a broken engine, it is everyone waiting for one file to finish growing.

Published by Pinal Dave on SQLAuthority. More of my work at pinaldave.com.


Discover more from SQL Authority with Pinal Dave

Subscribe to get the latest posts sent to your email.

Disk, File format, SQL DMV, SQL Error Messages, SQL Extended Events, SQL Log, SQL Server, SQL Server Configuration
Previous Post
Comparing Old and New Values in a Trigger With inserted and deleted
Next Post
SQL SERVER – Always On AG – HADRAG: Did not Find the Instance to Connect in SqlInstToNodeMap Key

Related Posts

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.