아래는 'Microsoft Sync Framework에 대한 고찰'이라는 제목의 글입니다.

http://blog.powerumc.kr/214

 

여기서는 MSF의 동기화 알고리즘과 위의 글에서 '당신이 원하는 것은 뭐야?'에 대해서 살펴보도록 하겠습니다.

 


이해를 돕기 위해서 위의 그림을 실제 코드로 나타내면 아래와 같습니다.

SyncOperationStatistics stats;

SyncOrchestrator agent = new SyncOrchestrator();

agent.Direction = direction;

agent.LocalProvider = providerNameA;

agent.RemoteProvider = providerNameB;

stats = agent.Synchronize();

=> MSF에서 제공해주는 SyncOrchestrator클래스를 생성하고 Direction를 지정해주는데 Direction에서는 Upload, Download, UploadAndDownload, DownloadAndUpload가 있습니다. 그리고 로컬과 원격 Provider를 지정해주고 Synchronize 메소드를 호출해줌으로써 동기화를 수행합니다.

Source는 파일, DB 등이 될 수 있습니다. 대상 소스, 원본 소스라고도 표현을 하는데 이 표현이 헷갈려 좀 더 일반적인(?) Client Server라고 표현했습니다.

MSF에서 제공해주는 Provider를 상속받아서 Client Server Provider를 구현해주고 위의 코드와 같이 호출만 해주면 동기화를 수행하는 것입니다.

MSF에서 제공해주는 Provider의 중요 메소드들은 아래와 같습니다.

메소드명

역할

Source

BeginSession

항목과 항목에 대한 메타데이터를 가져옵니다.

Client(2),
Server(1)

GetChangeBatch

Sever측의 항목 변경에 대한 메타데이터를 Client Provider에게 제공합니다.

Server(3)

ProcessChangeBatch

각 항목 변경에 대해서 SaveItemChange()를 호출합니다.

Client(4)

SaveItemChange

변경된 항목에 대해서 자신의 메타데이터에 기록합니다.

Client(5)

EndSession

변경된 항목을 저장해주고 메타데이터를 닫아줍니다.

Client(6),
Server(7)

=> Direction Download로 주고 Synchronize()메소드를 실행하면 위의 표에서()안에 넣은 순서대로 Provider의 메소드가 실행이 됩니다. MSF에서는 위의 과정을 통해서 단방향 동기화됩니다. 설명의 편의상 Client Server로 표현했을 뿐 만약에 Direction의 방향이 Upload로 변경된다면 Client->Server가 되고 Server->Client가 되어 동기화 과정을 수행하게 됩니다. 마지막으로 Direction DownloadAndUpload UploadAndDownload로 처리하였을 경우에는 위의 과정이 두 번 실행되는 것을 의미합니다.

 

그럼 이제 '당신이 원하는 것이 뭐야?'에 대해서 알아보도록 하겠습니다. 

여기서 설명하는 모든 과정은 아래의 예제에 해당하는 것입니다.

Sync101 RCA with Metadata Store(WCF)

http://code.msdn.microsoft.com/Release/ProjectReleases.aspx?ProjectName=sync&ReleaseId=1713

 

위의 예제의 아키텍처를 간단히 그려본 것입니다. 'Microsoft Sync Framework에 대한 고찰'이라는 글에서도 나오지만 파일로 동기화를 하려면 원격 서버의 폴더를 공유해야하고 노출된 폴더의 사용자 권한 문제 라던지 공유가 되더라도 파일이 10,000개 까지만 공유되는 문제도 있다고 합니다. 이러한 문제들이 WCF를 사용하여 해결할 수 있습니다.

 

먼저 Server측에서는 기존에 만들어 놓은 Provider를 멤버로 하여 메소드들을 WCF의 오퍼레이션(메소드)으로 노출시켜주는 것입니다. 그러면 동기화를 수행할 때마다 위의 표에서 나온 메소드들이 순차적으로 실행이 됩니다. Client측에서는 RemoteProviderProxy라는 프록시 클래스를 만들어서 WCF를 호출해줍니다. 이 과정을 조금 더 자세히 설명하면 WCF에 노출된 클래스의 Client 클래스가 만들어지는데 System.ServiceModel.ClientBase<T>를 상속받는 클래스입니다. 이 클래스를 MSF Provider에 넣을 수 없기 때문에 RemoteProviderProxy라는 클래스를 만들어 MSF에서 제공해주는 Provider를 상속받고 이 안에서 WCF Client클래스를 멤버로 하여 메소드들을 노출해주는 것입니다.

 

Posted by resisa
,

"저장 프로시저 사용해보셨나요?" 어떤 분이 저에게 이렇게 묻는다면 저는 "아니요. "라고 대답을 할 것입니다. 안타깝게도(?) 프로젝트에서 전 저장 프로시저를 사용해보지 못했습니다. 그래서 전 저장프로시저에 대해서 잘모릅니다. 이번에는 진짜로 제가 받은 질문입니다. 개발시에 Inline쿼리와 저장 프로시저중에 선택을 해야 한다면 어떤 것을 선택할 것이냐는 질문입니다. 개발인력이 많다면 전 Inline쿼리를 사용할 것이라고 대답했습니다. 개발인력이 많다는 것은 제 개인적인 생각이지만 저처럼 저장 프로시저를 사용해보지 못한 사람들도 많을 것이라는 생각때문이였고 저장 프로시저에 대해서 제 자신이 잘 모르기 때문입니다. 그 분이 다시 저에게 이런 말을 했습니다. 저장 프로시저는 컴파일이 된 이후에 다음에 사용할 경우에는 캐쉬된 상태로 사용되기 때문에 당연히 저장 프로시저를 사용해야 된다는 말이였습니다. 그래서 제가 정적SQL인지 동적SQL인지 명칭이 확실하지는 않지만 파라미터를 사용하는 쿼리는(MSSQL은 @문자로 오라클은 :문자로 받을 경우) 컴파일을 한번만 한다고 말했지만 그 분은 아니라고 하셨습니다. 그럼 한 번 확인해볼까요?

테스트 환경은 운영체제는 Vista, DB는 MSSQL2005이며 System.Data.SqlClient를 사용해서 확인해보도록 하겠습니다. 코드는 아래와 같습니다.
1. 파라미터 없는 SQL

string ConnStr = "Data Source=(local);Initial Catalog=TestDB;Persist Security Info=True;User ID=sa;Password=dsdvp";

string query = " SELECT * FROM Student WHERE ID = '2' "; 

SqlConnection conn = new SqlConnection(ConnStr);

SqlCommand cmd = new SqlCommand(query, conn); 

conn.Open();

cmd.ExecuteNonQuery(); 

conn.Close();


2. 파라미터 있는 SQL
string ConnStr = "Data Source=(local);Initial Catalog=TestDB;Persist Security Info=True;User ID=sa;Password=dsdvp";

string query = " SELECT * FROM Student WHERE ID = @ID "; 

SqlConnection conn = new SqlConnection(ConnStr);

SqlCommand cmd = new SqlCommand(query, conn); 

SqlParameter param = new SqlParameter("ID", "2");

cmd.Parameters.Add(param); 

conn.Open();

cmd.ExecuteNonQuery(); 

conn.Close();


3. 저장 프로시저
string ConnStr = "Data Source=(local);Initial Catalog=TestDB;Persist Security Info=True;User ID=sa;Password=dsdvp"; 

SqlConnection conn = new SqlConnection(ConnStr);

SqlCommand cmd = new SqlCommand();

cmd.Connection = conn;

cmd.CommandType = CommandType.StoredProcedure;

cmd.CommandText = "TestProc"; 

conn.Open();

cmd.ExecuteNonQuery(); 

conn.Close();

