redis

package
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 13, 2026 License: MIT Imports: 9 Imported by: 0

Documentation

Overview

Package redis provides a Redis- or Valkey-backed gateway.Outbox, so a connection's unacknowledged tail survives a node failure: a client that reconnects to a different process still finds the messages the original process had sent but not yet had acknowledged, and they are redelivered. It is a separate package specifically so that importing the root gateway package never pulls in github.com/redis/go-redis/v9 for applications that use gateway.MemoryOutbox, or no Outbox at all.

Data model

Each connection id is one Redis hash. Its message fields are keyed by a message id this Outbox mints (see Outbox.Append) and hold the value "<seq>:<payload>", where seq is the decimal sequence and payload is the raw bytes after the first colon (a colon never appears in the decimal prefix, so the split is unambiguous and payload stays binary-safe). Two reserved fields never collide with a minted message id (see seqField): seqField holds the per-connection sequence counter, and ackGenField holds the Ack generation fencing floor (see gateway.Outbox.Ack). The hash shape is chosen over a sorted set because gateway.Outbox.Ack removes by message id, which a hash does in one HDEL; a sorted set keyed by Seq would force a scan to find the member carrying a given id. Ordering by Seq, which Unacked must return, is recovered by sorting the (small, in-flight) tail on the client after HGETALL rather than paid for on every write.

The sequence is assigned server-side by HINCRBY on seqField inside the append script, not by the calling process, so it stays monotonic across every process and node appending to the same connection through this Redis and across a restart of any of them. That is what makes the persisted Seq trustworthy for a client that dedupes on it: a per-process counter would restart at 1 on reboot and collide with a still-stored message. The counter lives in the same hash as the messages, so it is reclaimed together with them by DropConn's DEL or the TTL, and every operation still touches exactly one key, so a Redis Cluster routes each to one slot with no hash tags.

The Ack generation floor recorded in ackGenField is checked and raised atomically with the message removal it gates, inside ackScript, so an Ack whose generation trails a previously accepted one for the same connection is rejected rather than applied - see gateway.ErrStaleOwner. ackScript is a true no-op for a connection hash that does not exist, so a stream of acks for connection ids this Outbox never appended to cannot create keys.

Only HINCRBY/HSET/HGET/HGETALL/HDEL/EXISTS/DEL and PEXPIRE are used, all present and identical on Redis 7.2 and Valkey 8.

Expiry

With WithTTL, the per-connection hash carries a Redis TTL re-armed on every Append, so a connection that never reconnects to drain or DropConn its tail is reclaimed by the server rather than leaking. Without it (the default) a hash lives until Ack empties it or DropConn deletes it. The TTL bounds at-least-once storage, not delivery: a message whose key expires before the client reconnects is simply not redelivered, the same outcome as if the client had never come back.

Index

Constants

View Source
const DefaultKeyPrefix = "gateway:outbox:"

DefaultKeyPrefix namespaces outbox keys away from every other kind of key a gateway deployment may keep in the same Redis database (notably the presence and coordinator backends).

Variables

This section is empty.

Functions

This section is empty.

Types

type Option

type Option func(*Outbox)

Option configures an Outbox created with New.

func WithKeyPrefix

func WithKeyPrefix(prefix string) Option

WithKeyPrefix namespaces every key this Outbox reads or writes, so multiple gateway deployments (or unrelated applications) can share one Redis instance/database without colliding. Defaults to DefaultKeyPrefix.

func WithTTL

func WithTTL(d time.Duration) Option

WithTTL bounds how long an unacknowledged tail is retained after its last Append, so a connection that never reconnects to drain it cannot leak storage. The TTL is re-armed on every Append. A non-positive duration (the default) means the tail lives until Ack empties it or DropConn deletes it.

type Outbox

type Outbox struct {
	// contains filtered or unexported fields
}

Outbox is a gateway.Outbox backed by a Redis or Valkey client. It is safe for concurrent use.

func New

func New(client goredis.UniversalClient, opts ...Option) *Outbox

New creates an Outbox backed by client. client may be a *redis.Client, *redis.ClusterClient, *redis.Ring, or any other goredis.UniversalClient implementation, pointed at either a Redis or a Valkey server; every operation touches exactly one key, so the cluster case needs no hash tags.

func (*Outbox) Ack

func (o *Outbox) Ack(ctx context.Context, connID, msgID string, generation uint64) error

Ack removes the message identified by msgID from connID, fenced by generation (see the gateway.Outbox.Ack doc comment): a generation trailing the floor already recorded for connID is rejected with gateway.ErrStaleOwner instead of applied. It is otherwise idempotent: acking an unknown id on a connection this Outbox has appended to (already dropped, expired, or never present) deletes nothing and is not an error, and acking a connID this Outbox has never appended to at all is a true no-op that records no fencing state and creates no key.

func (*Outbox) AdvanceGeneration

func (o *Outbox) AdvanceGeneration(ctx context.Context, connID string, generation uint64) error

AdvanceGeneration implements gateway.OutboxGenerationAdvancer. See advanceGenerationScript.

func (*Outbox) Append

func (o *Outbox) Append(ctx context.Context, connID string, payload []byte) (string, uint64, error)

Append records payload as unacknowledged for connID under a freshly minted message id, stamping it with a server-assigned sequence. It re-arms the connection key's TTL when one is configured.

func (*Outbox) DropConn

func (o *Outbox) DropConn(ctx context.Context, connID string) error

DropConn removes all outbox state for connID. It is idempotent: dropping a connection with no stored tail is not an error.

func (*Outbox) Unacked

func (o *Outbox) Unacked(ctx context.Context, connID string) ([]gateway.PersistedMessage, error)

Unacked returns every message still unacknowledged for connID in ascending Seq order. The hash yields the tail unordered; it is sorted here because Redis stores no order across hash fields and the in-flight tail is small.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL