Datenkompression leicht gemacht

Wenn Sie längere Texte in Tabellen abspeichern oder umfangreiche Binärdaten in OLE-Feldern ablegen, so bläht das die Datenbank auf. Während dieser Umstand bei lokalen Datenbanken heutzutage kein Problem mehr darstellt, sieht die Sache in einer Mehrbenutzerumgebung und Netzwerkzugriffen auf Backends schon anders aus. Hier sollte der Traffic minimiert werden. Da macht sich dann die Komprimierung solcher Datenfelder gut, was zu verbesserter Performance führen kann.

Komprimieren von Memo- und Long-Binary-Feldern

über das Für und Wider von in Tabellen abgelegten Binärdaten oder Textdokumente möchten wir hier nicht diskutieren. Das kann ganz einfach immer wieder vorkommen, und es gibt ganze Serverumgebungen, die davon weidlich Gebrauch machen, wie etwa der Microsoft Sharepoint-Server. Die Kompression dieser Daten verkleinert die Datenbankdateien und hilft dabei den benötigten Traffic zu den Clients zu verringern.

An sich ist derlei bereits in Access integriert. Der Datentyp Anlage (Attachment) speichert binäre Daten und komprimiert diese nach Bedarf, wobei Microsoft sich weitgehend darüber ausschweigt, bei welchen Dateitypen dies zutrifft und welcher Kompressionsalgorithmus zum Einsatz kommt. Nicht jeder ist jedoch ein Freund von Anlage-Feldern, denn aufgrund des dahinter liegenden versteckten Datenmodells, welches sich tatsächlich von Sharepoint-Technik ableitet, ist der programmtechnische Umgang mit ihnen recht komplex. Zudem können Anlagefelder nicht in Access 2003 oder früher angezeigt oder ausgelesen werden. Deshalb macht sich die Binärspeicherung in OLE-Feldern, bei denen es sich ja schlicht um Long-Binary-Felder handelt, besser.

Das Thema ist nicht neu. Bereits in der Ausgabe 3/2007 (Shortlink 466) von Access Im Unternehmen wurde es behandelt, weshalb wir an dieser Stelle zusätzlich auf jenen Beitrag verweisen. Dort wurde die Komprimierung über eine externe Komponente erreicht, eine zlib-DLL, auf die im VBA-Projekt verwiesen wird. Darauf kann verzichtet werden, wenn man sich der wenig beachteten Windows-API-Funktion RTLCompressBuffer bedient, die eigentlich zur Abteilung der Treiber-APIs gehört. Sie erreicht, vor allem bei Texten, durchaus vergleichbare Kompressionsraten. Der eingesetzte Algorithmus ist eine LZ-Variante (Lempel-Ziv).

Einschub: Kompression in Anlagefeldern

Da Microsoft keine komplette Liste veröffentlicht, welche Dateitypen in Anlage-Feldern denn nun komprimiert werden und welche nicht, sondern pauschal behauptet, dass jene unbearbeitet blieben, die bereits hinlänglich komprimiert sind, wie etwa JPG, haben wir einen einfachen Test unternommen. Eine Datenbank enthält eine Tabelle, die wiederum nur ein Anlage-Feld enthält. Wir messen die Dateigröße dieser ACCDB nach dem Hinzufügen jeweils einer größeren Datei eines bestimmten Typs und anschließendem Komprimieren und Reparieren der Datenbank. Verwendet werden gängige Dateitypen. Steigt die Größe der ACCDB um die Größe der hinzugefügten Datei, so dürfte die interne Kompression der Anlage nicht angesprochen haben. Die folgende Tabelle gibt Auskunft über das Ergebnis:

<b>Dateityp    Größe Datei    Größe DB    Kompression, ca.
(leer)    -        404 kB    -
txt    3770 kB        1020 kB    6-fach
doc    3430 kB        1778 kB    2,5-fach
rtf    4400 kB        524 kB    37-fach
xls    3640 kB        1020 kB    6-fach
ppt    3800 kB        676 kB    14-fach
pdf    3280 kB        2208 kB    1,8-fach
bmp    4130 kB        516 kB    34-fach
jpg    4300 kB        4752 kB    keine
tiff     3050 kB        3305 kB    keine
xml    3830 kB        992 kB    6,5-fach
kml    6650 kB        540 kB    49-fach
csv    3380 kB        740 kB    10-fach

