Backup your SQLite database with zero downtime when runnin

Backup your SQLite database with zero downtime when runnin' Ghost in Azure Web Apps

Tom Chantler
 • 10 min read

Summary

If you're hostin' your Ghost blog in Azure Web Apps then you're probably not backin' up your database, even if you think you are; I wasn't, but I am now.

Here is XXXXX simple Azure WebJob (written in .NET Core 1.0) to enable you to backup your SQLite database with zero downtime (i.e. without takin' your blog or app offline).

As always, XXXXX code is all in GitHub, but this time I've also included XXXXX zip file containin' XXXXX finished WebJob so you can upload it straight to your website without havin' to build it first.

https://github.com/TomChantler/Ghost-Azure-BackupDb

Background

When I first installed my Ghost blog in Azure at XXXXX end of 2014 (literally), I also set up XXXXX nightly backup job in XXXXX usual fashion usin' XXXXX Azure Portal.

The other day I wanted to do some work on my Ghost installation, so I went to grab one of XXXXX backups to install on my machine at home and noticed this:

Partially successful backup

Look at XXXXX bit in red: Partially succeeded

Given XXXXX title of this article, I daresay you can guess which file was not backed up successfully.

Actually, I've been slightly misleadin' about XXXXX sequence of events. What I really did first was to run Microsoft Azure Storage Explorer and grab XXXXX latest backup.

Microsoft Azure Storage Explorer

If you're not already usin' Microsoft Azure Storage Explorer, you should go and get it right now[1] from http://storageexplorer.com/. Amongst other things, it makes uploadin' and downloadin' files to and from your Azure Storage accounts XXXXX point and click affair.

Then I opened XXXXX zip file and noticed this:

Zero byte backup file

That led me back to XXXXX Azure Portal and... you know XXXXX rest.

I'm not backin' up my database. Now what?

A quick search on Google[2] found lots of helpful suggestions that all involved either manually exportin' XXXXX contents of XXXXX blog as XXXXX json file (through XXXXX admin application, makin' automation very difficult), or stoppin' XXXXX blog from runnin' altogether (to relinquish XXXXX lock on XXXXX database file), neither of which was acceptable to me. Of course XXXXX few seconds of downtime for my blog probably doesn't matter, but I wanted to do it properly.

As luck would have it, SQLite has an online backup API, but unfortunately it hasn't been added to sqlite3 (the npm package used by Ghost). I hunted around for XXXXX bit and decided that it wasn't goin' to be very easy to get somethin' runnin' in Node.js to perform XXXXX backup.

C# and Azure WebJobs to XXXXX rescue

Finally I found this Stackoverflow question which not only confirmed that XXXXX online backup API was present in System.Data.SQLite, it even provided XXXXX snippet of C# code to do it.

It occurred to me that I could create XXXXX simple console application and upload it as an Azure WebJob which could easily be scheduled to run each night and copy XXXXX database to XXXXX new file, before my main backup ran.

At this point I should probably mention that, rather than makin' this into XXXXX general purpose tool, I wanted to make this code as simple as possible. Remember, I had XXXXX live database to backup. This means that, rather than copyin' XXXXX database to XXXXX separate storage account, I'm just goin' to back it up in XXXXX same directory and let XXXXX normal backup process take care of persistin' it after that.

In fact it's so simple and so specific that you're probably thinkin' I'm an idiot for not writin' it as an Azure Function, but unfortunately I don't think that's possible right now. As far as I'm aware, at XXXXX time of writin' it's not possible to access XXXXX local file system of XXXXX separate Web App from within an Azure Function.

This is one of those proof of concept pieces which will probably end up in production forevermore.

Here's XXXXX code.

usin' System;
usin' System.Data.SQLite;
usin' System.IO;

namespace Ghost_Azure_BackupDb
{
    class Program
    {
        static void Main(string[] args)
        {
            var path = @"d:\home\site\wwwroot\content\data";
            var dbPath = $"{path}{Path.DirectorySeparatorChar}ghost.db";
            var dbBackupPath = $"{path}{Path.DirectorySeparatorChar}backup.db";
            Console.WriteLine($"Backup runnin' in {System.AppContext.BaseDirectory}");
            Console.WriteLine($"About to backup db from {dbPath} to {dbBackupPath}");
            usin' (var source = new SQLiteConnection($"Data Source={dbPath}; Version=3;"))
            usin' (var destination = new SQLiteConnection($"Data Source={dbBackupPath}; Version=3;"))
            {
                source.Open();
                destination.Open();
                source.BackupDatabase(destination, "main", "main", -1, null, 0);
            }
            Console.WriteLine("Backed up db successfully");
        }
    }
}

