store

package
v0.1.0-beta.4 Latest Latest
Warning

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

Go to latest
Published: Aug 3, 2026 License: MIT Imports: 9 Imported by: 0

Documentation

Overview

Package store defines the persistence contract shared by the SQLite and Postgres backends. All methods take an explicit `now` where time matters so timing behavior is fully deterministic under a fake clock.

Index

Constants

View Source
const (
	ErrKindTimeout   = "timeout"
	ErrKindConnect   = "connect"
	ErrKindDNS       = "dns"
	ErrKindTLS       = "tls"
	ErrKindNon2xx    = "non_2xx"
	ErrKindLeaseLost = "lease_lost"
	ErrKindConfig    = "config"
)

Attempt error kinds.

Variables

View Source
var (
	ErrNotFound           = errors.New("not found")
	ErrAlreadyExists      = errors.New("already exists")
	ErrFailedPrecondition = errors.New("failed precondition")
)

Functions

func RunMigrations

func RunMigrations(ctx context.Context, db *sql.DB, dir fs.FS) error

RunMigrations applies numbered NNNN_*.sql files from dir in order, tracking applied versions in schema_migrations. Each migration runs in its own transaction.

Types

type Attempt

type Attempt struct {
	ID             int64
	TaskPK         int64
	QueueID        int64
	Number         int
	DispatchTime   time.Time
	ResponseTime   *time.Time
	ResponseStatus int // 0 = no response
	ErrorKind      string
	ErrorSnippet   string
	Duration       time.Duration
}

type ClaimBudget

type ClaimBudget struct {
	QueueID  int64
	N        int
	PurgedAt *time.Time
}

type Event

type Event struct {
	Kind         EventKind
	QueueID      int64
	Queue        string
	ScheduleTime time.Time // EventTaskReady
	TaskPK       int64     // EventRunTask
}

type EventKind

type EventKind int

Event kinds emitted by the store for dispatcher wakeup. Events are a latency optimization only — the dispatcher's periodic reconcile is the correctness backstop.

const (
	EventTaskReady EventKind = iota
	EventQueueChanged
	EventRunTask
)

type HTTPTarget

type HTTPTarget struct {
	BaseURL         string            // resolves relative task paths; "" = tasks must use absolute URLs
	Method          string            // "" = no override
	HeaderOverrides map[string]string // merged over task headers; queue wins
	SigningSecret   string            // Standard Webhooks secret ("whsec_..."); "" = unsigned
}

HTTPTarget is queue-level routing/override config applied at dispatch time.

type ListQueuesOpts

type ListQueuesOpts struct {
	NamePrefix string
	PageSize   int
	PageToken  string
}

type ListTasksOpts

type ListTasksOpts struct {
	Queue     string
	State     TaskState // "" = all non-deleted
	IDPrefix  string
	View      View
	PageSize  int
	PageToken string
}

type NewTask

type NewTask struct {
	Queue            string
	ID               string // "" = server-generated
	URL              string
	Method           string
	Headers          map[string]string
	Body             []byte
	DispatchDeadline time.Duration
	RetryOverride    *RetryOverride
	ScheduleTime     time.Time
}

type Outcome

type Outcome int
const (
	OutcomeSucceeded Outcome = iota
	OutcomeFailed
	OutcomeRetry
)

type Queue

type Queue struct {
	ID              int64
	Name            string
	State           QueueState
	RateLimits      RateLimits
	Retry           RetryConfig
	Target          HTTPTarget
	DeadLetterQueue string
	PurgedAt        *time.Time
	ConfigVersion   int64
	CreatedAt       time.Time
	UpdatedAt       time.Time
}

type QueuePatch

type QueuePatch struct {
	State                   *QueueState
	MaxDispatchesPerSecond  *float64
	MaxBurstSize            *int
	MaxConcurrentDispatches *int
	RetryMaxAttempts        *int
	RetryMaxRetryDuration   *time.Duration
	RetryMinBackoff         *time.Duration
	RetryMaxBackoff         *time.Duration
	RetryMaxDoublings       *int
	TargetBaseURL           *string
	TargetMethod            *string
	TargetHeaderOverrides   *map[string]string
	SigningSecret           *string
	DeadLetterQueue         *string
}

QueuePatch applies only non-nil fields.

type QueueSeed

type QueueSeed struct {
	QueueID      int64
	Pending      int64
	Leased       int64
	NextSchedule *time.Time
}

type QueueState

