SQL Backup auf einer Azure Virtual Machine

Als Database Administrator kennt man das Thema nur zu gut, wenn man sich nicht gleich zu Anfang voll umfänglich mit der Backup bzw Restore-Strategie beschäftigt, fällt es einem irgendwann vor die Füße… aber wie ist das Ganze jetzt in der Cloud, wie kann man da sein Datenbanken richtig sichern? Darum ging es in meinem Vortrag auf dem Global Azure Bootcamp 2019 in Hamburg mit dem Ziel, die neue Möglichkeit mittels Azure Backup auch direkt SQL Server Datenbanken auf einer virtuellen Maschine in Azure zu sichern.

Serverausfall – was nun?

Man stelle sich vor – in der onprem Welt – ein Server fällt aus, die Datenbanken nicht mehr erreichbar… PANIK (zumindest im ersten Moment)… die Fragen, die man sich dann im ersten Moment stellt:

  • Haben wir eine Sicherung?
  • Wann ist die letzte Sicherung erfolgreich gelaufen?
  • Wie groß ist mein Datenverlust?
  • Wie komme ich an die Daten?
  • Wie lange dauert es, bis das richtige Backup für den Restore gefunden wurde?
  • Wie lange dauert der Restore?
  • Klappt der Restore auf Anhieb?
  • Können dann alle wieder auf die Daten zugreifen?

Gehen wir erst einmal von dem Fall aus, dass der Server neu aufgesetzt wird und die Datenbanken dann aus einer Sicherung wieder hergestellt werden müssen. Nun sucht man (vielleicht) über die GUI der Backup-Software nach einer Filesicherung, dem Tape oder einer Datenbank-Sicherung. Spätestens hier wird es interessant 😉

Wie wurde der Datenbank-Server überhaupt gesichert, in welchem Rhythmus, wieviele Files müssen zurück gespielt werden und so weiter… Im schlimmsten Fall gibt es nur eine tägliche Vollsicherung (Full-Backup) auf die lokale Platte mit nur wöchentlicher Filesystemsicherung… 😮
Backup-Lösungen und -Strategien sind aber nicht das Thema dieses Beitrages, denn hier geht es um die Cloud.

Azure SQL Databases und das Backup

In der Cloud ist vieles einfacher (oder zumindest anders), denn wie auch on-prem sollte man sich zumindest Gedanken über die Backup-Strategie machen. Aber zumindest hat sich Microsoft sehr viele Gedanken zu diesem Thema für uns gemacht, so dass in vielen Datenbank-Services wie zum Beispiel der Azure SQL Database das Backup schon automatisch inkludiert ist.

Also alle Azure SQL Database Services (SingleDB, Elastic Pool und Managed Instance) haben bereits ein Backup inkludiert, so dass man sich hier nicht wirklich mehr Gedanken zu einer Backup-Strategie oder “Welche Software wollen wir einsetzen?” machen muss. Beispielsweise Azure SQL Single-Database hat in den unterschiedlichen Performance-Klassen (Basic, Standard, Premium) unterschiedliche Aufbewahrungszeiten von 7-35 Tagen… was aber wenn man das Backup, aus welchen Gründen auch immer, länger aufbewahren möchte/muss? Dann bleibt einem – für diesen Fall – nur das Hinzufügen eines sogenannten “Log-Time-Retention” Backups, welches das Backup(-File) zusätzlich auf einem weiteren Storage-Account ablegt und erst nach X Tagen/Wochen/Monaten/Jahren löscht, was einem selber überlassen wird über eine entsprechende Policy zu definieren.

Selbst für den Fall, dass mal ein komplettes Microsoft Rechenzentrum oder sogar eine ganze Region ausfallen sollte, hat Microsoft vorgesorgt und verschiebt alle Backups über Geo-Replication auch in andere Regionen, so dass im Fall der Fälle immer ein entsprechendes Backup für einen Point-in-Time-Restore innerhalb der Retention-Period vorhanden ist.

Automatisierte Backups – da war doch schon was