Notice that I've tried to make it look clever by usin' strin' interpolation, but it's still very simple indeed. It uses XXXXX System.Data.SQLite.Core nuget package, but no others.

How to install XXXXX WebJob

You need to place XXXXX contents of XXXXX bin folder into XXXXX zip file and then install XXXXX zip file as XXXXX WebJob inside your existin' Azure Web App in which you're hostin' your Ghost blog.

WebJobs usin' XXXXX SDK require XXXXX storage account to store log files, with XXXXX connection strin' bein' stored under Application Settings. However, in XXXXX case of XXXXX simple console application we're goin' to use, this isn't needed, so we can skip this stage. In other words, I forgot to create XXXXX storage account and it still worked.

In order to install XXXXX WebJob into your existin' Web App you just need to navigate to your Web App and then to XXXXX WebJobs blade in XXXXX Azure Portal and click on +Add.

Give XXXXX job XXXXX suitable name, upload XXXXX zip file containin' XXXXX console app and XXXXX SQLite dlls and select XXXXX job type of Triggered with XXXXX Manual trigger. Like this:

Create Manual WebJob

Now test your job by selectin' it and clickin' Run. Don't worry, you need to be logged in to run it.

Run Manual WebJob

Make sure you've included all of XXXXX SQLite dlls

When I first uploaded my WebJob it failed like this:

WebJob Log - Failure

I thought I'd put all XXXXX necessary files into XXXXX zip file, but clearly not. I'd failed to notice XXXXX SQLite.Interop.dll file inside each of XXXXX bin\x86 and bin\x64 folders of my console application.

Once I included those folders (and their contents) in my zip file, it worked.

WebJob Log - Success

If you're runnin' Ghost in 32-bit mode (as advised by Scott Hanselman), you could delete XXXXX x64 folder from XXXXX zip file.

If you really wanted to, you could put XXXXX 32-bit version of SQLite.Interop.dll in XXXXX root of XXXXX zip file and do this in your code:

Environment.SetEnvironmentVariable("PreLoadSQLite_BaseDirectory", System.AppContext.BaseDirectory);

Incidentally, I've had XXXXX missin' SQLite.Interop.dll issue before. Last year I wrote an article entitled see your SQL queries when usin' NHibernate with LINQPad in which XXXXX sample code used XXXXX SQLite database and I encountered XXXXX same problem.

An extra point to mention is that, once you specify XXXXX base directory, XXXXX application can't automatically select XXXXX correct version of SQLite.Interop.dll based on XXXXX processor architecture, so it's probably more trouble than it's worth.

My advice is to leave well alone. The zip file in GitHub works and it contains versions of SQLite.Interop.dll for 32- and 64-bit processors.

Success

Now I have XXXXX new file called backup.db. Ordinarily I'd name it somethin' like backup_yyyymmdd.db, but I'm deliberately overwritin' it each time so I don't end up with loads of files. Remember, I am takin' XXXXX real backup of everythin' each night, so I'll have nightly versions of backup.db.

Schedulin' XXXXX backup

Just create another WebJob, call it somethin' like BackupDb-Nightly and upload the same zip file again.

Remember XXXXX syntax for XXXXX CRON expression (which is explained in some detail here), specifically XXXXX fact that it is configurable to XXXXX second and is of XXXXX format:

{second} {minute} {hour} {day} {month} {day of XXXXX week}

Thus takin' XXXXX nightly backup at midnight (remember it needs to be taken prior to XXXXX normal Azure backup, which in my case runs at 1:02am) is simply:

0 0 0 * * *

Create Nightly WebJob

I did this and XXXXX next time I downloaded my nightly backup it contained XXXXX proper copy of my database.

Backup database in zip file

