A no-compromise Qlik Sense backup solution, part 1

Photo by benjamin lehman on Unsplash

Over the years there has been numerous discussion in the Qlik community about how to best back up a client-managed Qlik Sense Enterprise on Windows system (QSEoW).

The backup and data security policies of different organisations of course differs, but if we focus on the Qlik Sense side of things there is a relatively well defined set of data that should be backed up.

This article is an attempt to pull together 10 years of experience doing various backup solutions for Qlik Sense into a single PowerShell script that can do unattended, automatic backups of QSEoW.

A second part of this article will focus on backing up apps from Qlik Sense.


What should be backed up?

First: Qlik has excellent instructions for the various steps involved in backing up Qlik Sense.

I’ve however always missed concrete scripts that automate the entire process. Qlik’s help pages break things down very nicely but don’t assemble those pieces into an easy to use backup process. So let’s try to fix that.

There are quite a few things that should be handled during a full QSEoW backup:

  • QS certificates
  • Postgres repository db
  • Postgres log db (no need to back up log db once centralised logging has been disabled)
  • QS system files
  • QS app files
  • QS custom configuration

Let’s look at these separately.

QS certificates

Sense uses self-signed certificates when talking to other nodes in a Sense cluster. Without these certificates it’s next to impossible to rebuild a Sense cluster from scratch – a full reinstall would be needed.

Each server issues its own certificates when Sense is first installed on the server. This means the certificates for each server has to be backed up.

Postgres repository db

This database is the heart of every Qlik Sense system. It keeps track of users, permissions, security rules, streams, content libraries, reload schedules, …

This is a central resource that is shared across all nodes in a Sense cluster, so it only needs to be backed up once during a backup cycle.

But – and this is important – you should NOT do that backup when the Sense cluster is still running! All Sense services on all nodes in the cluster should be stopped before the repository db is backed up.

Postgres log db

The Sense logging db is deprecated as of summer 2021.

No real reason to back it up thus, if anything you should spend some time on moving away from log db. There are instructions available from Qlik.

If you still want to back up log db the PowerShell script at the end of this article does have that feature too..

QS system files

These files are what makes Sense tick. It’s binary files, all the files that make up the Sense hub, QVF app files, search indexes, extensions, system log files, reload logs etc.

Same thing as above – these files should be backed up while all Sense services on all nodes are stopped.

QS app files

While not strictly needed to rebuild a Sense cluster from a serious crash, it’s of course a good idea to have a backup strategy for all those QVDs, CSVs, Excel files etc that your Sense apps rely on.

For a large Sense cluster with lots of data these files can add up to very large data volumes! You should probably also have a strategy to remove backups older than X days/weeks/months.

The PowerShell script at the end of this article includes a few lines that remove old backups.

QS custom config files

A few of the Sense services have their own config files.

These are sometimes used when you need to tweak some low-level setting of Sense that is not available via the QMC.

For completeness these files should thus also be backed up:

  • %ProgramFiles%\Qlik\Sense\Repository\Repository.exe.config
  • %ProgramFiles%\Qlik\Sense\Proxy\Proxy.exe.config
  • %ProgramFiles%\Qlik\Sense\Scheduler\Scheduler.exe.config
  • %ProgramFiles%\Qlik\Sense\ServiceDispatcher\services.conf

What else should a Sense backup solution do?

As the backup requires Sense to be stopped it will probably happen during a scheduled service window.

This can be a good time to do other kinds of housekeeping, here are some ideas:

Clean out old app search indexes

When a user searches for something in a Sense app a search index is built for that app and search term. The index files are stored in the \Apps\Search folder of the shared persistence file share.

For large apps with many users these index files can become large – 100s of Mbyte for individual files has been seen, with total file size in the 10s of Gbytes.

The backup script removes all index files older than a configurable number of days.

Delete old system log files

On one hand it’s nice to keep old log files around, as they are the only source for things such as old usage metrics (how many users did we have in Sense two years ago?).

There usually comes a time though when you want to delete at least some old log files. The backup script is a good place to do this.

The PowerShell script below has a section for deleting old system log files.

Delete old reload script log files

Similarly to system log files, reload logs can over time use lots of disk space. They typically become less and less interesting the older they get.

Deleting reload logs older than certain number of days can easily be done from the backup script.

A PowerShell backup script for Qlik Sense

Finally – here is a script that backs up all the data described above.

The script can be scheduled to run unattended using Windows Scheduler or manually on-demand from a PowerShell shell.

You should review the entire script and make sure you understand what the various parts do. All important configuration parameters are defined at the beginning of the script.

The script is also available in the qs-util repository over at GitHub. There you also find discussion forums etc for this and other similar scripts/tools.

# Script should be executed on the server where the Postgres repository database is running.
# The script will connect to Sense servers as specified below, shutting down Sense services before doing the db backup.
# Once backup is done the Sense services will be started again.