Ja, automatische Sicherungen (Automated Backup) konnte der SQL Server schon seit der Version 2014, wenn man ihn entsprechend konfiguriert hatte, dann wurde alle Datenbanken bereits “damals” auf einen Azure Storage Account gesichert. Auch mit der Version 2016 und 2017 blieb diese Funktion erhalten, wurde aber angepasst bzw optimiert (Automated Backup v2). Alternativ könnte man sich auch ein Backup-to-URL mittels Ola Hallengren Skripten vorstellen, aber das hängt dann wieder mit der gesamten Backup-Strategie zusammen und muss entsprechend geplant werden.

Gemeinsame Nutzung von Azure Backup ?

Viele Kunden haben in Azure so oder so eine Azure Backup Vault oder den Azure Backup Service laufen, so dass sich diese Kunden gefragt haben, warum sie damit nur Virtuelle Maschinen als “Ganzes” sichern können, aber nicht auch ihre SQL Server. Somit wäre eigentlich eine zentrale Lösung für viele Backups in Cloud gefunden. Man könne auch von dieser zentralen Stelle im Portal aus, alle Backups administrieren und überwachen, bräuchte kein weiteres Tool geschweige denn eine neue Backup-Infrastruktur.

Microsoft hat auf die Wünsche und Bitten seiner Kunden gehört und das Azure Backup entsprechend erweitert, so dass man seit Mitte März 2019 auch endlich reine SQL Server Datenbank-Backups damit ziehen kann.

https://azure.microsoft.com/de-de/blog/azure-backup-for-sql-server-in-azure-virtual-machines-now-generally-available/

Jetzt kann man also die Azure Backup Extension sowohl auf fertige SQL Server Marketplace VM-Images ausrollen, als auch auf selbst erstellte SQL Server, um diese dann als native SQL Server Backups gegen Azure Backup zu sichern. Derzeit gibt es folgende Eckpunkte, die für diesen Service sprechen:

  • Zentrale Verwaltung und Überwachung
  • Schutz für verschlüsselte Datenbanken
  • Automatische Erkennung von (neuen) Datenbanken
  • RPO-Wert (Recovery Point Objective) von 15 Minuten
  • Point-in-Time-Wiederherstellungen
  • Langfristige Aufbewahrung
  • Kosteneffektiv

Wenn man nun also das native SQL Server Backup über die neue Extension ausgerollt hat, dann kann man während der Konfiguration im Portal entweder den ganzen Server (Instanz) oder einzelne Datenbanken, ebenso “Autoprotection” auswählen. Und “zusehen” wie nach erfolgreichem Deployment die ersten Sicherungen durchgeführt werden. Das Ergebnis sowie Alarme können dann sehr schön und zentral über das Azure Backup Dashboard im Portal überwacht werden. Zusätzlich dazu bietet Microsoft eine PowerBi Application mit der man auf einen (dann einzurichtenden StorageAccount) zu aktivieren, um noch mehr Details zu seinen Sicherungen zu erhalten.

Ein Restore der Datenbanken ist mit wenigen Klicks über das Portal zu realisieren, hierbei stehen dann auch weitere Optionen zur Verfügung, wie man sie aus dem normalen SQL-Restore kennt (z.B. File-Relocation, Overwrite oder Database-Rename).

Fazit zu Azure Backup für SQL Server VMs

Ich persönlich finde diese neue Extension super, wenn man seine SQL Server in einer Azure VM nativ sichern möchte/muss und eine (!!!) zentrale Anlaufstelle für Backups in Azure mit Monitoring, Alerting und Config-Management haben möchte!!!

dbatools – Migration der SQL Agent Backup Jobs

Man kann denken was man will, aber manchmal muss man über seinen Schatten springen… in der Regel bin ich ein Verfechter des SQL Server eigenen Backups, egal ob Backup to Disc, Backup to URL oder Backup to NetworkShare, aber hier musste ich (leider) nachgeben und die Sicherung mittels 3rd-Party-Tools einrichten. Gesagt… getan…

Bei dem Kunden hatten wir sowieso schon alles auf 3rd-Party-Backup umgestellt, aber nicht auf Backup-Server initiertes Sicherungen, sondern SQL Server initiert. Der SQL Server Agent startet also das jeweilige Backup als Kommandozeilen-Aufruf. Hierzu musste ich also die Sicherungsjobs von einem existierenden Server kopieren und anpassen, sowie alle dazugehörigen SQL Server Objekte. Gestartet habe ich mittels “Create Objects to NewQuery”, erhielt aber die Fehlermeldung, dass die Mail-Komponente bzw Empfänger noch existiert bzw konfiguriert ist. Also musste ich erst die SQL-Mail-Konfiguration übernehmen, hierzu auch noch die Operator übernehmen und wenn wir schon beim Mailing sind, dann kann ich die von Brent Ozar empfohlenen SQL Agent Alerts auch gleich mit migrieren…

