C# DataSet 압축


DataSet, DataTable(이하 DataSet으로만 언급하겠지만 DataTable도 같은 사항이다)을 무척이나 많은 용도로 사용하고 있다. DB 리턴값으로 사용하고 네트워크로 통해 넘겨서 클라이언트에서 사용할 수 있도록 한다.


그렇지만 많의 양의 DataSet을 옮기는 가운데 Loading time이 발생하게 된다. 더군다나 네트워크를 통해서 데이타를 보낼때에 더 심각한 UI 멈춤 현상이 발생 할 수도 있다. 그렇지만 네트워크로 조금이라도 압축된 상태로 줄어 들어 넘어 온다면 리드타임을 줄이는 도움이 돌 것으로 기대하며 Dataset압축에 대해서 알아보자.



아래는 DataSet 압축과 압축해제에 관한 소스 전체이다.

/// <summary>
/// DataSet 압축
/// </summary>
/// <param name="ds">압축할 DataSet</param>
/// <param name="remortingFormat">SerializationFormat 종류 default : Binary</param>
/// <returns>string으로 변환해서 반환 한다.</returns>
/// <remarks>
/// DataSet에 대해서 압축 한다.
/// 네트워크 동신에서 많은 양을 압축하여 보낼 때 사용 하면 된다.
/// </remarks>
/// <example>
/// <code> DataSet 압축
/// CompressDataSet(DataSet);
/// </code>
/// </example>
/// <exception cref="InvalidCastException">
/// </exception>
/// <exception cref="ArgumentException">
/// </exception>
public string CompressDataSet(DataSet dsSerializationFormat remortingFormat = SerializationFormat.Binary)
{
    var compressedByte = CompressDataSetBytes(dsremortingFormat);
    return Convert.ToBase64String(compressedByte);
}
 
/// <summary>
/// DataSet 압축
/// </summary>
/// <param name="ds">압축할 DataSet</param>
/// <param name="remortingFormat">SerializationFormat 종류 default : Binary</param>
/// <returns>byte[] 변환해서 반환 한다.</returns>
/// <remarks>
/// DataSet에 대해서 압축 한다.
/// 네트워크 동신에서 많은 양을 압축하여 보낼 때 사용 하면 된다.
/// </remarks>
/// <example>
/// <code> DataSet을 압축한다.
/// CompressDataSet(DataSet);
/// </code>
/// </example>
/// <exception cref="InvalidCastException">
/// </exception>
/// <exception cref="ArgumentException">
/// </exception>
public byte[] CompressDataSetBytes(DataSet dsSerializationFormat remortingFormat = SerializationFormat.Binary)
{
    #region Serialize
    ds.RemotingFormat = remortingFormat;
    BinaryFormatter bf = new BinaryFormatter();
    byte[] inSerializeByte = null;
    using (MemoryStream ms = new MemoryStream())
    {
        bf.Serialize(msds);
        inSerializeByte = ms.ToArray();
    }
    #endregion
 
    byte[] outPut = BytesCompress(inSerializeByte);
            
    return outPut;
}
 
/// <summary>
/// byte[]를 압축한다.
/// </summary>
/// <param name="compressByte">압축할 byte[] 데이타</param>
/// <returns>byte[] 압축된 데이타를 반환 한다..</returns>
/// <seealso cref="http://msdn.microsoft.com/ko-kr/library/system.io.compression.deflatestream.aspx"/>
/// <remarks>
/// byte[]를 DeflateStream을 이용해서 압축한다.
/// </remarks>
/// <example> byte[]를 압축한다.
/// <code>
/// BytesCompress(byte[] compressByte);
/// </code>
/// </example>
/// <exception cref="InvalidCastException">
/// </exception>
/// <exception cref="ArgumentException">
/// </exception>
public byte[] BytesCompress(byte[] compressByte)
{
    #region compress
    byte[] outPut = null;
    using (MemoryStream outStream = new MemoryStream())
    {
        using (DeflateStream objZS = new DeflateStream(outStreamCompressionMode.Compress))
        {
            objZS.Write(compressByte0compressByte.Length);
            objZS.Flush();
            objZS.Close();
        }
        outPut = outStream.ToArray();
    }
    #endregion
 
    return outPut;
}
 
/// <summary>
/// DataSet을 압축 해제 한다.
/// </summary>
/// <param name="compressedDsStr">압축 해제할 데이타 string</param>
/// <param name="remortingFormat">SerializationFormat 종류 default : Binary</param>
/// <returns>압축 해제한 DataSet를 반환 한다.</returns>
/// <remarks>
/// 이 메소드는 압축된 DataSet의 Byte[] -> string이 입력해야 정상적으로 동작한다.
/// </remarks>
/// <example> DataSet 압축 해제(string)
/// <code>
/// byte[] compressedByte = Convert.FromBase64String(compressedDsStr);
/// DecompressDataSet(compressedByte, remortingFormat);
/// </code>
/// </example>
/// <exception cref="InvalidCastException">
/// </exception>
/// <exception cref="ArgumentException">
/// </exception>
public DataSet DecompressDataSet(string compressedDsStrSerializationFormat remortingFormat = SerializationFormat.Binary)
{
    byte[] compressedByte = Convert.FromBase64String(compressedDsStr);
    return DecompressDataSet(compressedByteremortingFormat);
}
 
/// <summary>
/// DataSet을 압축 해제 한다.
/// </summary>
/// <param name="compressedBytes">압축 해제할 데이타 DataSet byte[]</param>
/// <param name="remortingFormat">SerializationFormat 종류 default : Binary</param>
/// <returns>압축 해제한 DataSet를 반환 한다.</returns>
/// <seealso cref="http://msdn.microsoft.com/ko-kr/library/system.data.dataset.aspx"/>
/// <remarks>
/// 이 메소드는 압축된 DataSet의 Byte[]를 입력해야 정상적으로 동작한다.
/// </remarks>
/// <example> 
/// <code> 압축된 DataSet의 byte[]를 압축 해제 한다.
/// byte[] compressedByte = Convert.FromBase64String(compressedDsStr);
/// DecompressDataSet(compressedBytes, remortingFormat);
/// </code>
/// </example>
/// <exception cref="InvalidCastException">
/// </exception>
/// <exception cref="ArgumentException">
/// </exception>
public DataSet DecompressDataSet(byte[] compressedBytesSerializationFormat remortingFormat = SerializationFormat.Binary)
{
    var outMs = BytesDeCompress(compressedBytes);
 
    #region Deserialize
    DataSet outDs = new DataSet();
    outDs.RemotingFormat = remortingFormat;
 
    BinaryFormatter bf = new BinaryFormatter();
    outDs = (DataSet)bf.Deserialize(outMsnull);
    #endregion
    return outDs;
}
 
/// <summary>
/// byte[] 압축 해제 한다.
/// </summary>
/// <param name="compressedBytes">압축 해제할 byte[]</param>
/// <returns>압축 해제된 byte[]를 반환 한다.</returns>
/// <seealso cref="http://msdn.microsoft.com/ko-kr/library/system.io.compression.deflatestream.aspx"/>
/// <remarks>
/// byte[]에 대해서 DeflateStream으로 압축 해제 한다.
/// 파라메터는 압축된 byte[] 넘겨야 정상 동작 한다.
/// </remarks>
/// <example> byte[]를 압축 해제 한다.
/// <code>
/// var compressBytes = BytesCompress(byte[]);
/// var outMs = BytesDeCompress(compressBytes);
/// </code>
/// </example>
/// <exception cref="InvalidCastException">
/// </exception>
/// <exception cref="ArgumentException">
/// </exception>
public Stream BytesDeCompress(byte[] compressedBytes)
{
    #region UnCompress
    MemoryStream inMs = new MemoryStream(compressedBytes);
 
    MemoryStream outMs = null;
    using (DeflateStream zipStream = new DeflateStream(inMsCompressionMode.Decompresstrue))
    {
        byte[] buffer = new byte[32768];
        outMs = new MemoryStream();
 
        //Stream의 모든 데이타를 복사 한다.
        while (true)
        {
            int read = zipStream.Read(buffer0buffer.Length);
            if (read <= 0)
                break;
            outMs.Write(buffer0read);
        }
 
        outMs.Flush();
        outMs.Seek(00);   //재 사용을 위해 처음으로 되돌린다.
 
        zipStream.Flush();
        zipStream.Close();
    }
    #endregion
 
    return outMs;
}



(소스 코드 자체에 주석과 직관적인 코딩으로 충분히 파악이 가능할 것으로 예상하므로 별도의 설명을 생략)
※ 장황한 설명 보다는 주석과 소스코드 자체 만으로도 이해할 수 있도록 포스팅을 하는데 주안점을 두도록 하였습니다. 실제 개발에서 필요한 소스는 단순히 Copy & Paste 만으로도 사용할 수 있도록 노력할 것이며 주석을 이용해 nDoc이나 별도의 자동 DOC 제작 유틸로 API 문서를 만드는 데에도 도움이 되었으면 한다.
DOC에 대한 프로그램 정보 정보 Util link


위 코드에 대해서 사용법과 확인 사항을 테스트 메소드를 통해서 알려 주도록 하겠다.

[TestMethod] public void DataSet_Compress_DeCompress_TestMethod() {     //테스트할 DataSet 생성     DataSet ds = new DataSet();     DataTable dt = new DataTable();     ds.Tables.Add(dt);     dt.Columns.Add(new DataColumn("column1"));     dt.Columns.Add(new DataColumn("column2"));     dt.Columns.Add(new DataColumn("column3"));     dt.Columns.Add(new DataColumn("column4"));     for (int i = 0i < 100i++)     {         var row = dt.NewRow();         row[0= "value1밸류";         row[1= "value2밸류";         row[2= "value3밸류";         row[3= "value4밸류";         dt.Rows.Add(row);     }     var compressDs = CompressDataSetBytes(ds);          //DataSet을 압축하여 byte[]로 반환 테스트     var temp1 = Convert.ToBase64String(compressDs);     //string으로 변환     var temp2 = DecompressDataSet(temp1);               //변환된 string이 정상적으로 압축 해제되는지 테스트     var deCompressDs = DecompressDataSet(compressDs);   //압축된 byte[]를 압축 해제 테스트     #region 검증 작업     bool isValid = true;     foreach (DataRow dr in deCompressDs.Tables[0].Rows)     {         if ("value1밸류" == dr[0].ToString() && "value2밸류" == dr[1].ToString() && "value3밸류" == dr[2].ToString() && "value4밸류" == dr[3].ToString())         {             continue;         }         else         {             isValid = false;             break;         }     }     #endregion     Assert.IsTrue(isValid); //검증 완료 }

실행 시켜 보면 위 코드가 정상적으로 테스트를 통과 하는 것을 볼 수 있을 것이다.



위 부분에서 만든 메소드 중에 DataSet에 대해서만 압축과 해제를 하도록 하지 않는 부분이 있다.

- byte[] BytesCompress(byte[] compressByte)

- Stream BytesDeCompress(byte[] compressedBytes)


이 두 메소드를 이용하면 일반적인 object도 압축과 해제를 할 수 있을것으로 예상하나 아직 테스트를 해보지 안았다. 다음 포스트에 테스트한 것을 올려 보도록 하겠다.






C# MemoryStream Compression


Code Project에서 올라온 Memory기반 압축 Util이다.


http://www.codeproject.com/Articles/6834/MemoryStream-Compression


관련 글 : 2013/02/15 - [.Net Framework] - [Compress] GZipStream - 문자열 압축과 해제



사용 방법은 아래와 같다.


//압축 사용 법 using ICSharpCode.SharpZipLib.BZip2; BZip2OutputStream zosCompressed = new BZip2OutputStream(msCompressed); string sBuffer = "This represents some data being compressed."; //압축 해제할 문자열 byte[] bytesBuffer = Encoding.ASCII.GetBytes(sBuffer); zosCompressed.Write(bytesBuffer, 0, bytesBuffer.Length); zosCompressed.Finalize(); zosCompressed.Close(); bytesBuffer = msCompressed.ToArray(); string sCompressed = Encoding.ASCII.GetString(bytesBuffer); //결과값 : QlpoOTFBWSZTWZxkIpsAAAMTgEABBAA+49wAIAAxTTIxMTEImJhNNDIbvQaWyYEHiwN49LdoKNqKN2C9ZUG5+LuSKcKEhOMhFNg=



//압축 해제 사용 법
MemoryStream msUncompressed = 
    new MemoryStream(Encoding.ASCII.GetBytes(sCompressed));
BZip2InputStream zisUncompressed = new BZip2InputStream(msUncompressed);
bytesBuffer = new byte[zisUncompressed.Length];
zisUncompressed.Read(bytesBuffer, 0, bytesBuffer.Length);
zisUncompressed.Close();
msUncompressed.Close();
string sUncompressed = Encoding.ASCII.GetString(bytesBuffer);


자세한 사항은 해당 링크에서 확인해 보기를 바라며 소스도 같이 다운로드 받을 수 있을 것이다.





C# GZipStream 문자열 압축과 해제



많은 문자열을 네트워크를 통해 보낼때 CPU 비용 보다는 네트워크 비용을 줄이기 위해 사용할 수 있을것이다. 하지만 압축과 해제는 CPU뿐만 아니라 메모리에서도 제약 사항을 많이 받는다. 최대 4GB까지 지원하지만 압축된 데이터를 저장할 수 있는 메모리 공간과 압축 해제된 데이타를 저장할 수 있는 공간이 필요 하기에 예상보다 많은 메모리 사용량이 필요 할 수 있다. 그러르모 언제든지 OutOfMemoryException(OOM)이 발생할 수 있으며, InSufficientMemoryException 또한 발생할 수 있다. 그래서 실 사용에서는 사용환경에 맞게 최대 허용가능한 용량을 산정하여 사용할 필요가 있다.


실예로 모 사이트에서 수만 row의 값을 압축하여 클라이언트에 내려 보냈으나 압축해제시 OutofMemoryException이 발생하여 고생을 했던적이 있다. 그리고 서버상에서는 여러 사용자가 요청할 수도 있는 환경이기에 더욱더 주의해서 사용해야 한다.


아래는 실제로 메모리 상에서 문자열을 압축하고 해제하는 코드다.


압축 메소드

/// <summary>
/// 중복된 많은 문자열을 용량을 줄일 때 사용
/// , 메모리가 많이 사용되기 때문에 충분히 테스트가 되어야 하며 사전에 사용할 수 있는 최대값을 결정하고 허용된 범위 안에서만 되도록 한다.
/// </summary>
/// <param name="str">압축할 문자열</param>
/// <returns>압축된 문자열</returns>
/// <remarks>
/// UTF-8기반 문자열 압축 - 최대 4Gb 이하만 사용
/// , 메모리가 많이 사용되기 때문에 충분히 테스트가 되어야 하며 사전에 사용할 수 있는 최대값을 결정하고 허용된 범위 안에서만 되도록 한다.
/// </remarks>
/// <example> 문자열 압축
/// <code>
/// string str = "compress string";
/// string compressed = Compression(str);
/// </code>
/// </example>
/// <exception cref="OutOfMemoryException"></exception>
/// <exception cref="InsufficientMemoryException">사용 가능한 메모리가 없을때 발생</exception>
public static string Compression(string str)
{
    var rowData = Encoding.UTF8.GetBytes(str);
    byte[] compressed = null;
    using (var outStream = new MemoryStream())
    {
        using (var hgs = new GZipStream(outStreamCompressionMode.Compress))
        {
            //outStream에 압축을 시킨다.
            hgs.Write(rowData0rowData.Length);
        }
        compressed = outStream.ToArray();
    }
 
    return Convert.ToBase64String(compressed);
}


압추해제 메소드

/// <summary>
/// 문자열 압축 해제
/// , 메모리가 많이 사용되기 때문에 충분히 테스트가 되어야 하며 사전에 사용할 수 있는 최대값을 결정하고 허용된 범위 안에서만 되도록 한다.
/// </summary>
/// <param name="compressedStr">압축된 문자열</param>
/// <returns>평문 문자열</returns>
/// <remarks>
///  UTF-8기반 문자열 압축 해제 - 최대 4Gb 이하만 사용
///  , 메모리가 많이 사용되기 때문에 충분히 테스트가 되어야 하며 사전에 사용할 수 있는 최대값을 결정하고 허용된 범위 안에서만 되도록 한다.
/// </remarks>
/// <example> 문자열 압축 해제
/// <code>
/// string deCompressed = DeCompression(compressedStr); //압축 해제된 문자열
/// </code>
/// </example>
/// <exception cref="OutOfMemoryException"></exception>
/// <exception cref="InsufficientMemoryException">사용 가능한 메모리가 없을때 발생</exception>
public static string DeCompression(string compressedStr)
{
    string output = null;
    byte[] cmpData = Convert.FromBase64String(compressedStr);
    using (var decomStream = new MemoryStream(cmpData))
    {
        using (var hgs = new GZipStream(decomStreamCompressionMode.Decompress))
        {
            //decomStream에 압축 헤제된 데이타를 저장한다.
            using (var reader = new StreamReader(hgs))
            {
                output = reader.ReadToEnd();
            }
        }
    }
 
    return output;
}


(소스 코드 자체에 주석과 직관적인 코딩으로 충분히 파악이 가능할 것으로 예상하므로 별도의 설명을 생략)
※ 장황한 설명 보다는 주석과 소스코드 자체 만으로도 이해할 수 있도록 포스팅을 하는제 주안점을 두도록 하겠습니다.
  진짜로 필요한 소스는 단순히 Copy & Paste만드르도 사용할 수 있도록 노력할 것이며 주석을 nDoc이나 별도의 자동 DOC 제작 유틸로 API 문서를 만드는 데에도 도움이 되었으면 한다.
DOC Util link



다음은 위 메소드가 정상적으로 동작하는지 테스트를 해 보도록 하겠다.

[TestMethod]
public void Compress_DeCompress_TestMethod()
{
    string str = "양복 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press 양복 press";
    string compressed = Compression(str);
    int fooLength = compressed.Length;      // 60   - 약 90% 정도가 압축이 되었다.
    int barLength = str.Length;             // 704
    string deCompressed = DeCompression(compressed);
    int bazLength = deCompressed.Length;    // 704
 
    //System.IO.BinaryWriter
    //System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
 
    Assert.AreEqual(strdeCompressed);
}

(위 테스트는 중복된 문자열이 많게 하여 테스트를 진행하였다.)


압축과 해제를 적절한 곳에 제한적으로 사용한다면 훌륭한 모듈이지만 과도하게 적용하여 사용하다가 보면 CPU나 메모리 제한으로 언제 어떠한 일이 발생할지 모른다. 그러므로 적절히 제한적으로 사용하여 시스템의 전체적인 퍼포먼스를 제대로 사용할 수 있는 개발자가 되었으면 한다.


Tip!

1. 압축에 관계된 오픈 소스가 여러가지 존재 하고 있다.

.Net Framework에서 기본제공 하는 속도나 메모리 제약 사항을 뛰어 넘는 훌륭한 솔루션들이 많이 있지만 제한에 대한 높이만 다를뿐 언제든지 이와 같은 문제에 직면할 수 있으므로 주의 해서 사용 해야 한다.


2. 다음에는 문자열만 압축하는 것이 아닌 Object 자체 까지도 압축할 수 있는 방법을 알아 보도록 하겠다.


+ Recent posts