Why?
Last year we launched a new version of SportsCommander.com, which offered volleyball organizations across the country the ability to promote their tournaments and accept registrations for a negligible fee. Having grown out of our previous hosting company, we tried hosting the platform on Windows Azure, and for the most part it’s been great. Also, the price was right.
We are also hosting our data in SQL Azure, which again for the most part has been great. It has performed well enough for our needs, and it abstracts away a lot of the IT/DBA maintenance issues that we would really rather not worry about.
Of course, nothing is perfect. We’ve had a few snags with Azure, all of which we were able to work around, but it was a headache.
One of the biggest issues for us was the ability to run regular backups of our data, for both disaster recovery and testing purposes. SQL Azure does a great job of abstracting away the maintenance details, but one of the things you lose is direct access to the SQL backup and restore functionality. This was almost a deal-breaker for us.
Microsoft’s response to this issue is that they handle all of the backups and restores for you, so that if something went wrong with the data center, they would handle getting everything up and running again. Obviously this only solves part of the problem, because many companies want to have their own archive copies of their databases, and personally I think doing a backup before a code deployment should be an absolute requirement. Their answer has been “if you need your own backups, you need to build your own solution.”
Microsoft is aware of this need, and it has been the top-voted issue on their Azure UserVoice site for a while.
In poking around the interwebs, I saw some general discussion of how to work around this, but very little concrete detail. After hacking around for a while, I came up with a solution that has worked serviceably well for us, so I figured I’d share it with y’all.
What?
In order to address these concerns, Microsoft introduced the ability to copy a database in SQL Azure. So, as a limited backup option, you can create a quick copy of your database before a deployment, and quickly restore it back if something fails. However, this does not allow for archiving or exporting the data from SQL Azure, so all of the data is still trapped in the Azure universe.
Apparently another option is SSIS. Since you can connect to Azure through a standard SQL connection, theoretically you could export the data this way. Now I am no SSIS ninja, so I was just never able to get this working with Azure, and I was spending far too much time on something that I shouldn’t need to be spending much time on.
I’ve heard rumblings Microsoft’s Sync Framework could address the issue, but, uh, see the previous point. Who’s got time for that?
So of course, Red Gate to the rescue. Generally speaking their SQL Compare and SQL Data Compare solve this type of problem beautifully, they are excellent at copying SQL content from one server to another to keep them in sync. The latest formal release of their products (v8.5 as of this writing) does not support SQL Azure. However, they do have beta versions of their new v9.0 products, which do support SQL Azure. Right now you can get time-locked beta versions for free, so get yourself over to http://www.red-gate.com/Azure and see if they are still available. If you’re reading this after the beta program has expired, just pony up the cash and by them, they are beyond worth it.
How?
OK, so how do we set this all up? Basically, we create a scheduled task that creates a copy of the database on SQL Azure, downloads the copy to a local SQL Server database, and then creates a zipped backup of that database.
First, you need a SQL Server database server. And go install the Azure-enabled versions of SQL Compare and SQL Data Compare.
Also, go get a copy of 7-Zip, if you have any interest in zipping the backups.
The scheduled task will execute a batch file. Here’s that batch file:
SET SqlAzureServerName=[censored]
SET SqlAzureUserName=[censored]
SET SqlAzurePassword=[censored]
SET LocalSqlServerName=[censored]
SET LocalSqlUserName=[censored]
SET LocalSqlPassword=[censored]
echo Creating backup on Azure server
sqlcmd -U %SqlAzureUserName%@%SqlAzureServerName% -P %SqlAzurePassword% -S %SqlAzureServerName% -d master -i C:\SQLBackups\DropAndRecreateAzureDatabase.sql
echo Backup on Azure server complete
echo Create local database SportsCommander_NightlyBackup
sqlcmd -U %LocalSqlUserName% -P %LocalSqlPassword% -S %LocalSqlServerName% -d master -i C:\SQLBackups\DropAndRecreateLocalDatabase.sql
echo Synchronizing schema
"C:\Program Files (x86)\Red Gate\SQL Compare 9\SQLCompare.exe" /s1:%SqlAzureServerName% /db1:SportsCommanderBackup /u1:%SqlAzureUserName% /p1:%SqlAzurePassword% /s2:%LocalSqlServerName% /db2:SportsCommander_NightlyBackup /u2:%LocalSqlUserName% /p2:%LocalSqlPassword% /sync
echo Synchronizing data
"C:\Program Files (x86)\Red Gate\SQL Data Compare 9\SQLDataCompare.exe" /s1:%SqlAzureServerName% /db1:SportsCommanderBackup /u1:%SqlAzureUserName% /p1:%SqlAzurePassword% /s2:%LocalSqlServerName% /db2:SportsCommander_NightlyBackup /u2:%LocalSqlUserName% /p2:%LocalSqlPassword% /sync
echo Backup Local Database
for /f "tokens=1-4 delims=/- " %%a in (‘date /t’) do set XDate=%%d_%%b_%%c
for /f "tokens=1-2 delims=: " %%a in (‘time /t’) do set XTime=%%a_%%b
SET BackupName=SportsCommander_Backup_%XDate%_%XTime%
sqlcmd -U %LocalSqlUserName% -P %LocalSqlPassword% -S %LocalSqlServerName% -d master -Q "BACKUP DATABASE SportsCommander_NightlyBackup TO DISK = ‘C:\SQLBackups\%BackupName%.bak’"
"C:\Program Files\7-Zip\7z.exe" a "C:\SQLBackups\%BackupName%.zip" "C:\SQLBackups\%BackupName%.bak"
del /F /Q "C:\SQLBackups\%BackupName%.bak"
echo Anonymize Database For Test Usage
sqlcmd -U %LocalSqlUserName% -P %LocalSqlPassword% -S %LocalSqlServerName% -d SportsCommander_NightlyBackup -i "C:\SQLBackups\AnonymizeDatabase.sql"
The first thing this does is run a SQL script against the SQL Azure server (DropAndRecreateAzureDatabase.sql). This script will create a backup copy of the database on Azure, using their new copy-database functionality. Here’s that script:
DROP DATABASE SportsCommanderBackup
GO
CREATE DATABASE SportsCommanderBackup AS COPY OF SportsCommander
GO
DECLARE @intSanityCheck INT
SET @intSanityCheck = 0
WHILE(@intSanityCheck < 100 AND (SELECT state_desc FROM sys.databases WHERE name=’SportsCommanderBackup’) = ‘COPYING’)
BEGIN
— wait for 10 seconds
WAITFOR DELAY ’00:00:10′
SET @intSanityCheck = @intSanityCheck+1
END
GO
DECLARE @vchState VARCHAR(200)
SET @vchState = (SELECT state_desc FROM sys.databases WHERE name=’SportsCommanderBackup’)
IF(@vchState != ‘ONLINE’)
BEGIN
DECLARE @vchError VARCHAR(200)
SET @vchError = ‘Failed to copy database, state = ”’ + @vchState + ””
RAISERROR (@vchError, 16, 1)
END
GO
A few notes here:
-
We are always overwriting the last copy of the backup. This is not an archive; that will be on the local server. Instead, this always the latest copy. Besides, extra Azure databases are
expensive.
-
For some reason SQL Azure won’t let you run a DROP DATABASE command in a batch with other commands, even though SQL 2008 allows it. As a result, we can’t wrap the DROP DATABASE in an “IF(EXISTS(“ clause. So, we need to always just drop the database, which means you’ll have to create an initial copy the database drop for the first time you run the script.
-
The CREATE DATABASE … AS COPY OF will return almost immediately, and the database will be created, but it is not done the copying. That is actually still running in the background, and it could take a minute or two to complete depending on the size of the database. Because of that, we sit in a loop and wait for the copy to finish before continuing. We put a sanity check in there to throw an exception just in case it runs forever.
Once that is complete, we create a local database and copy the Azure database down into that. There are several ways to do this, but we chose to keep a single most-recent version on the server, and then zipped backups as an archive. This gives a good balance of being able to look at and test against the most recent data, and having access to archived history if we really need it, while using up as little disk space as possible.
In order to create the local database, we run a very similar script (DropAndRecreateLocalDatabase.sql):
IF(EXISTS(SELECT * FROM sys.databases WHERE Name=’SportsCommander_NightlyBackup’))
BEGIN
DROP DATABASE SportsCommander_NightlyBackup
END
CREATE DATABASE SportsCommander_NightlyBackup
In this case, we actually can wrap the DROP DATABASE command in a “IF(EXISTS”, which makes me feel all warm and fuzzy.
After that, it’s a matter of calling the SQL Compare command line to copy the schema down to the new database, and then calling SQL Data Compare to copy the data down into the schema. At this point we have a complete copy of the database exported from SQL Azure.
As some general maintenance, we then call sqlcmd to backup the database out to time-stamped file on the drive, and then calling 7-Zip to compress it. You might want to consider dumping this out to a DropBox folder, and boom-goes-the-dynamite, you’ve got some seriously backed-up databii.
Lastly, we run an AnonymizeDatabase.sql script to clear out and reset all of the email addresses, so that we can use the database in a test environment without fear of accidentally sending bogus test emails out to our users, which I’ve done before and it never reflected well on us.
Run that batch file anytime you want to get a backup, or create a scheduled task in Windows to run it every night.
Anyhoo, that’s about it. It’s quick, it’s dirty, but it worked for us in a pinch. Microsoft is just getting rolling on Azure and adding more stuff every month, so I’m sure they will provide a more elegant solution sooner or later, but this will get us by for now.
Have you had a similar experience? How are you handling SQL Azure backups?