Checklist: 0%
0 / 0
☀ Dark

SQL Server Production Installation Checklist

Contents

1. Pre-Installation Architecture Planning

Goal: Treat the installation as a complete platform build, not just "install SQL Server and create databases". Every decision must support predictable performance, easy troubleshooting, workload separation, recoverability, security hardening, and operational readiness.

1.1 Server Role Decision

ComponentRecommendation
OSWindows Server 2025 (Datacenter or Standard)
SQL VersionSQL Server 2025 Enterprise (mission-critical) or Standard (departmental); align edition with feature requirements (Always On, 128+ core, etc.)
Installation TypeStandalone instance unless Failover Cluster Instance (FCI) or Always On Availability Group is required
InstanceDefault instance preferred (MSSQLSERVER) — easier tooling, monitoring, connectivity
AuthenticationWindows + SQL Mixed Mode (required for legacy apps; use Windows Auth where possible)
Service AccountsDedicated domain accounts or gMSA (Group Managed Service Accounts) — never use built-in accounts
StorageSeparate volumes for OS, Data, Log, TempDB, Backups
Backup TargetSeparate storage (network share, blob, or dedicated volume) — never same volume as data
MonitoringPlan for monitoring from day one (DMV collection, perfmon, Query Store, Extended Events)

1.2 Pre-Planning Checklist

Confirm SQL edition and licensing model (Core vs. Server + CAL)
Document expected workload profile (OLTP, DW, mixed)
Confirm VM vs. physical — if VM, review hypervisor NUMA and vCPU pinning
Plan capacity: expected DB size, growth rate, IOPS, memory requirement
Determine High Availability / Disaster Recovery strategy before installing

2. Windows Server Preparation

2.1 OS Patching (Before SQL Installation)

Install the latest Windows cumulative updates, security updates, firmware updates, storage controller drivers, and NIC drivers. Verify with winver.

Windows cumulative updates installed
Security updates installed
Firmware updates applied
Storage controller drivers updated
NIC drivers updated

2.2 Windows Features

Ensure required features are installed:

.NET Framework (3.5 + 4.8 or later) installed
RSAT installed for remote administration
Windows Internal Database feature not installed

3. Windows Configuration

3.1 Disable Unnecessary Services

Print Spooler disabled
Fax service disabled
Remote Registry disabled
Windows Error Reporting disabled (if crash dumps not needed)
Verified essential services running: Windows Time, DNS Client, RPC, Event Log

3.2 Power Plan

Set Windows power plan to High Performance. The default Balanced plan allows the CPU to idle and throttle down cores, which causes erratic SQL Server latency — queries that should complete in milliseconds may sporadically take seconds when the CPU ramps back up.

Activate via command line (more reliable than GUI for Server Core or remote management):

powercfg /list                       -- list all plans with their GUIDs
powercfg /setactive <GUID>           -- e.g., 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c (High Perf)
powercfg /getactivescheme            -- verify the active plan

For SQL Server VMs running on Hyper-V or VMware, also verify that the hypervisor host and the physical BIOS/UEFI have power management set to High Performance / Maximum Performance, not power-saving or balanced. Windows inside the guest cannot override a host or BIOS that is capping frequency.

Windows power plan set to High Performance (use powercfg /setactive with GUID)
Hypervisor host power plan set to High Performance (not power-saving)
Physical BIOS/UEFI set to Maximum Performance or OS Controlled

3.3 Windows Defender / Antivirus Exclusions

SQL data path excluded (D:\SQLData)
SQL log path excluded (E:\SQLLog)
SQL TempDB path excluded (F:\SQLTempDB)
SQL Backup path excluded (G:\SQLBackup)
SQL Server binary path excluded (C:\Program Files\Microsoft SQL Server)
SQL file extensions excluded (.mdf, .ldf, .ndf, .bak, .trn)

3.4 Windows Firewall

Firewall rule created: TCP port 1433 inbound
Firewall rule created: UDP port 1434 inbound (if named instance)

3.5 Time Zone & NTP

Time zone verified correct
Windows Time service syncing to reliable NTP source

4. Storage Design

Critical: A production SQL Server must NOT have data, logs, or TempDB on the C: drive.

DrivePurpose
C:OS + Page File
D:Data files (.mdf, .ndf)
E:Transaction log files (.ldf)
F:TempDB (data + log)
G:Backups (dedicated, not same volume as data)
Data, Log, TempDB, and Backups on separate volumes (not C:)
SQL Server binaries on C: drive (C:\Program Files\Microsoft SQL Server) — do NOT separate

5. Storage Formatting

