Shrink, Damn’d Log! Shrink, I Say!

Ever have a database log file grow to epic proportions on the weekend? I have. Ever forget to set up job monitoring on that server? I have. Ever get so pissed off that you decided to write a job to automatically shrink the log files whenever they grow? I have.

I used my rudimentary knowledge of Service Broker, Event Notifications, and hackery to create a solution to my problem. For starters, I added the following stored procedure to every database:

CREATE SCHEMA dba ;
GO

IF OBJECT_ID(N'dba.ShrinkLog', N'P') IS NOT NULL
  DROP PROCEDURE dba.ShrinkLog ;
GO

CREATE PROCEDURE dba.ShrinkLog
  WITH EXECUTE AS OWNER
AS
  BEGIN
    DECLARE @error AS VARCHAR(2000) = 'LOG_FILE_AUTO_GROW encountered' ;
    RAISERROR (@error, 10, 1) WITH LOG

    DECLARE @size_in_mb AS DECIMAL(18, 5) ;
    DECLARE @max_log_size_in_mb AS DECIMAL(18, 5) = 10240 ;

    SELECT  @size_in_mb = ( size * 8.0 ) / 1024.0
    FROM    sys.database_files
    WHERE   data_space_id = 0 ;

    IF @size_in_mb > @max_log_size_in_mb
      BEGIN

        SET @error = 'log shrink occured on ' + DB_NAME() + ' at '
          + CONVERT(VARCHAR(50), GETDATE(), 101) + '. log file was '
          + CAST(@size_in_mb AS VARCHAR(50)) + ' mb in size.' ;

        RAISERROR (@error, 10, 1) WITH LOG

        DBCC SHRINKFILE (2, 1024) ;
      END
  END
GO  

Once I had my procedure in place, I needed a way to fire this procedure. Rather than mindlessly attempt to shrink the log files every X minutes, I decided that I should only shrink the file after a log growth has occurred. This means that, in theory, I could stop the log from growing… because, you know, I’m a bastard. We don’t want the log to NEVER grow, just shrink when it gets too big – which is possible because this might be a problem between log backups. That’s why there’s a check to see if the log file is greater than an arbitrary maximum size.

The next step was to actually figure out how to make this procedure fire. I wasn’t sure about it, but I thought that there was a SQL Server Event Notification that would fire whenever the log file grew. Turns out that I was right: LOG_FILE_AUTO_GROW.

Now, I knew from reading Mladen Prajdić’s blog that I could use the SQL Server Service Broker to respond to system events and, so, I did just that.

USE SystemEvents ;
GO

IF OBJECT_ID(N'dbo.LogShrinkResponse', N'P') IS NOT NULL
  DROP PROCEDURE dbo.LogShrinkResponse ;
GO

CREATE PROCEDURE dbo.LogShrinkResponse
AS
  BEGIN
    DECLARE @msgBody XML
    DECLARE @dlgId UNIQUEIDENTIFIER

  -- you can change this to get all messages at once
    WHILE ( 1 = 1 )
      BEGIN

        BEGIN TRANSACTION ;

        BEGIN TRY 

            ;
          RECEIVE TOP (1)
                @msgBody = message_body,
                @dlgId = conversation_handle FROM LogShrinkNotificationQueue ;

          IF @@ROWCOUNT = 0
            BEGIN
              IF @@TRANCOUNT > 0
                BEGIN
                  ROLLBACK ;
                END

              BREAK ;
            END

          DECLARE @database_name AS SYSNAME ;
          DECLARE @data AS XML ;
          DECLARE @statement AS NVARCHAR(2000) ;

          SET @data = EVENTDATA() ;

          SET @database_name = @data.value('(/EVENT_INSTANCE/DatabaseName)[1]',
                                           'NVARCHAR(128)') ;

          SET @statement = N'USE ' + @database_name
            + '; EXEC dba.ShrinkLog ; '

          EXEC sp_executesql @statement ;

          IF @@TRANCOUNT > 0
            BEGIN
              COMMIT ;
            END
        END TRY

        BEGIN CATCH
          IF @@TRANCOUNT > 0
            BEGIN
              ROLLBACK ;
            END

          -- write any error in to the event log
          DECLARE @errorNumber BIGINT ,
            @errorMessage NVARCHAR(2048) ,
            @dbName NVARCHAR(128)
          SELECT  @errorNumber = ERROR_NUMBER() ,
                  @errorMessage = ERROR_MESSAGE() ,
                  @dbName = DB_NAME()

          RAISERROR (N'Error WHILE receiving Service Broker message FROM queue LogShrinkNotificationQueue.
                        DATABASE Name: %s; Error number: %I64d; Error Message: %s',
            16, 1, @dbName, @errorNumber, @errorMessage) WITH LOG ;
        END CATCH ;
      END
  END
