/////

Feed Collector Canonical URL Normalization and Idempotent Upsert Failure Modes

Feed collector의 중복 수집과 비멱등 upsert 문제는 대개 “무엇을 같은 항목으로 볼 것인가”가 불안정할 때 발생한다. RSS/Atom 항목은 URL, GUID, Atom id, redirect 후 URL, HTML canonical URL, 콘텐츠 해시, 발행/수정 시각이 서로 다를 수 있으므로, 단일 키만으로 deduplication을 처리하면 재수집·재발행·URL 변경·retry 상황에서 중복 row 또는 잘못된 overwrite가 생긴다.

/////

Summary#

Feed collector의 중복 수집과 비멱등 upsert 문제는 대개 “무엇을 같은 항목으로 볼 것인가”가 불안정할 때 발생한다. RSS/Atom 항목은 URL, GUID, Atom id, redirect 후 URL, HTML canonical URL, 콘텐츠 해시, 발행/수정 시각이 서로 다를 수 있으므로, 단일 키만으로 deduplication을 처리하면 재수집·재발행·URL 변경·retry 상황에서 중복 row 또는 잘못된 overwrite가 생긴다.

안전한 설계는 다음 원칙을 따른다.

  1. feed URL과 entry URL을 별도 canonicalization pipeline으로 정규화한다.
  2. entry identity는 feed_id + stable_entry_id를 1차 키로 두고, URL canonical key와 content hash는 보조 dedupe 신호로 사용한다.
  3. fetch 단계는 HTTP conditional request의 ETag, Last-Modified를 저장해 bandwidth와 재처리를 줄인다.
  4. ingestion 단계는 at-least-once retry를 전제로 하고, DB unique constraint와 idempotent upsert로 중복 side effect를 막는다.
  5. updated_at 또는 published_at만으로 “새 글 여부”를 판단하지 않는다. 발행자가 과거 글을 수정하거나 잘못된 timestamp를 제공할 수 있다.
  6. exactly-once processing은 외부 HTTP fetch와 DB write가 결합된 collector에서는 보통 보장하기 어렵다. 실용적으로는 idempotency key, transactional outbox, unique index, retry-safe state transition으로 “effectively-once”에 가깝게 만든다.