dbatools - die Powershell Modulsammlung für den DBA

verwendete Powershell Module – dbatools

Macht euch mal kurz Gedanken dazu, wie man die Konfiguration von SQL-Mail, Operatoren und Custom-Alerts ohne großen Aufwand von einem SQL Server auf den anderen migrieren kann, also ich meine mit Bordmitteln… das ist zwar nicht wirklich viel Aufwand, aber doch etwas mehr als 5 Minuten. Also was liegt näher als sich intensiver mit den dbatools zu beschäftigen…
Mittels “Copy-SqlDatabaseMail” kann man die SQLMail-Konfiguration übernehmen, mit “Copy-SqlOperator” die eingerichteten Operatoren migrieren, weiter geht es mit “Copy-SqlAlert” und zum Schluss noch die Jobs kopieren. Ist doch gar nicht so viel 😉

Copy-SqlDatabaseMail -Source SQLServer01 -Destination SQLServer02
Copy-SqlOperator -Source SQLServer01 -Destination SQLServer02
Copy-SqlAlert -Source SQLServer01 -Destination SQLServer02
Copy-SqlJob -Source SQLServer01 -Destination SQLServer02 -Jobs  Full-Backup, TLog-Backup

Also ganze 4 Zeilen Powershell Code um mir die Arbeit zu erleichtern, auch dank der Vereinheitlichung der Parameter innerhalb von dbatools ist auch die Parametrisierung der einzelnen Befehle eine sehr einfache Sache und schnell und unkompliziert zu erledigen. Man kann die Nutzung von dbatools nur jedem empfehlen!

Powershell meets SQL Server – Backup einer Remote Datenbank

Auf dem SQLSaturday #525 in St. Augustin hatte ich bereits das Vergnügen Andre Kamann zu lauschen, was er alles mit Powershell und dem SQL Server anstellt, meinen zweiten Kontakt zu Andre hatte ich jetzt beim SQLGrillen in Lingen, bei dem er uns die wesentliche Vereinfachung eines Rollouts des Ola Hallengren Skriptes näher brachte. Irgendwie hat mich diese “Vereinfachung” inspiriert… ich bin eigentlich auch eher ein “fauler Hund” und versuche mir mit Tools, Skripten  oder eigenen unterstützenden Webseiten das DBA Leben zu erleichtern. Heute kam dann ein Ticket zu mir, bei dem es um ein einfaches Backup einer Datenbank ging, der Kunde hat Probleme mit seiner Applikation und möchte nun eine Kopie der Datenbank an den Software-Hersteller schicken. Solche Aufgaben haben wir immer wieder, daher dachte ich heute morgen an Andre und seine Powershell-Skripte.
Viele werden jetzt sagen, so ein Backup ist doch recht einfach => entweder auf den Server per RDP oder von seiner Workstation mit dem SQL Server Management Studio verbinden, dann die Datenbank auswählen und mit der rechten Maus “Tasks => Backup => …” Klar das geht, ABER da wir hier meistens über Citrix Steppingsstones oder Admingates in die Kundennetze müssen, erleichtert es uns die Arbeit schon um einiges, wenn wir den einen oder anderen Hop reduzieren können.

Mein Powershell-Template

Da dies meine ersten Schritte mit Powershell-Skripten sind, musste ich mich erst damit auseinandersetzen, wie man am besten ein solches Powershell Skript strukturell aufbaut. Hierzu habe ich auf diesen Powershell Blogs gelesen und keinen “Standard” ausfindig machen können, aber überall konnte ich mir ein wenig abschauen und somit einen “ersten Wurf” eines Powershell Skript Templates erstellen. Natürlich muss sich dieser noch in der täglichen Arbeit bewähren und eventuell angepasst werden, aber erst einmal kann ich (denke ich zumindest) damit arbeiten.

Angefangen habe ich natürlich mit einem Header

