Documentation
¶
Overview ¶
Package feed serves the ordered event log to pull consumers: cursor pages over expanded envelopes, an SSE-friendly tail, and named compare-and-swap cursors so a replicated consuming service reads as one logical consumer. Design: docs/design/event-delivery.md.
Index ¶
Constants ¶
const DefaultDeadLetterRetention = 30 * 24 * time.Hour
DefaultDeadLetterRetention bounds how long a dead delivery is kept.
It is far longer than the event retention (7 days by default) because a dead letter is the record of a delivery that never succeeded: it must outlive the events it references long enough for an operator to notice and redrive. It is a bound, not a policy against redriving — 30 days is well past any attempt window.
const DefaultParkedRetention = 30 * 24 * time.Hour
DefaultParkedRetention bounds how long a parked envelope is kept before the pruner deletes it.
Pruning a parked envelope is deliberate data loss: the event was committed but never delivered, and after the prune it never will be. The default is therefore generous — 30 days, matching the dead-letter retention and far past the window in which the parked gauge should have been alerted on and the envelopes redriven. It is a bound on unbounded growth, not a policy against redriving.
Variables ¶
var ErrCursorConflict = errors.New("feed: cursor position changed since read")
ErrCursorConflict is returned when a compare-and-swap commit loses the race — another consumer replica advanced the cursor first.
var ErrGone = errors.New("feed: cursor older than retention; re-baseline required")
ErrGone is returned when a cursor points before the retention floor: events were pruned, so the consumer must re-baseline instead of silently missing history.
Functions ¶
This section is empty.
Types ¶
type CursorStore ¶
type CursorStore interface {
Get(ctx context.Context, tenant valueobjects.TenantID, consumer string) (int64, error)
// Commit advances the cursor with compare-and-swap: it fails with
// ErrCursorConflict unless the stored position equals expected.
Commit(ctx context.Context, tenant valueobjects.TenantID, consumer string, position, expected int64, now time.Time) error
}
CursorStore persists named consumer cursors.
type Interactor ¶
type Interactor struct {
// contains filtered or unexported fields
}
Interactor implements the feed usecases.
func NewInteractor ¶
func NewInteractor(store Store, cursors CursorStore, acl *fieldacl.Resolver) *Interactor
NewInteractor wires the feed usecases. The resolver applies the field ACL to value payloads; pass nil to serve the feed unfiltered, which is only correct when every caller of this interactor is already privileged.
func (*Interactor) CommitCursor ¶
func (i *Interactor) CommitCursor(ctx context.Context, consumer string, position, expected int64) error
CommitCursor advances a named cursor via compare-and-swap. Returns ErrCursorConflict when another replica advanced it first.
func (*Interactor) List ¶
func (i *Interactor) List(ctx context.Context, in ListInput) (*ListOutput, error)
List pages the event log. Returns ErrGone when After points before the retention floor.
type ListOutput ¶
type ListOutput struct {
Items []Event `json:"items"`
// NextCursor feeds the following request's After; equals After when
// the page is empty.
NextCursor int64 `json:"next_cursor"`
}
ListOutput is one feed page.
type Pruner ¶
type Pruner struct {
// contains filtered or unexported fields
}
Pruner deletes events past retention on an interval.
func (*Pruner) WithDeadLetterRetention ¶ added in v1.3.0
WithDeadLetterRetention overrides how long a dead delivery is kept before it stops pinning its envelope. Non-positive values are ignored.
func (*Pruner) WithParkedRetention ¶ added in v1.5.0
WithParkedRetention overrides how long a parked envelope is kept before it is deleted. Deleting a parked envelope is deliberate data loss — keep this well past the alerting-and-redrive window. Non-positive values are ignored.
type Store ¶
type Store interface {
// List returns events with feed_seq > after for the tenant, in feed
// order, optionally filtered by event type.
List(ctx context.Context, tenant valueobjects.TenantID, after int64, types []string, limit int) ([]Event, error)
// Floor returns the smallest retained feed_seq for the tenant (0 when
// no events are retained).
Floor(ctx context.Context, tenant valueobjects.TenantID) (int64, error)
// Prune deletes expanded envelopes recorded before the cutoff whose
// deliveries have all settled. Returns rows removed.
Prune(ctx context.Context, cutoff time.Time) (int, error)
// PruneDeadLetters deletes DEAD delivery rows older than the cutoff, so
// their envelopes become prunable. Returns rows removed.
//
// The envelope prune deliberately keeps anything a dead delivery still
// references — the evidence an operator needs in order to redrive it —
// but nothing else ever deleted a dead row, so a single decommissioned
// endpoint pinned its envelopes for ever and FLEXITYPE_EVENT_RETENTION
// stopped bounding the outbox or the feed at all. The bound has to exist
// somewhere; it exists here, well past the attempt window.
PruneDeadLetters(ctx context.Context, cutoff time.Time) (int, error)
// PruneParked deletes envelopes that were parked before the cutoff.
// Returns rows removed.
//
// A parked envelope has no feed_seq, so the envelope prune never
// reached it and one poisonous event type could grow the outbox for
// ever. The bound has to exist somewhere; it exists here, under its own
// long retention. Deleting a parked envelope is DELIBERATE data loss —
// the event was never delivered and never will be — so the retention is
// far past the window in which an operator should have noticed the
// parked gauge and redriven.
PruneParked(ctx context.Context, cutoff time.Time) (int, error)
}
Store reads the expanded event log.