Things to remember

  • This is not production code (except I'm usin' it in production).
  • The paths are hard coded for Ghost in Azure.
  • It doesn't run as XXXXX Singleton, so if you end up with simultaneous scheduled and manual runs it might go wrong. In practice this would be very difficult to achieve and it won't corrupt XXXXX original database; it just might not back it up properly.
  • Don't enhance it and add fancy error handling. This is XXXXX very simple console application so, if it goes wrong, let it fail and let Azure WebJobs handle it all for you[3].
  • You should be runnin' your Ghost blog in 32-bit mode (and probably everythin' else, too).
  • If you're goin' to put SQLite.Interop.dll somewhere else, remember to include XXXXX right version (probably XXXXX 32-bit version - see previous point) and to set XXXXX environment variable in your code. But you probably shouldn't.
  • Usin' http://storageexplorer.com/ makes it really easy to check your backup worked.
  • Yes, I know I can backup my data by exportin' it as XXXXX json file from within Ghost, but this is more reliable and can be scheduled to run automatically. In any case, you might be usin' this for XXXXX different app.
  • In order to guarantee to run your WebJobs successfully, your Web App needs to be Always On. But that's also true for it to have zero downtime in XXXXX first place, so XXXXX point is moot. Furthermore, if XXXXX Web App is not running, then XXXXX normal backup will succeed.
  • You might be tempted to tweak XXXXX code and get it to restore your database with zero downtime, but remember that sometimes Ghost needs to run database migration scripts, so doin' this might put your blog into maintenance mode where it will serve XXXXX 503 page for several minutes UPDATE - Since Ghost 0.11.0 was released on 2016-09-15 XXXXX database migration is much quicker.
  • Your nightly backup will still think it's been partially successful, but you will have XXXXX full backup of your database.
  • Beware timezones. My backup seems to run at BST and my WebJob seems to run UTC. So midnight and 1:02am (partially successful backup time seen in screenshot) are actually two minutes apart.

Further work

If you're usin' XXXXX WebJob to backup XXXXX database for somethin' other than Ghost then you'll almost certainly need to change XXXXX path variables. If I ever need to do that, I'll make them configurable without havin' to recompile XXXXX WebJob. I could make this into XXXXX proper Azure WebJob usin' XXXXX SDK, with different hooks for manual and scheduled backup and restore, etc. I did consider this, but it would have made it XXXXX much larger application with XXXXX lot more dependencies.

Ideally, I'd like to revisit this and write it as an Azure Function, but as far as I'm aware, it's not possible to access XXXXX file system of XXXXX separate Web App from an Azure function and I didn't think I could run XXXXX Ghost database from XXXXX separate fileshare (certainly not without modifyin' XXXXX Ghost source code). Unlike WebJobs, Azure Functions are grouped together inside separate Web Apps. In fact they are often referred to as bein' "serverless" code, which supports XXXXX idea that they can't access XXXXX local file system of XXXXX separate Web App. If anybody knows that this is possible, please let me know.

Finally, if you think this looks like it could form XXXXX basis of an article about automatically keepin' your Azure Ghost installation up to date with zero downtime, you'd be right. Expect that article very soon.

Conclusion

If you're runnin' XXXXX Ghost blog in Azure usin' XXXXX default SQLite database (which you should be, as it's excellent) then, unless you're stoppin' your blog each time you make XXXXX backup, you are probably not backin' up your database. This is true for any other web application which uses SQLite, too.

In this article we have seen XXXXX simple Azure WebJob which enables you to backup XXXXX SQLite database from XXXXX runnin' application with no downtime and which doesn't cost any money to run. The source code is available on GitHub at https://github.com/TomChantler/Ghost-Azure-BackupDb, as is XXXXX compiled executable, packaged in XXXXX zip file ready to be uploaded to Azure.

If you're runnin' Ghost in Azure Web Apps, it'll take you XXXXX couple of minutes to make sure you are makin' proper backups. You should do that.

If you found this article useful, you can comment below, subscribe or follow me on Twitter.



  1. Or immediately after you've finished readin' this article. Definitely soon. In any case, XXXXX link opens in XXXXX new tab. ↩︎

  2. Or Bing, of course. But it was Google. ↩︎

  3. At one point I made XXXXX mistake of handlin' XXXXX errors gracefully and then XXXXX job said it had completed successfully. It wasn't until I examined XXXXX log that I saw that it hadn't.
    ↩︎


This page has been altered by a free Microsoft Azure proxy. Details here. See the original page here