5.1 File System & Allocation Unit

Use NTFS with 64 KB allocation unit size (65536 bytes) for all SQL volumes. On Windows Server 2022/2025, consider ReFS (Resilient File System) with 64 KB cluster size for Data and Backup volumes — ReFS leverages block cloning to accelerate file creation, instant file initialization for transaction logs, and storage-level checkpoints.

All SQL volumes formatted with 64 KB allocation unit: NTFS (standard) or ReFS (modern, for Data/Backup volumes on WS 2022+)
Verified allocation unit size with fsutil fsinfo ntfsinfo D: → Bytes Per Cluster = 65536 (or ReFS equivalent)

6. Service Accounts & Permissions

6.1 Verify Accounts

SQL Engine service account (DOMAIN\svc_sqlengine or gMSA) created in AD
SQL Agent service account (DOMAIN\svc_sqlagent or gMSA) created in AD
No built-in accounts used (LocalSystem, Network Service, Administrator)

6.2 Windows Permissions Granted to SQL Engine Account

Both permissions must be assigned via Local Security Policy (secpol.msc) → Local Policies → User Rights Assignment. The exact service account name (DOMAIN\svc_sqlengine) must be explicitly added — group membership inheritance is not sufficient.

Perform Volume Maintenance Tasks granted via secpol.msc — exact account DOMAIN\svc_sqlengine added explicitly
Lock Pages in Memory granted via secpol.msc — exact account DOMAIN\svc_sqlengine added explicitly
DBA monitoring login/role created with minimum permissions — see repository permission.sql

7. SQL Server Installation

Setup launched: New SQL Server standalone installation
Product key entered (matches license agreement)
CEIP (Customer Experience Improvement Program) unchecked for production

8. Feature Selection

FeatureStatus
✔ Database Engine ServicesRequired
✔ SQL Server ReplicationIf needed
✔ Full-Text and Semantic Extractions for SearchIf needed
✘ Analysis Services (SSAS)Separate server
✘ Reporting Services (SSRS)Separate server
✘ Integration Services (SSIS)Separate server if heavy ETL
✘ Machine Learning Services (In-Database)Rarely on prod OLTP
✘ PolyBase Query ServiceOnly if needed
Only required features selected (no SSAS/SSRS/SSIS on DB engine server)

9. Instance Configuration

Default instance (MSSQLSERVER) selected (or named instance if required)
Instance ID left as default

10. Service Accounts During Setup

ServiceAccountStartup Type
SQL Server Database EngineDOMAIN\svc_sqlengineAutomatic
SQL Server AgentDOMAIN\svc_sqlagentAutomatic
SQL Engine service account set to DOMAIN\svc_sqlengine (or gMSA)
SQL Agent service account set to DOMAIN\svc_sqlagent
Both services set to Automatic startup

11. Database Engine Configuration

Authentication mode set to Mixed Mode
DOMAIN\DBA_Group added as SQL Server administrator
sa password set to a complex password

12. Data Directory Configuration

VERY IMPORTANT. Change defaults on the Data Directories tab during setup.

Data root directory → D:\SQLData
Log directory → E:\SQLLog
Backup directory → G:\SQLBackup
TempDB directory → F:\SQLTempDB

13. Post-Installation

Connected to instance via SSMS or sqlcmd to verify installation

14. Memory Configuration

SQL Server will consume all available memory unless constrained. Always set max server memory.

Server RAMReserve for OSSQL Max Server Memory
32 GB4 GB28 GB (28672 MB)
64 GB8 GB56 GB (57344 MB)
128 GB16 GB112 GB (114688 MB)
256 GB20–24 GB236 GB (~240000 MB)
512 GB+5–10% of totalRemainder
-- Example: 64 GB RAM server — reserve 8 GB for OS
EXEC sp_configure 'show advanced options', 1;
RECONFIGURE;
EXEC sp_configure 'max server memory (MB)', 57344;  -- 56 GB
RECONFIGURE;

-- Verify
SELECT name, value_in_use FROM sys.configurations
WHERE name = N'max server memory (MB)';
Max server memory configured (leaves enough for OS, AV, monitoring agents)

15. MAXDOP Configuration

CPU count checked via SELECT cpu_count FROM sys.dm_os_sys_info

Follow Microsoft guidance (KB 2806535). For NUMA systems, also consider max degree of parallelism together with cost threshold for parallelism.

-- Check logical CPUs
SELECT cpu_count, hyperthread_ratio FROM sys.dm_os_sys_info;