type QueueState string
const (
	QueueRunning  QueueState = "running"
	QueuePaused   QueueState = "paused"
	QueueDisabled QueueState = "disabled"
)

type QueueStats

type QueueStats struct {
	Pending         int64 // due now or overdue
	Running         int64
	Succeeded       int64
	Failed          int64
	ScheduledFuture int64 // pending with schedule_time in the future
	OldestDueAt     *time.Time
	Dispatched1m    int64
	Errors1m        int64
	Dispatched1h    int64
	Errors1h        int64
}

type QueueUpsert

type QueueUpsert struct {
	Name            string
	RateLimits      RateLimits
	Retry           RetryConfig
	Target          HTTPTarget
	KeepSecret      bool // true = leave stored signing secret untouched (secret omitted from request)
	DeadLetterQueue string
}

QueueUpsert is the full desired configuration for PUT semantics. State is intentionally absent: upserts never flip running/paused/disabled, so a config upsert on every enqueue cannot accidentally resume a paused queue.

type RateLimits

type RateLimits struct {
	MaxDispatchesPerSecond  float64
	MaxBurstSize            int // 0 = auto: clamp(ceil(rate), 1, 500)
	MaxConcurrentDispatches int
}

RateLimits mirrors GCP Cloud Tasks queue rate limits.

func DefaultRateLimits

func DefaultRateLimits() RateLimits

DefaultRateLimits / DefaultRetryConfig mirror GCP Cloud Tasks queue defaults.

func (RateLimits) EffectiveBurst

func (r RateLimits) EffectiveBurst() int

EffectiveBurst resolves MaxBurstSize=0 (auto) the way GCP derives it.

type RetryConfig

type RetryConfig struct {
	MaxAttempts      int           // includes the first attempt; -1 = unlimited
	MaxRetryDuration time.Duration // measured from the first attempt; 0 = unlimited
	MinBackoff       time.Duration
	MaxBackoff       time.Duration
	MaxDoublings     int
}

RetryConfig mirrors GCP Cloud Tasks retry semantics.

func DefaultRetryConfig

func DefaultRetryConfig() RetryConfig

type RetryOverride

type RetryOverride struct {
	MaxAttempts      *int           `json:"max_attempts,omitempty"`
	MaxRetryDuration *time.Duration `json:"max_retry_duration_ms,omitempty"`
	MinBackoff       *time.Duration `json:"min_backoff_ms,omitempty"`
	MaxBackoff       *time.Duration `json:"max_backoff_ms,omitempty"`
	MaxDoublings     *int           `json:"max_doublings,omitempty"`
}

RetryOverride is a per-task partial override of the queue RetryConfig.

func (*RetryOverride) Merge

func (o *RetryOverride) Merge(base RetryConfig) RetryConfig

Merge applies the override on top of a queue RetryConfig.

type Store