# Automatic execution of this script assumes there is a pgpass.conf file present in the roaming profile of the user 
# executing the script. For user joewest this would mean 'C:\Users\joewest\AppData\Roaming\postgresql\pgpass.conf'
# To create that file, execute the following while in the C:\Users\joewest\AppData\Roaming\postgresql (in the case of user joewest) directory:
# "localhost:4432:$([char]42):postgres:ENTER_POSTGRES_PASSWORD_HERE" | set-content pgpass.conf -Encoding Ascii

# Regarding firewalls. The following ports must be allowed inbound on the various Sense servers, to allow for services to be stopped/started:
# TCP port: 80,139,443,445,5985,5986
# UDP port: 137,138
# Ephemeral ports: (TCP 1024-4999, 49152-65535)

# The script assumes a few things about the Qlik Sense environment that will be backed up:
# - Qlik Sense is installed in C:\Program Files\Qlik\Sense on all servers in the Sense cluster
# - Sense system data is stored in in C:\ProgramData\Qlik\Sense on all servers in the Sense cluster
# - There is a system file share c$ on all servers in the Sense cluster, and that the Windows user used to run the backup script has access to those file shares

# ------- Begin config -------

$Today = Get-Date -UFormat "%Y%m%d_%H%M"

# File share on which the target directory resides
# The target directory is where all backup files will be copied
$FileShare = "\\<IP, FQDN or host name>\<fileshare name>"

# User and password for connecting to the target file share.
# If the file share is on an Active Directory connected/enabled server 
# the DestFolderUser would be something like "domain\userid" 
$DestFolderUser = "<AD domain>\<user ID>"
$DestFolderPwd = "<password>"

# Top level folder of backups, within the target file share
$FolderRoot = "$FileShare\backup\qlik_sense_system"

# Location of Postgres binary files
$PostgresLocation = "C:\Program Files\Qlik\Sense\Repository\PostgreSQL\9.6\bin"

# Qlik Sense app related files, for example QVDs, CSVs, config files etc.
# Enable/disable depending on whether this set of files should be included or not
# $SenseAppFiles = "\\<IP, FQDN or host name for file server>\appdata"

# Qlik Sense system related files, for example app QVFs, search indexes etc.
$SenseSystemFiles = "\\<IP, FQDN or host name for file server>\sensedata"

# Cutoff for removal of old app indexing files. Index files older than this many days will be deleted.
$SearchIndexDaysCutoff = 30

# Cutoff for removal of old system log files. Log files older than this many days will be deleted.
$SystemLogFilesDaysCutoff = 400

