We recently moved data centers. Our earlier data center was not in a clustered environment, and although we did not have trusted domain, we utilized pass-through security in order to accomplish the log shipping. However, with our new data center, our SQL Servers are clustered on 2 virtualized nodes. Since clusters are not aware of local accounts, and we could not find any way we could accomplish this w/o trusted domains, we decided to build our own home grown log shipping process. I will describe this and provide some sample scripts. Please bear with me, as this is my first time blogging in a long time.
To start with, we wrote a stored proc which checks whether there is a net use connection established between 2 servers (shares), and run this stored proc every minute. Basic outline is as follows:
1 2 3 4 5 6 7 8 9 10 11 | DECLARE @cmd1 nvarchar(500) DECLARE @tbl TABLE (ret varchar(500)) SET @cmd1 = 'net use' INSERT @tbl EXEC xp_cmdshell @cmd1 DELETE @tbl WHERE NOT ret LIKE '%\\[host-server-ip]\%' OR ret IS NULL IF NOT EXISTS(select * from @tbl where ret like '%OK%') BEGIN SET @cmd1 = 'net use \\[host-server-ip] /u:username password /Persistent:yes' EXEC xp_cmdshell @cmd1 |
1 2 3 | DECLARE @cmd nvarchar(255) SET @cmd = 'c:\RichCopy64.exe \\[source-server-created-above]\tLogBackups\logFolder "F:\logFolder\" /P /V /FIF "*.trn"' EXEC xp_cmdshell @cmd |
Another step was to create a table on the destination server:
CREATE TABLE [dbo].[dbName_logRestores]( [logRestore_id] [INT] IDENTITY(1,1) NOT NULL, [database_name] [VARCHAR](50) NOT NULL, [last_copied] [VARCHAR](50) NOT NULL CONSTRAINT [DF_dbName_logsRestored_last_copied] DEFAULT (''), [date_created] [datetime] NOT NULL CONSTRAINT [DF_dbName_logsRestored_date_copied] DEFAULT (getdate()), [last_restored] [VARCHAR](50) NOT NULL CONSTRAINT [DF_dbName_logsRestored_last_loaded] DEFAULT (''), [date_restored] [datetime] NOT NULL CONSTRAINT [DF_dbName_logsRestored_date_loaded] DEFAULT ('1900-01-01') ) ON [PRIMARY] |
Last step was to build a script which would go through the files in a given directory, put them in a table (sorted), and do restores one by one. Once your restores are caught up, you need to run this job as frequently as you do log backups.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | --Gets the file from a given folder, and puts it in dbName_logRestores table SET nocount ON --variables to hold the 'date', 'filename', 'directory cmd', 'path', 'delete command' and the date of the most 'recent backup - max date' DECLARE @date DATETIME , @fileName NVARCHAR(75) , @dir NVARCHAR(255) , @loadPath NVARCHAR(500) , @cmd1 NVARCHAR(330) , @maxDate DATETIME , @databaseName SYSNAME , @id INT , @dateRestored DATETIME , @restorePath NVARCHAR(500) , @moveLog VARCHAR(500) , @undoFile VARCHAR(500) , @lastRestored VARCHAR(50) , @errMsg VARCHAR(500) , @errSeverity INT , @errState INT -- set directory path ex. 'c:\mssql\backup\' SET @loadPath = '"F:\dbName"' SET @restorePath = 'F:\dbName' SET @moveLog = '''dbName_log'' to ''J:\MSSQL\Data\dbName_Log.ldf''' SET @undoFile = 'F:\undo_dbName.dat' --create the dir command to retrieve directory contents SET @dir = 'dir ' + @loadPath --Initialize restore data SET @dateRestored = '1900-01-01' --create table to hold directory contents IF OBJECT_ID('tempdb.dbo.#tmpDir') IS NULL CREATE TABLE #tmpDir ( id INT IDENTITY , ret NVARCHAR(1000) ) ELSE TRUNCATE TABLE #tmpDir --insert directory contents into temp table using the xp_cmdshell procedure to execute the DOS 'dir' command INSERT #tmpDir EXEC master.dbo.xp_cmdshell @dir --remove unwanted directory content...need just backup files delete all the rest DELETE #tmpDir WHERE NOT ret LIKE '%.trn%' OR ret IS NULL --loop through backup filenames and delete those older than IF EXISTS ( SELECT * FROM #tmpDir AS tmp LEFT OUTER JOIN DBADatabase.dbo.dbName_logRestores AS lr ON LTRIM(RTRIM(RIGHT(tmp.ret, CHARINDEX(' ', REVERSE(tmp.ret))))) = last_copied WHERE tmp.ret LIKE '%.trn%' AND last_copied IS NULL ) BEGIN INSERT DBADatabase.dbo.dbName_logRestores ( database_name , last_copied , date_created ) SELECT 'dbName' AS database_name , LTRIM(RTRIM(RIGHT(tmp.ret, CHARINDEX(' ', REVERSE(tmp.ret))))) AS last_copied , CASE WHEN tmp.ret LIKE '% PM %' THEN LEFT(tmp.ret, CHARINDEX(' PM ', tmp.ret) + 3) WHEN tmp.ret LIKE '% AM %' THEN LEFT(tmp.ret, CHARINDEX(' AM ', tmp.ret) + 3) END AS date_created FROM #tmpDir AS tmp LEFT OUTER JOIN DBADatabase.dbo.dbName_logRestores AS lr ON LTRIM(RTRIM(RIGHT(tmp.ret, CHARINDEX(' ', REVERSE(tmp.ret))))) = last_copied WHERE tmp.ret LIKE '%.trn%' AND last_copied IS NULL END --delete the msdb history leaving the most recent backup file in the history tables --exec sp_delete_backuphistory convert(varchar(10), @maxDate, 101) WHILE EXISTS ( SELECT * FROM DBADatabase.dbo.dbName_logRestores WHERE date_restored = '1900-01-01' ) BEGIN SELECT TOP 1 @id = logRestore_id , @lastRestored = last_copied , @cmd1 = 'restore log ' + database_name + ' from disk = ''' + @restorePath + '\' + last_copied + ''' with standby = ''' + @undoFile + ''', move ' + @moveLog FROM DBADatabase.dbo.dbName_logRestores WHERE date_restored = '1900-01-01' ORDER BY date_created BEGIN TRY --print @cmd1 EXEC sp_executesql @cmd1 END TRY BEGIN CATCH SELECT @errMsg = ERROR_MESSAGE() , @errSeverity = ERROR_SEVERITY() , @errState = ERROR_STATE() RAISERROR(@errMsg, @errSeverity, @errState) GOTO cleanup END CATCH UPDATE DBADatabase.dbo.dbName_logRestores SET last_restored = @lastRestored , date_restored = GETDATE() WHERE logRestore_id = @id END cleanup: SET nocount OFF |
I hope that helps to understand log shipping process where you cannot establish a trusted domain or pass-through security. Please post comments if you have any questions, or can suggest a better way to do it. For one of my other databases, I had to use Idera SQLSafe to do log backup because I could not transfer huge files and restore them over the period of 15 minutes. I will post that in future blog.