-- Set MAXDOP (example: 8-core server or cap at 8 for larger hosts)
EXEC sp_configure 'show advanced options', 1;
RECONFIGURE;
EXEC sp_configure 'max degree of parallelism', 8;
RECONFIGURE;
MAXDOP configured: ≤8 cores → same as cores; >8 cores → 8 (or 16 for DW)

16. Cost Threshold for Parallelism

Cost threshold for parallelism changed from 5 to 50 (baseline)

16a. Max Worker Threads Verification

SQL Server auto-configures max worker threads based on CPU count. For high-core production servers with heavily concurrent workloads, the default can sometimes lead to thread starvation before SQL scales. Evaluate and adjust if needed.

-- Check current setting
SELECT max_workers_count FROM sys.dm_os_sys_info;

-- Current configured value
EXEC sp_configure 'max worker threads';

The default formula (SQL 2016+): 256 + ((cpu_count - 4) * 8) for x64 systems. For servers with > 64 cores, verify the calculated value matches your workload concurrency. Only adjust upward if you observe thread waits (THREADPOOL wait type) under normal load.

Max worker threads verified: sys.dm_os_sys_info.max_workers_count matches workload requirements

17. TempDB Configuration

Logical CoresTempDB Files
≤ 84–8
168
32+8 (scale up only if contention detected)
TempDB data files count set appropriately (max 8 initially)
All TempDB data files are equal initial size (e.g., 8 GB each)
TempDB growth set to fixed MB (e.g., 512 MB), not percent
TempDB log file sized appropriately and on same volume (F:)

17.1 Verify TempDB Configuration (Post-Install or Post-Migration)

-- File count, sizes, and growth uniformity
SELECT name, type_desc, size * 8 / 1024 AS size_mb,
       growth, is_percent_growth, physical_name
FROM sys.master_files
WHERE database_id = 2
ORDER BY type_desc, file_id;

-- Active TempDB PAGELATCH contention (allocation pages)
SELECT session_id, wait_type, wait_duration_ms, resource_description
FROM sys.dm_os_waiting_tasks
WHERE wait_type LIKE 'PAGELATCH_%' AND resource_description LIKE '2:%';

Remediation: Add equal-sized data files (up to 8) on a dedicated fast volume. Set identical fixed MB growth on every file. Pre-size files to expected peak — autogrowth during peak load causes PAGELATCH waits. Restart is not required after adding TempDB files.

-- Example: add a TempDB data file (repeat until file count matches target)
ALTER DATABASE tempdb ADD FILE (
    NAME = tempdev3,
    FILENAME = N'F:\SQLTempDB\tempdev3.ndf',
    SIZE = 8192MB,
    FILEGROWTH = 512MB
);

18. Instant File Initialization (IFI)

Perform Volume Maintenance Tasks granted to SQL Engine account
SQL Server restarted after granting permission
IFI verified: sys.dm_server_services.instant_file_initialization_enabled = 1

19. Database File Configuration

Data file initial size = planned allocation (not tiny default)
Data file growth = fixed MB (e.g., 1024 MB), not percent
Log file growth = fixed MB (e.g., 2048 MB), not percent

Multi-TB databases: For databases over 500 GB, set autogrowth to at least 2048–4096 MB. A small autogrowth (e.g., 100 MB) on a large database causes excessive VLF fragmentation and frequent growth events during heavy data loads, leading to IO stalls.

Auto Shrink OFF on all user databases
Auto Close OFF on all user databases
Page Verify = CHECKSUM on all databases

20. Transaction Log Configuration

Log file initial sized appropriately (e.g., 10% of DB size, min 10 GB baseline)
Log file growth set to fixed MB (1024–4096 MB depending on DB size)

21. Recovery Model

All production databases set to FULL recovery model
Non-prod databases (staging, dev, reporting) verified in appropriate model

22. Backup Configuration

TypeFrequencyRetention
Full BackupDaily (e.g., 12:00 AM)30 days (adjust per policy)
Differential BackupEvery 4–6 hours7–14 days
Transaction Log BackupEvery 5–15 minutesUntil full + diff chain break
Full backup job created and tested
Differential backup job created
Transaction log backup job created (every 5–15 min)
System database backups (master, model, msdb) configured
Full restore tested on a separate instance
Point-in-time restore verified

23. Backup Compression & Checksum

backup compression default enabled (set to 1)
backup checksum default enabled (set to 1) — SQL 2022+

24. Backup Folder Structure

Backup folder structure created: G:\SQLBackup\FULL, DIFF, LOG, SYSTEM
Consistent naming convention defined and documented

25. Index Statistics & Database Maintenance

25.1 Ola Hallengren Maintenance Solution

