Skip to content

Cascade-TTL EVALSHA runs inside the action's MULTI — NOSCRIPT is unrecoverable #271

Description

@yedidyakfir

Summary

The TTL-cascade Lua script is run via EVALSHA inside the triggering action's transactional pipeline (MULTI/EXEC). If Redis has evicted the script (server restart, failover to a replica that never loaded it, or SCRIPT FLUSH), the EVALSHA returns NOSCRIPT at EXEC time — and this is not recoverable.

Why it's unrecoverable

Redis transactions do not roll back on a runtime error. NOSCRIPT is an execution-time error, so at EXEC:

  • the action's own writes (e.g. JSON.SET, INCRBY, list append) commit, while
  • the cascade EVALSHA fails and the cascade silently does not run.

You cannot transparently recover:

  • Replaying the whole transaction re-applies the action's writes — double-counts any non-idempotent command (aincrease, append, apop, …).
  • Only the script alone can be safely rerun (a NOSCRIPT means it never executed), but that necessarily happens outside the committed transaction.

Impact

Rare in normal operation (scripts are loaded at init_rapyer, so NOSCRIPT only happens on eviction/restart/failover), but when it occurs the TTL cascade to referenced targets silently does not happen for that operation, so referenced models can expire earlier than intended. Fails silently, which makes it hard to detect.

Proper fix

Defer the cascade to a standalone, awaited arun_sha after the owning pipeline commits. arun_sha already implements NOSCRIPT recovery (catch → SCRIPT LOAD all scripts → retry), and a standalone rerun is exactly-once because NOSCRIPT means the script never ran. This requires a post-commit deferral mechanism: enqueue the cascade during the action flush, then have the pipeline creator drain the queue after a successful execute() (reusers don't drain).

Note

This deferral infrastructure existed (a DeferredCascade protocol + PendingTTLCascade carrier + a context-scoped queue drained post-commit) but was removed to reduce complexity in the current change; the cascade now runs inline in the action's MULTI, accepting this limitation. This issue tracks restoring correct NOSCRIPT handling by re-introducing post-commit deferral.

Tasks

  • Re-introduce a post-commit deferral mechanism (enqueue during flush; pipeline creator drains after successful execute())
  • Run the cascade as a standalone awaited arun_sha (with its NOSCRIPT reload+retry) after commit
  • Verify a NOSCRIPT mid-operation no longer drops the cascade (test with SCRIPT FLUSH between init and a cascade-triggering action)

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingforeign-keysForeign key relationshipspriority:highHigh prioritytransactionsTransaction safetyttlTTL management

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions