Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일

WCF에서 DataSet을 binary encoding으로 직렬화하는 방법

테스트용으로 다음과 같이 DataSet을 반환하는 WCF를 만들고,

public DataSet GetDataSet()
{
    DataSet ds = new DataSet();

    DataColumn nameCol = new DataColumn("Name", typeof(string));
    DataColumn birthCol = new DataColumn("Birth", typeof(DateTime));
    DataColumn emailCol = new DataColumn("Email", typeof(string));
    DataColumn familyCol = new DataColumn("Family", typeof(byte));

    DataTable table = new DataTable("MemberInfo");

    table.Columns.Add(nameCol);
    table.Columns.Add(birthCol);
    table.Columns.Add(emailCol);
    table.Columns.Add(familyCol);

    table.Rows.Add("Anderson", new DateTime(1950, 5, 20), "anderson@gmail.com", 2);
    table.Rows.Add("Jason", new DateTime(1967, 12, 3), "jason@gmail.com", 0);
    table.Rows.Add("Mark", new DateTime(1998, 3, 2), "mark@naver.com", 1);
    table.Rows.Add("Jennifer", new DateTime(1985, 5, 6), "jennifer@jennifersoft.com", 0);

    ds.Tables.Add(table);

    return ds;            
}

WCF 클라이언트로서 윈폼 앱을 만들어 다음과 같이 호출했습니다.

var ch = new ChannelFactory<ITestService>(
    "customHttpBinding_ITestService",
    new EndpointAddress("http://169.254.223.95:2524/TestService.svc"));

var svc = ch.CreateChannel();

DataSet ds = svc.GetDataSet();
System.Diagnostics.Trace.WriteLine(ds.GetXml());

이 사이의 통신을 가로채면 다음과 같습니다. (Body 영역은 보기 편하게 일부러 들여쓰기 편집을 했습니다.)

POST http://169.254.223.95:2524/TestService.svc HTTP/1.1
Content-Type: text/xml; charset=utf-8
VsDebuggerCausalityData: uIDPo+slpM8ZksBGuDd+qgAC8QYAAAAAj4yuJGaVKESSxzkDQNjf3pIuoAs6l5FJlArYEHPUGKcACQAA
SOAPAction: "http://tempuri.org/ITestService/GetDataSet"
Host: 169.254.223.95:2524
Content-Length: 135
Expect: 100-continue
Accept-Encoding: gzip, deflate

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
    <s:Body>
        <GetDataSet xmlns="http://tempuri.org/"/>
    </s:Body>
</s:Envelope>

HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Vary: Accept-Encoding
Server: Microsoft-IIS/8.0
X-SourceFiles: =?UTF-8?B?ZDpcc2V0dGluZ3NcRGVza3RvcFxkYXRhc2V0X2FzX2JpblxXaW5kb3dzRm9ybXNBcHBsaWNhdGlvbjFcV2ViQXBwbGljYXRpb24xXFRlc3RTZXJ2aWNlLnN2Yw==?=
X-Powered-By: ASP.NET
Date: Tue, 27 Aug 2013 05:53:36 GMT
Content-Length: 1884

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
    <s:Body><
        GetDataSetResponse xmlns="http://tempuri.org/">
            <GetDataSetResult>
                <xs:schema id="NewDataSet" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
                    <xs:element name="NewDataSet" msdata:IsDataSet="true" msdata:UseCurrentLocale="true">
                        <xs:complexType>
                            <xs:choice minOccurs="0" maxOccurs="unbounded">
                                <xs:element name="MemberInfo">
                                    <xs:complexType>
                                        <xs:sequence>
                                            <xs:element name="Name" type="xs:string" minOccurs="0"/>
                                            <xs:element name="Birth" type="xs:dateTime" minOccurs="0"/>
                                            <xs:element name="Email" type="xs:string" minOccurs="0"/>
                                            <xs:element name="Family" type="xs:unsignedByte" minOccurs="0"/>
                                        </xs:sequence>
                                    </xs:complexType>
                                </xs:element>
                            </xs:choice>
                        </xs:complexType>
                    </xs:element>
                </xs:schema>
                <diffgr:diffgram xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
                    <NewDataSet xmlns="">
                        <MemberInfo diffgr:id="MemberInfo1" msdata:rowOrder="0" diffgr:hasChanges="inserted"><Name>Anderson</Name><Birth>1950-05-20T00:00:00+09:00</Birth><Email>anderson@gmail.com</Email><Family>2</Family></MemberInfo><MemberInfo diffgr:id="MemberInfo2" msdata:rowOrder="1" diffgr:hasChanges="inserted"><Name>Jason</Name><Birth>1967-12-03T00:00:00+09:00</Birth><Email>jason@gmail.com</Email><Family>0</Family></MemberInfo><MemberInfo diffgr:id="MemberInfo3" msdata:rowOrder="2" diffgr:hasChanges="inserted"><Name>Mark</Name><Birth>1998-03-02T00:00:00+09:00</Birth><Email>mark@naver.com</Email><Family>1</Family></MemberInfo><MemberInfo diffgr:id="MemberInfo4" msdata:rowOrder="3" diffgr:hasChanges="inserted"><Name>Jennifer</Name><Birth>1985-05-06T00:00:00+09:00</Birth><Email>jennifer@jennifersoft.com</Email><Family>0</Family>
                        </MemberInfo>
                    </NewDataSet>
                </diffgr:diffgram>
            </GetDataSetResult>
        </GetDataSetResponse>
    </s:Body>
</s:Envelope>


"Content-Length: 1884"라는 결과가 나오는군요. ^^




사실, 이 테스트를 해보는 것은 예전의 고객사 사이트에서 발생한 GZIP 인코딩 문제 때문입니다.

제니퍼 닷넷 적용 사례 (4) - GZIP 인코딩으로 인한 성능 하락
; https://www.sysnet.pe.kr/2/0/1484

System.IO.MemoryStream, ArraySegment 의 효율적인 사용법
; https://www.sysnet.pe.kr/2/0/1483

GZIP을 풀어놓자니 DataSet 크기가 여전히 마음에 걸립니다. 그나마 성능 손실 없이 데이터를 조금이라도 줄이는 방법은 바이너리 인코딩을 쓰는 것이 대안일 수 있는데요. 의외로 방법은 간단합니다. 우선, 서버 쪽 web.config에서 기존의 바인딩을 다음과 같이 binaryMessageEncoding + httpTransport로 적용해 주고,

<?xml version="1.0"?>

<configuration>
   <!-- ...[생략]... -->

  <system.serviceModel>
    <bindings>
      <customBinding>
        <binding name="HttpBinaryBindingConfig">
          <binaryMessageEncoding>
            <readerQuotas maxStringContentLength="65536000" maxArrayLength="65536000"
                          maxBytesPerRead="4096000" />
          </binaryMessageEncoding>
          <httpTransport
            maxReceivedMessageSize="65536000"
            transferMode="Buffered"
            hostNameComparisonMode="StrongWildcard"
            authenticationScheme="Anonymous" />
        </binding>
      </customBinding>
    </bindings>

    <behaviors>
      <serviceBehaviors>
        <behavior name="TestService_Behavior">
          <serviceMetadata httpGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>

    <services>
      <service behaviorConfiguration="TestService_Behavior"
               name="WebApplication1.TestService">
        <endpoint address="" binding="customBinding" bindingConfiguration="HttpBinaryBindingConfig"
                  contract="WebApplication1.ITestService">
        </endpoint>
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
      </service>
    </services>

    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
  </system.serviceModel>
</configuration>

클라이언트 측의 app.config도 그에 맞게 변경시켜주면 됩니다.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <bindings>
      <customBinding>
        <binding name="customHttpBinding_ITestService">
          <binaryMessageEncoding>
             <readerQuotas maxDepth="32" maxStringContentLength="65536000" maxArrayLength="65536000"
                maxBytesPerRead="4096000" maxNameTableCharCount="16384" />  
          </binaryMessageEncoding>
          <httpTransport
            maxReceivedMessageSize="65536000"
              transferMode="Buffered"
              hostNameComparisonMode="StrongWildcard"
            authenticationScheme="Anonymous" />
        </binding>
      </customBinding>
    </bindings>
    <client>
      <endpoint address="http://169.254.223.95:2524/TestService.svc" binding="customBinding"
          bindingConfiguration="customHttpBinding_ITestService" contract="ServiceReference1.ITestService"
          name="customHttpBinding_ITestService" />
    </client>
  </system.serviceModel>
</configuration>

그 외 변경되는 것은 아무것도 없습니다. 이렇게만 바꿔주고 통신을 시작하면 다음과 같이 클라이언트 <-> 서버 간에 통신이 바이너리로 이뤄지는 것을 확인할 수 있습니다.

POST http://169.254.223.95:2524/TestService.svc HTTP/1.1
Content-Type: application/soap+msbin1
Host: 169.254.223.95:2524
Content-Length: 329
Expect: 100-continue
Accept-Encoding: gzip, deflate
Connection: Close

V
s
aVD
???*http://tempuri.org/ITestService/GetDataSetD???i??p?L???jY?D,D*?@VsDebuggerCausalityDataAhttp://schemas.microsoft.com/vstudio/diagnostics/servicemodelsink?<???_??*s?I??:?L{???????$f?(D??9@???.?
:??I?
?s??? ??D???*http://169.254.223.95:2524/TestService.svcV@
GetDataSethttp://tempuri.org/

