방법: 사용자 지정 변경 내용 추적 시스템 사용
응용 프로그램에서 서버 데이터베이스의 변경 내용을 추적하여 후속 동기화 세션 동안 이러한 변경 내용을 클라이언트에게 전달해야 하는 경우가 많습니다. 이 항목에서는 변경 내용 추적 시스템의 요구 사항에 대해 설명하고 Sync Framework에서 사용할 수 있는 사용자 지정 시스템을 만드는 방법을 보여 줍니다. 사용자 지정 변경 내용 추적이 적합한 경우도 있지만, 이로 인해 작업이 복잡해지고 서버 데이터베이스 성능에 영향을 줄 수 있습니다. SQL Server 2008을 사용하는 경우 SQL Server 변경 내용 추적 기능을 사용하는 것이 좋습니다. 자세한 내용은 방법: SQL Server 변경 내용 추적 사용을 참조하십시오.
동기화 시나리오의 서버 요구 사항
Sync Framework은 서버 데이터베이스에 대한 영향을 최소화하도록 디자인되었습니다. 따라서 응용 프로그램에서 사용할 기능 수준이 높을수록 서버 데이터베이스의 변경 내용 추적 기능을 많이 수정해야 합니다. 이때 다음과 같은 사항을 고려해야 합니다.
다운로드 전용 데이터 스냅숏의 경우에는 변경할 필요가 없습니다.
전체 변경 내용 추적 및 충돌 검색을 사용하는 양방향 동기화의 경우에는 가장 많은 변경이 필요합니다.
다음 표에서는 Sync Framework를 활용하는 방법과 서버 데이터베이스에 대한 해당 요구 사항을 보여 줍니다.
시나리오 | 기본 키 또는 고유한 열1 | 업데이트 시간 추적 | 삽입 시간 추적 | 삭제 시간 추적 | 클라이언트 ID의 업데이트 추적 | 클라이언트 ID의 삽입 추적 | 클라이언트 ID의 삭제 추적 |
---|---|---|---|---|---|---|---|
클라이언트에 데이터 스냅숏 다운로드 |
아니요 |
아니요 |
아니요 |
아니요 |
아니요 |
아니요 |
아니요 |
클라이언트에 증분 삽입 및 업데이트 다운로드 |
예 |
예 |
예2 |
아니요 |
아니요 |
아니요 |
아니요 |
클라이언트에 증분 삽입, 업데이트 및 삭제 다운로드 |
예 |
예 |
예2 |
예 |
아니요 |
아니요 |
아니요 |
서버에 삽입 업로드 |
예 |
아니요 |
아니요 |
아니요 |
아니요 |
아니요3 |
아니요 |
서버에 삽입 및 업데이트 업로드 |
예 |
아니요 |
아니요 |
아니요 |
아니요3 |
아니요3 |
아니요 |
서버에 삽입, 업데이트 및 삭제 업로드. |
예 |
아니요 |
아니요 |
아니요 |
아니요3 |
아니요3 |
아니요3 |
충돌 검색을 통한 양방향 삽입 및 업데이트 |
예 |
예 |
예2 |
아니요 |
예4 |
예4 |
아니요 |
충돌 검색을 통한 양방향 삽입, 업데이트 및 삭제 |
예 |
예 |
예2 |
예 |
예4 |
예4 |
예4 |
1 기본 키는 모든 노드에서 고유해야 하며 다시 사용하면 안 됩니다. 행이 삭제되면 해당 행의 기본 키가 다른 행에 사용되지 않아야 합니다. 일반적으로 분산 환경에서는 ID 열을 사용하지 않는 것이 좋습니다. 기본 키에 대한 자세한 내용은 분산 환경에 대해 적절한 기본 키 선택을 참조하십시오.
2 삽입과 업데이트를 구분하려는 경우 필요합니다. 자세한 내용은 이 항목 뒷부분의 "클라이언트로 다운로드할 데이터 변경 내용 결정"을 참조하십시오.
3 둘 이상의 클라이언트에서 행을 변경할 수 있는 경우 변경을 수행한 클라이언트를 확인하는 데 필요합니다. 자세한 내용은 이 항목의 "데이터를 변경한 클라이언트 확인"을 참조하십시오.
4 변경 작업을 수행한 클라이언트에 변경 내용을 다시 에코하지 않으려는 경우 필요합니다. 자세한 내용은 이 항목의 "데이터를 변경한 클라이언트 확인"을 참조하십시오.
참고
위에서 설명한 변경 내용 이외에도 데이터 액세스용으로 저장 프로시저를 만들려고 합니다. 이 설명서에 포함되어 있는 대부분의 예제에서는 코드에서 수행하는 작업을 보다 쉽게 표시할 수 있는 인라인 SQL을 사용합니다. 프로덕션 응용 프로그램의 경우에는 코드를 캡슐화하고, 일반적으로 성능이 보다 뛰어나며, 올바르게 작성하는 경우 인라인 SQL의 보안을 향상시키는 저장 프로시저를 사용해야 합니다.
클라이언트로 다운로드할 데이터 변경 내용 결정
다운로드 전용 및 양방향 동기화의 경우에는 Sync Framework에서 클라이언트로 다운로드할 변경 내용을 결정할 수 있도록 서버에서 변경 내용을 추적해야 합니다. Sync Framework에서 변경 내용 추적 지원 방법을 구체적으로 정의하지는 않지만, 일반적으로 동기화할 각 테이블에 대해 다음 방식을 사용할 수 있습니다.
행을 서버 데이터베이스에 삽입한 시간을 추적하는 열을 추가합니다.
행을 서버 데이터베이스에서 마지막으로 업데이트한 시간을 추적하는 열 및 필요한 경우 트리거를 추가합니다.
행을 서버 데이터베이스에서 삭제한 시간을 추적하는 삭제 표식 테이블 및 트리거를 추가합니다. 데이터를 서버에서 삭제하지는 않지만 클라이언트로 삭제 내용을 보내야 하는 경우에는 기본 테이블에서 논리 삭제 내용을 추적할 수 있습니다. bit 형식의 열을 사용하면 삭제가 발생할 때 삭제되는 행 및 추적할 다른 열을 표시할 수 있습니다
이러한 열 및 삭제 표식 테이블은 앵커와 함께 다운로드할 삽입, 업데이트 및 삭제를 결정하는 데 사용됩니다. 앵커는 동기화할 변경 내용 집합을 정의하는 데 사용되는 지점입니다. 다음 쿼리를 검토하십시오.
SelectIncrementalInsertsCommand 속성에 대해 지정하는 쿼리입니다. 이 쿼리는 다음과 같이 Sync Framework 샘플 데이터베이스의
Sales.Customer
테이블에서 증분 삽입을 다운로드합니다.SELECT CustomerId, CustomerName, SalesPerson, CustomerType FROM Sales.Customer WHERE InsertTimestamp > @sync_last_received_anchor AND InsertTimestamp <= @sync_new_received_anchor
이 속성 및 동기화 명령과 관련된 다른 속성에 대한 자세한 내용은 방법: 스냅숏, 다운로드, 업로드 및 양방향 동기화 지정을 참조하십시오.
SelectNewAnchorCommand 속성에 대해 지정하는 쿼리입니다. 이 쿼리는 지정 시간 값을 검색합니다.
InsertTimestamp
열에는 타임스탬프 값이 저장됩니다. 그러므로 쿼리에서는 SQL Server 2005 서비스 팩 2에 도입된 Transact-SQLMIN_ACTIVE_ROWVERSION
함수를 사용하여 다음과 같이 서버 데이터베이스에서 타임스탬프 값을 검색합니다.SELECT @sync_new_received_anchor = MIN_ACTIVE_ROWVERSION - 1
MIN_ACTIVE_ROWVERSION은 현재 데이터베이스에서 가장 낮은 활성 timestamp(rowversion이라고도 함) 값을 반환합니다. timestamp 값은 아직 커밋되지 않은 트랜잭션에 사용되는 경우 활성 상태입니다. 데이터베이스에 활성 값이 없으면 MIN_ACTIVE_ROWVERSION은 @@DBTS + 1과 같은 값을 반환합니다. MIN_ACTIVE_ROWVERSION은 timestamp 값을 사용하여 변경 내용 집합을 그룹화하는 데이터 동기화 등의 시나리오에 유용합니다. 응용 프로그램이 앵커 명령에 MIN_ACTIVE_ROWVERSION이 아닌 @@DBTS를 사용하는 경우에는 동기화가 발생할 때 활성 상태인 변경 내용이 누락될 수 있습니다.
Sales.Customer
테이블이 처음으로 동기화되면 다음과 같은 프로세스가 수행됩니다.
새 앵커 명령이 실행됩니다. 이 명령은
0x0000000000000D49
값을 반환합니다. 이 값은 클라이언트 데이터베이스에 저장됩니다. 테이블은 아직 동기화되지 않은 상태이므로 클라이언트 데이터베이스에는 이전 동기화에서 생성된 앵커 값이 저장되어 있지 않습니다. 이 경우 Sync Framework는 SQL Server timestamp 데이터 형식에 대해 사용 가능한 가장 작은 값인0x0000000000000000
을 사용합니다. Sync Framework는 다음과 같은 쿼리를 실행하며 이 쿼리는 테이블에서 모든 행과 스키마를 다운로드합니다.exec sp_executesql N'SELECT CustomerId, CustomerName, SalesPerson, CustomerType FROM Sales.Customer WHERE (InsertTimestamp > @sync_last_received_anchor AND InsertTimestamp <= @sync_new_received_anchor)',N'@sync_last_received_anchor timestamp, @sync_new_received_anchor timestamp', @sync_last_received_anchor=0x0000000000000000, @sync_new_received_anchor=0x0000000000000D49
두 번째 동기화 중에 새 앵커 명령이 실행됩니다. 마지막 동기화 이후 행이 삽입되었으므로 명령이 값
0x0000000000000D4C
를 반환합니다. 테이블은 이전에 동기화되었으므로 Sync Framework가 앵커 값0x0000000000000D49
를 검색할 수 있습니다. 이 값은 이전 동기화의 클라이언트 데이터베이스에 저장됩니다. 다음과 같은 쿼리가 실행되며 두 앵커 값 사이에 삽입된 행만 테이블에서 다운로드됩니다.exec sp_executesql N'SELECT CustomerId, CustomerName, SalesPerson, CustomerType FROM Sales.Customer WHERE (InsertTimestamp > @sync_last_received_anchor AND InsertTimestamp <= @sync_new_received_anchor)', N'@sync_last_received_anchor timestamp, @sync_new_received_anchor timestamp', @sync_last_received_anchor=0x0000000000000D49, @sync_new_received_anchor=0x0000000000000D4C
업데이트 및 삭제 명령의 예제는 방법: 클라이언트에 증분 데이터 변경 내용 다운로드 및 방법: 클라이언트와 서버 간에 양방향 증분 데이터 변경 내용 교환을 참조하십시오.
위에서 설명한 것처럼 앵커 값을 검색하는 데 사용되는 명령은 서버 데이터베이스에 있는 추적 열의 데이터 형식에 따라 달라집니다. 이 설명서의 예제에서는 rowversion이라고도 하는 SQL Server timestamp를 사용합니다. SQL Server datetime 열을 사용하려면 새 앵커 명령의 쿼리가 다음과 같아야 합니다.
SELECT @sync_new_received_anchor = GETUTCDATE()
앵커에 사용할 데이터 형식을 결정하려면 응용 프로그램 요구 사항을 평가하고 서버 데이터베이스 스키마를 변경할 정도를 고려해야 합니다. 데이터베이스가 개발 중인 경우에는 추가할 열과 트리거를 정확하게 지정할 수 있습니다. 데이터베이스가 프로덕션 단계인 경우에는 옵션이 보다 제한될 수 있습니다. 다음 지침을 참조하십시오.
동기화 그룹의 모든 테이블은 같은 데이터 형식과 새 앵커 명령을 사용해야 합니다. 가능한 경우에는 모든 그룹에 대해 같은 데이터 형식 및 명령을 사용하십시오.
datetime 데이터 형식은 이해하기가 쉬우며, 테이블에는 행이 수정된 시간을 추적하는 열이 이미 포함되어 있는 경우가 많습니다. 그러나 클라이언트의 시간대가 다른 경우에는 이 데이터 형식을 사용하는 데 문제가 있을 수 있습니다. 이 데이터 형식을 사용하는 경우 증분 변경 내용을 선택하면 트랜잭션이 누락될 수 있습니다.
timestamp 데이터 형식은 정확도가 높으며 클록 시간을 사용하지 않습니다. 그러나 SQL Server 데이터베이스의 각 테이블에는 이러한 데이터 형식의 열이 하나만 포함될 수 있습니다. 그러므로 삽입과 업데이트를 구분해야 하는 경우에는 binary(8) 등의 다른 데이터 형식으로 된 열을 추가하고 해당 열에 타임스탬프 값을 저장할 수 있습니다. 예제를 보려면 데이터베이스 공급자용 설치 스크립트 방법 항목을 참조하십시오. timestamp 데이터 형식은 서버 데이터베이스를 백업에서 복원하는 경우 문제가 될 수 있습니다. 자세한 내용은 Sync Framework에서 지원하는 데이터베이스 개체을 참조하십시오. 위에서 설명한 것처럼 새 앵커를 선택하는 명령에는 MIN_ACTIVE_ROWVERSION을 사용하는 것이 좋습니다.
데이터를 변경한 클라이언트 확인
데이터를 변경한 클라이언트를 확인해야 하는 이유는 크게 두 가지입니다.
업로드 전용 및 양방향 동기화에서 충돌 검색 및 해결을 지원하려는 경우
서버 및 클라이언트와 둘 이상의 클라이언트가 지정된 행을 변경할 수 있는 경우 변경을 수행한 항목을 확인해야 할 수 있습니다. 이 정보를 확인하면 예를 들어 특정 변경 내용을 보다 우선적으로 적용하는 코드를 작성할 수 있습니다. 이 정보가 없으면 행에 대한 마지막 변경 내용이 유지됩니다.
양방향 동기화 중에 클라이언트에 변경 내용을 다시 에코하지 않도록 하려는 경우
Sync Framework는 변경 내용을 먼저 서버로 업로드한 다음 클라이언트로 다운로드합니다. 변경을 수행한 클라이언트의 ID를 추적하지 않는 경우 변경 내용이 동일한 동기화 세션 동안 서버로 업로드된 후에 클라이언트로 다시 다운로드됩니다. 이와 같이 변경 내용을 다시 에코할 수 있는 경우도 있지만 그렇지 않은 경우도 있습니다.
변경 내용 추적과 마찬가지로 Sync Framework에서는 ID 추적 지원 방법을 구체적으로 정의하지는 않습니다. 그러나 일반적으로는 동기화할 각 테이블에 대해 다음 방식을 사용할 수 있습니다.
각 삽입 작업을 수행한 항목을 추적하는 열을 기본 테이블에 추가합니다.
각 업데이트 작업을 수행한 항목을 추적하는 열을 기본 테이블에 추가합니다.
각 삭제 작업을 수행한 항목을 추적하는 열을 삭제 표식 테이블에 추가합니다.
이러한 열 및 테이블은 ClientId 속성과 함께 각 삽입, 업데이트 또는 삭제를 수행한 클라이언트를 확인하는 데 사용됩니다. 스냅숏 동기화 이외의 다른 방법을 사용하여 테이블을 처음으로 동기화하면 Sync Framework는 클라이언트를 식별하는 GUID 값을 해당 클라이언트에 저장합니다. 이 ID는 각 SyncAdapter의 선택 및 업데이트 쿼리에 사용할 수 있도록 DbServerSyncProvider로 전달됩니다. ID 값은 ClientId 속성을 통해 사용할 수 있습니다. 다음 Transact-SQL 쿼리를 참조하십시오.
SELECT CustomerId, CustomerName, SalesPerson, CustomerType FROM
Sales.Customer WHERE InsertTimestamp > @sync_last_received_anchor AND
InsertTimestamp <= @sync_new_received_anchor AND InsertId <>
@sync_client_id
이 쿼리는 서버에 대해 수행한 삽입 작업을 추적하는 앞서 나온 쿼리와 비슷합니다. WHERE
절의 문은 현재 동기화 중인 클라이언트가 수행하지 않은 삽입 작업만이 다운로드되도록 지정합니다.
또한 응용 프로그램에서는 Sync Framework를 통해 GUID 값 대신 서버의 정수를 사용하여 클라이언트를 식별할 수 있습니다. 자세한 내용은 방법: 세션 변수 사용을 참조하십시오.
서버 준비 예제
다음 예제에서는 가장 복잡한 응용 프로그램 시나리오(충돌 검색을 포함하는 양방향 삽입, 업데이트 및 삭제 작업)를 처리하기 위한 추적 인프라가 포함된 Sync Framework 샘플 데이터베이스에서 Sales.Customer
테이블을 설정하는 방법을 보여 줍니다. 덜 복잡한 시나리오의 경우에는 전체 인프라가 필요하지 않습니다. 자세한 내용은 이 항목 앞부분의 "동기화 시나리오의 서버 요구 사항"을 참조하십시오. 이 예제의 개체 및 추가 개체를 만드는 전체 스크립트는 데이터베이스 공급자용 설치 스크립트 방법 항목을 참조하십시오. 이러한 개체를 사용하는 방법에 대한 자세한 내용은 방법: 스냅숏, 다운로드, 업로드 및 양방향 동기화 지정을 참조하십시오.
이 단원의 예제에서는 다음 단계를 수행하여 서버를 준비합니다.
Sales.Customer
스키마를 확인합니다. 테이블에 변경 내용 추적에 사용할 수 있는 기본 키 및 열이 있는지 여부를 확인합니다.삽입 및 업데이트를 수행할 시간 및 위치를 추적하는 열을 추가합니다.
삭제 표식 테이블을 만들고
Sales.Customer
테이블에 트리거를 추가하여 삭제 표식 테이블을 채웁니다.
Sales.Customer 스키마 확인
다음 코드 예제에서는 Sales.Customer
테이블의 스키마를 보여 줍니다. 이 테이블의 CustomerId
열에는 기본 키가 있으며, 변경 내용 추적에 사용할 수 있는 열은 없습니다.
CREATE TABLE SyncSamplesDb.Sales.Customer(
CustomerId uniqueidentifier NOT NULL PRIMARY KEY DEFAULT NEWID(),
CustomerName nvarchar(100) NOT NULL,
SalesPerson nvarchar(100) NOT NULL,
CustomerType nvarchar(100) NOT NULL)
삽입 및 업데이트 작업을 추적하는 열 추가
다음 코드 예제에서는 네 개의 열인 UpdateTimestamp
, InsertTimestamp
, UpdateId
및 InsertId
를 추가합니다. UpdateTimestamp
열은 SQL Server timestamp
열로, 행이 업데이트될 때 자동으로 업데이트됩니다. 위에서 설명한 것처럼 테이블에 timestamp
열 하나만 있을 수 있습니다. 그러므로 InsertTimestamp
열은 기본값이 @@DBTS + 1
인 binary(8)
열입니다. 이 예제에서는 삽입 작업을 수행한 후에 UpdateTimestamp
및 InsertTimestamp
열의 값이 같도록 @@DBTS
에서 반환하는 값을 추가합니다. 이를 수행하지 않으면 각 행이 삽입된 후에 업데이트되는 것처럼 표시됩니다.
Sync Framework가 각 클라이언트에 대해 만드는 ID는 GUID이므로 두 개의 ID 열은 uniqueidentifier
열이 됩니다. 열의 기본값은 00000000-0000-0000-0000-000000000000
입니다. 이 값은 서버에서 업데이트 또는 삽입 작업을 수행했음을 나타냅니다. 뒤에 나오는 예제의 경우 삭제 표식 테이블에 DeleteId
열이 포함되어 있습니다.
ALTER TABLE SyncSamplesDb.Sales.Customer
ADD UpdateTimestamp timestamp
ALTER TABLE SyncSamplesDb.Sales.Customer
ADD InsertTimestamp binary(8) DEFAULT @@DBTS + 1
ALTER TABLE SyncSamplesDb.Sales.Customer
ADD UpdateId uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'
ALTER TABLE SyncSamplesDb.Sales.Customer
ADD InsertId uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'
이제 열이 추가되었습니다. 다음 예제 코드에서 인덱스를 추가합니다. 여기서 추가되는 인덱스 및 예제 코드의 다른 인덱스는 동기화 중에 쿼리되는 열에서 만들어집니다. 인덱스는 서버 데이터베이스에서 변경 내용 추적을 구현할 때 인덱스를 고려해야 함을 강조하기 위해 추가됩니다. 서버 성능과 동기화 성능이 균형을 이루도록 조정해야 합니다.
CREATE NONCLUSTERED INDEX IX_Customer_UpdateTimestamp
ON Sales.Customer(UpdateTimestamp)
CREATE NONCLUSTERED INDEX IX_Customer_InsertTimestamp
ON Sales.Customer(InsertTimestamp)
CREATE NONCLUSTERED INDEX IX_Customer_UpdateId
ON Sales.Customer(UpdateId)
CREATE NONCLUSTERED INDEX IX_Customer_InsertId
ON Sales.Customer(InsertId)
삭제 작업을 추적하는 삭제 기록 테이블 추가
다음 코드 예제에서는 테이블을 채우는 클러스터된 인덱스 및 트리거가 포함된 삭제 표식 테이블을 만듭니다. Sales.Customer
테이블에서 삭제 작업이 수행되면 트리거가 Sales.Customer_Tombstone
테이블에 행을 삽입합니다. 트리거는 삽입 작업을 수행하기 전에 Sales.Customer_Tombstone
테이블에 삭제된 행의 기본 키를 포함하는 행이 이미 있는지 여부를 확인합니다. 이는 행을 Sales.Customer
에서 삭제한 다음 다시 삽입했다가 다시 삭제한 경우 발생합니다. 이러한 행이 Sales.Customer_Tombstone
에 있으면 트리거는 해당 행을 삭제하고 다시 삽입합니다. Sales.Customer_Tombstone
의 DeleteTimestamp
열도 업데이트할 수 있습니다.
CREATE TABLE SyncSamplesDb.Sales.Customer_Tombstone(
CustomerId uniqueidentifier NOT NULL PRIMARY KEY NONCLUSTERED,
CustomerName nvarchar(100) NOT NULL,
SalesPerson nvarchar(100) NOT NULL,
CustomerType nvarchar(100) NOT NULL,
DeleteId uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000',
DeleteTimestamp timestamp)
CREATE TRIGGER Customer_DeleteTrigger
ON SyncSamplesDb.Sales.Customer FOR DELETE
AS
BEGIN
SET NOCOUNT ON
DELETE FROM SyncSamplesDb.Sales.Customer_Tombstone
WHERE CustomerId IN (SELECT CustomerId FROM deleted)
INSERT INTO SyncSamplesDb.Sales.Customer_Tombstone (CustomerId, CustomerName, SalesPerson, CustomerType)
SELECT CustomerId, CustomerName, SalesPerson, CustomerType FROM deleted
SET NOCOUNT OFF
END
CREATE CLUSTERED INDEX IX_Customer_Tombstone_DeleteTimestamp
ON Sales.Customer_Tombstone(DeleteTimestamp)
CREATE NONCLUSTERED INDEX IX_Customer_Tombstone_DeleteId
ON Sales.Customer_Tombstone(DeleteId)