type Store interface {
	Migrate(ctx context.Context) error
	Ping(ctx context.Context) error
	Close() error
	Dialect() string

	// Events streams dispatcher wakeup hints (task ready, queue config
	// changed, force-run). Best-effort: events may be dropped under load;
	// the dispatcher reconcile loop is the correctness backstop.
	Events() <-chan Event

	// Queues
	UpsertQueue(ctx context.Context, u QueueUpsert, now time.Time) (Queue, UpsertOutcome, error)
	CreateQueue(ctx context.Context, u QueueUpsert, now time.Time) (Queue, error)
	PatchQueue(ctx context.Context, name string, p QueuePatch, now time.Time) (Queue, error)
	GetQueue(ctx context.Context, name string) (Queue, error)
	GetQueuesByID(ctx context.Context, ids []int64) ([]Queue, error)
	ListQueues(ctx context.Context, opts ListQueuesOpts) ([]Queue, string, error)
	DeleteQueue(ctx context.Context, name string) error
	// SetQueueState is used by pause/resume; disabled is set via PatchQueue.
	SetQueueState(ctx context.Context, name string, state QueueState, now time.Time) (Queue, error)
	PurgeQueue(ctx context.Context, name string, now time.Time) (Queue, error)
	QueueStats(ctx context.Context, name, idPrefix string, now time.Time) (QueueStats, error)

	// Tasks
	CreateTask(ctx context.Context, t NewTask, now time.Time) (Task, error)
	CreateTasks(ctx context.Context, ts []NewTask, now time.Time) ([]TaskResult, error)
	GetTask(ctx context.Context, queue, taskID string, view View) (Task, error)
	ListTasks(ctx context.Context, opts ListTasksOpts) ([]Task, string, error)
	// DeleteTask tombstones the row (state=deleted) for the dedup window.
	DeleteTask(ctx context.Context, queue, taskID string, now time.Time) error
	// ForceRun moves a pending task's schedule_time to now and emits
	// EventRunTask. The dispatcher dispatches it immediately, bypassing
	// rate limits and PAUSED state (GCP parity).
	ForceRun(ctx context.Context, queue, taskID string, now time.Time) (Task, error)
	ListAttempts(ctx context.Context, queue, taskID string) ([]Attempt, error)

	// Dispatcher
	SeedDispatch(ctx context.Context) ([]QueueSeed, error)
	// ClaimTasks atomically leases up to budget.N due pending tasks per
	// queue, incrementing dispatch_count and stamping first_attempt_at.
	ClaimTasks(ctx context.Context, now time.Time, leaseToken string, leaseGrace time.Duration, budgets []ClaimBudget) ([]Task, error)
	// ClaimTaskForRun leases one specific task regardless of schedule_time
	// or queue pause state (still refuses disabled queues).
	ClaimTaskForRun(ctx context.Context, now time.Time, leaseToken string, leaseGrace time.Duration, taskPK int64) (Task, bool, error)
	// NextPending returns the min schedule_time among pending tasks.
	NextPending(ctx context.Context, queueID int64, now time.Time) (*time.Time, error)
	// FinishAttempt records the attempt and applies the outcome, fenced on
	// (taskPK, leaseToken, state=leased). Returns false when the fence
	// fails (lease expired/stolen or task deleted/purged) — the result is
	// then discarded.
	FinishAttempt(ctx context.Context, now time.Time, taskPK int64, leaseToken string, att Attempt, outcome Outcome, nextSchedule time.Time) (bool, error)
	ListExpiredLeases(ctx context.Context, now time.Time, limit int) ([]Task, error)
	// CleanupTerminal hard-deletes terminal rows older than their TTL
	// (floored by the dedup window). Returns rows deleted.
	CleanupTerminal(ctx context.Context, now time.Time, succeededTTL, failedTTL, dedupWindow time.Duration, limit int) (int64, error)
	// HardDeletePurged removes pending tasks superseded by a purge watermark.
	HardDeletePurged(ctx context.Context, limit int) (int64, error)
}

type Task

type Task struct {
	PK               int64
	QueueID          int64
	Queue            string // queue name (joined for convenience)
	ID               string
	State            TaskState
	URL              string // absolute URL or path starting with "/" (resolved at dispatch)
	Method           string
	Headers          map[string]string
	Body             []byte
	DispatchDeadline time.Duration
	RetryOverride    *RetryOverride
	ScheduleTime     time.Time
	CreatedAt        time.Time
	FirstAttemptAt   *time.Time
	FinishedAt       *time.Time
	DispatchCount    int
	ResponseCount    int
	LeaseToken       string
	LeaseExpiresAt   *time.Time
	LastAttemptAt    *time.Time
	LastStatus       int // 0 = none
	LastError        string
}

type TaskResult

type TaskResult struct {
	Task Task
	Err  error // ErrAlreadyExists etc.; nil on success
}

type TaskState

type TaskState string
const (
	TaskPending   TaskState = "pending"
	TaskLeased    TaskState = "leased"
	TaskSucceeded TaskState = "succeeded"
	TaskFailed    TaskState = "failed"
	// TaskDeleted rows are invisible through the API; they persist only as
	// dedup tombstones until retention cleanup removes them.
	TaskDeleted TaskState = "deleted"
)

type UpsertOutcome

type UpsertOutcome int
const (
	UpsertCreated UpsertOutcome = iota
	UpsertUpdated
	UpsertNoop
)

type View

type View string
const (
	ViewBasic View = "basic" // omits headers and body
	ViewFull  View = "full"
)

Directories

Path Synopsis
Package postgres implements store.Store on PostgreSQL via pgx.
Package postgres implements store.Store on PostgreSQL via pgx.
Package sqlite implements store.Store on SQLite via the pure-Go modernc.org/sqlite driver.
Package sqlite implements store.Store on SQLite via the pure-Go modernc.org/sqlite driver.
Package storetest is the conformance suite every store backend must pass.
Package storetest is the conformance suite every store backend must pass.

Jump to

Keyboard shortcuts

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