HTTP/1.1 200 OK
Content-Length: 1512
Content-Type: application/soap+msbin1
Server: Microsoft-IIS/8.0
X-SourceFiles: =?UTF-8?B?ZDpcc2V0dGluZ3NcRGVza3RvcFxkYXRhc2V0X2FzX2JpblxXaW5kb3dzRm9ybXNBcHBsaWNhdGlvbjFcV2ViQXBwbGljYXRpb24xXFRlc3RTZXJ2aWNlLnN2Yw==?=
X-Powered-By: ASP.NET
Date: Tue, 27 Aug 2013 08:08:23 GMT
Connection: close

V
s
aVD
???2http://tempuri.org/ITestService/GetDataSetResponseD???i??p?L???jY?V@GetDataSetResponsehttp://tempuri.org/@GetDataSetResultAxsschemaid?
NewDataSet  xs http://www.w3.org/2001/XMLSchema?  msdata$urn:schemas-microsoft-com:xml-msdataAxselementname?
NewDataSetmsdata  IsDataSet?truemsdataUseCurrentLocale?trueAxs
complexTypeAxschoice minOccurs? maxOccurs?  unboundedAxselementname?
MemberInfoAxs
complexTypeAxssequenceAxselementname?Nametype? xs:string  minOccurs?Axselementname?Birthtype?
xs:dateTime    minOccurs?Axselementname?Emailtype? xs:string  minOccurs?Axselementname?Familytype?xs:unsignedByte   minOccurs?Adiffgrdiffgram diffgr)urn:schemas-microsoft-com:xml-diffgram-v1   msdata$urn:schemas-microsoft-com:xml-msdata@
NewDataSet?@
MemberInfodiffgrid?
MemberInfo1msdatarowOrder?diffgr
hasChanges?inserted@Name?Anderson@Birth?1950-05-20T00:00:00+09:00@Email?anderson@gmail.com@Family?2@
MemberInfodiffgrid?
MemberInfo2msdatarowOrder?diffgr
hasChanges?inserted@Name?Jason@Birth?1967-12-03T00:00:00+09:00@Email?jason@gmail.com@Family?@
MemberInfodiffgrid?
MemberInfo3msdatarowOrder?2diffgr
hasChanges?inserted@Name?Mark@Birth?1998-03-02T00:00:00+09:00@Email?mark@naver.com@Family?@
MemberInfodiffgrid?
MemberInfo4msdatarowOrder?3diffgr
hasChanges?inserted@Name?Jennifer@Birth?1985-05-06T00:00:00+09:00@Email?jennifer@jennifersoft.com@Family?

이전 textMessageEncoding + HTTP로 했었을 때는 1884바이트였던 것이 binaryMessageEncoding + HTTP로 바꾸니 1512바이트로 약 20% 정도의 데이터 길이가 감소했습니다. 거의 공짜로 감소된 것 치고는 괜찮은 비율입니다. 그런데, 좀 만족스럽지 않군요. ^^

직렬화된 데이터를 보니 diffgram을 유지하고 있는 것이 별로 마음에 들지 않습니다. WCF 메소드에서 딱 1라인만 추가를 해봤습니다. (사실, 서비스로부터 받아오는 데이터의 diffgram이 유지될 이유가 별로 없지요. ^^)

public DataSet GetDataSet()
{
    DataSet ds = new DataSet();

    // ...[생략]...

    ds.Tables.Add(table);

    ds.AcceptChanges();

    return ds;            
}

그런데, AcceptChanges로 인한 결과가 매우 흥미롭습니다. 이렇게 바뀌어진 WCF 메소드는 textMessageEncoding + HTTP로는 오히려 1998바이트로 늘어났고, binaryMessageEncoding + HTTP일 때는 1396바이트로 더 줄었습니다. 그럼 1884바이트 대비 1396바이트로 하면 36% 정도의 데이터가 감소했습니다.




이에 관해서 혹시 누군가 비교한 데이터가 있지 않을까 찾아봤는데 다음의 글이 나왔습니다.

WCF over HTTPS, Compression, and Binary Binding
; http://architects.dzone.com/articles/wcf-over-https-compression-and

dataset_as_bin_1.png

텍스트 인코딩인 경우 262,149 바이트였던 것을 바이너리로 바꾸면 155,565 바이트로 40%까지 감소되었다는 테스트 결과입니다. 물론, binaryMessageEncoding + GZIP 인코딩은 86%까지 감소되었다고 나오니 어느 것을 희생할 지는... 충분한 테스트를 거쳐서 판단하는 것이 좋겠습니다.

아니면 공사가 좀 크겠지만, DataSet보다 좀 더 경량화된 DTO(Data Transfer Object)의 사용이 답일 수도 있습니다.

풀리지 않는 숙제죠. ^^

