메뉴 바로가기 검색 및 카테고리 바로가기 본문 바로가기

한빛출판네트워크

IT/모바일

닷넷 2.0의 데이터 압축기능 사용하기

한빛미디어

|

2007-01-19

|

by HANBIT

12,020

제공: 한빛 네트워크
저자 : Wei-Meng Lee
역자 : 이대엽
원문 : http://www.windowsdevcenter.com/pub/a/windows/2006/09/12/using-data-compression-in-net-20.html

닷넷 프레임워크 2.0에서 사용할 수 있는 새로운 API 중의 하나는 System.IO.Compression 네임스페이스에 위치한 새로운 압축 클래스군이다. 이 네임스페이스의 새로운 두 클래스는 GZipStream과 DeflateStream인데, 이 압축 클래스들을 이용하면 잘 알려져 있는 GZip과 Deflate 알고리즘을 사용하는 여러분의 닷넷 애플리케이션에서 압축과 압축해제를 가능케 할 수 있다.

데이터 압축에서 주목하지 않을 수 없는 응용 분야 중 하나는 네트워크상으로 전송되는 데이터의 양을 줄이는 것이다. 이것은 대역폭에 대한 비용이 주요 관심사일 경우에 특히 중요한데, 데이터 집합을 통해 데이터베이스의 내용을 반환하는 웹 서비스의 경우를 생각해 보자. 데이터 집합의 내용은 웹 서비스로서 외부에 노출될 경우 XML로서 전달된다. 값비싼 미디어(GPRS와 같은)를 통해 접속된 클라이언트에 의해 웹 서비스가 이용될 경우 교환되는 데이터의 모든 바이트는 금액으로 환산할 수 있다. 그러므로 이러한 형태의 웹 서비스를 클라이언트가 좀 더 값싸게 이용할 수 있게 하기 위하여 서버상의 데이터를 압축하고 클라이언트 측에서 수신했을 때 압축 해제하는 것은 현명한 일이다.

이 기사에서는 웹 서비스 환경에서 닷넷 2.0에 포함되어 있는 압축 클래스를 어떻게 이용하는지 보여줄 것이다. 여러분은 여러분의 애플리케이션에서 압축기능을 사용하는 것의 이점을 보게 될 것이며 따라서 여러분의 애플리케이션에서 그것을 사용할 것인지에 대해 결정할 수 있게 된다.

샘플 애플리케이션 만들기

가장 먼저 비주얼 스튜디오 2005를 이용하여 웹 서비스를 만들어 보도록 하자. 프로젝트의 이름은 C:DatasetWS로 지정한다. Service.vb 파일에서 다음의 네임스페이스들을 임포트(import)한다.
Imports System.Data
Imports System.Data.SqlClient
Imports System.Diagnostics
Imports System.IO
Imports System.IO.Compression
Imports System.Text.Encoding
다음으로 getRecords() 웹 메소드를 정의하여 그것이 Northwind 데이터베이스로부터 Employee 테이블을 데이터집합으로서(바이트 배열로 노출된다) 반환하도록 한다.

주의사항: 이 예제는 샘플 데이터베이스로 Northwind를 포함하는 SQL 서버 2005 익스프레스(SQL Server 2005 Express) 에디션을 사용한다. SQL 서버 2005 익스프레스에는 기본적으로 아무런 샘플 데이터베이스도 따라오지 않으므로 여러분이 직접 샘플 데이터베이스를 설치할 필요가 있다. 여러분은 pubs와 Northwind 샘플 데이터베이스를 설치 스크립트를 다운로드 함으로써 설치할 수 있다. 여러분의 시스템에 설치 스크립트가 설치되면 비주얼 스튜디오 2005 명령 프롬프트(시작 -> 프로그램 -> Microsoft Visual Studio 2005 -> Visual Studio Tools -> Visual Studio 2005 명령 프롬프트)로 가서 설치 스크립트가 들어있는 디렉터리로 이동한다. pubs와 Northwind 데이터베이스를 설치하기 위해 아래와 같이 입력한다:
C:SQL Server 2000 Sample Databases>sqlcmd -S .SQLEXPRESS -i instpubs.sql
C:SQL Server 2000 Sample Databases>sqlcmd -S .SQLEXPRESS -i instnwnd.sql

     _
    Public Function getRecords() As Byte()
        Dim connStr As String = _
           "Data Source=.SQLEXPRESS;Initial Catalog=Northwind;" & _
           "Integrated Security=True"
        Dim sql As String = "SELECT * FROM Employees"
        Dim conn As SqlConnection = New SqlConnection(connStr)
        Dim comm As SqlCommand = New SqlCommand(sql, conn)
        Dim dataadapter As SqlDataAdapter = New SqlDataAdapter(comm)
        Dim ds As DataSet = New DataSet()

        "---연결을 열고 데이터 집합을 채운다 ---
        conn.Open()
        dataadapter.Fill(ds, "Employees_table")
        conn.Close()

        "---데이터 집합을 XML로 변환한다 ---
        Dim datadoc As System.Xml.XmlDataDocument = _
           New System.Xml.XmlDataDocument(ds)
        Dim dsXML As String = datadoc.InnerXml
        Return ASCII.GetBytes(dsXML)
    End Function
필자가 데이터집합을 데이터집합 객체로 반환하는 대신 그것을 바이트 배열로 반환하도록 하였음에 주목하라. 이것은 나중에 압축을 용이하게 해준다.

지금까지가 웹 서비스를 위해 필요한 모든 것이다. 웹 서비스를 테스트하기 위해서는 단순히 F5만 누르고 웹 서비스 URL만 확인하면 된다. 여러분이 보는 것은 아래에 있는 것과 비슷한 것이어야 한다:
http://localhost:11496/DatasetWS/Service.asmx
11496은 비주얼 스튜디오 2005가 내 웹 서비스를 실행하는데 사용한 임의의 포트번호이다. 여러분의 컴퓨터에서는 다른 숫자를 가질 것이다.

이제 솔루션에 윈도 애플리케이션 프로젝트를 추가하도록 하자. 파일 -> 추가 -> “새 프로젝트”로 가서 현재 솔루션에 새로운 Windows Application 프로젝트를 추가한다. 프로젝트의 이름은 C:DatasetWSConsumer로 지정하자.

기본 Form1에 다음의 컨트롤들을 배치시킨다(그림 1 참조) • DataGridView • Label • Button


[그림 1] 기본 Form1 변형

이전에 생성했던 웹 서비스에 웹 참조를 추가한다. 웹 참조의 이름은 "dataWS"라 지정한다(그림 2 참조). 참조 추가를 클릭한다.


[그림 2] 웹 서비스에서 웹 참조 추가하기

Form1의 비하인드 코드로 전환하여 다음의 네임스페이스를 임포트한다:
Imports System.IO
Imports System.IO.Compression
Imports System.Text.Encoding
Double-click on the Load button to switch to its event handler. Code the following:
    Private Sub btnLoad_Click( _
       ByVal sender As System.Object, _
       ByVal e As System.EventArgs) _
       Handles btnLoad.Click

        "---웹 서비스에 프록시 객체를 생성한다---
        Dim ws As New dataWS.Service
        "---데이터 집합 객체를 생성한다 ---
        Dim ds As New DataSet
        "---스톱워치 객체를 생성한다 ---
        Dim sw1, sw2 As New Stopwatch

        "---다운로드에 소요된 시간을 잰다 ---
        sw1.Start()
        "---웹 서비스에 접속한다---
        Dim dsBytes As Byte() = ws.getRecords

        Label1.Text = "Size of download: " & dsBytes.Length

        "---바이트 배열을 문자열로 변환한 다음
        " 데이터 집합 객체로 읽어들인다 ---
        ds.ReadXml(New IO.StringReader(ASCII.GetString(dsBytes)))
        sw1.Stop()

        Label2.Text = "Time spent: " & sw1.ElapsedMilliseconds & "ms"

        "---DataGridView 컨트롤에 바인딩시킨다 ---
        DataGridView1.DataSource = ds
        DataGridView1.DataMember = "Employees_table"
    End Sub