GO

CREATE QUEUE LogShrinkNotificationQueue
WITH STATUS = ON,
ACTIVATION ( PROCEDURE_NAME = dbo.LogShrinkResponse, MAX_QUEUE_READERS = 1, EXECUTE AS 'dbo'
) ;
GO

CREATE SERVICE LogShrinkNotificationService ON QUEUE LogShrinkNotificationQueue
([http://schemas.microsoft.com/SQL/Notifications/PostEventNotification]) ;
GO

CREATE ROUTE LogShrinkNotificationRoute
WITH SERVICE_NAME = 'LogShrinkNotificationService',
ADDRESS = 'LOCAL' ;

CREATE EVENT NOTIFICATION LogShrinkNotificationEvent ON SERVER FOR LOG_FILE_AUTO_GROW
TO SERVICE 'LogShrinkNotificationService', 'current database' ;
GO

/*
DROP PROCEDURE dba.ShrinkLog
DROP EVENT NOTIFICATION LogShrinkNotificationEvent ON SERVER
DROP ROUTE LogShrinkNotificationRoute
DROP SERVICE LogShrinkNotificationService
DROP QUEUE LogShrinkNotificationQueue
*/

Here’s what’s going on up there.

We create a procedure that will be called when the log shrink notification is caught. It loops through our queue and grabs the top message off each time and processes it. If there’s a message, it will pull the name of the database out of the XML from the event notification. We use the database name to build dynamic SQL that we execute to fire off the log shrink procedure.

The rest of the code just sets up the service broker queue, notification, routing, and event handler. It’s basic, boiler plate, code that tells SQL Server to set up service broker on the current database and listen for th LOG_FILE_AUTO_GROW event.

Comments

6 Comments so far. Leave a comment below.
  1. If you mark a sproc as system object you don’t have to put it inside every db.
    Just in master and it runs in the context of the user db.
    I played with this here:
    http://weblogs.sqlteam.com/mladenp/archive/2007/01/18/58287.aspx

  2. I used to shrink a lot more than I do now, until I found articles like this one: http://www.brentozar.com/archive/2009/08/stop-shrinking-your-database-files-seriously-now/.

    I think we should all think thrice before habitually shrinking log files.

    Cheers.

    • There’s a huge difference in the performance implications of shrinking a transaction log file vs shrinking a data file.

      Log file shrinks don’t require that data pages be physically reorganized on disk. The “unused” pages at the end of the log file are released back to the operating system, ready to be re-used by the ravenous log file once again.

      In a perfect world, I wouldn’t have to shrink log files to avoid 4AM page outs because mission critical business systems have stopped functioning. However, best practices, reality, business requirements, and managers don’t always coexist together. When they don’t, I want a robust, automatic, solution.

  3. So I have to ask– what’s going on in the workload to cause the log to grow? That seems like it’s the problem that needs to be resolved.

    My experience with log growth on the weekend is that it’s frequently due to an overlap of maintenance jobs that prevent clearing of the log and require using lots of log— classic combo of a long-running backup and an index maintenance job, for example.

    Is it something of that nature, or is it longrunning user or application transactions that need to be modified?

Add Your Comments

Disclaimer
Your email is never published nor shared.
Required
Required
Tips

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <ol> <ul> <li> <strong> <p>

Ready?

This site is protected with Urban Giraffe's plugin 'HTML Purified' and Edward Z. Yang's Powered by HTML Purifier. 401 items have been purified.