DataSet 사용을 반대하는 의견
    .NET Architecture Case Study #1 - Part 1 
     ; http://blog.naver.com/saltynut/120168527432

DataSet 사용을 찬성하는 의견
    DataSet 이야기
    ; http://www.simpleisbest.net/post/2012/09/12/About-DataSet.aspx

(첨부된 파일은 위의 테스트를 진행한 프로젝트입니다.)




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]







[최초 등록일: ]
[최종 수정일: 8/18/2021]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 16  17  18  19  20  21  22  23  24  25  26  27  28  29  [30]  ...
NoWriterDateCnt.TitleFile(s)
12897정성태1/2/20228237.NET Framework: 1128. C# - 화면 캡처한 이미지를 ffmpeg(FFmpeg.AutoGen)로 동영상 처리 [4]파일 다운로드1
12896정성태1/1/202211197.NET Framework: 1127. C# - FFmpeg.AutoGen 라이브러리를 이용한 기본 프로젝트 구성파일 다운로드1
12895정성태12/31/20219611.NET Framework: 1126. C# - snagit처럼 화면 캡처를 연속으로 수행해 동영상 제작 [1]파일 다운로드1
12894정성태12/30/20217570.NET Framework: 1125. C# - DefaultObjectPool<T>의 IDisposable 개체에 대한 풀링 문제 [3]파일 다운로드1
12893정성태12/27/20219227.NET Framework: 1124. C# - .NET Platform Extension의 ObjectPool<T> 사용법 소개파일 다운로드1
12892정성태12/26/20217172기타: 83. unsigned 형의 이전 값이 최댓값을 넘어 0을 지난 경우, 값의 차이를 계산하는 방법
12891정성태12/23/20217064스크립트: 38. 파이썬 - uwsgi의 --master 옵션
12890정성태12/23/20217223VC++: 152. Golang - (문자가 아닌) 바이트 위치를 반환하는 strings.IndexRune 함수
12889정성태12/22/20219675.NET Framework: 1123. C# - (SharpDX + DXGI) 화면 캡처한 이미지를 빠르게 JPG로 변환하는 방법파일 다운로드1
12888정성태12/21/20217739.NET Framework: 1122. C# - ImageCodecInfo 사용 시 System.Drawing.Image와 System.Drawing.Bitmap에 따른 Save 성능 차이파일 다운로드1
12887정성태12/21/20219886오류 유형: 777. OpenCVSharp4를 사용한 프로그램 실행 시 "The type initializer for 'OpenCvSharp.Internal.NativeMethods' threw an exception." 예외 발생
12886정성태12/20/20217675스크립트: 37. 파이썬 - uwsgi의 --enable-threads 옵션 [2]
12885정성태12/20/20217926오류 유형: 776. uwsgi-plugin-python3 환경에서 MySQLdb 사용 환경
12884정성태12/20/20216978개발 환경 구성: 620. Windows 10+에서 WMI root/Microsoft/Windows/WindowsUpdate 네임스페이스 제거
12883정성태12/19/20217889오류 유형: 775. uwsgi-plugin-python3 환경에서 "ModuleNotFoundError: No module named 'django'" 오류 발생
12882정성태12/18/20217014개발 환경 구성: 619. Windows Server에서 WSL을 위한 리눅스 배포본을 설치하는 방법
12881정성태12/17/20217458개발 환경 구성: 618. WSL Ubuntu 20.04에서 파이썬을 위한 uwsgi 설치 방법 (2)
12880정성태12/16/20217360VS.NET IDE: 170. Visual Studio에서 .NET Core/5+ 역어셈블 소스코드 확인하는 방법
12879정성태12/16/202113649오류 유형: 774. Windows Server 2022 + docker desktop 설치 시 WSL 2로 선택한 경우 "Failed to deploy distro docker-desktop to ..." 오류 발생
12878정성태12/15/20218672개발 환경 구성: 617. 윈도우 WSL 환경에서 같은 종류의 리눅스를 다중으로 설치하는 방법
12877정성태12/15/20217327스크립트: 36. 파이썬 - pymysql 기본 예제 코드
12876정성태12/14/20217160개발 환경 구성: 616. Custom Sources를 이용한 Azure Monitor Metric 만들기
12875정성태12/13/20216791스크립트: 35. python - time.sleep(...) 호출 시 hang이 걸리는 듯한 문제
12874정성태12/13/20216819오류 유형: 773. shell script 실행 시 "$'\r': command not found" 오류
12873정성태12/12/20217954오류 유형: 772. 리눅스 - PATH에 등록했는데도 "command not found"가 나온다면?
12872정성태12/12/20217801개발 환경 구성: 615. GoLang과 Python 빌드가 모두 가능한 docker 이미지 만들기
... 16  17  18  19  20  21  22  23  24  25  26  27  28  29  [30]  ...