본질적으로 이것은 데이터 집합을 읽어오기 위해 웹 서비스에 접속한 다음 읽어온 데이터 집합을DataGridView 컨트롤에 바인딩 시킨다. 애플리케이션을 테스트하기 위해 F5를 누른다. Load 버튼을 클릭하고 나면 DataGridView 컨트롤이 나타날 것이다. 그림 3은 그 결과를 보여준다.


[그림 3] DataGridView 컨트롤에 데이터 집합 바인딩하기

Load 버튼을 처음 눌렀을 때 여러분은 DataGridView 컨트롤이 로딩되는데 잠깐 동안의 시간이 걸림을 느낄 수 것이다. 그 다음부터는 데이터베이스로부터 전달되는 데이터가 웹 서비스 종단에서 캐싱되기 때문에 로딩이 훨씬 더 빨라질 것이다. 다운로드 되는 데이터의 크기와 다운로드 하는데 걸리는 시간을 관찰하라. 여러분은 데이터를 다운로드 하는데 필요한 시간의 평균치를 구하기 위해서는 Load 버튼을 몇 번 클릭해야 한다. 이 경우 266KB를 다운로드 하는데 (평균적으로) 약 65ms가 걸렸다.

압축하기

압축이 어떻게 앞에서 작성했던 애플리케이션을 향상시킬 수 있는지 알아보기 위해, 압축기능을 지원하게끔 프로젝트를 수정해 보자. 웹 서비스의 끝의 Service.vb에 Compress() 함수를 아래와 같이 추가한다.
    Public Function Compress(ByVal data() As Byte) As Byte()
        Try
            "---ms는 압축된 데이터를 저장하는데 사용된다 ---
            Dim ms As New MemoryStream()
            Dim zipStream As Stream = Nothing

            zipStream = New GZipStream(ms, _
                            CompressionMode.Compress, True)
            "---혹은---
            "zipStream = New DeflateStream(ms, _
            "                CompressionMode.Compress, True)

            "---데이터에 저장되어 있는 정보를 이용하여 압축함 ---
            zipStream.Write(data, 0, data.Length)
            zipStream.Close()

            ms.Position = 0
            "---압축된 데이터(바이트 배열)를 저장하는데 사용한다---
            Dim compressed_data(ms.Length - 1) As Byte

            "---메모리 스트림의 내용을 바이트 배열로 읽어들인다---
            ms.Read(compressed_data, 0, ms.Length)
            Return compressed_data
        Catch ex As Exception
            Return Nothing
        End Try
    End Function
기본적으로 이 함수는 GZipStream 클래스를 이용하여 바이트 배열에 저장되어 있는 데이터를 압축한 다음 압축된 데이터를 스트림 객체에 저장한다. 그런 다음 압축된 데이터는 바이트 배열로 반환된다.

이 Compress() 함수를 사용하기 위해 getRecords() 웹 메소드를 아래와 같이 수정한다.
     _
    Public Function getRecords() As Byte()
        Dim connStr As String = _
           "Data Source=.SQLEXPRESS;Initial Catalog=Northwind;" & _
           "Integrated Security=True"
        Dim sql As String = "SELECT * FROM Employees"
        Dim conn As SqlConnection = New SqlConnection(connStr)
        Dim comm As SqlCommand = New SqlCommand(sql, conn)
        Dim dataadapter As SqlDataAdapter = New SqlDataAdapter(comm)
        Dim ds As DataSet = New DataSet()

        "---연결을 열고 데이터 집합을 채운다 ---
        conn.Open()
        dataadapter.Fill(ds, "Employees_table")
        conn.Close()

        "---데이터 집합을 XML로 변환한다 ---
        Dim datadoc As System.Xml.XmlDataDocument = _
           New System.Xml.XmlDataDocument(ds)
        Dim dsXML As String = datadoc.InnerXml

        "---압축을 수행한다 ---
        Dim compressedDS() As Byte
        compressedDS = Compress(UTF8.GetBytes(dsXML))
        Return compressedDS
        "-------------------------
    End Function
압축해제 하기