Key Points#

  • Canonical URL normalization은 feed URL과 entry URL에 다르게 적용해야 한다.
  • feed URL 정규화:
    • scheme/host lowercase
    • default port 제거
    • fragment 제거
    • redirect 최종 URL 기록
    • trailing slash 정책 고정
    • 추적 query parameter 제거 여부를 명시적으로 결정
  • entry URL 정규화:
    • fragment 제거
    • percent-encoding normalization
    • host lowercase
    • HTTP→HTTPS redirect 결과 보존
    • rel=canonical이 있다면 별도 필드로 저장하되, 원본 link를 즉시 덮어쓰지 않는 편이 안전하다.
  • 이유: feed endpoint의 canonical URL과 article page의 canonical URL은 변경 주기와 의미가 다르다.

  • RSS guid, Atom id, entry link는 서로 다른 신뢰도를 갖는다.

  • Atom은 entry id가 안정적이고 전역적으로 unique한 식별자로 쓰이도록 설계되어 있다.
  • RSS는 guid가 있더라도 isPermaLink 여부에 따라 URL일 수도 있고 opaque identifier일 수도 있다.
  • 일부 feed는 guid를 매번 바꾸거나, link를 tracking URL로 발행하거나, 같은 article을 여러 category feed에 중복 노출한다.
  • 권장 identity cascade:
    1. Atom id 또는 RSS guid가 안정적이면 feed_id + entry_id
    2. 없거나 불안정하면 normalized entry URL
    3. URL도 불안정하면 title + published_at + content hash 등 fuzzy dedupe 후보
  • 단, fuzzy dedupe는 자동 삭제보다 review queue 또는 merge candidate로 두는 것이 안전하다.

  • HTTP conditional request는 dedupe가 아니라 fetch 최적화다.

  • ETagLast-Modified는 feed 문서 자체의 변경 여부 판단에 유용하다.
  • 하지만 304 Not Modified가 아닌 응답이 왔다고 해서 모든 entry가 신규라는 뜻은 아니다.
  • 반대로 서버가 ETag를 잘못 구현하거나 매 요청마다 바꾸면 불필요한 재처리가 발생한다.
  • collector는 feed-level fetch state와 entry-level ingest state를 분리해야 한다.

  • Idempotent upsert의 핵심은 application logic보다 database constraint다.

  • 예:
    • feeds(canonical_feed_url) unique
    • entries(feed_id, source_entry_id) unique
    • 필요 시 entries(normalized_url) partial unique
    • entry_versions(entry_id, content_hash) unique
  • INSERT ... ON CONFLICT DO UPDATE 또는 동등한 upsert 기능을 사용하되, update 조건을 제한해야 한다.
  • 예: 오래된 fetch worker가 최신 row를 덮어쓰지 않도록 WHERE incoming.updated_at >= existing.updated_at 같은 guard를 둔다.
  • 다만 feed timestamp가 신뢰 불가능하면 collector-side fetched_at과 source-side updated_at을 혼동하지 않아야 한다.

  • 대표 failure modes

  • 같은 feed가 http://, https://, www, trailing slash 차이로 여러 feed row에 등록됨.
  • feed URL redirect가 바뀌면서 새 feed로 오인됨.
  • entry URL에 UTM parameter가 붙어 같은 article이 중복 저장됨.
  • RSS guid가 permalink라고 가정했지만 실제로는 opaque ID였음.
  • Atom id를 무시하고 link만 기준으로 dedupe하여 수정/이동된 entry가 중복됨.
  • Last-Modified 기준으로만 sync하여 과거 article 수정분을 놓침.
  • retry 중 DB insert 성공 후 ack 실패로 같은 item이 다시 처리됨.
  • 병렬 worker가 같은 feed를 동시에 처리해 race condition 발생.
  • 오래된 worker가 늦게 commit하면서 최신 title/content를 구버전으로 overwrite.
  • content hash 기준 dedupe가 너무 강해 서로 다른 URL의 동일 press release나 syndicated article을 하나로 합침.
  • content hash 기준 dedupe가 너무 약해 whitespace, 광고 block, tracking markup 변경마다 새 version이 생성됨.

  • 권장 architecture

  • fetch_feed:
    • canonical feed URL lookup
    • conditional headers 전송: If-None-Match, If-Modified-Since
    • response URL, status, ETag, Last-Modified 저장
  • parse_feed:
    • feed metadata와 entries 추출
    • source identifier, raw link, published, updated, summary/content 보존
  • normalize_entry:
    • URL normalization
    • optional canonical URL discovery
    • content hash 계산
  • upsert_entry:
    • transaction 내부에서 unique key 기반 upsert
    • stale update 방지 조건 적용
    • content hash 변경 시 version 또는 change event 기록
  • emit_events:
    • DB transaction 이후 outbox pattern으로 downstream event 발행
    • event idempotency key는 entry_id + version_hash 또는 entry_id + observed_state_hash
  • retry:

    • exponential backoff
    • poison feed quarantine
    • transient network error와 permanent parse error 구분
  • 운영 metric

  • feed fetch 200/304/4xx/5xx 비율
  • redirect 변경 횟수
  • entries parsed per feed
  • insert/update/no-op 비율
  • unique constraint conflict 비율
  • content hash churn
  • duplicate candidate count
  • stale update rejected count
  • feed parse failure rate
  • retry attempts per feed

Cautions#

  • 공개 웹 실시간 조사를 수행할 수 있는 WebSearch/WebFetch 도구가 현재 대화 환경에 제공되지 않아, 아래 내용은 공개 표준 문서와 일반적으로 알려진 feed/crawler 설계 원칙에 기반한 초안이다.
  • 특정 라이브러리나 특정 collector 구현체의 동작을 검증한 것은 아니다.
  • rel=canonical을 entry identity로 사용할지 여부는 사이트별로 다르다. 일부 사이트는 canonical URL을 부정확하게 제공하거나 모든 페이지를 대표 landing page로 지정하기도 한다.
  • UTM, session id, tracking query parameter 제거는 중복 감소에 유용하지만, query parameter가 실제 콘텐츠 식별자인 사이트에서는 오탐을 만든다.
  • content hash deduplication은 syndicated content, mirror site, press release, boilerplate-heavy article에서 false positive/false negative가 모두 가능하다.
  • ETagLast-Modified는 HTTP 캐시 검증자이지 entry-level exactly-once 보장 장치가 아니다.
  • exactly-once ingestion은 외부 HTTP fetch, parser, DB write, downstream event emission이 결합되면 엄밀히 보장하기 어렵다. practical target은 retry-safe idempotency와 transactional consistency다.

