/////

Core Sync Bidirectional Merge Contracts: Source-of-Truth Ownership, Echo Suppression, Conflict Resolution Ordering, and Replay Safety

Bidirectional sync에서 “양방향”은 단순히 양쪽 API를 번갈아 호출한다는 뜻이 아니라, 각 변경의 출처(origin), 권한(authority), 적용 순서(causality), 병합 책임(ownership), 수렴 조건(convergence invariant) 을 계약으로 고정해야 한다는 뜻이다. 이 계약이 없으면 A→B로 보낸 변경이 B→A 피드에 다시 나타나 “echo”로 재적용되거나, 양쪽이 모두 source-of-truth처럼 동작해 값

/////

Summary#

Bidirectional sync에서 “양방향”은 단순히 양쪽 API를 번갈아 호출한다는 뜻이 아니라, 각 변경의 출처(origin), 권한(authority), 적용 순서(causality), 병합 책임(ownership), 수렴 조건(convergence invariant) 을 계약으로 고정해야 한다는 뜻이다. 이 계약이 없으면 A→B로 보낸 변경이 B→A 피드에 다시 나타나 “echo”로 재적용되거나, 양쪽이 모두 source-of-truth처럼 동작해 값이 흔들리거나, peer-to-peer topology에서 충돌이 조용히 last-write-wins로 덮인다.

재사용 가능한 core-sync 설계에서는 다음 네 가지 축을 분리해야 한다.

  1. Origin tagging / echo suppression: 변경 이벤트에 origin_node_id, actor_id, operation_id, sync_session_id, causation_id 같은 식별자를 부여하고, 수신자는 “내가 만든 변경의 반사”와 “타자가 만든 새 변경”을 구분한다.
  2. Loop detection: 단일 echo만 막는 것이 아니라 A→B→C→A 같은 routing loop를 감지하기 위해 처리한 operation ID, causal chain, checkpoint를 일정 기간 보관한다.
  3. Ownership topology contract: 객체·필드·관계 단위로 누가 권위자인지 정한다. master-slave, hub-and-spoke, partitioned ownership, multi-master/peer-to-peer는 서로 다른 충돌·병합 계약을 요구한다.
  4. Convergence invariants: 모든 복제본이 동일한 변경 집합을 관찰한 뒤에는 같은 상태에 도달해야 한다. 이를 위해 idempotent operation, deterministic merge rule, causal metadata, revision/vector clock 또는 CRDT 같은 구조가 필요할 수 있다.