클라이언트 측에서는 Decompress() 함수를 Form1의 비하인드 코드에 추가한다.
    Public Function Decompress(ByVal data() As Byte) As Byte()
        Try
            "---압축된 데이터를 ms로 복사한다 ---
            Dim ms As New MemoryStream(data)
            Dim zipStream As Stream = Nothing

            "---ms에 저장되어 있는 데이터를 이용하여 압축해제한다 ---
            zipStream = New GZipStream(ms, CompressionMode.Decompress)
            "---혹은---
            "zipStream = New DeflateStream(ms, _
            "                CompressionMode.Decompress, True)

            "---압축해제된 데이터를 저장하는 데 사용한다 ---
            Dim dc_data() As Byte

            "---압축해제된 데이터는 zipStream에 저장되며; 
            " 그것들을 바이트 배열로 추출한다 ---
            dc_data = ExtractBytesFromStream(zipStream, data.Length)

            Return dc_data
        Catch ex As Exception
            Return Nothing
        End Try
    End Function
압축된 데이터는 메모리 스트림 객체로 복사된 다음 GZipStream 클래스를 이용하여 압축해제 된다. 압축 해제된 데이터는 ExtractFromStream() 메소드를 이용하여 바이트 배열로 추출되는데 이것은 아래에 정의되어 있다:
    Public Function ExtractBytesFromStream( _
       ByVal stream As Stream, _
       ByVal dataBlock As Integer) _
       As Byte()

        "---스트림 객체로부터 바이트를 추출한다 ---
        Dim data() As Byte
        Dim totalBytesRead As Integer = 0
        Try
            While True
                "---점진적으로 데이터 바이트 배열의 크기를 증가시킨다 ---
                ReDim Preserve data(totalBytesRead + dataBlock)
                Dim bytesRead As Integer = _
                   stream.Read(data, totalBytesRead, dataBlock)
                If bytesRead = 0 Then
                    Exit While
                End If
                totalBytesRead += bytesRead
            End While
            "---바이트 배열이 추출된 바이트의 수를 정확하게 포함하고 있는지 확인하라---
            ReDim Preserve data(totalBytesRead - 1)
            Return data
        Catch ex As Exception
            Return Nothing
        End Try
    End Function
여러분이 압축해제된 데이터의 실제 크기를 알 수 없기 때문에 압축해제된 데이터를 저장하는데 사용되는 데이터 배열의 크기를 점진적으로 증가시켜야만 한다. dataBlock 파라미터는 한번에 복사된 데이터의 수를 알려준다. 쓸만한 어림잡이 계산은 다음과 같이 압축된 데이터의 크기를 블록 사이즈로 이용하는 것이다.
"---데이터는 압축된 데이터를 포함하는 배열이다
dc_data = ExtractBytesFromStream(zipStream, data.Length)
getRecords() 웹 메소드에 의해 반환되는 데이터가 지금은 압축되어 있기 때문에 그것을 데이터 집합 객체로 불려질 수 있기 이전에 압축해야 할 필요가 있다. Load 버튼 이벤트 핸들러를 다음과 같이 수정한다:
    Private Sub btnLoad_Click( _
       ByVal sender As System.Object, _
       ByVal e As System.EventArgs) _
       Handles btnLoad.Click

        "---웹 서비스에서 프록시 객체를 생성한다 ---
        Dim ws As New dataWS.Service
        "---데이터 집합 객체를 생성한다 ---
        Dim ds As New DataSet
        "---스톱워치 객체를 생성한다 ---
        Dim sw1, sw2 As New Stopwatch

        sw1.Start()
        Dim dsBytes As Byte() = ws.getRecords
        Label1.Text = "Size of download: " & dsBytes.Length

        "---압축해제를 수행한다 ---
        Dim decompressed_dsBytes() As Byte
        sw2.Start()
        decompressed_dsBytes = Decompress(dsBytes)
        sw2.Stop()
        Label3.Text = "Decompression took: " & _
           sw2.ElapsedMilliseconds & "ms"
        ds.ReadXml(New _
           IO.StringReader(ASCII.GetString(decompressed_dsBytes)))
        "---------------------------

        sw1.Stop()
        Label2.Text = "Time spent: " & sw1.ElapsedMilliseconds & "ms"
        DataGridView1.DataSource = ds
        DataGridView1.DataMember = "Employees_table"
    End Sub