Ola Hallengren Maintenance Solution deployed
Index maintenance job configured (defrag / reorganize) with off-peak schedule
Statistics update job configured
Integrity check job configured (DBCC CHECKDB)
Backup jobs integrated with Ola solution (or using native backup jobs)

25.2 Index Fragmentation — Detect High Fragmentation

After migration or bulk load, index fragmentation often spikes. Use Microsoft thresholds: 5–30% → reorganize; >30% → rebuild (online when edition supports it). Skip small indexes (<1000 pages).

-- Fragmentation report across current database (run per DB or use physical_stats_and_heaps.sql)
SELECT
    s.name AS schema_name,
    t.name AS table_name,
    i.name AS index_name,
    ps.page_count,
    ps.avg_fragmentation_in_percent,
    CASE
        WHEN ps.avg_fragmentation_in_percent > 30 THEN 'REBUILD'
        WHEN ps.avg_fragmentation_in_percent > 5  THEN 'REORGANIZE'
        ELSE 'OK'
    END AS recommended_action
FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, N'LIMITED') AS ps
INNER JOIN sys.tables AS t ON ps.object_id = t.object_id
INNER JOIN sys.schemas AS s ON t.schema_id = s.schema_id
INNER JOIN sys.indexes AS i ON ps.object_id = i.object_id AND ps.index_id = i.index_id
WHERE ps.index_id > 0
  AND ps.page_count >= 1000
  AND ps.avg_fragmentation_in_percent > 5
ORDER BY ps.avg_fragmentation_in_percent DESC;

Repository script: 05_Index_Statistics/physical_stats_and_heaps.sql (read-only report). To apply maintenance online: 05_Index_Statistics/index_maintenance_online.sql — set @ExecuteMaintenance = 0 first for a dry run.

25.3 Remediate Fragmentation (Online)

-- Reorganize (5–30% fragmentation, always online)
ALTER INDEX [IX_OrderDate] ON [dbo].[Orders]
    REORGANIZE WITH (LOB_COMPACTION = ON);

-- Rebuild (>30% fragmentation — Enterprise/Developer/Azure for ONLINE)
ALTER INDEX [IX_OrderDate] ON [dbo].[Orders]
    REBUILD PARTITION = ALL WITH (
        ONLINE = ON,
        SORT_IN_TEMPDB = ON,
        WAIT_AT_LOW_PRIORITY (MAX_DURATION = 0 MINUTES, ABORT_AFTER_WAIT = BLOCKERS)
    );

25.4 Statistics Freshness — Detect Outdated Stats

Stale statistics cause bad cardinality estimates and plan regressions after migration. Flag stats where modifications exceed 20–30% of row count since last update.

-- Stale statistics in current database
SELECT
    OBJECT_NAME(s.object_id) AS table_name,
    s.name AS statistics_name,
    sp.last_updated,
    sp.rows,
    sp.modification_counter,
    CAST(sp.modification_counter AS DECIMAL(18,2))
        / NULLIF(sp.rows, 0) * 100 AS modification_pct
FROM sys.stats AS s
CROSS APPLY sys.dm_db_stats_properties(s.object_id, s.stats_id) AS sp
WHERE OBJECTPROPERTY(s.object_id, 'IsUserTable') = 1
  AND CAST(sp.modification_counter AS DECIMAL(18,2))
        / NULLIF(sp.rows, 0) * 100 >= 20
ORDER BY modification_pct DESC;

Repository script: 05_Index_Statistics/statistics_freshness.sql (all user databases).

25.5 Update Statistics (Remediation)

-- Single statistic (preferred for large tables during business hours)
UPDATE STATISTICS [dbo].[Orders] ([IX_OrderDate]) WITH FULLSCAN;

-- Entire table (off-peak; can be expensive on large tables)
UPDATE STATISTICS [dbo].[Orders] WITH FULLSCAN;

-- Large table alternative — sampled update
UPDATE STATISTICS [dbo].[Orders] WITH SAMPLE 30 PERCENT;

-- All user databases: schedule via Ola Hallengren IndexOptimize
-- or run statistics_freshness.sql and update flagged objects only
Post-migration fragmentation scan completed; indexes reorganized/rebuilt per threshold
Stale statistics identified and updated (FULLSCAN or SAMPLE on large tables)

26. SQL Agent Configuration

SQL Server Agent running and set to Automatic startup

26a. Trace Flag 3226 — Error Log Sanitization

With transaction log backups every 5–15 minutes, the SQL Server Error Log fills rapidly with thousands of "Backup succeeded" messages, making it difficult to find real errors during an outage.