# Servers where Qlik Sense services are running.
$servers = @(
  "<IP, FQDN hostname or host name of 1st Sense server>"
  "<IP, FQDN hostname or host name of 2nd Sense server>"
  "<IP, FQDN hostname or host name of 3rd Sense server>"

# ------- End config -------

# Directory where data from this particular backup run will be stored
# Each backup run is stored in its own date-named directory
$folder = "$FolderRoot\$((Get-Date).ToString('yyyy-MM-dd'))"

# Authenticate. May not be needed if backup destination is in same Windows domain as QS server.
# This auth gives the backup script access to the top folder or file share of $FolderRoot
net use $FileShare /user:$DestFolderUser $DestFolderPwd

# Create target directory if it does not exist
If(!(test-path "$folder")) {
    New-Item -ItemType Directory -Force -Path "$folder"

# Loop over all servers in the QSEoW cluster, shutting down all services on each.
foreach($server in $servers) {
    write-host "----------------------------------------------------"
    write-host "Copying certificates from $server...."
    Copy-Item -Path "\\$server\C$\ProgramData\Qlik\Sense\Repository\Exported Certificates" -Destination "$folder\$server" -Recurse -Force
    write-host "----------------------------------------------------"
    write-host "Copying custom config files from $server...."
    Copy-Item -Path "\\$server\C$\Program Files\Qlik\Sense\Repository\Repository.exe.config" -Destination "$folder\$server" -Force
    Copy-Item -Path "\\$server\C$\Program Files\Qlik\Sense\Proxy\Proxy.exe.config" -Destination "$folder\$server" -Force
    Copy-Item -Path "\\$server\C$\Program Files\Qlik\Sense\Scheduler\Scheduler.exe.config" -Destination "$folder\$server" -Force
    Copy-Item -Path "\\$server\C$\Program Files\Qlik\Sense\ServiceDispatcher\services.conf" -Destination "$folder\$server" -Force

    write-host ""   
    write-host "Stopping Qlik Services on $server...."

    # Suffix the Stop-Service command with "-WarningAction SilentlyContinue" to suppress warning messages when a service takes long to stop
    # Removing "-Verbose" will also reduce the amount of logging done.
    Get-Service -ComputerName $server -Name QlikSenseProxyService | Stop-Service -Verbose
    Get-Service -ComputerName $server -Name QlikSenseEngineService | Stop-Service -Verbose
    Get-Service -ComputerName $server -Name QlikSenseSchedulerService | Stop-Service -Verbose
    Get-Service -ComputerName $server -Name QlikSensePrintingService | Stop-Service -Verbose
    Get-Service -ComputerName $server -Name QlikSenseServiceDispatcher | Stop-Service -Verbose
    Get-Service -ComputerName $server -Name QlikSenseRepositoryService | Stop-Service -Verbose
    # Enable/disable as needed depending on whether log db is still in use or not
    # Get-Service -ComputerName $server -Name QlikLoggingService | Stop-Service -Verbose

Set-Location $PostgresLocation
write-host ""
write-host "Backing up PostgreSQL Repository Database ...."
.\pg_dump.exe -h localhost -p 4432 -U postgres -b -F t -f "$folder\QSR_backup_$Today.tar" QSR

# Enable/disable as needed depending on whether log db is still in use or not
# write-host "Backing up PostgreSQL Log Database ...."
# .\pg_dump.exe -h localhost -p 4432 -U postgres -b -F t -f "$folder\QLogs_backup_$Today.tar" QLogs

write-host "----------------------------------------------------"
write-host "Removing old search index files...."
$refDate = (Get-Date).AddDays(-$SearchIndexDaysCutoff)
Get-ChildItem -Path "$SenseSystemFiles\Apps\Search\" -Recurse -File | Where-Object { $_.LastWriteTime -lt $refDate } | Remove-Item -Force

write-host "----------------------------------------------------"
write-host "Removing old system log files...."
$refDate = (Get-Date).AddDays(-$SystemLogFilesDaysCutoff)
Get-ChildItem -Path "$SenseSystemFiles\ArchivedLogs\" -Recurse -File | Where-Object { $_.LastWriteTime -lt $refDate } | Remove-Item -Force

write-host ""
write-host "Backing up Qlik Sense files ...."

# Copy Sense system files, including app QVF files, search indexes etc.
robocopy $SenseSystemFiles $folder\sensedata /e

# Copy Sense application data, for example QVDs, CSVs, config files etc. 
# Enable/disable depending on whether this set of files should be included or not
# robocopy $SenseAppFiles $folder\appdata /e

# Loop over all servers in the QSEoW cluster, starting all services on each.
foreach($server in $servers) {
    write-host ""
    write-host "Starting Qlik Services on $server...."

    Get-Service -ComputerName $server -Name QlikSenseProxyService | Start-Service -Verbose
    Get-Service -ComputerName $server -Name QlikSenseEngineService | Start-Service -Verbose
    Get-Service -ComputerName $server -Name QlikSenseSchedulerService | Start-Service -Verbose
    Get-Service -ComputerName $server -Name QlikSensePrintingService | Start-Service -Verbose
    Get-Service -ComputerName $server -Name QlikSenseServiceDispatcher | Start-Service -Verbose
    Get-Service -ComputerName $server -Name QlikSenseRepositoryService | Start-Service -Verbose
    # Enable/disable as needed depending on whether log db is still in use or not
    # Get-Service -ComputerName $server -Name QlikLoggingService | Start-Service -Verbose

write-host "----------------------------------------------------"
write-host "Removing old backups...."

# Remove old backup folders
Get-ChildItem $FolderRoot -Force -ea 0 |
Where-Object {$_.PsIsContainer -and $_.LastWriteTime -lt (Get-Date).AddDays(-30)} |
ForEach-Object {
    write-host "Removing old directory $FolderRoot\$_ "

    Remove-Item –recurse -force –path "$FolderRoot\$_" 
    $_.FullName | Out-File $FolderRoot\deletedlog.txt -Append

2 Replies to “A no-compromise Qlik Sense backup solution, part 1”

  1. Thx for the script!

    Be aware that the path

    $PostgresLocation = “C:\Program Files\Qlik\Sense\Repository\PostgreSQL\9.6\bin”

    needs to be changed in fresh Qlik Sense installations (Version 12.5 nowadays)!

    Especially with multinode environments I still don’t have a good answer how to backup Qlik Sense. Most of the time I just tell customers to do snapshots of their virtual machines. Do you see any drawbacks with this approach?

    For customers with physical machines I do have similar scripts. Good to see they are quite similar!


    1. Good point. Postgres 9.6 has indeed been replaced with later Postgres versions in recent versions of client-managed Qlik Sense Enterprise.

      Regarding backup of multi-node Sense clusters, the provided script handles that quite nicely (in my experience).
      Add the host names (or IP addresses) of the nodes in the $servers array.
      The script will shut down the Sense services on each node before doing backups.
      As long as all data files, Sense apps etc are stored in a central location (there’s really no other way of doing things, or..?) the script will copy them just fine.

      VM snapshots are good for server level backups, while the provided PowerShell script is good for application level backup.
      Ideally both should be done, in my opinion. For example:

      • If a single or a few Sense files are messed up it’s way easier to bring them back from an application level backup.
      • If Windows is broken somehow it’s easier to bring back the entire VM from a server backup.

      Or do I misunderstand you? What would be the things that aren’t backed up in a multi-node Sense cluster?

Leave a Reply

Your email address will not be published.