Quick Answer
PAGEIOLATCH_KP waits occur when SQL Server requests exclusive access to a data page being read from disk with "Keep" mode protection to prevent the page from being removed from memory during the read operation. High PAGEIOLATCH_KP waits indicate slow disk I/O subsystem performance or insufficient memory forcing excessive physical reads.
Root Cause Analysis
PAGEIOLATCH_KP waits trigger when the buffer pool manager cannot locate a requested page in memory and must initiate physical I/O to retrieve it from disk. The "KP" (Keep) latch mode prevents the buffer pool's lazy writer and checkpoint processes from flushing the page back to disk while the read operation completes.
The buffer pool manager uses a hash table to track pages in memory. When a page request misses this cache, SQL Server allocates a buffer frame, places a KP latch on it, and submits an asynchronous read request to the storage subsystem. The requesting thread enters PAGEIOLATCH_KP wait state until the I/O completes and the latch releases.
SQL Server 2016 introduced significant improvements to latch management with indirect checkpoint and accelerated database recovery features. SQL Server 2019 added intelligent query processing optimizations that can reduce unnecessary page reads. SQL Server 2022's buffer pool parallel scan improvements help reduce latch contention during large table scans.
The severity depends on duration and frequency. Waits under 50ms typically indicate normal operation. Consistent waits exceeding 100ms signal storage performance issues. Waits over 500ms indicate serious I/O subsystem problems requiring immediate attention.
Memory pressure exacerbates this wait type by forcing frequent page evictions from the buffer pool, creating a cycle where pages read from disk get immediately flushed due to memory constraints, only to be re-read moments later.
AutoDBA checks Buffer pool configuration, I/O subsystem 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
-- Identify databases with highest PAGEIOLATCH_KP waits
SELECT
DB_NAME(database_id) AS database_name,
file_id,
sample_ms,
num_of_reads,
num_of_bytes_read,
io_stall_read_ms,
io_stall_read_ms / NULLIF(num_of_reads, 0) AS avg_read_stall_ms
FROM sys.dm_io_virtual_file_stats(NULL, NULL)
WHERE num_of_reads > 0
ORDER BY io_stall_read_ms DESC;
-- Current PAGEIOLATCH_KP wait statistics and trends
SELECT
wait_type,
waiting_tasks_count,
wait_time_ms,
max_wait_time_ms,
signal_wait_time_ms,
wait_time_ms / NULLIF(waiting_tasks_count, 0) AS avg_wait_time_ms
FROM sys.dm_os_wait_stats
WHERE wait_type LIKE 'PAGEIOLATCH_KP'
AND waiting_tasks_count > 0;
-- Identify sessions currently experiencing PAGEIOLATCH_KP waits
SELECT
s.session_id,
s.status,
s.command,
s.cpu_time,
s.logical_reads,
s.reads,
s.writes,
w.wait_type,
w.wait_duration_ms,
w.resource_description,
t.text AS current_sql
FROM sys.dm_exec_sessions s
INNER JOIN sys.dm_os_waiting_tasks w ON s.session_id = w.session_id
INNER JOIN sys.dm_exec_connections c ON s.session_id = c.session_id
OUTER APPLY sys.dm_exec_sql_text(c.most_recent_sql_handle) t
WHERE w.wait_type = 'PAGEIOLATCH_KP'
AND s.is_user_process = 1;
-- Buffer pool hit ratio and memory pressure indicators
SELECT
counter_name,
cntr_value,
cntr_type
FROM sys.dm_os_performance_counters
WHERE object_name LIKE '%Buffer Manager%'
AND counter_name IN (
'Buffer cache hit ratio',
'Page life expectancy',
'Free list stalls/sec',
'Lazy writes/sec'
);
-- Top objects causing physical reads
SELECT TOP 20
OBJECT_SCHEMA_NAME(ius.object_id) AS schema_name,
OBJECT_NAME(ius.object_id) AS object_name,
ius.user_seeks + ius.user_scans + ius.user_lookups AS total_reads,
ius.user_seeks,
ius.user_scans,
ius.user_lookups,
ius.last_user_seek,
ius.last_user_scan,
ius.last_user_lookup
FROM sys.dm_db_index_usage_stats ius
INNER JOIN sys.objects o ON ius.object_id = o.object_id
WHERE ius.database_id = DB_ID()
AND o.type = 'U'
ORDER BY total_reads DESC;
Fix Scripts
Increase Maximum Server Memory (if insufficient) This script increases SQL Server's maximum memory allocation when memory pressure causes excessive page evictions.
-- Check current memory configuration
SELECT
name,
value_in_use,
description
FROM sys.configurations
WHERE name IN ('max server memory (MB)', 'min server memory (MB)');
-- Set max server memory to 80% of system memory (example: 32GB system = 25600MB)
-- CRITICAL: Calculate appropriate value for your system first
EXEC sp_configure 'max server memory (MB)', 25600;
RECONFIGURE WITH OVERRIDE;
Caveat: Test memory allocation changes during maintenance windows. Monitor for resource contention with other applications.
Enable Instant File Initialization Reduces I/O wait times by eliminating zero-initialization of data files during autogrowth events.
-- Check if IFI is enabled (SQL Server 2016+)
SELECT
service_account,
instant_file_initialization_enabled
FROM sys.dm_server_services
WHERE servicename LIKE 'SQL Server (%';
-- If disabled, enable through Windows security policy:
-- Grant "Perform volume maintenance tasks" privilege to SQL Server service account
-- Restart SQL Server service to take effect
Caveat: Requires Windows administrator privileges to modify security policy. Plan service restart during maintenance window.
Optimize Autogrowth Settings Prevents performance degradation from frequent small autogrowth events.
-- Review current autogrowth configuration
SELECT
DB_NAME(database_id) AS database_name,
name AS logical_name,
physical_name,
size * 8 / 1024 AS size_mb,
CASE
WHEN is_percent_growth = 1
THEN CAST(growth AS VARCHAR(10)) + '%'
ELSE CAST(growth * 8 / 1024 AS VARCHAR(10)) + ' MB'
END AS growth_setting,
is_percent_growth
FROM sys.master_files
ORDER BY database_id, file_id;
-- Set fixed growth increments (example: 512MB for data, 128MB for log)
USE [master];
ALTER DATABASE [YourDatabase]
MODIFY FILE (NAME = 'YourDatabase_Data', FILEGROWTH = 512MB);
ALTER DATABASE [YourDatabase]
MODIFY FILE (NAME = 'YourDatabase_Log', FILEGROWTH = 128MB);
Caveat: Size increments based on database workload patterns. Monitor disk space closely after changes.
Create Missing Indexes Reduces random I/O by improving query execution plans.
-- Identify high-impact missing indexes
SELECT TOP 10
dm_mid.database_id,
dm_migs.avg_total_user_cost * (dm_migs.avg_user_impact / 100.0) *
(dm_migs.user_seeks + dm_migs.user_scans) AS improvement_measure,
'CREATE INDEX [IX_' + OBJECT_NAME(dm_mid.object_id, dm_mid.database_id) + '_'
+ REPLACE(REPLACE(REPLACE(ISNULL(dm_mid.equality_columns,''), ', ', '_'), '[', ''), ']', '') +
CASE
WHEN dm_mid.inequality_columns IS NOT NULL
THEN '_' + REPLACE(REPLACE(REPLACE(dm_mid.inequality_columns, ', ', '_'), '[', ''), ']', '')
ELSE ''
END + '] ON ' + dm_mid.statement
+ ' (' + ISNULL (dm_mid.equality_columns,'')
+ CASE WHEN dm_mid.equality_columns IS NOT NULL AND dm_mid.inequality_columns IS NOT NULL
THEN ',' ELSE '' END
+ ISNULL (dm_mid.inequality_columns, '')
+ ')'
+ ISNULL (' INCLUDE (' + dm_mid.included_columns + ')', '') AS create_index_statement
FROM sys.dm_db_missing_index_groups dm_mig
INNER JOIN sys.dm_db_missing_index_group_stats dm_migs ON dm_migs.group_handle = dm_mig.index_group_handle
INNER JOIN sys.dm_db_missing_index_details dm_mid ON dm_mig.index_handle = dm_mid.index_handle
WHERE dm_mid.database_id = DB_ID()
ORDER BY improvement_measure DESC;
Caveat: Evaluate index maintenance overhead against query performance gains. Test in development environment first.
AutoDBA generates fix scripts like these automatically, with impact estimates and rollback SQL included.
Prevention
Configure SQL Server maximum memory to leave sufficient RAM for the operating system while maximizing buffer pool size. Monitor Page Life Expectancy, targeting values above 300 seconds for stable systems.
Implement proactive index maintenance using Ola Hallengren's scripts or SQL Server Maintenance Plans. Focus on rebuilding fragmented indexes above 30% fragmentation and reorganizing indexes between 5-30% fragmentation.
Establish storage performance baselines using perfmon counters: Physical Disk Average Disk sec/Read should remain below 20ms for OLTP workloads, below 100ms for data warehouse operations.
Configure database file autogrowth with fixed MB increments rather than percentage growth. Size data files appropriately during initial deployment to minimize autogrowth events during normal operations.
Deploy read replicas or Always On Availability Groups to offload reporting queries from primary transactional systems, reducing I/O pressure on production databases.
Monitor sys.dm_io_virtual_file_stats regularly to identify storage performance trends before they impact users. Alert on average read latencies exceeding 50ms consistently.
Need hands-on help?
Dealing with persistent pageiolatch_kp issues across your environment? Samix Technology provides hands-on SQL Server performance consulting with 15+ years of production DBA experience.