Spensara Spensara Knowledge Base & Documentation Repository

Repository Docs & Knowledge Base

Consolidated Spensara documentation repository (722 Markdown & architecture documents in ../repository)
Built Word Documents (.docx): Spensara Overview (.docx) Capability Assessment (.docx) Brink POS Ingestion Analysis (.docx) The Borg Ingestion Architecture (.docx)
etl/docs/features/ETL/kds-cdc/kds-cdc-scaling-and-incremental-curation.md 6,261 bytes · Updated Oct 23, 2026 10:44 AM EDT
Raw File

KDS CDC Scaling And Incremental Curation

Why this note exists

Current KDS CDC curated apply is correct and replay-safe for low to moderate throughput, but some targets still use slice-level refresh behavior.

Example observed at runtime:

  • one item_bump message arrived,
  • staging.kds_item_bumps loaded one row,
  • staging_to_curated for curated.kitchen_item_status_events logged deleted 187 rows and wrote 187 rows.

This is expected with current SQL shape, but it is not good long-term if volume grows very large.

Current behavior

Some KDS CDC curated targets rebuild the affected tenant/store projection from staging instead of applying only the keys changed by the current upstream batch.

For curated.kitchen_item_status_events, current pattern is effectively:

  1. find all matching event rows for store,
  2. delete matching curated rows,
  3. reinsert the full current result set from staging joins.

This gives us:

  • idempotent reruns,
  • simple recovery semantics,
  • deterministic final state,
  • no need for fine-grained merge logic.

But it also gives us:

  • large delete/insert churn,
  • repeated scans of staging and curated tables,
  • wider transaction conflict windows,
  • more Redshift/Postgres serialization conflicts,
  • worse latency for tiny changes,
  • poor scaling at very high row counts.

Why this does not scale to 200M rows

At very large scale, slice-level refresh becomes too expensive.

Main problems:

  • one small CDC change can trigger large table rewrites,
  • delete + reinsert patterns create heavy vacuum/churn pressure,
  • long-running transactions increase conflict probability,
  • write amplification becomes much larger than actual business change,
  • per-message near-real-time processing degrades as slice size grows.

If tenant/store/day slices become large enough, this pattern becomes operationally noisy and cost-heavy even if correctness remains acceptable.

Target direction

Move curated apply from full slice refresh to batch-keyed incremental apply.

Core idea:

  • each upstream batch already knows which business keys changed,
  • curated stages should only touch rows affected by those keys,
  • delete/merge/upsert should be scoped to current changed keys, not the full store projection.

For KDS CDC this usually means driving curated work from current batch identities such as:

  • order.guid
  • item.guid
  • item.order_guid
  • item_bump.guid
  • item_bump.item_guid

1. Keep staging append-or-replace by batch

Staging can remain batch-oriented and retry-safe.

That means:

  • current load stages still write normalized rows into staging,
  • reruns remain safe,
  • staging continues to be source for curated transforms.

2. Add changed-key scope per curated target

Each curated target should compute a small changed-key set from the current upstream batch.

Examples:

  • curated.kitchen_orders: keys = changed order.guid
  • curated.kitchen_order_items: keys = changed item.guid or changed item.order_guid
  • curated.kitchen_item_status_events: keys = changed item_bump.guid and possibly changed parent item.guid

Then curated SQL should only delete/reinsert or merge rows for those keys.

3. Prefer key-scoped delete + insert or merge

Instead of deleting whole store slice, do one of these:

  • delete curated rows where primary key is in current changed-key set, then insert recomputed rows for same keys,
  • or use MERGE / upsert semantics when warehouse engine and target shape support it well.

This reduces work from:

  • "rewrite everything for this store"

to:

  • "rewrite only rows touched by this batch".

4. Separate current-state targets from history targets

Not every curated target has same shape.

  • current-state tables should support key-scoped replacement/upsert,
  • history/event tables may prefer append-only ingestion plus dedupe guards.

kitchen_item_status_events should be reviewed carefully here because it behaves more like event/state-transition data than a tiny mutable dimension.

5. Partition by natural boundaries

At larger scale, partitioning should align with common isolation boundaries such as:

  • tenant,
  • business date,
  • maybe store,
  • maybe event date for history-heavy targets.

This reduces scan size and conflict scope.

Suggested implementation plan

Phase 1: reduce churn without redesigning whole pipeline

Short-term improvement:

  • keep current stage model,
  • add changed-key extraction from current upstream batch details,
  • update curated target SQL to delete/reinsert only affected keys.

This is the best next step because it preserves existing replay model and requires smaller code changes.

Phase 2: add explicit curated apply strategy per target

Introduce target metadata such as:

  • FullSliceRefresh
  • ChangedKeyReplace
  • Merge
  • AppendOnly

This makes target behavior explicit instead of embedding all strategy decisions inside handwritten SQL only.

Phase 3: optimize heavy targets independently

For highest-volume targets:

  • review physical table design,
  • add narrower indexes/partitioning,
  • isolate event-history workloads from current-state workloads,
  • consider larger batch windows for heavy event streams if near-real-time is not required.

Operational guidance

Until incremental curated apply is implemented, expect these symptoms on some targets:

  • row counts in logs much larger than incoming CDC count,
  • occasional serializable conflicts under concurrent slice processing,
  • successful retry/recovery even after one failed attempt,
  • higher churn on event-like curated tables.

These symptoms do not necessarily mean data loss. They usually mean current SQL is doing more work than the incoming change size suggests.

Recommendation

For KDS CDC go-forward work, preferred evolution is:

  1. keep durable landing, replay, and stage orchestration as-is,
  2. change staging_to_curated from slice-level refresh to changed-key incremental apply,
  3. apply target-specific strategies for high-volume curated tables,
  4. treat full-store refresh as fallback/recovery path, not default hot-path behavior.

That approach keeps correctness and recovery model, while making throughput and cost much more realistic for very large datasets.