=> Inline쿼리나 저장 프로시저가 컴파일되는지의 여부를 알기 위해서 이전에 SQL 튜닝교육을 받았던 문서를 보다가 운영체제에서 제공해주는 XP에서는 관리도구-성능 Vista에서는 관리도구-안정성 및 성능모니터를 사용하였습니다. 성능모니터를 하기 위해서는 카운터를 추가해주어야 합니다. SQL Server:SQL Statstics에서 SQL Compilations/sec과 SQL Re-Complilations/sec를 추가해주고 배율을 좀 더 확인하기 쉽도록 1에서 10으로 변경해준 후에 위의 구문 3개를 실행해보았습니다.
먼저 1, 2, 3번을 차례대로 실행했을 경우의 화면입니다.
=> 파라미터가 없는 SQL이 가장 높게 나타나는 것을 볼 수 있고 파라미터가 있는 SQL과 저장 프로시저를 동일하게 나타납니다. 이후의 1, 2, 3번 어떤 것을 계속 실행하여도 다시 컴파일 되는 것을 볼 수 없습니다. 혹시나 1, 2, 3번을 실행한 순서에 관계가 있나 싶어 서비스에서 SQL를 내렸다 다시 올려 번갈아 가면서 실행해 보았지만 순서만 달라질 뿐 위의 결과와 똑같은 것을 확인할 수 있었습니다. 여기서 Inline쿼리도 저장 프로시저와 마찬가지로 한번만 컴파일 된다는 것을 알 수 있습니다. 저는 2, 3번만 컴파일이 한번 될 줄 알았는데 1번도 컴파일은 한번만 됩니다. 그런데 왜 1번만 컴파일 과정에서 높게 나타날까요? SQL Server Profiler로 알아보도록 하겠습니다.

새추적를 누르고 '모든 이벤트 표시'를 체크해준 후에 Stored Procedures와 TSQL의 모든 이벤트를 체크해줍니다. 그리고 1, 2, 3번을 실행시킨 화면입니다.

1. 파라미터 없는 SQL

2. 파라미터 있는 SQL
3. 저장 프로시저
=> 차이가 보이시나요? 1번에서는 SP:CacheInsert가 두 번 되는 것을 확인하실 수 있습니다. 그래서 2번이나 3번보다 컴파일시에 더 높게 나타나는 것입니다. 다음은 컴파일 된 이후에 1번을 실행시킨 것을 추적한 화면입니다.

=> SP:CacheHit가 일어나는 것으로 보아 그냥 날리는 Inline의 쿼리의 경우에도 캐쉬가 되는 것을 알 수있습니다. 더군다나 파라미터가 있는 SQL의 경우에는 마치 저장 프로시저처럼 실행이 되는 것도 알 수 있었습니다. 컴파일도 캐쉬도 Inline쿼리가 저장 프로시저보다 사용하기에 나쁜 이유는 되지 않는 것 같습니다.

다음은 제가 SQL 튜닝 교육을 받았던 강사님의 홈페이지에 대한 저장 프로시저에 대한 내용입니다.
http://www.sqlworld.pe.kr/
1. 저장 프로시져에 사용된 모든 구문이 미리 분석되어 최적화된 후 처음 수행시 메모리에 올려져 이후에 사용 될때는 메모리에 올려진 내용이 수행되므로 속도가 월등히 빠릅니다.
2. 복잡한 퀴리문을 네트워크를 통하여 서버로 보낼 필요가 없이, 단지 저장 프로시져를 호출하는 간단한 내용만 서버로 전달되므로 네트워크 트래픽이 감소됩니다.
3. 특정 테이블에 대한 권한이 없는 사용자 계정에 저장 프로시져를 수행 할 수 있는 권한을 주어 필요한 작업을 할 수있게 할 수 있으므로 보안성을 높일 수 있습니다.
4. 특정 기능을 수행하는 저장 프로시져를 만들어 두면 여러 응용프로그램에서 이를 활용 할 수 있습니다. 즉, 특정처리를 위한 모듈화작업이 가능합니다. 모듈화가 되어 있으므로 응용프로그램 전체의 수정없이 해당 저장 프로시져만을 수정하여 원하는 기능 구현을 할수 있습니다.
=> 1번의 경우 월등히 빠르다는 위에서 제가 테스트한 결과와 조금은 다른 내용으로 저에게 질문을 하신 분과 같은 의견입니다. 2번의 경우가 제가 표현을 못했던 가장 중요한 이유입니다. 복잡한 쿼리문을 네트워크로 전송하는 것보다는 저장 프로시저의 이름만 넘기는 것이 당연히 네트워크의 부하를 주지 않을 것입니다. 하지만 이 경우도 일반적인 응용 프로그램이 아닌 웹 프로그램이면서 웹서버와 DB가 같이 돌고 있는 경우라면 그렇게 큰 차이가 있을까요? 3번의 경우에는 응용 프로그램 자체에서 권한 관리를 한다면 굳이 상관없어 보입니다. 4번이 인라인과 저장 프로시저를 어떤 것을 선택해야 하는지의 가장 중요한 지표가 되는 것 같습니다. 쿼리 부분을 프로그램에서 관리할 것인지 DB안에 저장 프로시저로 관리할 것인지의 여부입니다.

일반적으로 비지니스 로직 레이어에서 트랜잭션을 발생시킵니다. 저장 프로시저를 사용하면 저장 프로시저안에서 트랜잭션 처리를 하게 되고 실질적으로 비지니스 로직 레이어가 의미가 없어져 버립니다. 하지만 이 트랜잭션이라는 것이 단순히 DB에 대한 트랜잭션만 있어야 하는 것일까요? 파일이나 레지스트리에 대한 모든 것에 트랜잭션으로 한꺼번에 처리하고 싶을 경우에도 저장 프로시저로 모든 것이 가능할까란 생각이 제일 먼저 듭니다. 또한 비지니스 로직에 계산 로직 등 복잡한 로직이 들어간 상태에서 트랜잭션 처리를 해야 한다면 저장 프로시저에서는 어떻게 처리할까요? 다시 한번 말씀드리지만 저는 저장 프로시저를 싫어하는 사람이 아니라 저장 프로시저에 대해서 잘 모르는 사람입니다. 아마 정답은 상황에 맞게 Inline쿼리와 저장 프로시저를 적절하게(?) 사용하는 것 아닐까요?

Posted by resisa
,

먼저 HierarchicalDataTemplate에 대한 MSDN 예제를 간단히 살펴보고 계층적으로 똑같은 도메인 객체를 바인딩할 경우와 TreeView의 성능에 대한 문제에 대해서 살펴보도록 하겠습니다.

아래는 MSDN에서 HierarchicalDataTemplate 예제입니다.
http://msdn.microsoft.com/ko-kr/library/ms771440.aspx

아래는 위의 예제를 실행시킨 화면과 소스입니다.

public class Division

{

    public Division(string name)

    {

        _name = name;

        _teams = new List<Team>();

 

    }

 

    string _name;

 

    public string Name { get { return _name; } }

 

    List<Team> _teams;

 

    public List<Team> Teams { get { return _teams; } }

 

}

<src:ListLeagueList x:Key="MyList"/>


<HierarchicalDataTemplate DataType    = "{x:Type src:League}"

                        ItemsSource = "{Binding Path=Divisions}">

<TextBlock Text="{Binding Path=Name}"/>

</HierarchicalDataTemplate>

 

<HierarchicalDataTemplate DataType    = "{x:Type src:Division}"

                        ItemsSource = "{Binding Path=Teams}">

<TextBlock Text="{Binding Path=Name}"/>

</HierarchicalDataTemplate>

 

<DataTemplate DataType="{x:Type src:Team}">

<TextBlock Text="{Binding Path=Name}"/>

</DataTemplate>

<TreeView>

  <TreeViewItem ItemsSource="{Binding Source={StaticResource MyList}}" Header="My Soccer Leagues" />

</TreeView>

 

=> 먼저 바인딩되는 객체는 XAML코드에 정의된 key MyList ListLeagueList : List<League> 타입의 객체입니다. 이중에서 Division 클래스와 Team 클래스의 관계를 살펴보면 1:N의 관계로 설정이 된 것을 볼 수 있고 이렇게 계층적으로 나타낼 수 있는 객체를 HierarchicalDataTemplate를 이용해서 바인딩 하는 것을 볼 수 있습니다.


그렇다면 동일 클래스를 계층적으로 나타내고 싶을 경우에 도메인 모델을 어떻게 설계해야 할까요? 처음에는 단순하게 도메인 모델을 여러개로 나눠서 계층적으로 설계를 하다가 계층이 많아지면 계속 그 만큼 만들어주어야 하는 비효율적이고 수동적인 느낌이 들었습니다. 그래서 그냥 심플하게 자신의 클래스 안에 List<>형태로 자신을 가지고 있으면 된다는 생각이 들었습니다. 저는 기초코드성 코드를 계층적으로 나타낼 경우에 아래와 같이 도메인 모델 클래스를 만들었습니다.
public class BaseCode

{

    public BaseCode(string name)

    {

        this.Name = name;

        SubList = new List<BaseCode>();

    }

 

    public string Name { get; set; }

 

    public List<BaseCode> SubList { get; set; }

}

<src:ListBaseCodeList x:Key="MyList"/>

 

<HierarchicalDataTemplate DataType    = "{x:Type src:BaseCode}"

                        ItemsSource = "{Binding Path=SubList}">

<TextBlock Text="{Binding Path=Name}"/>

</HierarchicalDataTemplate>

=>  BaseCode 클래스를 살펴보면 BaseCode 클래스 안의 프로퍼티로 List<>형태로 자신과 똑같은 클래스를 가지고 있습니다. 그러면 그 List<>안에 있는 클래스도 마찬가지로 List<>형태의 자신의 클래스를 가지고 있는 것처럼 계층적으로 도메인 모델을 나타낼 수 있습니다. 이어서 XAML코드를 살펴보면 HierarchicalDataTemplate를 하나만 해준 것을 볼 수 있습니다. 이것은 바로 DataType 프로퍼티의 힘으로 똑같은 데이터 타입을 계층적으로 표시해줍니다. 아래 화면은 실행화면입니다.


=> 여기서 몇 가지 생각해 볼 사항들이 있습니다. 자식 노드에서 부모 노드의 값을 알고 싶거나 TreeView에 바인딩 되는 데이터의 양이 너무 많을 경우 트리를 펼칠 경우에 자식의 자식 노드(펼치는 순간 자식 노드를 펼칠 수 있는지 없는지를 알아야하기 때문에)를 가져오는 방법 등입니다.

일반적으로 TreeView에 바인딩 되는 데이터를 가공(쿼리 결과 값등)하는 시간이 그렇게 오래 걸리지 않는다면 TreeView에 모든 데이터를 바인딩하고 TreeView에 성능을 개선하는 방법으로 해결하면 될 것이라 생각됩니다.
그럼 TreeView의 성능에 대한 문제점에 대해서 살펴보도록 하겠습니다.
표시할 데이터의 양이 많아지면 프로그램에 성능에 당연히 영향을 주게됩니다. 많은 데이터를 컨트롤에 바인딩 할 경우에는 그 항목을 표시해주기 위한 레이아웃 컨테이너를 만들고 해당 레이아웃의 크기와 위치를 계산하기 때문입니다. 이러한 성능과 관련된 요소를 크게 2가지로 나타낼 수 있습니다.
1. 컨테이너의 재활용
2. UI 가상화
컨테이너의 재활용은 위에서 말했던 레이아웃 컨테이너를 만들고 사라지는 과정 대신에 이미 한번 만들어진 컨테이너를 재활용을 하는 것을 말하며 UI가상화는 항목이 실제로 표시될 때까지는 해당 항목에 대한 항목 컨테이너 생성 및 연결된 레이아웃에 대한 계산을 지연시키는 것을 말합니다.

그럼 이 두가지를 사용한 예제는 MSDN에 있습니다. 단순히 프로퍼티를 설정해줌으로써 성능상의 얼마만큼의 영향을 주는지 직접 한번 실행해보시면 조금은 놀라실 수 있을 것입니다.
http://msdn.microsoft.com/ko-kr/library/cc716882.aspx

VirtualizingStackPanel.IsVirtualizing="True"

VirtualizingStackPanel.VirtualizationMode="Recycling"

=> 예제에서는 최상위 항목을 100개 그 항목마다 하위 항목을 10개를 가지고 있었는데 그렇게 큰 차이는 느껴지지 않아 최상위 항목을 1000개로 늘려보았더니 위의 프로퍼티를 지정해줄 경우에는 이전과 차이가 없었지만 위의 프로퍼티를 지정해주지 않을 경우에는 엄청나게 느린 것을 확실히 느낄 수 있었습니다.
Posted by resisa
,