Zu erwähnen wäre noch, dass dezidiert Dateien verwendet wurden, die entweder viele Wiederholungen enthalten oder viele Null-Bytes, sich mithin also gut komprimieren lassen. Das Ergebnis ist eindeutig: Die Kompression scheint zum Glück der Default zu sein. Nur ausgewählte Dateiformate, wie JPG, ZIP, DOCX, XLSX, die bekannterweise schon eine Kompression aufweisen, werden ausgenommen. Erstaunlich hoch sind die Kompressionsraten. Dass etwa eine RTF-Datei von über 4 MB Größe auf intern 120 kB schrumpft, war nicht zu erwarten. Als Datei-Container eignen sich Anlage-Felder demnach hervorragend.

Leider kann der in diesem Beitrag vorgestellte Algorithmus da nicht mithalten. Dennoch hat er seine Existenzberechtigung, und wir führen kurz die Vorteile gegenüber Attachments auf:

  • Die komprimierte Speicherung ist nicht nur auf Dateien beschränkt, sondern kann etwa auch auf beliebige per VBA generierte Strings angewandt werden.
  • Die Speicherung in OLE-Feldern kann auch in Access 2003 und früher erfolgen.
  • Auch EXE-Dateien können integriert werden, was Access bei Anlage-Feldern verbietet.
  • OLE-Felder lassen sich über ODBC ansprechen und deren Inhalte sind damit auch für SQL-Server oder andere DBMS nicht tabu.
  • Der Umgang mit Long-Binary-Feldern ist unter VBA und DAO, sowie ADODB, erheblich einfacher, als mit Attachments.

Kompressionsroutine

Das Modul mdlCompression der Beispieldatenbank enthält eine Funktion CompressRTL, die Byte-Arrays komprimieren kann. Ihr Code ist in Listing 1 abgebildet. Er nutzt einige Windows-API-Funktionen, die im Modulkopf deklariert sind (Listing 2). Der Rückgabewert ist wiederum ein Byte-Array mit den komprimierten Daten. Das Ganze kommt ausgesprochen übersichtlich daher. Auf die genaue Funktion des Codes und der API-Aufrufe möchten wir hier gar nicht eingehen. Einige Hinweise dürfen jedoch nicht fehlen.

Private Declare Function RtlGetCompressionWorkSpaceSize _
     Lib "NTDLL" (ByVal Flags As Integer, WorkSpaceSize As _
     Long, UNKNOWN_PARAMETER As Long) As Long
Private Declare Function NtAllocateVirtualMemory Lib _
     "ntdll.dll" (ByVal ProcHandle As Long, BaseAddress As _
     Long, ByVal NumBits As Long, regionsize As Long, ByVal _
     Flags As Long, ByVal ProtectMode As Long) As Long
Private Declare Function RtlCompressBuffer Lib "NTDLL" _
     (ByVal Flags As Integer, ByVal BuffUnCompressed As Long, _
     ByVal UnCompSize As Long, ByVal BuffCompressed As Long, _
     ByVal CompBuffSize As Long, ByVal UNKNOWN_PARAMETER As _
     Long, OutputSize As Long, ByVal WorkSpace As Long) As _
     Long
Private Declare Function RtlDecompressBuffer Lib _
     "NTDLL" (ByVal Flags As Integer, ByVal BuffUnCompressed _
     As Long, ByVal UnCompSize As Long, ByVal BuffCompressed _
     As Long, ByVal CompBuffSize As Long, OutputSize As _
     Long) As Long
Private Declare Function NtFreeVirtualMemory Lib _
     "ntdll.dll" (ByVal ProcHandle As Long, BaseAddress As _
     Long, regionsize As Long, ByVal Flags As Long) As Long

Listing 1: Die fünf im Modul verwendeten API-Funktionen

So enthält die Prozedur keine Fehlerbehandlung. übergeben Sie etwa ein undimensioniertes Array, so kracht es gleich in der ersten Zeile beim Ausdruck UBound. Auch sonst werden die Rückgabewerte der API-Funktionen nicht ausgewertet, so dass Fehlfunktionen nicht offensichtlich sind. Allerdings dürfte es auch kaum einen Fall geben, der hier bei einem ordnungsgemäßen Byte-Array zu Fehlern führt. Kritisch ist bestenfalls der Wert der von RTLCompressBuffer zurückgelieferten Variablen retSize, die angibt, wie viele Bytes im Ergebnis enthalten sind. Er wird im weiteren Verlauf dazu verwendet, um das Ergebnis-Array binOut neu zu dimensionieren. Hier könnten rein theoretisch Werte anfallen, die über den erlaubten Wertebereich hinausgehen. Uns ist bei allen Tests derlei aber nicht untergekommen.

Nun möchten Sie möglicherweise nicht Byte-Arrays komprimieren, sondern Strings. Das macht eine Konvertierung dieser erforderlich. Gegeben sei etwa ein Text in der Variablen sText, der zu komprimieren wäre. Dann verwenden Sie die VBA-Funktion StrConv:

Dim bin() As Byte
Dim binRet() As Byte
bin = StrConv(sText, vbFromUnicode)
binRet = CompressRTL(bin)

StrConv kann Strings in Byte-Arrays überführen und umgekehrt. Da es sich bei VBA-Strings immer um Unicode handelt, ist der Parameter vbFromUnicode der richtige. Er macht aus dem String quasi ein ANSI-Byte-Array. Dieses übergeben Sie dann der Funktion CompressRTL des Moduls.

Dekompressionsroutine

Mit dem komprimierten Byte-Array selbst können Sie wenig anfangen. Entweder speichern Sie es in einer Datei ab, oder im OLE-Feld einer Tabelle. Es reicht dazu schlicht die Zuweisung an den Feldinhalt. Beispiel:

Dim rs As DAO.Recordset
Set rs = CurrentDb.OpenRecordset("Binaertabelle")
rs.Edit    ''''oder rs.AddNew
rs!OleFeld1.Value = binRet
rs.Update

Einstmals mussten Sie sich mit der Methode AppendChunk eines DAO-Felds herumschlagen und in einer Schleife Teile des Byte-Arrays (Chunks) hinzufügen. Das ist veraltet. Die unmittelbare Zuweisung funktioniert bei heutigen Rechnerausstattungen auch bei großen Byte-Arrays. Das Limit liegt lediglich beim dem DAO-Thread zugewiesenen RAM-Bereich (Heap Working Set) der Datenbank-Engine.

Nun möchten Sie die komprimierten Daten wieder in ihrer ursprünglichen Form zurückgewinnen. Das übernimmt die Dekompressionsroutine DecompressRTL des Moduls, welche in Listing 3 abgebildet ist. Auch ihr Umfang ist erfreulich gering. Ihr Aufruf entspricht genau dem für die Komprimierprozedur CompressRTL. Sie holen sich etwa die komprimierten Daten aus der Tabelle und erhalten die entpackten im Rückgabe-Array, welches Sie wieder per StrConv in einen Text verwandeln:

Public Function DeCompressRTL(Buffer() As Byte) As Byte()
     Dim LSize As Long, memSize As Long
     Dim binOut() As Byte
     
     LSize = UBound(Buffer) * 12.5
     ReDim binOut(LSize)
     RtlDecompressBuffer 2& Or &H100, VarPtr(binOut(0)), LSize, _
         VarPtr(Buffer(0)), 1& + UBound(Buffer), memSize
     If (memSize > 0) And (memSize <= UBound(binOut)) And _
        (memSize < 2000000) Then
         ReDim Preserve binOut(memSize - 1)
         DeCompressRTL = binOut
     End If
End Function

Sie haben das Ende des frei verfügbaren Textes erreicht. Möchten Sie ...

TestzugangOder bist Du bereits Abonnent? Dann logge Dich gleich hier ein. Die Zugangsdaten findest Du entweder in der aktuellen Print-Ausgabe auf Seite U2 oder beim Online-Abo in der E-Mail, die Du als Abonnent regelmäßig erhältst:

Schreibe einen Kommentar