リレーショナル・データベースは系統的なデータのリポジトリを提供し,ほとんどのデータ型を効率的に格納する。ただし例外もある。例えばバイナリ型のデータは圧縮すればサイズを大幅に縮小できる場合がある。従ってバイナリ型の大きなデータがある場合は,圧縮によって必要なデータ容量を大幅に削減できる。

 SQL Serverに圧縮機能は組み込まれていないが,Common Language Runtime(CLR)を利用すればSQL Server 2005に圧縮機能を追加できる。ここではSQL Server 2005におけるデータの圧縮方法,およびフレキシビリティが低く廃止予定のimageデータ型を置き換える新しいデータ型であるvarbinary(max)の活用方法を説明する。この記事の例を最後まで見て行けば,SQL Serverデータの圧縮方法,そしてSQL Server 2005をパワーアップするための.NET Frameworkコードの活用方法を理解できるだろう。

.NET FrameworkがZip処理機能を提供

 データをデータベースに格納する前に圧縮し,データベースから取得後に伸張するためには,カスタム・ソリューションを開発者が構築しなければならないというのが長年の常識だった(圧縮の概要は別掲記事「圧縮の基礎」を参照)。これらの作業を大幅に単純化するのが.NET Frameworkである。SQL Server 2005およびVisual Studio 2005に含まれる.NET Framework 2.0は,名前空間System.IO.Compressionを提供する。この名前空間のメソッドを使用することによって,データの圧縮および伸張を容易に行うことができる。

 すべての.NET言語で利用可能な名前空間System.IO.Compressionは,データの圧縮および伸張のための2つのクラス,DeflateStreamおよびGZipStreamを提供する。DeflateStreamクラスは,DEFLATEアルゴリズム(RFC 1951)を実装している。一方のGZipStreamクラスは,1つのファイルを圧縮するgzipフォーマット(RFC 1952)を実装している。このフォーマットもまたDEFLATEアルゴリズムをベースにしている。クラス名が示唆している通り,DeflateStreamクラスおよびGZipStreamクラスはデータ・ストリームを扱う。ストリームは単純なバイト・シーケンスではなく,オブジェクトを操作するメソッドを備えたオブジェクトである。

 サンプル圧縮プログラムを見る前に,圧縮アプリケーション作成の鍵になるステップを一通り見ていく。最初にSystem.IOおよびSystem.IO.Compression名前空間の命令を使用するために,Visual BasicのImportsまたはC#のusing文を追加する必要がある。

' VB Imports statements
Imports System.IO 'Streamクラスを使用するため
Imports System.IO.Compression 'DeflateStreamとGZipStreamクラスを使用するため

// C# code
using System.IO;  //Streamクラスを使用するため
using System.IO.Compression;  //DeflateStreamとGZipStreamクラスを使用するため

 また,SQL Serverネイティブのデータ型であるSqlBytes型を,関数の引数および戻り値として使用する必要がある。SqlBytesは新しい名前空間System.Data.SqlTypeに含まれている。この名前空間はSQL Server 2005ネイティブのデータ型向けのクラスを提供する。.NETのドキュメントによれば,これらのクラスは「.NET(CLR)で提供されるデータ型を置き換える,より安全で高速なデータ型を提供する」という。これを追加すると,SqlType名前空間にINullableインターフェースが実装され,変数にnull値を格納できるようになる。

 また,SQL Serverネイティブのデータ型を使用することによって,型変換の問題も回避できる。SQLCLR関数内のByte配列のような,.NET共通のデータ型も使用できるが,.NETのデータ型は実行時に対応するSqlTypeに暗黙的に変換される。この型変換によってパフォーマンスが若干低下し,また変換エラーの可能性も出てくる。

 例えば,我々がCompressBytes関数を最初に書いたときには,Byte配列を使用していた。そのコードをSQL Server 2005に移植後,Byte配列が実行時にvarbinary(8000)に暗黙的に変換されていることを発見した。このデータ変換は8Kバイトを超える大きなデータを扱う場合の障害となった。

 データを圧縮するためには,SqlBytes型を返すユーザー定義関数(UDF)をマネージ・コードで作成する必要がある。

Public Shared Function CompressBytes _
(ByVal UncompressedBytes As SqlBytes) As SqlBytes

 圧縮処理では,MemoryStreamオブジェクトおよびGZipStreamオブジェクトを次のように使用する。

Dim outputStream As New MemoryStream '圧縮データを格納する
Dim zipStream As Stream '圧縮処理に使用するzipストリーム
zipStream = New GZipStream(outputStream, CompressionMode.Compress)

 この関数はGZipStreamオブジェクトのWriteメソッドを呼び出して圧縮を実行する。このメソッドは圧縮されたデータをMemoryStreamオブジェクト(出力ストリーム)に書き出す。次に挙げるコードを見れば分かるように,Writeメソッドでは多くの非圧縮データが必要になる(このことの重要性は後で説明する)。

zipStream.Write(UncompressedBytes.Value, 0, CInt(UncompressedBytes.Length))

 圧縮データを出力ストリームに書き出した後に必要な操作は,関数からのデータを戻すことだけである。

Return New SqlBytes(outputStream.ToArray)

 このように圧縮は容易であり,圧縮されたデータの伸張もほとんど同じくらいに簡単である。伸張するにはGZipStream::Readメソッドを使用する。Writeメソッドと同様に,Readメソッドでも多くの非圧縮データが必要である。汎用的な圧縮および伸張ラッパー・クラスの開発時に,我々は多くの圧縮データおよび非圧縮データを常に確保しておくことが不利であることに気付き,ブロックに分けて圧縮および伸張するチャンク指向のアプローチのほうが有利であると判断した。このアプローチでは,多くのデータを常に確保しておくという面倒な操作が不要になる。