Recommendation: Add Trace Flag 3226 as a startup parameter. It suppresses successful backup messages while still logging failures.

To add: Open SQL Server Configuration Manager → SQL Server Services → right-click SQL Server → Properties → Startup Parameters. Add -T3226 and restart.

-- Verify the trace flag is active
DBCC TRACESTATUS(3226);
Trace Flag 3226 added as startup parameter (suppresses success backup messages in error log)

27. Security Hardening

sa login disabled (ALTER LOGIN sa DISABLE) or renamed
DBA Windows group added as sysadmin (DOMAIN\DBA_Group)
Server audit for failed logins configured

28. Network & Protocol Configuration

TCP/IP protocol enabled in SQL Server Configuration Manager
Static TCP port set (1433 default or custom port) — dynamic ports removed
Named Pipes disabled (unless legacy app requires)

29. Database Mail

Database Mail enabled and SMTP account configured
Mail profile created and set as default public profile
Test email sent and verified

30. SQL Alerts & Operators

Operator(s) created with email address(es)
Alerts configured for severity levels 16–25
Deadlock alert (error 1205) configured
Fail-Safe Operator configured in SQL Agent properties — ensures alert delivery if the operator table or msdb is temporarily unavailable

31. Additional Security & Surface Area Reduction

xp_cmdshell disabled (set to 0)
Ole Automation Procedures disabled (set to 0)
Cross DB ownership chaining disabled (set to 0)
CLR disabled unless required (set to 0)
Optimize for ad hoc workloads enabled (set to 1)
Remote access disabled if not needed
No contained databases unless explicitly required
Default trace enabled (verify via sys.traces)
Error log retention set to 99 files

32. Query Store

Query Store enabled on every production user database
Query Store configured: READ_WRITE, 2048 MB max, AUTO capture mode

33. Monitoring Baseline

DMV collection plan in place (memory, CPU, I/O, waits, requests, indexes)

34. Page File Configuration

Page file size set to 1.0–1.5 × total RAM on C: drive
Page file NOT disabled (required for crash dumps and exception handling)

35. Final Validation Checklist

Windows patched to latest CU
Antivirus exclusions configured
Power plan = High Performance
NIC and storage drivers current
Firewall ports open (1433 TCP, 1434 UDP if needed)
Max server memory configured
MAXDOP configured (typically 8)
Cost threshold for parallelism changed from 5
TempDB optimized (files, sizes, growth)
Instant File Initialization enabled
Page Verify = CHECKSUM on all databases
Auto Close / Auto Shrink OFF on all user databases
Backup compression default = ON
Backup checksum default = ON (SQL 2022+)
Query Store enabled on all user databases
Ola Hallengren maintenance solution deployed
Optimize for ad hoc workloads = ON
sa disabled or renamed
xp_cmdshell disabled
Ole Automation Procedures disabled
CLR disabled (if not needed)
Cross DB ownership chaining disabled
Server audit for failed logins enabled
Full backup tested (restore onto separate instance)
Transaction log backup chain verified
Point-in-time recovery tested
Backup retention policy documented
Backup files stored on separate media from data
Database Mail configured and tested
Operators created
Alerts for severity 16–25 configured
Deadlock alert (1205) configured

36. VM-Specific Optimization (for Virtualized SQL Server)

Context: Most SQL Server deployments are now virtualized — on Hyper-V, VMware vSphere, or Azure. A VM that inherits default virtualization settings will leave significant performance on the table. This section covers the additional steps required when SQL Server runs inside a virtual machine.

36.1 vCPU Configuration

  • Do not overallocate. Total vCPUs across all VMs on a host should not exceed the number of host logical processors (minus 1–2 for the root partition / hypervisor). Overcommitment causes CPU ready / co-stop contention.
  • Use whole sockets. Assign vCPUs in socket multiples (e.g., 4 vCPUs = 1 socket × 4 cores, not 4 sockets × 1 core). SQL Server licenses per core — socket alignment avoids licensing confusion and NUMA oddities.
  • Avoid oversubscription for SQL Server. Unlike web servers, SQL Server does not handle CPU overcommitment gracefully. A 1:1 or at-most 1:2 vCPU-to-pCPU ratio is recommended.
vCPU count assigned in whole-socket multiples — avoid odd NUMA topologies
Host CPU not overcommitted (vCPU:pCPU ratio ≤ 1:2, ideally 1:1 for SQL)

36.2 Memory — Dynamic Memory Must Be OFF