필자는 또한 압축 해제를 수행하는데 시간이 얼마나 걸리는 지도 재어 보았는데 여러분이 직접 압축 해제를 수행하는데 실질적으로 걸리는 시간이 얼마인지를 확인해 보는 것도 괜찮은 생각이다.

이제 이걸로 끝이다! F5를 눌러 프로그램을 테스트해보라. 평소대로 Load 버튼을 여러 번 클릭하여 각 작업마다의 데이터 크기 뿐만 아니라 소요되는 시간을 관찰해 보라. 그림 4는 필자가 관찰했던 평균 시간을 보여준다.


[그림 4] 압축 사용하기

수치 분석하기

여러분이 획득한 수치들을 조사해 보는 것은 여러분의 애플리케이션에서 압축을 사용하는 것의 유용함을 이해하는데 있어 좋은 방법이다.

[표 1]은 압축을 사용하기 전후에 획득된 자료들을 요약한 것이다.

태스크 다운로드 크기(바이트) 소요시간(ms) 압축해제 시간(ms)
압축기능 사용하지 않음 266330 65 -
압축기능 사용 124148 83 13
[표 1] 압축 사용 전후에 획득한 자료

먼저 압축기능을 사용한 것을 관찰해 보자면, 데이터 크기는 46 퍼센트의 압축율을 보이면서 266330에서 124148 바이트로 감소하였다. 필자가 압축 해제시간(약 13ms가 소요된)만을 측정하였음에도 불구하고, 압축에 소요된 시간은 거의 동일하다. 13ms의 압축해제 시간은 데이터를 전달하고 읽어들이는데 필요로 하는 상대적으로 조금 더 긴 시간(83ms)에 비해 적다. 전체적으로 압축기능을 사용하면 DataGridView 컨트롤을 보여주는데 필요한 시간이 다소 증가한다.

서로 다른 데이터 크기에 대한 실험에서 압축과 압축해제에 걸리는 시간은 일정하게 좀 더 많거나 적게 걸린다는 것을 확인하였다. 예를 들어 Employee 테이블을 읽어 들이는 것 대신 필자는 [Order Details] 테이블을 읽어 들여보았다. 그 테이블의 압축되지 않은 크기는 343902 바이트였는데 압축을 하고 난 후의 크기는 7.3 퍼센트의 압축율을 보인 24987 바이트였다. 그러나 압축 해제시간은 더 작은 데이터 블록을 압축할 때와 거의 동일하다.

흥미롭게도 같은 크기를 차지하지만 다른 내용을 가지는 데이터의 두 블록은 매우 다른 압축율을 보여줄 수 있으며(수치가 낮으면 낮을수록 좋다), 그리고 텍스트 파일은 .exe와 .jpg 파일과 같은 이진파일에 비해 훨씬 더 압축이 잘된다. 압축을 효과적으로 하려면 압축될 데이터의 블록이 커야 하며 작은 블록의 데이터를 압축하는 것은 실질적으로 데이터 크기와 압축과 압축해제에 소요되는 귀중한 시간을 낭비할 뿐이다.

또한 웹 서비스 측에서 압축을 사용하는 것은 웹 서버의 작업부하를 증가시킬 것이라는 것을 주지해야 하며 따라서 압축을 사용할지 말아야 할지를 결정하는데 참고사항들을 찾아볼 필요가 있다.

요약

이 기사에서 여러분은 닷넷 2.0의 새로운 압축 클래스를 어떻게 사용하는지에 대해 알아보았다. 이러한 클래스들을 구현하는 것이 시중에서 구할 수 있는 것(마이크로소프트가 인정하는)만큼 효율적이지는 않을지 몰라도 여러분의 데이터 크기를 감소시킬 필요가 있을 경우에 매우 쓸모 있을 것이다. 그뿐만 아니라 그것들은 무료이며 따라서 필자는 아무런 불만이 없다!


Wei-Meng Lee (weimenglee.blogspot.com)는 기술자이며 최신 마이크로소프트 기술에 대한 실무 교육을 전문으로 하는 기술회사인 Developer Learning Solution의 설립자이다.
TAG :
댓글 입력
자료실

최근 본 책0