HADR_TRANSPORT_FLOW_CONTROL is an Availability Group primary holding back sends to one replica because too many messages to that replica are still unacknowledged. It is the replica-level gate, so it throttles every database going to that secondary at once. Run the check to see which replica has a send queue that keeps growing.Microsoft’s definition: waiting when the number of outstanding unacknowledged Always On messages is over the out flow control threshold, counted per availability replica rather than per database. The Availability Group performance page describes two flow control gates on the primary: a transport gate, one per availability replica, and a database gate, one per availability database. Once either gate reaches its message limit, log stops being sent to that replica, or for that database, until acknowledgements from the secondary bring the count back under it.
This wait is the transport gate. The database gate has its own wait, HADR_DATABASE_FLOW_CONTROL, and telling the two apart is most of the diagnosis.
Is It a Problem?
Short bursts on a busy Availability Group are the gate doing its job, and the gates do not change the synchronization health of a replica. It matters when it is sustained, because while the gate is shut nothing more goes to that replica. On an asynchronous secondary that is unsent log, which is your potential data loss growing; the longer the flow control waits, the higher your RPO. On a synchronous secondary the commit cannot be acknowledged until its log is sent and hardened, so it can also push up HADR_SYNC_COMMIT, which users feel as slow commits.
Judge it over an interval rather than the cumulative totals. For a live view, the Availability Replica counters Flow Control/sec and Flow Control Time (ms/sec) show how often flow control kicked in during the last second and how long log messages waited for it; time divided by count is the average wait.
Transport or Database Flow Control?
Both waits mean the primary has stopped sending because acknowledgements are not coming back fast enough. The difference is what the gate is counting:
| HADR_TRANSPORT_FLOW_CONTROL | HADR_DATABASE_FLOW_CONTROL | |
|---|---|---|
| Gate | One per availability replica. | One per availability database. |
| What it counts | Unacknowledged messages to that replica, from every database it hosts. | Queued messages for one database. |
| Message limit | 8192, or 16384 with the higher limits. | 1792 on x64, or 7168 with the higher limits. |
| What it points at | The path to that replica: the network, or a secondary that is slow to harden and acknowledge. | One database’s log being scanned faster than the network sends it. |
| Extended event | database_transport_flow_control_action | hadron_database_flow_control_action |
So the shape of the problem tells you which one you have. Every database going to one secondary backing up together is the transport gate. One database backing up while its neighbours on the same replica are fine is the database gate.
Common Causes
- Network latency or throughput between the primary and that secondary too low for the log rate, so acknowledgements come back slowly and the outstanding count sits at the limit. Common with secondaries in another site or region.
- Slow log hardening on the secondary, for example a log file sharing a disk with the data file under a reporting workload. The secondary hardens slowly, acknowledges slowly, and flow control kicks in more often and for longer.
- Several busy databases on the same replica. The transport gate is shared by all of them, so their combined log volume is what fills it.
- Network noise. A high Resent Messages/sec counter points at latency and noise on the link.
- Older builds, or builds without the higher gate limits, below.
The Check To Run
Rank it against everything else with Get Wait Statistics. Then put the two flow control waits side by side on the primary, with sys.dm_os_wait_stats filtered to the Availability Group send path:
SELECT
wait_type,
waiting_tasks_count,
wait_time_ms,
max_wait_time_ms,
signal_wait_time_ms
FROM sys.dm_os_wait_stats
WHERE wait_type IN (N'HADR_TRANSPORT_FLOW_CONTROL', N'HADR_DATABASE_FLOW_CONTROL', N'HADR_SYNC_COMMIT')
ORDER BY wait_time_ms DESC;
If the transport wait is the bigger of the two, the next question is which replica. On the primary, sys.dm_hadr_database_replica_states returns a row per secondary database, so you can see every database going to each replica together:
SELECT
ar.replica_server_name,
ar.availability_mode_desc,
DB_NAME(drs.database_id) AS database_name,
drs.synchronization_state_desc,
drs.log_send_queue_size,
drs.log_send_rate,
drs.redo_queue_size
FROM sys.dm_hadr_database_replica_states AS drs
JOIN sys.availability_replicas AS ar ON ar.replica_id = drs.replica_id
WHERE drs.is_local = 0
ORDER BY ar.replica_server_name, drs.log_send_queue_size DESC;
Both queue and rate columns are in KB and KB per second. A replica where every database has a log_send_queue_size that keeps growing between runs is the one behind the transport gate. To see whether it will ever catch up, compare log_send_rate with the Log Bytes Flushed/sec counter for the database, and if log is flushed faster than it is sent, the send queue and the potential data loss grow without limit. Check Always On Availability Group Latency reads the same view with more context. The queries need VIEW SERVER STATE.
What To Do
- Check the link to the replica the check named: latency with ping, and the Resent Messages/sec counter. The fix is more network bandwidth, or removing or reducing other traffic on the link.
- Check the secondary’s log disk. If the log file shares a disk with the data files and a reporting workload, move it to its own disk so hardening, and the acknowledgement, are not queued behind reads.
- Reduce the bursts. Index rebuilds and bulk loads across several databases on the same replica all count against the one transport gate, so spread them out rather than running them together.
- Know the version. SQL Server 2022 raised the number of messages each gate allows; on SQL Server 2019 CU9, 2017 CU18 and 2016 SP1 CU16 and later, trace flag 12310 at startup gives the higher limits, and it cannot be set with
DBCC TRACEON. A higher limit lets more messages be outstanding; it does not make a slow link faster. Test before using it.
The HADR Commit and Flow Waits
These are the Availability Group waits on the path a commit takes from the primary to its secondaries: capturing log, sending it, waiting for it to harden, and the controls that throttle it. The main page is HADR_SYNC_COMMIT.
- HADR_SYNC_COMMITSynchronous commit. The primary waiting for a synchronous secondary to harden the log.The main page for this family
- HADR_DATABASE_FLOW_CONTROLDatabase flow control. Sends for one database paused because its queue of unacknowledged messages is full.Worth a look when it climbs
- HADR_TRANSPORT_FLOW_CONTROLTransport flow control. Sends to one replica paused because too many messages to it are unacknowledged.Worth a look when it climbs. This page.
- HADR_GROUP_COMMITGroup commit. Commits held briefly so several fit into one log block; expected.Worth a look when it climbs
- HADR_LOGPROGRESS_SYNCLog progress. Concurrency control while updating each replica’s log progress.Worth a look when it climbs
- HADR_LOGCAPTURE_WAITLog capture. Waiting for log records to become available; expected when caught up.Usually noise
- PREEMPTIVE_HADR_LEASE_MECHANISMLease. The Availability Group lease manager scheduling.Worth investigating
- HADR_SEEDING_LIMIT_BACKUPSAutomatic seeding. Recorded during automatic seeding; reported as long and benign.Worth a look when it climbs
Frequently Asked Questions
What is the difference between HADR_TRANSPORT_FLOW_CONTROL and HADR_DATABASE_FLOW_CONTROL?
Scope. The transport gate counts unacknowledged messages to one replica across all of its databases; the database gate counts queued messages for one database. Transport flow control points at the path to a replica, database flow control at one database sending faster than the link carries it. HADR_DATABASE_FLOW_CONTROL has its own page.
Does this wait mean my secondary is not synchronized?
No. The flow control gates do not affect the synchronization health state of a replica, so the dashboard can show healthy while this wait climbs. What it does affect is how much log is waiting to be sent, and so your RPO.
Will trace flag 12310 make it go away?
It raises the transport gate from 8192 to 16384 messages and the database gate from 1792 to 7168, which helps when the limit itself is the bottleneck. If the link or the secondary’s log disk is too slow for the log rate, the gate fills again, just later. It has to be set at startup, and SQL Server 2022 has the higher limits by default.
Which permission does the check need?
VIEW SERVER STATE. On SQL Server 2022 and later, the narrower VIEW SERVER PERFORMANCE STATE permission is enough for the two DMVs.
Where To Go Next
If HADR_TRANSPORT_FLOW_CONTROL is climbing, these are the next places to look.
- HADR_DATABASE_FLOW_CONTROL, the database-level gate, for when one database stands out on its own.
- HADR_SYNC_COMMIT, the Availability Group wait that costs users time.
- Check Always On Availability Group Latency, send and redo queues for each replica.
- High Availability, the scripts and guides for Availability Groups and replicas.
Leave a Reply