Push를 통한 데이타 Refresh 기법

[제목] Push를 통한 데이타 Refresh 기법

데이타를 서버로부터 가져오는 일반적인 방식은 클라이언트가 서버에 데이타를 요청하는 Request를 보내고, 서버는 요청된 데이타를 DB 테이블로부터 읽어 클라이언트에게 주는 것이다. 이러한 방식은 클라이언트가 필요할 때마다 자료를 서버에 요청한다는 점에 On-demand Pull 방식의 접근이라 할 수 있다.

만약 데이타가 주기적으로 자주 변하는 성격의 데이타라면 - 예를 들어 1분마다 자료가 변경되는 경우 - 자동 Refresh를 위해 아마 Timer 를 사용하여 주기적으로 데이타를 서버에 요청해서 새로운 데이타를 가져오는 방식을 사용할 수 있을 것이다. 이러한 주기적 데이타 Fetch는 기본적으로 Pull 방식을 이용한 Refresh 방식인데, 데이타 변경이 항시적이거나 주기적인 경우 유용한 접근 방식이라 볼 수 있다.

그런데, 이러한 Pull 방식을 데이타 변경이 매우 불규칙적으로 발생하는 경우에 적용한다면 어떠한 문제점이 발생할까? 즉, 데이타 변경이 어떤 경우는 1분에 한번씩 일어나다가 어떤 경우는 10시간마다 한번씩 일어나는데, 이때 만약 1분마다 데이타 요청을 보내면 너무 자주 불필요한 데이타 Refresh를 요청하는 것이 될 것이고, 만약 10시간마다 Refresh 한다면 거의 자동 Refresh라 부를 수도 없을 것이다.

이러한 문제점은 Push를 통한 데이타 Refresh 기법을 사용하여 해결할 수 있다. 즉, DB 서버에서 데이타 변경이 일어나면 이를 감지해서 클라이언트에게 통지를 보내는 것이다. 클라이언트는 서버에서 변경 통지를 받을 때만, 서버에 가서 새로운 데이타셋을 가져오면 된다. 기술적으로 이러한 아키텍쳐에는 두가지 사항이 필요하다. 첫째로 DB 서버가 데이타 변경을 Detect해서 클라이언트에게 변경 통보를 해 줘야하고, 둘째로 클라이언트의 Data Access (ex: ADO.NET) 측에서 변경을 감지하여 특정 코드를 실행할 수 있어야 한다.

첫번째 서버 데이타 감지 및 변경 통보는 SQL Server에서는 Service Broker를 이용하여 구현하게 된다. SQL Server의 Database 옵션에는 Service Broker를 Enable하는 옵션이 있는데, Push 기능을 이용하고자 하는 Database에서 이 옵션을 Enable 해주면 된다. 즉, SQL Server Management Studio의 Object Explorer에서 해당 Database 속성창을 열고, 옵션 탭에서 [Broker Enabled]를 True로 설정하면 된다 (또는 ALTER DATABASE 사용).

서비스 브로커 Enable

이렇게 서버 설정이 끝났으면, C# / ADO.NET 코드에서 System.Data.SqlClient.SqlDependency 클래스를 사용하여 변경을 통보 받는 부분을 구현한다. 먼저 SqlDependency.Start(connectionString) 메서드를 실행하여 연결 스트링에 있는 해당 SQL 서버에 Service Broker의 담당 Queue와 담당 Service객체를 생성하게 한다 (아래 그림 참조). 이는 C# 클라이언트 입장에서 미리 SQL 서버의 Service Broker를 이용할 준비를 해 놓는 것이다.

private void Form1_Load(object sender, EventArgs e)
{
	SqlDependency.Start(strConn);           
	this.GetData();
}
Service Broker 큐 시작 상태

이렇게 Service Broker가 준비된 후에는 SqlCommand를 사용하여 SQL 쿼리를 실행하여 데이타를 가져온다. 여기서 한가지 추가 사항은 SqlCommand 객체에서 SQL 실행 메서드 (ex: ExecuteReader)를 시작하기 전에 SqlDependency 객체를 생성하고 변경 통보를 받을 이벤트 핸들러를 지정한다는 것이다.

string sql = "SELECT [EmpId],[Name],[MgrId] FROM dbo.Emp";

using (SqlConnection conn = new SqlConnection(strConn))
{
	conn.Open();
	var cmd = new SqlCommand(sql, conn);
	// (optional) 이전 Notification 삭제
	cmd.Notification = null;
	
	// SqlDependency객체 생성
	var sqlDependency = new SqlDependency(cmd);
	// SqlDependency.OnChange 이벤트 핸들러 지정
	sqlDependency.OnChange += new OnChangeEventHandler(Dependency_OnChange);
	
	// cmd 객체 SQL 실행, 데이타 Fetch
	SqlDataReader rdr = cmd.ExecuteReader();

              // 그리드에 결과 표시
	DataTable dt = new DataTable();
	dt.Load(rdr);
	dataGridView1.DataSource = dt;
}

즉, 위의 예제에서 보면 cmd 객체를 파라미터로 받는 SqlDependency 클래스 객체를 만든 후, 변경 감지를 위한 OnChange 이벤트에 대한 이벤트 핸들러를 추가해 주고 있다.

여기서 한가지 주목할 점은 [SQL] 문을 어떻게 써야하는지에 대한 몇가지 제약 조건들이 있다는 것이다.
(1) 먼저 테이블명은 [schema].[tableName] 형식으로 반드시 [schema]를 붙여야 한다. 즉, 위의 경우 dbo.Emp 처럼 써야하고 dbo를 빼고 Emp만을 쓰면 에러가 발생한다.
(2) SELECT문에 SELECT할 모든 컬럼명을 명시해 주어야 한다. 즉, SELECT * FROM ... 처럼 모든 컬럼을 나타내는 * 를 사용해서는 안된다.

어쨌든 SQL 문장이 정상적으르 실행되었다면 (위의 예에서는) DataGridView에 데이타를 바인딩해서 뿌려주게 될 것이다. 이렇게 SqlCommand가 실행되고 결과를 그리드에 뿌리는 동시에, ADO.NET은 서버에서 변경 통보가 오는 지를 계속 모니터링하게 되고. 만약 변경 통보가 오면 아래와 같은 Dependency_OnChange() 이벤트 핸들러는 실행하게 된다.

void Dependency_OnChange(object sender, SqlNotificationEventArgs e)
{    
    if (e.Info.ToString().StartsWith("Invalid")) // 에러
    {
        MessageBox.Show(e.Info.ToString());
    }
    else // 정상
    {
        if (this.InvokeRequired)
        {
            this.Invoke((MethodInvoker)delegate { GetData(); });
        }
        else
        {
            GetData();
        }
    }
}

위의 이벤트 핸들러는 먼저 전달 파라미터인 e.Info를 체크하여 에러가 발생했는지 확인하고, 만약 정상이면 서버에서 데이타를 다시 가져와 데이타 Refresh를 하게 된다.

데이타 Refresh
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
    SqlDependency.Stop(strConn);
}

마지막으로 Service Broker를 모두 사용한 후에는 SqlDependency.Stop()을 호출하는데, Stop() 메서드가 호출되면 SQL 서버상의 Service Broker의 Queue와 Service가 지워지게 된다.



본 웹사이트는 광고를 포함하고 있습니다. 광고 클릭에서 발생하는 수익금은 모두 웹사이트 서버의 유지 및 관리, 그리고 기술 콘텐츠 향상을 위해 쓰여집니다.