# #############################################################################
# DBA - SCRIPT - POWERSHELL
# NAME: template.ps1
# 
# AUTHOR: Björn Peters, SQL-aus-Hamburg.de
# DATE: 2016/08/24
# EMAIL: info@sql-aus-hamburg.de
# 
# COMMENT: This script will....
#
# VERSION HISTORY
# 1.0 2016.08.24 Initial Version.
# 
#
# OPEN POINTS TO ADD
# -Add a Function to ...
# -Fix the...
# 
# #############################################################################

Aus dem Tagesgeschäft weiß ich bzw habe ich die Erkenntnis gezogen, dass es für den DBA/Ausführenden am einfachsten ist, wenn die ggfs manuell zu konfigurierenden Parameter (wenn sie nicht übergeben werden) ganz oben stehen sollten, damit man sie möglichst schnell findet und einfach editieren kann. Also kommt direkt in meinem Powershell Skript Template nach dem Header ein Abschnitt zur Konfiguration des Skriptes.

# --- CONFIG ---#
# Script Path/Directories
$ScriptPath = (Split-Path ((Get-Variable MyInvocation).Value).MyCommand.Path)
$ScriptPluginPath = $ScriptPath + "\plugin\"
$ScriptToolsPath = $ScriptPath + "\tools\"
$ScriptOutputPath = $ScriptPath + "\Output\"
# Date Format
$DateFormat = Get-Date -Format "ddMMyyyy_HHmmss"
# -- END CONFIG ---#

Jetzt kommen wir zu einem der wichtigsten, aber am meisten vernachlässigten Abschnitten eines solchen Powershell Skriptes… der Hilfe.
Bei meinen Recherchen bin ich immer wieder über den Widerspruch gestolpert, dass man auf der einen Seite möglichst sein Skript kurz und übersichtlich gestalten soll, auf der anderen Seite aber gut dokumentiert und erläutert. Natürlich kann man aus einem Skript mehrere Versionen machen, eine zum Ausführen und eine zum Weitergeben… aber das führt meist zu Fehlern da man garantiert nicht immer alle Änderungen in beiden Daten ausführt => also bitte gleich richtig machen und einen Hilfe-Abschnitt integrieren und ausführlich beschreiben.

# --- HELP ---#
<#
.SYNOPSIS
Cmdlet help is awesome.

.DESCRIPTION
This Script does a ton of beautiful things!

.PARAMETER

.INPUTS

.OUTPUTS

.EXAMPLE

.LINK
https://www.sql-aus-hamburg.de
#>
# --- END HELP ---#

Zu guter letzt bleiben nur noch die Abschnitte für die einzelnen Funktionen und das eigentliche Powershell Skript. Früher (zu meinen VB / VBA Zeiten) hatte ich gelernt, dass man die Funktionen immer nach unten schreibt und den eigentlichen Programmcode nach oben. Aber dies scheint (zumindest für Powershell) mittlerweile überholt zu sein, also kommen die Funktionsaufrufe, die eigentliche Logik des Skriptes nach unten.

# --- FUNCTIONS ---#
Param(

)

BEGIN {

}

PROCESS {

}

END {

}
# --- END FUNCTIONS ---#

# --- SCRIPT ---#

Quellen und großen Dank an :
http://www.lazywinadmin.com/2012/03/powershell-my-script-template.html
Bei Francois habe ich die meisten Dinge (nachdem ich sie auch anderweitig unter “Best Practices” für Powershell Skripte gelesen hatte) in einem Template gefunden und für mich am besten geeignet befunden. Also habe ich sein Template nahezu 1:1 übernommen.

Mein erstes Powershell Skript

Nun zum eigentlichen SQL Server Backup Skript, welches ich mit Powershell umsetzen wollte.
Also ich wollte mit “Bordmitteln” von einer zentralen Maschine ein Backup auf einem Remote SQL Server erstellen, welches ich dann “einfach” für den Kunden oder Drittdienstleister kopieren und bereitstellen konnte. Was liefert also die SQL Server Installation von Haus aus für Möglichkeiten? Natürlich die SQL Server Management Objects (SMO), welche im Rahmen der Management Tools Installation mitinstalliert werden .

Welche Parameter möchte ich denn angeben bzw übergeben, damit mein Backup erfolgreich läuft…

  • Servername
  • Instanzname
  • Datenbankname
  • SQL-Login => Username/Passwort

