TEMPDB, is one of SQL server hot-debated topics; there’s always something to say about its sizing, file placement or datafiles count in Multi-core instances.
WHY TEMPDB is controversial ?
This is simply because TEMPDB is a global resource that’s heavily used and very critical asset in a busy instance; this makes any advice against TEMPDB fall in the “It depends” category.
Any generalizations ?
As a starting point, some advice is usually thrown around for tempdb:
– Place Tempdb on a fast array , preferably On RAID10.
– Better if Tempdb is on an array not shared by other files if possible.
– Create more than one datafile if instance uses more than one CPU/Core.
– In case of multiple datafiles, better if all files have equal size.
More info can be found under Optimizing tempdb Performance
Not all of the above can be achieved especially when it comes to Storage requirements. However, at least it’s almost a “de facto” practice for instances using more than one CPU core, virtual or physical, to create more than one datafiles to reduce Tempdb contention when multiple CPUs(cores) try to allocate pages; you can read more info about this issue and TEMPDB improvements after SQL server 2000 under “Working with tempdb in SQL Server 2005” Whitepaper.
This blog isn’t about TEMPDB internals nor optimizations (please look at the above whitepaper for that); it’s just about this part of creating more than one datafile in case of multi-core instances.
In many cases, I’ve to create many datafiles on many instances manually, taking in consideration some parameters: Location , CPU counts, Size, File growth…etc.
This can be a bit tedious and I ran into some errors like creating files in wrong places like OS drive, creating more than enough files or using larger than the initial size should be.
For the above reasons, I created a query that will generate CREATE FILE statements to add new Datafiles to TEMPDB based on supplied parameters.
I wanted the script to give me the statements based on the following parameters:
– Number of cores used by the instance : this is stored internally; I used parts of code from “Who was that Affinity Masked Man?” to return the number of cores used by an instance.
FYI, When SQL server instance addresses more than 32 cores, information is stored in “affinity64 mask” Option.
– Location: either specify the location of new files or the location of Tempdb primary file will be used.
The script will throw a warning if the proposed location does NOT exist. It will also throw a warning if the SUM of proposed files’ size is more than the available free drive space.
– File size: It’s often advised to give the files the same size for optimal proportional-fill performance. If one file is bigger than the others, it will be chosen more often for activity, not the best situation.
The script will throw a warning if ANY of the existing files size is different than the new files’ proposed size. If you want to align the files size then you’ll have to change the proposed size or change the existing file(s) size.
– Growth: If the files are allowed to grow then best if they grow with same pace to keep thier sizes aligned; This can be easily changed after that.
I usually prefer not to grow the files to control their space and make sure the files were remain same size but I “TRY” to ensure the database has enough free space.
– Files count per core: this is always the most debatable option and advices differed after SQL server 2000 one-file-per-core especially since SQL server can now support 256 Logical cores.
MVP & ex-MS Paul Randal summarized this in following blog with the conclusion that you may be ok with the number of tempdb data files equal to 1/4 to 1/2 the number of processor cores..
I used the following formula to determine the number of files that should exist ; FEEL free to change that
IF Cores <= 8 then one file per core
IF between 8 & 32 cores inclusive then files = 1/2 # cores
IF more than 32 cores then files = 1/4 # cores
The script will take all the above considerations and formulate an ALTER DATABASE ADD FILE statement; the number of statements are the number of suggested files – number of existing files.
The script in no way will make ANY changes but will raise some warnings related to path, space and existing files size.
Now the code; Please TEST, TEST, TEST WELL; the code is JUST a way to automate the creation of new files
/*————————————————————————– Written by Mohammed Mawla, The Pythian Group THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND; USE IT AT YOUR OWN RESPONSIBILITY PLEASE TEST THE CODE THROUGHLY BEFORE APPLYING IT TO ANY PRODUCTION —————————————————————————*/ USE Master GO SET NOCOUNT ON GO PRINT ‘– Instance name: ‘+ @@servername + ‘ ; /* Version: ‘ + @@version + ‘ */’ — Variables DECLARE @BITS Bigint — Affinty Mask ,@NUMPROCS Smallint — Number of cores addressed by instance ,@tempdb_files_count Int — Number of exisiting datafiles ,@tempdbdev_location Nvarchar(4000) — Location of TEMPDB primary datafile ,@X Int — Counter ,@SQL Nvarchar(max) ,@new_tempdbdev_size_MB Int — Size of the new files,in Megabytes ,@new_tempdbdev_Growth_MB Int — New files growth rate,in Megabytes ,@new_files_Location Nvarchar(4000) — New files path — Initialize variables Select @X = 1, @BITS = 1 SELECT @new_tempdbdev_size_MB = 4096 — Four Gbytes , it’s easy to increase that after file creation but harder to shrink. ,@new_tempdbdev_Growth_MB = 512 — 512 Mbytes , can be easily shrunk ,@new_files_Location = NULL — NULL means create in same location as primary file. IF OBJECT_ID(‘tempdb..#SVer’) IS NOT NULL BEGIN DROP TABLE #SVer END CREATE TABLE #SVer(ID INT, Name sysname, Internal_Value INT, Value NVARCHAR(512)) INSERT #SVer EXEC master.dbo.xp_msver processorCount — Get total number of Cores detected by the Operating system SELECT @NUMPROCS= Internal_Value FROM #SVer Print ‘– TOTAL numbers of CPU cores on server :’ + cast(@NUMPROCS as varchar(5)) SET @NUMPROCS = 0 — Get number of Cores addressed by instance. WHILE @X <= (SELECT Internal_Value FROM #SVer ) AND @x <=32 BEGIN SELECT @NUMPROCS = CASE WHEN CAST (VALUE AS INT) & @BITS > 0 THEN @NUMPROCS + 1 ELSE @NUMPROCS END FROM sys.configurations WHERE NAME = ‘AFFINITY MASK’ SET @BITS = (@BITS * 2) SET @X = @X + 1 END IF (SELECT Internal_Value FROM #SVer) > 32 Begin WHILE @X <= (SELECT Internal_Value FROM #SVer ) BEGIN SELECT @NUMPROCS = CASE WHEN CAST (VALUE AS INT) & @BITS > 0 THEN @NUMPROCS + 1 ELSE @NUMPROCS END FROM sys.configurations WHERE NAME = ‘AFFINITY64 MASK’ SET @BITS = (@BITS * 2) SET @X = @X + 1 END END If @NUMPROCS = 0 SELECT @NUMPROCS= Internal_Value FROM #SVer Print ‘– Number of CPU cores Configured for usage by instance :’ + cast(@NUMPROCS as varchar(5)) ————————————————————————————- — Here you define how many files should exist per core ; Feel free to change ————————————————————————————- — IF cores < 8 then no change , if between 8 & 32 inclusive then 1/2 of cores number IF @NUMPROCS >8 and @NUMPROCS <=32 SELECT @NUMPROCS = @NUMPROCS /2 — IF cores > 32 then files should be 1/4 of cores number If @NUMPROCS >32 SELECT @NUMPROCS = @NUMPROCS /4 — Get number of exisiting TEMPDB datafiles and the location of the primary datafile. SELECT @tempdb_files_count=COUNT(*) ,@tempdbdev_location=(SELECT REVERSE(SUBSTRING(REVERSE(physical_name), CHARINDEX(‘\’,REVERSE(physical_name)) , LEN(physical_name) )) FROM tempdb.sys.database_files WHERE name = ‘tempdev’) FROM tempdb.sys.database_files WHERE type_desc= ‘Rows’ AND state_desc= ‘Online’ Print ‘– Current Number of Tempdb datafiles :’ + cast(@tempdb_files_count as varchar(5)) — Determine if we already have enough datafiles If @tempdb_files_count >= @NUMPROCS Begin Print ‘–****Number of Recommedned datafiles is already there****’ Return End Set @new_files_Location= Isnull(@new_files_Location,@tempdbdev_location) — Determine if the new location exists or not Declare @file_results table(file_exists int,file_is_a_directory int,parent_directory_exists int) insert into @file_results(file_exists, file_is_a_directory, parent_directory_exists) exec master.dbo.xp_fileexist @new_files_Location if (select file_is_a_directory from @file_results ) = 0 Begin print ‘– New files Directory Does NOT exist , please specify a correct folder!’ Return end — Determine if we have enough free space on the destination drive Declare @FreeSpace Table (Drive char(1),MB_Free Bigint) insert into @FreeSpace exec master..xp_fixeddrives if (select MB_Free from @FreeSpace where drive = LEFT(@new_files_Location,1) ) < @NUMPROCS * @new_tempdbdev_size_MB Begin print ‘– WARNING: Not enough free space on ‘ + Upper(LEFT(@new_files_Location,1)) + ‘:\ to accomodate the new files. Around ‘+ cast(@NUMPROCS * @new_tempdbdev_size_MB as varchar(10))+ ‘ Mbytes are needed; Please add more space or choose a new location!’ end — Determine if any of the exisiting datafiles have different size than proposed ones. If exists ( SELECT (CONVERT (bigint, size) * 8)/1024 FROM tempdb.sys.database_files WHERE type_desc= ‘Rows’ and (CONVERT (bigint, size) * 8)/1024 <> @new_tempdbdev_size_MB ) PRINT ‘ /* WARNING: Some Existing datafile(s) do NOT have the same size as new ones. It”s recommended if ALL datafiles have same size for optimal proportional-fill performance.Use ALTER DATABASE and DBCC SHRINKFILE to resize files Optimizing tempdb Performance : https://msdn.microsoft.com/en-us/library/ms175527.aspx ‘ Print ‘****Proposed New Tempdb Datafiles, PLEASE REVIEW CODE BEFORE RUNNIG *****/ —————————————————————————————————————————————————————————— ‘ — Generate the statements WHILE @tempdb_files_count < @NUMPROCS BEGIN SELECT @SQL = ‘ALTER DATABASE [tempdb] ADD FILE (NAME = N”tempdev_new_0’+CAST (@tempdb_files_count +1 AS VARCHAR (5))+”’,FILENAME = N”’+ @new_files_Location + ‘tempdev_new_0’+CAST (@tempdb_files_count +1 AS VARCHAR(5)) +’.ndf”,SIZE = ‘+CAST(@new_tempdbdev_size_MB AS VARCHAR(15)) +’MB,FILEGROWTH = ‘+CAST(@new_tempdbdev_Growth_MB AS VARCHAR(15)) +’MB ) GO’ PRINT @SQL SET @tempdb_files_count = @tempdb_files_count + 1 END
Please feel free to post your suggestions for any modifications or fixes.
section that tests for data file size, is also looking at the ldf file.
So if you have identical sizes for your Data files, but log file is different, it throws a warning that is too generalized.
there is no reason the ldf needs to be the same config and start size as the data files.
i can see type_desc=’rows’ , that’s 4 datafiles
I notice that your script doesn’t adjust the size of the tempdev (the original file). Shouldn’t this be of equal size to the new files so that proportional fill works properly?
No , it doesn’t for multiple reasons
– It could be that primary file is larger than what we want and then we’ll need to shrink it , an operation that can be lengthy
– There could be more than one existing datafile and we’ll need to align the size for them all, not only the primary datafile.
That’s why I’ve added this warning in the code so that the DBA handle he size alignment
– there could be multiple data files already there and
— Determine if any of the exisiting datafiles have different size than proposed ones.
SELECT (CONVERT (bigint, size) * 8)/1024 FROM tempdb.sys.database_files
WHERE type_desc= ‘Rows’
and (CONVERT (bigint, size) * 8)/1024 @new_tempdbdev_size_MB
WARNING: Some Existing datafile(s) do NOT have the same size as new ones.
It”s recommended if ALL datafiles have same size for optimal proportional-fill performance.Use ALTER DATABASE and DBCC SHRINKFILE to resize files
Optimizing tempdb Performance : https://msdn.microsoft.com/en-us/library/ms175527.aspx
SELECT cpu_count FROM sys.dm_os_sys_info;
be used instead of using xp_msver and a temp table?
Hi, if we want to change tempdbdev_Growth_MB to % instead is it possible, if yes – how? (nice script btw)