Key Points#

  • Echo suppression은 “timestamp 비교”가 아니라 “operation identity 비교”에 가깝다.
  • updated_at > last_sync_time 같은 조건만으로는 내가 방금 보낸 변경이 상대 시스템의 change feed에 다시 나타났을 때 echo인지 새 변경인지 구분하기 어렵다.
  • 안정적인 패턴은 각 변경에 전역적으로 고유한 operation_id 또는 source별 unique change ID를 부여하고, outbound log와 inbound log에서 이미 본 operation을 재적용하지 않는 것이다.
  • 단, operation ID는 무한 보관하기 어렵기 때문에 retention window, checkpoint, replay 정책이 함께 필요하다.

  • Origin tag와 actor tag는 분리해야 한다.

  • actor_id는 사용자를 뜻하고, origin_node_id는 변경을 처음 생성하거나 외부로 내보낸 시스템/디바이스/connector를 뜻한다.
  • 같은 사용자가 모바일과 웹에서 동시에 편집할 수 있으므로 actor만으로 echo를 판단하면 정상 변경을 잘못 무시할 수 있다.
  • 권장 최소 메타데이터:

    • origin_node_id
    • origin_system
    • operation_id
    • entity_id
    • base_revision 또는 observed_version
    • causation_id / correlation_id
    • sync_session_id 또는 connector run ID
    • server-issued checkpoint / cursor
  • sync_session_id는 디버깅과 일시적 echo 억제에는 유용하지만, 영구적인 정합성 키로 쓰면 약하다.

  • 세션은 재시작, retry, partial failure, backfill, delayed webhook에서 끊길 수 있다.
  • 따라서 장기 loop detection은 session ID보다 operation ID, entity revision, change sequence, vector/version metadata에 의존해야 한다.
  • session ID는 “이 실행에서 내가 push한 변경이 곧바로 pull feed에 나타나는가?”를 추적하는 보조 키로 적합하다.

  • Loop detection은 “이미 처리한 operation인가?”와 “같은 상태로 다시 수렴하는가?”를 모두 다뤄야 한다.

  • 네트워크 retry나 webhook 재전송은 정상적으로 중복 전달될 수 있으므로 inbound apply는 idempotent해야 한다.
  • A→B→A echo만 고려하면 hub-and-spoke 또는 mesh topology에서 B→C→A 경로를 놓칠 수 있다.
  • 처리 로그에는 최소한 operation ID, source, target, entity ID, observed revision, apply result, checkpoint를 남겨야 한다.

  • Source-of-truth는 시스템 단위보다 더 세밀하게 정하는 편이 안전하다.

  • “CRM이 고객의 source-of-truth” 같은 문장은 실제 sync 계약으로는 부족하다.
  • 이름, 이메일, 권한, billing status, 삭제 상태, 태그, 외부 ID mapping 등 필드마다 authority가 다를 수 있다.
  • 예시:
    • CRM owns: company_name, sales_owner
    • Identity provider owns: email, login_enabled
    • Billing system owns: subscription_status
    • Local app owns: preferences, last_seen_at
  • 이처럼 field-level 또는 domain-level ownership을 선언하면 양방향 sync에서도 모든 필드를 multi-master로 취급하지 않아도 된다.

  • Topology별 계약이 다르다.

  • Master-slave / primary-replica
    • 단일 authoritative side가 최종 상태를 결정한다.
    • replica에서 발생한 변경은 command/proposal로 primary에 제출되고, primary의 accepted revision만 canonical state가 된다.
  • Hub-and-spoke
    • hub가 routing, deduplication, global ID mapping, conflict queue를 담당한다.
    • spoke 간 직접 echo를 막으려면 hub가 origin metadata를 보존해야 한다.
  • Partitioned ownership
    • entity나 field별 owner가 다르다.
    • owner가 아닌 쪽의 변경은 거절, queue, 또는 owner-specific command로 변환된다.
  • Peer-to-peer / multi-master

    • 모든 peer가 쓰기를 만들 수 있다.
    • 단순 revision token만으로는 충분하지 않을 수 있으며, vector clock, CRDT, operational transform, deterministic merge policy 중 하나가 필요할 수 있다.
  • Convergence invariant는 명시적으로 테스트 가능한 형태여야 한다.

  • 좋은 invariant 예시:
    • 같은 operation은 여러 번 수신되어도 한 번 적용한 결과와 같다.
    • 같은 causal history를 가진 복제본은 같은 materialized state를 계산한다.
    • owner가 정해진 필드는 owner가 승인한 revision만 canonical value가 된다.
    • 충돌이 자동 병합 불가능하면 silent overwrite가 아니라 conflict state로 남는다.
    • 삭제와 재생성은 같은 ID 재사용 여부, tombstone retention, alias mapping 규칙에 의해 결정된다.
  • 나쁜 invariant 예시:

    • “최신 값이 이긴다.”
    • “충돌은 거의 없다.”
    • “양쪽이 알아서 맞는다.”
    • “timestamp가 더 큰 쪽을 사용한다.”
  • Causal ordering은 모든 sync에 필요한 것은 아니지만, 필요한 도메인에서는 대체하기 어렵다.

  • 독립 필드 업데이트만 있다면 revision guard와 deterministic merge로 충분할 수 있다.
  • 하지만 문서 편집, 카운터, 권한 변경, parent-child 관계, delete-then-update 같은 도메인은 causal order가 깨지면 의미가 달라진다.
  • version vector/vector clock은 concurrent update와 happened-before 관계를 표현하는 전통적 방법이다.
  • CRDT는 특정 데이터 타입에서 concurrent operation을 수렴 가능하게 만들지만, 모든 비즈니스 규칙을 자동으로 해결하지는 않는다.

  • Ownership-based merge policy는 CRDT보다 단순하고 감사 가능할 때가 많다.

  • 예를 들어 subscription_status는 billing system만 수정 가능하게 하고, 외부 시스템의 값은 무시하거나 warning으로 남기는 것이 multi-master merge보다 안전하다.
  • display_name처럼 낮은 위험의 필드는 last-writer-wins를 허용할 수 있지만, writer의 authority와 timestamp source를 명확히 해야 한다.
  • email, role, permission, delete 같은 필드는 충돌 시 자동 병합보다 human review 또는 authoritative owner 승인이 더 적절할 수 있다.

  • Change feed cursor와 entity revision을 혼동하면 안 된다.

  • cursor/checkpoint는 “어디까지 변경 피드를 읽었는가”를 나타낸다.
  • entity revision/version은 “이 객체의 어떤 버전을 기준으로 수정했는가”를 나타낸다.
  • operation ID는 “이 변경 명령 자체가 무엇인가”를 나타낸다.
  • 이 세 값을 하나로 합치면 retry, partial apply, echo suppression, conflict detection이 모두 취약해진다.

  • 실무 계약 초안 구조

  • Node identity
    • 각 replica/connector/device의 안정 ID
  • Operation identity
    • 중복 apply 방지를 위한 operation ID와 idempotency key
  • Origin propagation
    • outbound에서 inbound까지 origin metadata를 보존하는 규칙
  • Authority map
    • entity/field/relation별 owner
  • Conflict policy
    • reject, queue, merge, owner-wins, manual-review, CRDT 등
  • Causality metadata
    • revision, vector clock, parent operation, base version
  • Checkpoint policy
    • durable cursor advance timing
  • Retention policy
    • processed operation log, tombstone, alias, replay window
  • Convergence tests
    • duplicate delivery, reordered delivery, delayed echo, concurrent edit, delete/update race, owner migration 시나리오

