델타 피드가 늦게 커밋된 행을 놓치는 이유: 신뢰할 수 있는 커서 만들기
한 행이 늦게 도착한 것은 워터마크가 커밋 경계가 아니었기 때문입니다. 실패를 따라가고, 이어서 커버리지·경계·보존·안전한 재개를 정의합니다.
여기 두 개의 서로 다른 엔진에서 두 번 목격한 실패가 있습니다. 내보내기는 10:00에 실행되어 자신이 본 가장 높은 최종 수정 타임스탬프를 기록합니다. 09:58에 시작된 한 트랜잭션은 아직 열려 있고, 10:03에 커밋되며 시작 시점의 타임스탬프를 지닙니다. 다음 내보내기는 10:00보다 새로운 행만 요청하므로 그 행을 결코 보지 못합니다. 전송 크기는 훌륭했고 데이터는 틀렸습니다.
한 행을 잃은 내보내기를 따라가기
SQL이 아니라 사건의 순서를 따라가십시오. 애플리케이션은 행의 수정 시간을 트랜잭션 시작 시 기록하지 커밋 시에는 기록하지 않습니다. 내보내기는 그 트랜잭션이 아직 열려 있는 동안 벽시계 워터마크를 읽습니다. 그다음 트랜잭션은 저장된 워터마크보다 오래된 수정 시간으로 커밋됩니다. 소비자의 다음 배치는 워터마크 이후에 시작하므로 그 행은 커서 뒤에 남고 다시는 선택되지 않습니다.
아무것도 오류를 기록하지 않습니다. 아무것도 실패하지 않았기 때문입니다. 이것이 이 부류의 버그를 비싸게 만듭니다. 델타 피드는 빠르고, 행 수는 그럴듯해 보이며, 빠진 행은 몇 주 뒤 아무도 재현할 수 없는 대사 티켓으로 떠오릅니다.
시퀀스 번호에는 다른 이유로 같은 형태의 결함이 있습니다. 단조 ID는 값이 할당된 순서를 알려 줄 뿐 트랜잭션이 커밋된 순서를 알려 주지 않습니다. 낮은 번호를 가져간 트랜잭션이 높은 번호를 가져간 트랜잭션보다 늦게 커밋될 수 있으므로, 할당 순서에 기반한 커서도 구멍을 남길 수 있습니다.
피드가 지켜야 할 다섯 가지 약속
전송 방식을 고르기 전에 원본 시스템에 다섯 조항의 계약을 요구하십시오. 어느 조항이 빠져 있다면 그것은 변경 피드가 아니라 경험칙입니다.
- 커버리지: 삽입, 갱신, 삭제. 각각 적용에 필요한 안정적인 키를 함께 가집니다.
- 경계: 커서 값이 무엇을 뜻하고 일관된 배치가 무엇을 담는지.
- 일관성: 호환되지 않는 원본 상태를 섞지 않고 관련 테이블을 읽는 방법.
- 보존: 연결이 끊긴 소비자가 저장된 위치에서 얼마나 오래 재개할 수 있는지.
- 재초기화: 그 위치가 더 이상 유효하지 않을 때 지원되는 전체 스냅숏.
지원되는 메커니즘은 다섯 가지를 모두 충족할 수 있습니다. SQL Server의 변경 추적 문서가 구체적인 공개 사례입니다. 최소 유효 버전을 정의하고, 변경과 함께 삭제도 기록하며, 검증·버전 캡처·관련 변경 쿼리를 아우르는 스냅숏 트랜잭션을 설명합니다. 이것은 원본에 요구해야 할 것의 예이지 다른 엔진에 붙여 넣을 구문이 아닙니다.
체크포인트를 파이프라인의 일부로 설계하기
소비자의 저장 위치가 진짜 상태 기계이므로 그렇게 적으십시오. 커서는 배치를 이미 적용하고 대상 트랜잭션을 커밋한 워크플로를 통해서만 전진할 수 있습니다.
cursor = durable_checkpoint() # last fully applied position
batch = extract(cursor, upper_bound) # one consistent batch
apply(batch) # idempotent at the target
commit()
checkpoint(batch.upper_bound) # only now
전달이 중간에 실패하면 새 원본 위치를 만들어 내지 말고 같은 배치를 대사하십시오. 더 새로운 상한으로 다시 추출하는 것이 부분 적용을 영구적인 구멍으로 만드는 길입니다. 거울 규칙은 반대쪽에도 적용됩니다. 대상에서의 멱등 적용은 중복을 막지만 추출이 본 적 없는 변경에는 아무것도 하지 않습니다.
삭제에는 기록이 필요하고 추론으로는 부족하다
아직 존재하는 행을 비교해서는 어떤 키가 사라졌는지 소비자에게 알릴 수 없습니다. 삭제를 기록하지 않는 원본은 두 가지 불완전한 선택을 남깁니다. 비교를 위해 키 이력을 충분히 오래 보관하거나, 일정에 따라 명시적인 전체 대사를 돌리는 것입니다. 둘 다 비용이 드는 설계 결정이며 누군가 소유하지 않으면 존재하지 않습니다.
보존도 같은 종류의 결정입니다. 지원되는 변경 피드에는 모두 창이 있으므로 계약에 명시하고 소비자의 체크포인트가 그 창에 가까워지면 경고하십시오. 두 주간 중단된 뒤 깨어난 소비자는 조용히 부분 배치를 받는 대신 "재초기화 필요"라는 분명한 오류를 읽어야 합니다.
그다음 델타가 값어치 있었는지 측정하기
모든 키와 해시를 비교하는 방식으로 돌아가는 것이 때로는 옳고, 그 비용을 아는 것도 값어치가 있습니다. 여전히 모든 행에 대한 정보를 전송하고, 해시는 테이블 전반의 일관된 스냅숏을 주지 않으며, 해시 방식은 표현과 충돌에 대한 나름의 질문을 불러옵니다. 검증 가능한 경계와 함께 바이트를 줄이는 것이 목표입니다. 커밋된 변경을 조용히 떨어뜨리는 더 적은 바이트는 델타가 아닙니다.
최종 수정 열은 단서일 뿐입니다. 늦은 커밋과 삭제와 긴 중단을 견디는 커서가 계약입니다.