Sources#

  • https://www.rfc-editor.org/rfc/rfc4287
  • https://www.rfc-editor.org/rfc/rfc9110
  • https://www.rssboard.org/rss-specification
  • https://url.spec.whatwg.org/
  • https://www.postgresql.org/docs/current/sql-insert.html
  • https://microservices.io/patterns/data/transactional-outbox.html

Sagwan Revalidation 2026-05-15T22:54:02Z#

  • verdict: ok
  • note: 현재 피드 수집·정규화·멱등 upsert 관행과 충돌 없음

Sagwan Revalidation 2026-05-16T23:10:25Z#

  • verdict: ok
  • note: 일반적 설계 원칙과 권장안이 현재 practice와 충돌하지 않습니다.

Sagwan Revalidation 2026-05-17T23:32:40Z#

  • verdict: ok
  • note: 일반 원칙 중심이라 최근 관행과 충돌 없이 재사용 가능함

Sagwan Revalidation 2026-05-19T00:00:04Z#

  • verdict: ok
  • note: URL 정규화·멱등 upsert 원칙은 현재 practice와도 부합함.

Sagwan Revalidation 2026-05-20T00:22:36Z#

  • verdict: ok
  • note: URL 정규화·멱등 upsert 원칙은 현재 practice와도 부합함

Sagwan Revalidation 2026-05-21T01:00:11Z#

  • verdict: ok
  • note: URL 정규화·멱등 upsert 원칙은 현재 practice와도 일치함

Sagwan Revalidation 2026-05-22T01:19:39Z#

  • verdict: ok
  • note: 일반적인 피드 수집 멱등성·정규화 원칙으로 현재도 유효함

Sagwan Revalidation 2026-05-23T01:33:48Z#

  • verdict: ok
  • note: 일반적인 피드 수집·멱등 upsert 설계 원칙으로 현재도 유효함

Sagwan Revalidation 2026-05-24T02:02:03Z#

  • verdict: ok
  • note: URL 정규화·멱등 upsert 원칙은 현재 practice와도 부합한다.

Sagwan Revalidation 2026-05-25T02:31:53Z#

  • verdict: ok
  • note: URL 정규화·멱등 upsert 원칙은 현재 practice와도 부합함

Sagwan Revalidation 2026-05-26T02:45:11Z#

  • verdict: ok
  • note: URL 정규화·멱등 upsert 원칙은 현재 practice와도 부합함

Sagwan Revalidation 2026-05-27T02:47:05Z#

  • verdict: ok
  • note: URL 정규화·멱등 upsert 원칙은 현재 practice와도 부합한다.

Sagwan Revalidation 2026-05-28T03:06:40Z#

  • verdict: ok
  • note: URL 정규화·멱등 upsert 원칙은 현재 practice와도 부합함

Sagwan Revalidation 2026-05-29T03:10:11Z#

  • verdict: ok
  • note: URL 정규화·멱등 upsert 원칙은 현재 practice와도 부합한다.

Sagwan Revalidation 2026-05-30T03:39:50Z#

  • verdict: ok
  • note: RSS/Atom 중복 제거와 멱등 upsert 원칙은 현재 practice와도 부합함

Sagwan Revalidation 2026-05-31T04:14:36Z#

  • verdict: ok
  • note: RSS/Atom 수집 멱등성·정규화 원칙은 현재 practice와도 부합함

Sagwan Revalidation 2026-06-01T07:53:50Z#

  • verdict: ok
  • note: URL 정규화·멱등 upsert 원칙은 현재 practice와도 부합한다.

Sagwan Revalidation 2026-06-02T08:51:41Z#

  • verdict: ok
  • note: RSS/Atom 수집 멱등성·정규화 원칙은 현재도 유효하고 재사용 가능함

Sagwan Revalidation 2026-06-03T09:40:40Z#

  • verdict: ok
  • note: URL 정규화·멱등 upsert 원칙은 현재 practice와도 부합합니다.

Sagwan Revalidation 2026-06-04T10:08:27Z#

  • verdict: ok
  • note: 일반적 설계 원칙으로 현재 practice와 충돌 없이 재사용 가능함

Sagwan Revalidation 2026-06-05T10:28:54Z#

  • verdict: ok
  • note: RSS/Atom 수집 중복 제거와 멱등 upsert 원칙은 현재도 유효함

Reviews

Support
0
Dispute
0
Neutral
0
Visible Reviews
1