info@techdevops.com | 437-991-3573 | Data Engineering Services
TechDevOps.com
Resources Tools
Experts in Microsoft SQL Server on Windows, Linux, Containers | Clusters, Always On, FCI | Migrations, Cloud, Performance



SQL Server Database Transaction Log Backups - Activity-based (vs Time-based)
by BF (Principal Consultant; Architecture; Engineering)
2017-08-18








With SQL Server 2017 CTP 2.1, a new DMF has been introduced called sys.dm_db_log_stats. With this DMV we can get the amount of Transaction Log growth since the last Transaction Log backup. This information enables us to only take Transaction Log backups based on activity, and thus when needed. For example, would it be more efficient to take Transaction Log backups after activity/transactions has occurred and log growth of 50MB? Or take Transaction Log backups at every 5min time interval and numerous auto-grow events have occurred and the Transaction Log file having a huge number of VLF's? Activity-based backups has many benefits.

Advantages taking Transaction Log backups based on activity/transactions occurring on a User Databases:
- No unnecessary/wasted Transaction Log backups files created in the file system
- No unnecessary Transaction Log auto-grow events occurring / No unnecessary # of VLF's existing in the Transaction Log File Structure
- No inconsistency of Transaction Log Backup files sizes

You can also setup alerts based on this data made available in sys.dm_db_log_stats.



T-SQL - Take SQL Server Database Transaction Log Backups - Activity-based (default):


Create Procedure dbo.spa_Admin_Database_TransactionLog_Backups
as

--Select * from sys.dm_db_log_stats(5)
--Create Table dbo.Log_Backups (LogID int Identity(1,1), Type varchar(150), Frequency varchar(25), total_vlf_count int, active_vlf_count int, total_log_size_mb decimal(18,1), active_log_size_mb decimal(18,1), log_since_last_log_backup_mb int, inserteddate datetime)

SET NOCOUNT ON

Declare @timebased int = 0
Declare @loggrowththreshold int = 50 --mb
Declare @log_since_last_log_backup_mb int
Declare @frequency varchar(25)

If @timebased = 0 Begin Set @frequency = 'Activity-based' End Else Begin Set @frequency = 'Time-based' End;
Select @timebased, @frequency
While(1=1)
Begin
If @timebased = 0
Begin
--Take Log Backup based on Activity/Transactions
Select @log_since_last_log_backup_mb = log_since_last_log_backup_mb from sys.dm_db_log_stats(5)
--Print @log_since_last_log_backup_mb
If @log_since_last_log_backup_mb > @loggrowththreshold
Begin
Begin Transaction
Insert into dbo.LOG_BACKUPS ([Type], Frequency, total_vlf_count, active_vlf_count, total_log_size_mb, active_log_size_mb, log_since_last_log_backup_mb, inserteddate)
Select 'Pre Log Backup', @frequency, total_vlf_count, active_vlf_count, total_log_size_mb, active_log_size_mb, log_since_last_log_backup_mb, getdate() from sys.dm_db_log_stats(5)
Commit

Declare @filename varchar(100)
Declare @time varchar(100)
Set @time = CONVERT(VARCHAR(8), GETDATE(), 108)
Set @time = REPLACE(@time, ':', '')
Set @filename = 'F:\Backups\test' + CONVERT(VARCHAR(35),getdate(),112) + '_' + @time + '.trn'
Print 'Transaction Log Backup started due too activity-based threshold passed - ' + convert(varchar(100),@log_since_last_log_backup_mb) + 'MB log growth since last log backup. (' + @filename + ')' + char(13)

Backup Log [Test] To Disk = @filename WITH FORMAT

Print char(13)