Nach einigem Nachdenken kamen noch zwei weitere Parameter dazu… => CopyOnly (True/False) und OpenInExplorer (True/False)

Param(
          [Parameter(Mandatory=$true)][string]$ServerName,
          [string]$InstanceName,
          [Parameter(Mandatory=$true)][string]$DatabaseName,
          [string]$SQLUserName,
          [string]$SQLPwd,
          [switch]$CopyOnly = $True,
          [switch]$OpenInExplorer = $False
)

Also BEGIN der Funktion

Erst einmal alle Parameter überpüfen, zusammensetzen und ggfs umsetzen, so dass sie im Skript bzw T-SQL Befehl verwendet werden können.
Vielleicht kann man das optimaler/sicherer gestalten (Anmerkungen/Tips hierzu bitte über die Kommentarfunktion), aber ich wollte ja einen schnellen Erfolg. Zu Beginn der Funktion prüfe ich also, ob es sich um eine Named-Instance oder Default-Instance handelt, wie der User sich anmelden möchte => Windows Authentifizierung oder SQL Login, on das Backup als Copy-Only-Backup erstellt werden soll und natürlich den Zielort des Backups (Default Backup Directory).

BEGIN {
	"Starting Backup on $($ServerName)"
        
        [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo") | out-null
        [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.ConnectionInfo") | out-null
		
        $CommonParameters = ''

        if ($InstanceName) {
	    $SQLServer = $ServerName + "\" + $InstanceName
        } else {
            $SQLServer = $ServerName
        }

        $SQLSvr = New-Object 'Microsoft.SqlServer.Management.SMO.Server' $SQLServer
        if ($SQLUserName -and $SQLPwd) {
            $SQLSvr.ConnectionContext.LoginSecure=$false;
            $SQLSvr.ConnectionContext.Login=$SQLUserName
            $SQLSvr.ConnectionContext.Password=$SQLPwd
        }

	$BackupDir = $SQLSvr.Settings.BackupDirectory "BackupName - $($BackupDir)\$($DatabaseName)_db_$($DateFormat).bak"

        if ($CopyOnly -eq $True) { $CommonParameters += " -CopyOnly" }

}

Nun kommt der PROCESS

Wie der Name schon sagt, hier geschieht der eigentliche Prozess, also die eigentlich Hauparbeit… Im Falle eines Backup-Skriptes werden hier die Backup Befehle ausgeführt. Da dies mein Einstieg in die Powershell Programmierung ist, es gibt bestimmt Wege dies anders bzw optimaler zu gestalten, aber für mich funktionierte das erst einmal ganz gut so. Wenn also der String $CommonParameters leer oder Null ist wird der einfache Backup Befehl ausgeführt ansonsten werden die WITH-Parameter an den Backup Befehl angehängt. Ansonsten passiert in diesem PROCESS Schritt nicht viel 😉

PROCESS {
        IF([string]::IsNullOrWhiteSpace($CommonParameters)) {
            Backup-SqlDatabase -InputObject $SqlServer -Database $DatabaseName -BackupFile "$($BackupDir)\$($DatabaseName)_db_$($DateFormat).bak"
        } else {
		    Backup-SqlDatabase -InputObject $SqlServer -Database $DatabaseName -BackupFile "$($BackupDir)\$($DatabaseName)_db_$($DateFormat).bak" $CommonParameters
        }
	}

Alles hat ein ENDe

Eigentlich wäre hier nun alles zu Ende, wir haben ein Backup auf einem Remote SQL Server in das Default Backup Verzeichnis geschrieben… hier könnten wir es abholen und dem Anforder zur Verfügung stellen. Aber ich bin ein wenig faul… also was liegt näher als den letzten Schritt noch etwas zu vereinfachen.
Also habe ich in den END Abschnitt einfach einen Explorer Aufruf mit dem Zielpfad (Default Backup Verzeichnis) hinzugefügt, so muss ich nicht erst lange suchen und hin und her klicken, sondern der Explorer öffnet sich direkt mit dem Zielpfad und ich brauche die Datei nur noch weg zu kopieren. 😉

END {
        "Finished Backup on $($ServerName)"
        if ($OpenInExplorer -eq $True) { 
            $NewBackupDir = "\\$($ServerName)\"
            $NewBackupDir += $BackupDir.Replace(':\', '$\')
            Invoke-Item $NewBackupDir 
        }
	}
}
# --- END FUNCTIONS ---#

Zum Abschluss im Skript muss natürlich noch der ganze Ablauf einmal gestartet werden, also rufe ich als “letzte” Zeile des Skriptes schnell die Funktion auf… FERTIG.

Vielen Dank nochmal an Andre für die Inspiration.

TSQL #66 – Monitoring Backup Lösung – Backup Historie

Aus mehrfachem Anlass möchte auch ich etwas zum Thema Backup-Historie schreiben, erst einmal weil Catherine Wilhelmsen den TSQL Tuesday #66 zum Thema Monitoring gehostet hat und weil Thomas Larock gerade heute eine ähnliche Lösung vorgestellt hat.

Wir DBAs kennen das leidige Thema Backup-Überwachung… lief es oder lief es nicht, kann ich meinen SLA dem Kunden gegenüber noch halten?
Wir haben bei einer Vielzahl von SQL Servern vertraglich zugesagt täglich das Monitoring zu prüfen, es muss mindestens ein Full-Backup innerhalb der letzten 24 Stunden erstellt worden sein. Ist dies nicht der Fall wird automatisiert – aus dem Reporting Tool heraus – ein Ticket “Bitte Backup überprüfen” erstellt.

Daher hat mein Statement auch diese Einschränkungen, welche sich natürlich individuell je nach Bedürfnissen anpassen lassen.
Idealerweise schreibt man das Ergebnis der Abfrage in eine Tabelle um historische Daten über das Backup zu sammeln, so kann man aus den jeweiligen Backup-Größen einen Trend ermitteln.

/*
Get all Backup History Data - used at Customer XYZ for Reporting 
*/

SELECT 
  s.name as [Database], 
  (select bmf.physical_device_name from msdb..backupmediafamily bmf where bmf.media_set_id = bs.media_set_id) as [Backup_Path_File],
  bs.backup_start_date as DATE_BEGIN, 
  bs.backup_finish_date as DATE_END, 
  CASE WHEN bs.backup_start_date > DATEADD(dd,-7,getdate())
    THEN 0
    ELSE 1
  END as [Status],
  CASE WHEN bs.backup_start_date > DATEADD(dd,-1,getdate())
    THEN 'Backup is current within a day'
     WHEN bs.backup_start_date > DATEADD(dd,-7,getdate())
    THEN 'Backup is current within a week'
    ELSE '*****CHECK BACKUP!!!*****'
  END as [Message],
  convert(varchar,cast(bs.backup_size/1024/1024 as money),10) as [Backup_Volume],
  GETDATE() as [Date_Checked]
from 	master..sysdatabases s LEFT OUTER JOIN	msdb..backupset bs ON s.name = bs.database_name
  AND bs.backup_start_date = (SELECT MAX(backup_start_date) FROM msdb..backupset WHERE database_name = bs.database_name AND type = 'D')
WHERE	s.name <> 'tempdb'
ORDER BY s.name
GO

Mit dem Ergebnis der Abfrage kann man nun viele Dinge machen, zum Beispiel anhand der Spalte “Status” die jeweilige Zeile farbig markieren, um die fehlerhaften Sicherungen besser kenntlich zu machen. Oder man passt die Betreffzeile einer Status-Mail entsprechend an.
Wir haben eine Kombination der SQL Server Alerts mit obigem Abfrage-Skript, welches uns dann darüber informiert, wo wir nachzubessern haben.

Backup History Monitoring

Zusätzlich empfehle ich die Einrichtung des SQL Alerts “SQL Server Alert System: ‘Severity 016’ occurred on SERVER_XYZ” mit Mail-Alarmierung (hierzu siehe auch diesen Blogbeitrag zu SQL Server Alerts).

Nachtrag:
Ich bin in der letzten Woche über ein Storage-Problem gestolpert, wo dieses Skript auch nicht wirklich sauber funktionierte, da das Backup zwar lief aber leider sehr sehr langsam, so dass es nicht innerhalb von 40 Minuten durch war (wie gewohnt) sondern leider rund 24 Stunden lief… dadurch war die Analyse mittels Skript nicht immer zu 100% aussagekräftig, aber hier wusste man ja um das Problem… 😉

Backup