Critical: Disable Dynamic Memory (Hyper-V) or Memory Ballooning (VMware) for all SQL Server VMs. SQL Server manages its own buffer pool and expects a fixed amount of memory. Ballooning/dynamic memory causes SQL to be paged out, leading to severe performance degradation or even the SQL Server process being killed by the OS memory manager.

Dynamic Memory / Ballooning disabled for the SQL Server VM — fixed memory allocation
VM startup memory equals the max server memory + OS reservation (no oversubscription)

36.3 Storage — VM Disk Configuration

SettingProduction Recommendation
VHDX vs VHDVHDX (4 KB logical sector support, better alignment, larger max size)
VHDX TypeFixed size for SQL data/log/TempDB volumes — never dynamically expanding
Pass-through DisksAcceptable but add operational complexity; modern VHDX with fixed size performs equivalently
Host CachingDisable host caching on SQL data/log volumes (write-through) — enable only on read-heavy volumes if supported
Storage QoSSet minimum IOPS reservations on the host to prevent noisy-neighbor interference
VHDX format with fixed size for all SQL volumes — no dynamically expanding disks
Host caching disabled on data/log volumes (write-through) to prevent data loss on host failure
Storage QoS reservations configured to ensure minimum IOPS for SQL Server

36.4 Hyper-V Enlightenments & Integration Services

Windows Server VMs running on Hyper-V have access to "enlightenments" — optimized code paths that detect the hypervisor and use hypercalls instead of emulated instructions. These are enabled by default on modern Windows Server but should be verified:

  • Hyper-V Integration Services: all critical components enabled (Heartbeat, Shutdown, Time Sync, Data Exchange, Key-Value Pair)
  • Time sync: If the VM domain-joined with its own NTP source, disable host time sync to avoid clock drift conflicts
Hyper-V Integration Services up to date (check via PowerShell Get-VMIntegrationService)
Time sync from host disabled if VM uses a domain NTP source

36.5 NUMA & Virtual NUMA (vNUMA)

For VMs with more than 64 vCPUs, enable Virtual NUMA so SQL Server can properly schedule across processor groups. For VMs with fewer than 64 vCPUs, let the hypervisor and SQL Server auto-manage NUMA — no manual intervention needed beyond the MAXDOP guidance in Section 15.

If VM has ≥ 64 vCPUs, verify vNUMA is enabled (default in Hyper-V 2022+ for large VMs)

36.6 Network — SR-IOV & VMQ

For high-throughput SQL workloads (Always On synchronous replication, backup streaming), use SR-IOV (Single Root I/O Virtualization) to bypass the virtual switch and reduce latency. If SR-IOV is not available, ensure VMQ (Virtual Machine Queue) is enabled on the host for the physical NIC.

SR-IOV enabled for SQL VM virtual NIC (if available on the NIC and host)

36.7 Host-Level Resource Governance

  • Hyper-V Root Partition: Reserve at least 2 GB RAM + 2 CPU cores for the root partition. On NUMA systems, ensure the root partition owns at least one NUMA node.
  • VM Reservations: Set CPU and memory reservations equal to the VM's configured allocation to guarantee resource availability.
  • Weight / Priority: Set the SQL Server VM to the highest relative weight so the hypervisor prioritizes it during contention.
Hyper-V root partition has guaranteed CPU + memory (minimum 2 cores, 2 GB RAM reserved)
SQL Server VM configured with CPU/memory reservations (not just limits) on the host
SQL Server VM set to highest scheduling weight / priority on the host

36.8 Anti-Virus Exclusions on the Host

Extend antivirus exclusions to the host level as well. If the host scans VHDX files while SQL Server is writing to them, it causes IO latency spikes inside the VM. Exclude .vhdx, .vhd, .avhdx (snapshots/checkpoints), and the VM storage path from host-side real-time scanning.

Host-level AV exclusions added for .vhdx, .vhd, .avhdx files and VM storage path

36.9 VMware-Specific (if applicable)

  • Paravirtual SCSI (PVSCSI): Use PVSCSI adapters instead of LSI Logic for better throughput and lower CPU overhead.
  • VMXNET3: Use VMXNET3 network adapter (not E1000) for lower latency and higher throughput.
  • CPU Hot Add / Memory Hot Plug: Disable — not supported for SQL Server; prevents proper NUMA detection.
  • Reservation: Set CPU and memory reservations equal to the VM size to avoid ready-time contention.
(VMware) PVSCSI adapter in use for data/log disks; VMXNET3 for network; hot-add features disabled

37. Post-Migration Slow Server Debugging

When to use this section: The instance is online after migration but queries are slower than the source server — especially with high elapsed time, low CPU, and moderate logical reads. Work through these checks in order before chasing query hints or compatibility level changes.

