effectmq
Explanation

Task identity and generations

How logical ids, generations, durable handles, and indeterminate writes fit together.

This page is about the identity model that lets EffectMQ deduplicate offers without confusing an old result with a later execution. It does not describe the mechanics of configuring retries or retention.

A task id names logical work

A task definition derives an idempotency key from its payload, unless the producer supplies an explicit task id or uses the default random identity. The queue name and task id together name logical work.

Stable identity turns producer retries into reconciliation. If the producer offers the same queue/id again with return-existing, Redis returns the stored generation without replacing its payload, state, retry history, lease, or outcome.

This is stronger than merely refusing a duplicate insert: the producer gets an authoritative handle for the generation that already exists.

A generation names one execution history

Logical work can be deliberately run again after settlement. EffectMQ models that as a new generation under the same queue/id. The generation number keeps the two execution histories and terminal results distinct.

A task handle therefore contains both taskId and generation. It also contains the event cursor recorded by the offer, the schema identity, and the storage protocol version. Those fields let wait reject the wrong decoder and observe the exact generation even if a newer one now exists.

Persisting only the task id collapses that distinction. A later caller could mistake generation 2's result for the generation 1 result it originally requested.

Indeterminate writes are an identity problem

A connection can fail after Redis commits a mutating script but before the producer receives the reply. From the producer's point of view, the write is indeterminate: either the generation exists or it does not.

Stable identity makes both possibilities converge. Repeating the same offer with return-existing creates the generation if the first write did not commit, or returns it unchanged if the first write did commit.

Changing the id, or choosing new-generation, destroys that convergence and can create additional work. new-generation represents an application decision to run settled logical work again; it is not a transport-recovery mechanism.

Schema identity is part of result identity

The same task id is not enough to prove that stored bytes have the expected meaning. EffectMQ stores schemaId with task values and copies it into handles. A mismatched queue definition fails with SchemaIdentityMismatch instead of decoding bytes through an unrelated schema.

This makes a schema-id change an explicit compatibility event. The application either retains a decoder for the old identity or migrates the stored values; silently reusing the name with incompatible schemas is rejected.

Task identity therefore has three layers: queue/id names logical work, generation names one execution history, and schema/protocol identity names the meaning of its durable values.

On this page