Replay

⚠️

The causet snapshots rebuild, causet snapshots rebuild-all, and causet projections replay commands on this page are proposed — they have no equivalent in the shipped causet-cli today. The real entity-level mechanism is causet recovery replay --stream <stream> (sandbox-only) — see Rebuilding Memory and causet recovery replay.

Replay is how Causet recovers and rebuilds derived state from the immutable event ledger. There are two types:

  1. Entity snapshot replay — re-run rules from ledger events to rebuild an entity’s current state.
  2. Projection rebuild — reset the Kafka consumer offset to re-process events and rebuild projection tables.

Both are safe because Causet’s rules are deterministic: the same events always produce the same resulting state.


When to Replay

ScenarioReplay Type
Entity snapshot is stale or corruptedEntity snapshot replay
Projection bug was fixed in a new IR versionProjection rebuild
New projection added to existing applicationProjection rebuild (historical events)
Projection handler error DLQ’d many eventsProjection rebuild (partial) or DLQ re-consume
Schema change broke derive expressionsProjection rebuild after IR fix
Disaster recovery — projections DB lostFull projection rebuild

Type 1: Entity Snapshot Replay

Entity snapshots are derived from ledger_events. If a snapshot is stale, incorrect, or missing, re-derive it from the ledger:

  1. Load all ledger_events for (entity_id, fork_id) ordered by sequence_number.
  2. Start from empty state ({}).
  3. For each event, apply the mutations declared for that event type in the active IR.
  4. Upsert the resulting state into entity_snapshots.

The cursor lock for the entity must be held during this operation to prevent concurrent intent processing.

Proposed CLI:

causet snapshots rebuild \
  --entity-id user-123 \
  --fork-id production
 
# Rebuild all snapshots for a fork (long-running)
causet snapshots rebuild-all \
  --fork-id production \
  --stream user

This does not require stopping the runtime or any Kafka operations.


Type 2: Projection Rebuild

Projection tables are derived from causet.projection-events.v1 (or, for a full rebuild, from the ledger re-published as projection events). To rebuild a projection:

Procedure

  1. Stop the projection-worker (or pause the consumer group for the affected fork).
  2. Optionally truncate the target table:
    SET LOCAL search_path = acme_concertapp_production;
    TRUNCATE artist_followers;
  3. Reset the consumer group offset to the beginning of causet.projection-events.v1 (or a specific timestamp checkpoint):
    # Using Kafka CLI
    kafka-consumer-groups.sh \
      --bootstrap-server localhost:9092 \
      --group projection-worker \
      --topic causet.projection-events.v1 \
      --reset-offsets \
      --to-earliest \
      --execute
  4. Restart the projection-worker.

Events are re-consumed from the reset offset. Mutations are idempotent — re-processing events that were previously applied produces the same result.

Proposed CLI:

causet projections replay \
  --projection artist_followers \
  --fork-id production
 
# Replay from a specific timestamp
causet projections replay \
  --projection artist_followers \
  --fork-id production \
  --from-timestamp 2024-01-01T00:00:00Z

Determinism Makes Replay Safe

Rules are deterministic:

same entity state + same intent → same events

And projection derive expressions are pure functions of the event payload. This means:

  • Replaying the same events always produces the same projection rows.
  • A rebuilt projection will be byte-for-byte identical to the original (for the same event sequence).

Side Effects During Replay

Warning: Side effects declared in side_effects rules (external API calls, webhook submissions, downstream submit ops) are triggered at intent processing time on the write path — not during projection rebuild.

Replay of projections does not re-trigger:

  • op: submit — follow-up intents
  • op: schedule — delayed intents
  • Any external integrations triggered via side effect rules

If you need to replay side effects (e.g. re-send a missed notification), that requires a separate reconciliation process outside Causet.

When writing side-effects rules, guard against re-execution using entity state:

side_effects:
  rules:
    - name: send_welcome_email
      when: { expr: "state.welcome_email_sent != true" }
      then:
        - op: submit
          action: SEND_EMAIL
          entity_id: intent.user_id
          payload:
            template: welcome

This pattern ensures the side effect only fires once even if the intent is re-submitted.


Projection Rebuild vs Migration

OperationPurpose
Projection rebuildRe-process events with the current (or corrected) projection definition. Same table schema, new data.
DDL migrationApply schema changes to the projection table — add column, change type, create index. Happens at deploy time from IR.

A rebuild re-populates data. A migration changes the table structure. Often you need both: deploy a new IR version (which applies DDL), then rebuild the projection to backfill the new column with historical data.


Replay from the Ledger (No Kafka)

If Kafka retention has expired and the projection events are no longer available, projections can be rebuilt by re-publishing ledger events as projection events:

  1. Query ledger_events for all events in the relevant time range and fork.
  2. Re-publish each event as a projection event envelope to causet.projection-events.v1.
  3. The projection worker consumes and materializes them.

This is an operational procedure performed with the Causet admin CLI or a custom migration script. No data loss — the ledger always has the complete history.


Replay and Multiple Forks

Replay is always scoped to a specific fork. Rebuilding production projections does not affect staging projections.