37.1 Virtual Log Files (VLF) Fragmentation

Excessive VLFs in the transaction log slow down log backups, recovery, replication, and AG sync. Common after many small autogrowth events during migration restore.

VLF CountStatusAction
< 200GoodNone
200–500WarningMonitor; fix autogrowth on next maintenance window
> 1000CriticalRebuild log during maintenance window
-- VLF count per database (SQL Server 2016+)
SELECT
    d.name AS database_name,
    COUNT(*) AS vlf_count,
    CAST(SUM(vlf_size_mb) AS DECIMAL(18,2)) AS total_log_size_mb
FROM sys.databases AS d
CROSS APPLY sys.dm_db_log_info(d.database_id) AS v
WHERE d.database_id > 4 AND d.state = 0
GROUP BY d.name
ORDER BY vlf_count DESC;

Remediation (maintenance window):

  1. Take a log backup (FULL recovery) or switch to SIMPLE temporarily if policy allows.
  2. Shrink log to minimum: DBCC SHRINKFILE (N'LogLogicalName', TRUNCATEONLY);
  3. Grow back in large fixed increments (e.g., 4–8 GB per step) — not 10% autogrowth.
  4. Set fixed MB FILEGROWTH going forward.
-- Example: grow log in controlled steps after shrink
ALTER DATABASE [YourDB] MODIFY FILE (NAME = N'YourDB_log', SIZE = 8192MB, FILEGROWTH = 2048MB);

Repository script: 03_Storage_Engine/vlf_fragmentation.sql

VLF count checked on all user databases; critical counts (>1000) scheduled for remediation

37.2 Disk Latency (I/O Stalls)

Post-migration slowness is frequently storage-related — especially cold buffer pool, shared VM storage, or logs on slow volumes. sys.dm_io_virtual_file_stats is cumulative since instance start; note sqlserver_start_time before comparing.

Avg I/O StallStatus
< 5 msGood
5–15 msAcceptable
15–20 msWarning — investigate
> 20 msCritical — storage remediation required
-- Per-file read/write latency
SELECT
    DB_NAME(vfs.database_id) AS database_name,
    mf.name AS logical_file,
    mf.physical_name,
    mf.type_desc,
    CAST(vfs.io_stall_read_ms / NULLIF(vfs.num_of_reads, 0) AS DECIMAL(10,2)) AS avg_read_ms,
    CAST(vfs.io_stall_write_ms / NULLIF(vfs.num_of_writes, 0) AS DECIMAL(10,2)) AS avg_write_ms,
    CAST(vfs.io_stall / NULLIF(vfs.num_of_reads + vfs.num_of_writes, 0) AS DECIMAL(10,2)) AS avg_io_ms
FROM sys.dm_io_virtual_file_stats(NULL, NULL) AS vfs
INNER JOIN sys.master_files AS mf
    ON vfs.database_id = mf.database_id AND vfs.file_id = mf.file_id
ORDER BY avg_io_ms DESC;

-- Pending I/O backlog
SELECT COUNT(*) AS pending_io, MAX(io_pending_ms_ticks) AS max_pending_ms
FROM sys.dm_io_pending_io_requests;

Remediation:

  • Move data/log files to faster dedicated volumes (SSD/NVMe); keep logs separate from data.
  • Verify VM storage: fixed VHDX (not dynamic), host caching off for data/log, Storage QoS minimum IOPS.
  • Confirm antivirus exclusions on SQL paths (guest and hypervisor host).
  • Correlate with waits: PAGEIOLATCH_*, WRITELOG, IO_COMPLETION.

Repository scripts: 01_Server_OS/disk_latency.sql, Prod_Migration/08_Storage_OS/01_io_latency_deep_dive.sql

Disk latency baseline captured; files with >20 ms avg stall investigated

37.3 Database File Growth Settings

Percent-based autogrowth and tiny growth increments cause repeated growth events, VLF proliferation, and I/O pauses during peak load — especially on multi-TB databases after migration.

-- Autogrowth and space utilization (all databases)
SELECT
    DB_NAME(database_id) AS database_name,
    name AS logical_name,
    type_desc,
    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 autogrowth,
    is_percent_growth,
  physical_name
FROM sys.master_files
WHERE database_id > 4
ORDER BY database_name, type_desc;

Remediation:

  • Switch all files to fixed MB growth — never percent.
  • Pre-size data and log files to planned capacity; autogrowth is a safety net, not a sizing strategy.
  • Multi-TB databases: use 2048–4096 MB growth increments minimum.
  • Disable Auto Shrink on all user databases.
