highI/O

IO_COMPLETION Wait Type Explained

Fix SQL Server IO_COMPLETION waits caused by storage bottlenecks, tempdb spills, log writes. Diagnostic queries, solutions, and prevention strategies.

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.

Related Pages