This blog post applies to\u00a0SQL Server 2012 CTP3 (11.0.1440).<\/span><\/span><\/em><\/p>\n SQL Server 2012 introduces the \u201cindirect checkpoints\u201d feature.\u00a0 <\/span>At a high level, it allows you to adjust the target recovery time of a specific database, rather than relying entirely on the SQL Server instance-level \u2018recovery interval (min)\u2019 setting. <\/span><\/span>To demonstrate the impact, I\u2019ll walk through a scenario where we\u2019re performing large batch inserts into a data warehousing\u00a0Fact table.\u00a0 <\/span>In this\u00a0demo I\u2019m using the Codeplex \u201cAdventureWorksDWDenali\u201d database.<\/span><\/span><\/p>\n As a first step, I\u2019ll back up the database so that I can reset the baseline in order to compare the default checkpoint behavior with the new indirect checkpoint setting:<\/span><\/span><\/p>\n USE master<\/span><\/span><\/p>\n GO<\/span><\/span><\/p>\n \n \n BACKUP DATABASE [AdventureWorksDWDenali] <\/span><\/span><\/p>\n TO\u00a0 <\/span>DISK = N’C:\\Program Files\\Microsoft SQL Server\\MSSQL11.MSSQLSERVER\\MSSQL\\Backup\\IndirectCheckpoint_AWDW.bak’<\/span><\/span><\/p>\n \u00a0<\/span><\/span>WITH NOFORMAT, NOINIT,\u00a0 <\/span>NAME = N’AdventureWorksDWDenali-Full Database Backup’, CHECKSUM<\/span><\/span><\/p>\n GO<\/span><\/span><\/p>\n \n Now with that out of the way I\u2019ll check the recovery interval of the SQL Server instance:<\/span><\/span><\/p>\n SELECT value_in_use\u00a0<\/span><\/span><\/p>\n FROM sys.configurations<\/span><\/span><\/p>\n WHERE name = ‘recovery interval (min)’<\/span><\/span><\/p>\n \n \n The value in use is \u201c0\u201d \u2013 the default.\u00a0 <\/span>So basically the estimated checkpoint will be every minute for an active database (although I\u2019m over-simplifying things here \u2013 since automatic checkpoint frequency varies based on other factors as well – but for this demo you’ll still see a clear difference in the before-and-after).<\/span><\/span><\/p>\n \n \n Now I\u2019ll check the indirect checkpoint time for the AdventureWorksDWDenali database:<\/span><\/span><\/p>\n \n \n SELECT target_recovery_time_in_seconds<\/span><\/span><\/p>\n FROM sys.databases<\/span><\/span><\/p>\n WHERE name = ‘AdventureWorksDWDenali’<\/span><\/span><\/p>\n \n \n This returns \u201c0\u201d \u2013 meaning we haven\u2019t configured this new SQL Server 2012 database option (yet) and so the SQL Server instance level setting is in effect.<\/span><\/span><\/p>\n \n \n Now I\u2019ll create an extended events session to track the individual checkpoint events for the database (database_id “5” happens to be the AdventureWorksDWDenali database on my instance):<\/span><\/span><\/p>\n \n \n CREATE EVENT SESSION [track_checkpoints] ON SERVER <\/span><\/span><\/p>\n ADD EVENT sqlserver.checkpoint_end(<\/span><\/span><\/p>\n \u00a0\u00a0\u00a0 <\/span><\/span>ACTION(sqlserver.database_id,sqlserver.database_name,sqlserver.is_system)<\/span><\/span><\/p>\n \u00a0\u00a0\u00a0 <\/span><\/span>WHERE ([sqlserver].[database_id]=(5))) <\/span><\/span><\/p>\n ADD TARGET package0.ring_buffer(SET max_memory=(2048))<\/span><\/span><\/p>\n GO<\/span><\/span><\/p>\n \n \n \n (By the way – I updated max_memory down to 2MB from the 100MB per Jonathan Kehayias’ heads up on potential\u00a0limits\u00a0– not to mention that this was more memory than needed for this example<\/em>).<\/span><\/span><\/p>\n \n \n \u00a0Next I\u2019ll turn it on:<\/span><\/span><\/p>\n \n \n ALTER EVENT SESSION [track_checkpoints] ON SERVER STATE=START<\/span><\/span><\/p>\n GO<\/span><\/span><\/p>\n \n \n And now I\u2019m going to push some I\/O by creating a new table and populating it multiple times from the FactInternetSales table:<\/span><\/span><\/p>\n \n \n USE AdventureWorksDWDenali<\/span><\/span><\/p>\n GO<\/span><\/span><\/p>\n \n \n SELECT *<\/span><\/span><\/p>\n INTO dbo.CheckPoint_Test_FactInternetSales<\/span><\/span><\/p>\n FROM dbo.FactInternetSales<\/span><\/span><\/p>\n GO<\/span><\/span><\/p>\n \n \n INSERT dbo.CheckPoint_Test_FactInternetSales<\/span><\/span><\/p>\n SELECT ProductKey, OrderDateKey, DueDateKey, ShipDateKey, CustomerKey, PromotionKey, CurrencyKey, SalesTerritoryKey, LEFT(CAST(NEWID() AS NVARCHAR(36)),20), SalesOrderLineNumber, RevisionNumber, OrderQuantity, UnitPrice, ExtendedAmount, UnitPriceDiscountPct, DiscountAmount, ProductStandardCost, TotalProductCost, SalesAmount, <\/span><\/span><\/p>\n TaxAmt, Freight, CarrierTrackingNumber, CustomerPONumber, OrderDate, DueDate, ShipDate<\/span><\/span><\/p>\n FROM dbo.FactInternetSales<\/span><\/span><\/p>\n GO 10<\/span><\/span><\/p>\n \n \n This shows the following rows affected:<\/span><\/span><\/p>\n \n \n (60398 row(s) affected)<\/span><\/span><\/p>\n Beginning execution loop<\/span><\/span><\/p>\n (60398 row(s) affected)<\/span><\/span><\/p>\n (60398 row(s) affected)<\/span><\/span><\/p>\n (60398 row(s) affected)<\/span><\/span><\/p>\n (60398 row(s) affected)<\/span><\/span><\/p>\n (60398 row(s) affected)<\/span><\/span><\/p>\n (60398 row(s) affected)<\/span><\/span><\/p>\n (60398 row(s) affected)<\/span><\/span><\/p>\n (60398 row(s) affected)<\/span><\/span><\/p>\n (60398 row(s) affected)<\/span><\/span><\/p>\n (60398 row(s) affected)<\/span><\/span><\/p>\n Batch execution completed 10 times.<\/span><\/span><\/p>\n \n \n Now I\u2019ll query the track_checkpoints Extended Event session (** and thanks to my colleague\u00a0Jonathan Kehayias for the following query which I just modified to query against track_checkpoints and then change a few column names **):<\/span><\/span><\/p>\n \n \n SELECT <\/span><\/span><\/p>\n \u00a0\u00a0\u00a0 <\/span><\/span>n.value(‘(event\/@name)[1]’, ‘varchar(50)’) AS event_name,<\/span><\/span><\/p>\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 <\/span><\/span>n.value(‘(event\/@timestamp)[1]’, ‘datetime’) as event_datetime<\/span><\/span><\/p>\n FROM<\/span><\/span><\/p>\n (\u00a0\u00a0\u00a0 <\/span>SELECT td.query(‘.’) as n<\/span><\/span><\/p>\n \u00a0\u00a0\u00a0 <\/span><\/span>FROM <\/span><\/span><\/p>\n \u00a0\u00a0\u00a0 <\/span><\/span>(<\/span><\/span><\/p>\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 <\/span><\/span>SELECT CAST(target_data AS XML) as target_data<\/span><\/span><\/p>\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 <\/span><\/span>FROM sys.dm_xe_sessions AS s<\/span>\u00a0\u00a0\u00a0 <\/span><\/span><\/span><\/p>\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 <\/span><\/span>JOIN sys.dm_xe_session_targets AS t<\/span><\/span><\/p>\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 <\/span><\/span>ON s.address = t.event_session_address<\/span><\/span><\/p>\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 <\/span><\/span>WHERE s.name = ‘track_checkpoints’<\/span><\/span><\/p>\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 <\/span><\/span>AND t.target_name = ‘ring_buffer’<\/span><\/span><\/p>\n \u00a0\u00a0\u00a0 <\/span><\/span>) AS sub<\/span><\/span><\/p>\n