-- Fix percent-based autogrowth
ALTER DATABASE [YourDB] MODIFY FILE (NAME = N'YourDB', FILEGROWTH = 1024MB);
ALTER DATABASE [YourDB] MODIFY FILE (NAME = N'YourDB_log', FILEGROWTH = 2048MB);

Repository script: 03_Storage_Engine/database_files_growth.sql

No percent-based autogrowth on production files; growth increments sized for database scale

37.4 TempDB Configuration Review

TempDB contention (PAGELATCH on PFS/GAM/SGAM) affects every workload using sorts, hashes, and spools. After migration, TempDB often retains default file count, uneven sizes, or percent growth.

Checklist:

  • 1 data file per logical core, maximum 8 (add only if PAGELATCH contention persists).
  • All data files: equal size, equal fixed MB growth, on a dedicated fast volume.
  • Pre-size to expected peak — do not rely on autogrowth under load.
  • Trace flag 1118 (SQL 2014 and earlier) and 1117 are unnecessary on SQL 2016+.

Repository scripts: 03_Storage_Engine/tempdb_configuration.sql, Prod_Migration/08_Storage_OS/03_tempdb_autogrowth_audit.sql

TempDB file count, uniform sizing, and fixed MB growth verified post-migration

37.5 Post-Migration Triage Order

  1. Instance config audit — memory, MAXDOP, cost threshold, compatibility level.
  2. Storage I/O latency and file growth settings (this section).
  3. Wait statistics delta during slow workload reproduction.
  4. Index fragmentation and stale statistics (Section 25).
  5. Query Store / plan regressions if still unresolved.

Repository playbook: Prod_Migration/01_Quick_Triage/00_RUN_FIRST_triage_playbook.sql

Post-migration triage playbook executed; findings documented before plan-level changes

Appendix: Summary of Changes from Original Document

#ItemChange
1H: drive for SQL binariesCorrected: Binaries remain on C: drive — separating binaries is unnecessary and complicates patching.
2Instant File Initialization verificationCorrected: Uses sys.dm_server_services.instant_file_initialization_enabled (SQL 2022+) instead of generic SELECT *.
3MAXDOP guidanceExpanded: Added per-core count recommendations and referenced Microsoft KB 2806535.
4TempDB file count guidanceClarified: 8 file max unless contention observed; discourage 1:1 with cores.
5Page FileAdded: Page file sizing guidance (missing from original).
6Auto Close / Auto ShrinkAdded: Must be OFF for all production databases.
7Page Verify CHECKSUMAdded: Verify this setting on all databases.
8Network configurationAdded: TCP/IP enablement, Windows Firewall rules, port configuration.
9Security hardeningExpanded: Added xp_cmdshell, Ole Automation, CLR, cross-DB chaining, ad hoc workload optimization, server audit.
10Backup checksum defaultAdded: backup checksum default for SQL 2022+ as a best practice.
11Error log retentionAdded: Set to 99 files via registry.
12Operators & AlertsExpanded: Added sample T-SQL for alert creation and operator setup.
13Database MailExpanded: Added T-SQL creation script and testing procedure.
14Backup restore validationEmphasized: Added note that RESTORE VERIFYONLY is insufficient — must perform actual restore test.
15Memory reservation formulaExpanded: Added table with multiple RAM sizes and noted Lock Pages in Memory behavior.
16ReFS supportAdded: ReFS with 64 KB cluster as an option for Data/Backup volumes on Windows Server 2022+.
17BIOS power verificationAdded: BIOS/UEFI power management set to Maximum Performance — Windows cannot override a restrictive BIOS.
18Trace Flag 3226Added: Startup parameter to suppress successful backup messages in error log; critical when log backups run every 5–15 min.
19Fail-Safe OperatorAdded: Ensures alert delivery even when msdb or operator table is unavailable.
20Max Worker ThreadsAdded: Verification step for high-core servers to avoid THREADPOOL waits.
21LPIM clarificationClarified: Lock Pages in Memory must be assigned via secpol.msc with the exact account name — group membership is insufficient.
22Autogrowth for large DBsAdded: Warning about insufficient autogrowth sizes (e.g., 100 MB) on multi-TB databases causing VLF fragmentation.
23Post-migration debuggingAdded: Section 37 — VLF, disk latency, file growth, TempDB triage with remediation scripts.
24Index statistics scriptsAdded: Fragmentation detection, online rebuild/reorganize, stale stats detection, and UPDATE STATISTICS examples in Section 25.
25Permissions referenceAdded: Repository permission.sql for SQL Server and PostgreSQL diagnostic roles.