macula_group_epoch (macula v13.4.0)

View Source

A sealed group's epochs (plans/DESIGN_E2E_SEALED_PUBSUB.md §4).

An epoch is a group key for a stretch of time: a 32-byte key drawn at random, independent of every other epoch's, so holding one says nothing about the next; an 8-byte id drawn at random, which names the epoch on every sealed event (seal_key_id) and is bound to the key only by the distributor's signed reply; and its times. Epochs are contiguous: the next one is issued where this one stops publishing. A publisher seals under an epoch until its publish_until; a subscriber accepts events under it until its accept_until, which is publish_until plus the longest a publication lives (60 minutes of ttl plus 5 of tolerance, as macula_frame verifies a publication), and with the same 5-minute clock tolerance. After that the key has no use and is erased.

The last third of an epoch is its ahead window: a pull in it is also handed the next epoch, and every holder re-pulls at a random instant in it, so a rotation is spread over that third rather than asked for in one instant.

Everything here is a pure function of the epochs and the time it is given.

Summary

Functions

Whether an event under Epoch is accepted at Now: until its accept_until, with the 5-minute clock tolerance a publication gets.

Epoch's ahead window, {From, To}: the last third of its publishing stretch, To excluded.

How long an epoch publishes by default: 15 minutes.

The epoch a publisher seals under at Now: the newest held epoch that has been issued and has not stopped publishing, or {error, no_current_epoch} when none has (so the publisher fails closed until a pull succeeds).

Whether Now falls in Epoch's ahead window.

The held epochs still of use at Now; the others are erased.

A fresh epoch issued at IssuedAt (milliseconds), publishing for RotateAfterMs.

The epoch after Epoch: issued where it stops publishing, with a fresh key and id.

The instant a holder re-pulls: Fraction (0.0 inclusive to 1.0 exclusive, drawn uniformly by the caller) of the way through Epoch's ahead window.

Types

epoch/0

-type epoch() ::
          #{id := <<_:64>>,
            key := <<_:256>>,
            issued_at := integer(),
            publish_until := integer(),
            accept_until := integer()}.

Functions

acceptable(_, Now)

-spec acceptable(epoch(), integer()) -> boolean().

Whether an event under Epoch is accepted at Now: until its accept_until, with the 5-minute clock tolerance a publication gets.

ahead_window(_, RotateAfterMs)

-spec ahead_window(epoch(), pos_integer()) -> {integer(), integer()}.

Epoch's ahead window, {From, To}: the last third of its publishing stretch, To excluded.

default_rotate_after_ms()

-spec default_rotate_after_ms() -> pos_integer().

How long an epoch publishes by default: 15 minutes.

for_publish(Held, Now)

-spec for_publish([epoch()], integer()) -> {ok, epoch()} | {error, no_current_epoch}.

The epoch a publisher seals under at Now: the newest held epoch that has been issued and has not stopped publishing, or {error, no_current_epoch} when none has (so the publisher fails closed until a pull succeeds).

in_ahead_window(Epoch, RotateAfterMs, Now)

-spec in_ahead_window(epoch(), pos_integer(), integer()) -> boolean().

Whether Now falls in Epoch's ahead window.

live(Held, Now)

-spec live([epoch()], integer()) -> [epoch()].

The held epochs still of use at Now; the others are erased.

new(IssuedAt, RotateAfterMs)

-spec new(integer(), pos_integer()) -> epoch().

A fresh epoch issued at IssuedAt (milliseconds), publishing for RotateAfterMs.

next(_, RotateAfterMs)

-spec next(epoch(), pos_integer()) -> epoch().

The epoch after Epoch: issued where it stops publishing, with a fresh key and id.

repull_at(Epoch, RotateAfterMs, Fraction)

-spec repull_at(epoch(), pos_integer(), float()) -> integer().

The instant a holder re-pulls: Fraction (0.0 inclusive to 1.0 exclusive, drawn uniformly by the caller) of the way through Epoch's ahead window.