Begin Transaction
Insert into dbo.LOG_BACKUPS ([Type], Frequency, total_vlf_count, active_vlf_count, total_log_size_mb, active_log_size_mb, log_since_last_log_backup_mb, inserteddate)
Select 'Post Log Backup', @frequency, total_vlf_count, active_vlf_count, total_log_size_mb, active_log_size_mb, log_since_last_log_backup_mb, getdate() from sys.dm_db_log_stats(5)
Commit
WAITFOR DELAY '00:00:01'
--Print 'Loop...'
End
End
If @timebased = 1
Begin --Take Log Backup based on a set Time Interval. T-Log autogrows(# VLF increased due to activity/transactions).
Begin Transaction
Insert into dbo.LOG_BACKUPS ([Type], Frequency, total_vlf_count, active_vlf_count, total_log_size_mb, active_log_size_mb, log_since_last_log_backup_mb, inserteddate)
Select 'Pre Log Backup', @frequency, total_vlf_count, active_vlf_count, total_log_size_mb, active_log_size_mb, log_since_last_log_backup_mb, getdate() from sys.dm_db_log_stats(5)
Commit

Set @time = CONVERT(VARCHAR(8), GETDATE(), 108)
Set @time = REPLACE(@time, ':', '')
Set @filename = 'F:\Backups\test' + CONVERT(VARCHAR(35),getdate(),112) + '_' + @time + '.trn'
Print 'Transaction Log Backup started due too time-based backup. ' + '(' + @filename + ')' + char(13)

Backup Log [Test] To Disk = @filename WITH FORMAT

Begin Transaction
Insert into dbo.LOG_BACKUPS ([Type], Frequency, total_vlf_count, active_vlf_count, total_log_size_mb, active_log_size_mb, log_since_last_log_backup_mb, inserteddate)
Select 'Post Log Backup', @frequency, total_vlf_count, active_vlf_count, total_log_size_mb, active_log_size_mb, log_since_last_log_backup_mb, getdate() from sys.dm_db_log_stats(5)
Commit
WAITFOR DELAY '00:05:00'
End

End
go


In this demo the procedure is taking Transaction Log backups after 50MB of log growth has occurred in the User Database. It keeps consistent log backup sizes and avoids the log having a high number of auto-grow events. It also avoids any log backups being taken when the database has zero transactional activity.





Generate Use Database Transaction Log Activity for demonstration purposes:


CREATE TABLE [dbo].[Orders](
[OrderID] [int] IDENTITY(1,1) NOT NULL,
[ClientID] [int] NOT NULL,
[OrderDate] [datetime] NULL,
[ModifiedDate] [datetime] NULL,
[OrderNotes] [varchar](1000) NULL,
CONSTRAINT [PK_Orders] PRIMARY KEY CLUSTERED
(
[OrderID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO


Create Procedure dbo.spa_Insert
as

/*
While (1=1)
Begin
Exec dbo.spa_Insert
End
*/

Declare @randomstring varchar(1000) = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
Set @randomstring = @randomstring + @randomstring + @randomstring + @randomstring + @randomstring
Declare @cnt int = 1000
Begin Transaction
While @cnt > 0
Begin
Insert into Orders (ClientID, OrderDate, ModifiedDate, OrderNotes) Select 7000, Getdate(), GetDate(), @randomstring
Set @cnt = @cnt - 1
End
Commit Transaction

WAITFOR DELAY '00:00:01'
go



Monitor T-SQL #1: Activity-Based

Logging table showing the activity for VLF and log file growth. It logs the before and after Log Backups results from sys.dm_db_log_stats. As stated above, the number of VLF's stays low and the backups are being taken in a consistent, predictable matter. Assuming you don't have huge, long-running transactions in your database, you should see similar consistency as below.

Select getdate() as 'Time', log_since_last_log_backup_mb, active_log_size_mb, total_log_size_mb, log_truncation_holdup_reason, * from sys.dm_db_log_stats(5)
Select * from dbo.LOG_BACKUPS order by logid desc






Monitor T-SQL #2: Return Trx Information

select
d.Name as 'Database',
sessiontrx.session_id as [SPID],
enlist_count as [Active Requests],
activetrx.transaction_id as [Trx ID],
activetrx.name as [Trx Name],
activetrx.transaction_begin_time as [Trx Start Time],
case transaction_type when 1 then 'Read/Write' when 2 then 'Read-Only' when 3 then 'System' when 4 then 'Distributed' else 'Unknown - ' + convert(varchar(20), transaction_type) end as [Transaction Type],
case transaction_state when 0 then 'Uninitialized' when 1 then 'Not Yet Started' when 2 then 'Active' when 3 then 'Ended (Read-Only)' when 4 then 'Committing' when 5 then 'Prepared' when 6 then 'Committed'
when 7 then 'Rolling Back' when 8 then 'Rolled Back' else 'Unknown - ' + convert(varchar(20), transaction_state) end as 'Transaction State',
case database_transaction_state when 1 then 'Uninitialized' when 3 then 'No Log Records' when 4 then 'Log Records' when 5 then 'Prepared' when 10 then 'Committed' when 11 then 'Rolled Back'
when 12 then 'Committing' else 'Unknown - ' + convert(varchar(20), database_transaction_state) end as 'DB Trx State',
database_transaction_log_record_count as [Log Records Total],
database_transaction_log_bytes_used as [Log Bytes Used],
database_transaction_log_bytes_reserved as [Log Bytes Reserved],
database_transaction_replicate_record_count as [Replication Records]
from
sys.dm_tran_active_transactions activetrx (nolock)
inner join sys.dm_tran_database_transactions dbtrx (nolock) on dbtrx.transaction_id = activetrx.transaction_id
inner join sys.databases d (nolock) on d.database_id = dbtrx.database_id
left join sys.dm_tran_session_transactions sessiontrx (nolock) on sessiontrx.transaction_id = activetrx.transaction_id
left join sys.dm_exec_requests req (nolock) on req.session_id = sessiontrx.session_id and req.transaction_id = sessiontrx.transaction_id
where sessiontrx.session_id is not null
and d.name not in ('Master', 'MSDB', 'Model')