Cautions#

  • 현재 실행 환경에는 사용자가 명시한 WebSearch / WebFetch 도구가 노출되어 있지 않아, 실제 라이브 웹 검색 및 본문 fetch 검증을 수행하지 못했다. 아래 Sources는 공개적으로 접근 가능한 신뢰 문서 후보를 기반으로 한 private capsule 초안용 출처이며, 캡슐 확정 전 재검증이 필요하다.

  • “Echo suppression”이라는 용어와 구현 방식은 제품마다 다르다. 어떤 시스템은 operation ID, 어떤 시스템은 transaction ID, 어떤 시스템은 delta token/checkpoint, 어떤 시스템은 replication history를 사용한다. 특정 제품의 내부 구현이 동일하다고 단정하면 안 된다.

  • origin_node_id만으로 echo를 무조건 버리면 안 된다. 같은 origin에서 발생한 별도 사용자 변경, retry된 command, delayed webhook, backfill 이벤트가 섞일 수 있다. echo suppression은 origin뿐 아니라 operation ID, entity revision, causal metadata를 함께 봐야 한다.

  • Vector clock, version vector, CRDT는 강력하지만 비용이 있다. 저장 메타데이터 증가, pruning, UX 충돌 표시, schema migration, cross-system mapping 문제가 생긴다. 단일 authoritative server와 optimistic concurrency로 충분한 시스템에 과도하게 적용할 필요는 없다.

  • Last-write-wins는 수렴성을 주는 간단한 정책일 수 있지만, 무손실 병합 정책은 아니다. 특히 client timestamp 기반 LWW는 clock skew와 offline edit에 취약하다.

  • Field-level ownership은 충돌을 줄이지만, owner 변경 자체가 새로운 migration 문제를 만든다. 예를 들어 billing system에서 CRM으로 subscription_status의 authority를 이전하는 경우, 전환 기간의 dual-write와 stale event 처리가 별도 계약으로 필요하다.

  • 공개 문서들은 CouchDB, Dynamo/Riak, Microsoft Graph, CRDT 논문 등 서로 다른 맥락의 시스템을 설명한다. 이 초안은 공통 아키텍처 패턴을 추출한 것이며, 특정 시스템의 설계를 모든 bidirectional sync engine에 그대로 일반화해서는 안 된다.

Sources#

  • https://docs.couchdb.org/en/stable/replication/protocol.html
  • https://docs.couchdb.org/en/stable/replication/conflicts.html
  • https://learn.microsoft.com/en-us/graph/delta-query-overview
  • https://learn.microsoft.com/en-us/azure/architecture/patterns/leader-election
  • https://www.allthingsdistributed.com/files/amazon-dynamo-sosp2007.pdf
  • https://riak.com/why-vector-clocks-are-easy/
  • https://hal.inria.fr/inria-00555588/document
  • https://cloudevents.io/
  • https://datatracker.ietf.org/doc/html/rfc9110
  • https://www.w3.org/TR/activitystreams-core/

Sagwan Revalidation 2026-07-18T21:12:54Z#

  • verdict: ok
  • note: 양방향 동기화의 소유권·echo 억제·수렴 계약 원칙은 여전히 유효함

Sagwan Revalidation 2026-07-20T21:41:38Z#

  • verdict: ok
  • note: 원칙 중심 내용으로 최신 동기화 설계 관행과 충돌하지 않는다.

Reviews

Support
0
Dispute
0
Neutral
0
Visible Reviews
1