Quick Answer
IO_COMPLETION waits occur when SQL Server threads wait for non-data page I/O operations to complete, including log writes, tempdb spills, backup operations, and index maintenance activities. High IO_COMPLETION waits typically indicate storage subsystem bottlenecks or inefficient queries generating excessive I/O load, requiring immediate investigation.
Root Cause Analysis
IO_COMPLETION waits represent the time SQL Server worker threads spend waiting for the Windows I/O subsystem to complete asynchronous I/O requests for non-data pages. The SQL Server I/O Manager issues these requests to the operating system and yields the worker thread to the scheduler, allowing other tasks to execute while the physical I/O completes.
These waits primarily occur during transaction log writes, tempdb sort/hash spill operations, backup and restore activities, DBCC operations, and bulk operations like CREATE INDEX or ALTER INDEX REBUILD. Unlike PAGEIOLATCH waits which handle data page reads from user databases, IO_COMPLETION encompasses system-level I/O operations that cannot be satisfied from the buffer pool.
The wait accumulates from the time the I/O request is submitted to Windows until the completion routine fires and the worker thread resumes execution. SQL Server 2016 introduced improved I/O completion port handling that reduced context switching overhead, while SQL Server 2019 enhanced asynchronous I/O processing for large memory configurations. SQL Server 2022 further optimized I/O completion handling for cloud storage scenarios with variable latency patterns.
High IO_COMPLETION waits often correlate with storage subsystem saturation, indicated by elevated disk queue lengths and response times. However, they can also result from memory pressure forcing excessive tempdb spills, frequent transaction log writes from poorly batched operations, or maintenance operations during peak business hours.
AutoDBA checks tempdb configuration, storage performance metrics, and memory pressure indicators across your entire SQL Server instance in 60 seconds. Download the free diagnostic script and see what else needs attention.
Diagnostic Queries
-- Current IO_COMPLETION wait statistics
SELECT
wait_type,
waiting_tasks_count,
wait_time_ms,
max_wait_time_ms,
signal_wait_time_ms,
wait_time_ms - signal_wait_time_ms AS resource_wait_time_ms,
CAST(100.0 * wait_time_ms / SUM(wait_time_ms) OVER() AS DECIMAL(5,2)) AS pct_total_waits
FROM sys.dm_os_wait_stats
WHERE wait_type = 'IO_COMPLETION'
AND wait_time_ms > 0;
-- Active sessions with IO_COMPLETION waits
SELECT
s.session_id,
s.login_name,
s.program_name,
r.command,
r.status,
r.wait_type,
r.wait_time,
r.wait_resource,
t.text AS current_sql,
s.reads,
s.writes,
s.logical_reads
FROM sys.dm_exec_sessions s
INNER JOIN sys.dm_exec_requests r ON s.session_id = r.session_id
CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) t
WHERE r.wait_type = 'IO_COMPLETION'
ORDER BY r.wait_time DESC;
-- I/O performance by database file
SELECT
DB_NAME(vfs.database_id) AS database_name,
mf.physical_name,
mf.type_desc,
vfs.num_of_reads,
vfs.num_of_writes,
vfs.io_stall_read_ms,
vfs.io_stall_write_ms,
CASE WHEN vfs.num_of_reads = 0 THEN 0
ELSE vfs.io_stall_read_ms / vfs.num_of_reads END AS avg_read_latency_ms,
CASE WHEN vfs.num_of_writes = 0 THEN 0
ELSE vfs.io_stall_write_ms / vfs.num_of_writes END AS avg_write_latency_ms
FROM sys.dm_io_virtual_file_stats(NULL, NULL) vfs
INNER JOIN sys.master_files mf ON vfs.database_id = mf.database_id
AND vfs.file_id = mf.file_id
WHERE vfs.io_stall > 0
ORDER BY (vfs.io_stall_read_ms + vfs.io_stall_write_ms) DESC;
-- Memory pressure indicators affecting I/O
SELECT
counter_name,
cntr_value,
cntr_type
FROM sys.dm_os_performance_counters
WHERE object_name = 'SQLServer:Memory Manager'
AND counter_name IN (
'Memory Grants Pending',
'Memory Grants Outstanding',
'Target Server Memory (KB)',
'Total Server Memory (KB)'
)
UNION ALL
SELECT
counter_name,
cntr_value,
cntr_type
FROM sys.dm_os_performance_counters
WHERE object_name = 'SQLServer:Buffer Manager'
AND counter_name IN ('Page life expectancy', 'Lazy writes/sec');
-- TempDB usage patterns
SELECT
t1.session_id,
t1.request_id,
t1.task_alloc_gb,
t1.task_dealloc_gb,
t1.task_alloc_gb - t1.task_dealloc_gb AS current_task_usage_gb,
t2.session_alloc_gb,
t2.session_dealloc_gb,
t2.session_alloc_gb - t2.session_dealloc_gb AS current_session_usage_gb
FROM (
SELECT
session_id,
request_id,
SUM(user_objects_alloc_page_count + internal_objects_alloc_page_count) * 8.0 / 1024 / 1024 AS task_alloc_gb,
SUM(user_objects_dealloc_page_count + internal_objects_dealloc_page_count) * 8.0 / 1024 / 1024 AS task_dealloc_gb
FROM sys.dm_db_task_space_usage
WHERE session_id > 50
GROUP BY session_id, request_id
) AS t1
INNER JOIN (
SELECT
session_id,
SUM(user_objects_alloc_page_count + internal_objects_alloc_page_count) * 8.0 / 1024 / 1024 AS session_alloc_gb,
SUM(user_objects_dealloc_page_count + internal_objects_dealloc_page_count) * 8.0 / 1024 / 1024 AS session_dealloc_gb
FROM sys.dm_db_session_space_usage
WHERE session_id > 50
GROUP BY session_id
) AS t2 ON t1.session_id = t2.session_id
WHERE t1.task_alloc_gb - t1.task_dealloc_gb > 0.1
ORDER BY current_task_usage_gb DESC;
Fix Scripts
Increase tempdb file count for reduced contention
-- Check current tempdb configuration
SELECT
name,
physical_name,
size * 8 / 1024 AS size_mb,
growth,
is_percent_growth
FROM sys.master_files
WHERE database_id = 2;
-- Add tempdb data files (adjust count based on CPU cores, max 8)
-- Test in dev first, requires restart to take effect
USE master;
GO
ALTER DATABASE tempdb
ADD FILE (
NAME = 'tempdev2',
FILENAME = 'C:\TempDB\tempdb2.mdf',
SIZE = 1024MB,
FILEGROWTH = 256MB
);
-- Repeat for additional files as needed
Optimize transaction log write patterns
-- Enable delayed durability for specific database (reduces log I/O)
-- WARNING: Risk of data loss on system failure, test thoroughly
ALTER DATABASE [YourDatabaseName]
SET DELAYED_DURABILITY = ALLOWED;
GO
-- For applications: Use delayed durability selectively
-- COMMIT TRANSACTION WITH (DELAYED_DURABILITY = ON);
Configure backup compression to reduce I/O
-- Enable backup compression instance-wide
EXEC sp_configure 'backup compression default', 1;
RECONFIGURE WITH OVERRIDE;
-- Verify setting
SELECT name, value, value_in_use
FROM sys.configurations
WHERE name = 'backup compression default';
Address memory pressure reducing tempdb spills
-- Increase max server memory if system has available RAM
-- Check current setting first
SELECT name, value, value_in_use, description
FROM sys.configurations
WHERE name = 'max server memory (MB)';
-- Increase max server memory (example: to 12GB, adjust for your system)
-- Leave 2-4GB for OS depending on total RAM
EXEC sp_configure 'max server memory (MB)', 12288;
RECONFIGURE WITH OVERRIDE;
AutoDBA generates fix scripts like these automatically, with impact estimates and rollback SQL included.
Prevention
Configure tempdb with multiple data files equal to CPU core count (maximum 8) sized equally with identical growth settings to minimize allocation contention. Place tempdb files on fast storage separate from user databases and transaction logs.
Implement proper index maintenance windows during off-peak hours to avoid IO_COMPLETION waits during business operations. Use ONLINE index operations where possible, and configure maintenance plans to limit concurrent operations based on storage subsystem capacity.
Monitor storage performance metrics continuously, establishing baselines for average read/write latencies per database file. Set alerts when average I/O latencies exceed 15ms for data files or 5ms for log files, indicating storage subsystem stress.
Configure SQL Server max memory settings appropriately, leaving sufficient memory for the operating system while maximizing buffer pool efficiency. Memory pressure forces tempdb spills and increases non-data page I/O significantly.
Implement query performance monitoring to identify operations causing excessive tempdb usage, sort spills, and hash spills. Address these through query optimization, index creation, or increasing memory grant configurations before they impact production I/O patterns.
Use backup compression and optimize backup schedules to minimize I/O impact during peak business hours. Consider implementing backup to multiple devices in parallel for large databases to distribute I/O load effectively.
Need hands-on help?
Dealing with persistent io_completion issues across your environment? Samix Technology provides hands-on SQL Server performance consulting with 15+ years of production DBA experience.