Documentation
¶
Index ¶
- Constants
- func GenerateSecureToken() (string, []byte, string, error)
- func HashSecret(secretStr string) []byte
- func RunMigrations(databaseURL string) error
- type APIKeyCreationResult
- type AuditEntry
- type AuditRepo
- type ClaimResult
- type CreateScheduleParams
- type CreateTenantParams
- type DB
- type EnqueueParams
- type EnqueueResult
- type HeartbeatParams
- type JobRepo
- func (r *JobRepo) CancelJob(ctx context.Context, tenantID, jobID uuid.UUID) (*domain.Job, error)
- func (r *JobRepo) ClaimJob(ctx context.Context, workerID uuid.UUID, ...) (*ClaimResult, error)
- func (r *JobRepo) ClaimJobs(ctx context.Context, workerID uuid.UUID, ...) ([]*ClaimResult, error)
- func (r *JobRepo) CompleteJobCancelled(ctx context.Context, tenantID, jobID, leaseToken uuid.UUID, durationMs int64) error
- func (r *JobRepo) CompleteJobFailure(ctx context.Context, tenantID, jobID, leaseToken uuid.UUID, errVal error, ...) error
- func (r *JobRepo) CompleteJobSuccess(ctx context.Context, tenantID, jobID, leaseToken uuid.UUID, ...) error
- func (r *JobRepo) CompleteJobsSuccessBatch(ctx context.Context, items []SuccessCompletion) ([]bool, error)
- func (r *JobRepo) EnqueueBatch(ctx context.Context, items []EnqueueParams) (results []*EnqueueResult, failed *EnqueueResult, err error)
- func (r *JobRepo) EnqueueJob(ctx context.Context, p EnqueueParams) (*EnqueueResult, error)
- func (r *JobRepo) EnqueueJobTx(ctx context.Context, tx pgx.Tx, p EnqueueParams) (*EnqueueResult, error)
- func (r *JobRepo) GetJob(ctx context.Context, tenantID, jobID uuid.UUID) (*domain.Job, error)
- func (r *JobRepo) GetJobAttempts(ctx context.Context, tenantID, jobID uuid.UUID) ([]domain.JobAttempt, error)
- func (r *JobRepo) GetQueueStats(ctx context.Context, tenantID uuid.UUID, queueName string) (map[string]int64, error)
- func (r *JobRepo) ListJobs(ctx context.Context, f ListJobsFilter) ([]domain.Job, error)
- func (r *JobRepo) ListenForJobs(ctx context.Context, wake chan<- struct{})
- func (r *JobRepo) RecoverExpiredLeases(ctx context.Context, batchSize int, backoffPolicy *domain.BackoffPolicy) (int, error)
- func (r *JobRepo) RenewLease(ctx context.Context, jobID, leaseToken uuid.UUID, leaseDuration time.Duration) (*RenewLeaseResult, error)
- func (r *JobRepo) ReplayDeadJob(ctx context.Context, tenantID, jobID uuid.UUID, actorKeyID *uuid.UUID, ...) (*domain.Job, error)
- type ListJobsFilter
- type RenewLeaseResult
- type RetentionCleanupResult
- type RetentionRepo
- type ScheduleRepo
- func (r *ScheduleRepo) CreateSchedule(ctx context.Context, p CreateScheduleParams) (*domain.JobSchedule, error)
- func (r *ScheduleRepo) DisableSchedule(ctx context.Context, tenantID, scheduleID uuid.UUID) error
- func (r *ScheduleRepo) GetSchedule(ctx context.Context, tenantID, scheduleID uuid.UUID) (*domain.JobSchedule, error)
- func (r *ScheduleRepo) ListSchedules(ctx context.Context, tenantID uuid.UUID) ([]domain.JobSchedule, error)
- func (r *ScheduleRepo) ParseNextOccurrence(cronExpr, timezone string, fromTime time.Time) (time.Time, error)
- func (r *ScheduleRepo) ProcessDueSchedules(ctx context.Context, batchSize int, misfireGrace time.Duration) (int, error)
- func (r *ScheduleRepo) SetSchedulePaused(ctx context.Context, tenantID, scheduleID uuid.UUID, paused bool) (*domain.JobSchedule, error)
- type SuccessCompletion
- type TenantRepo
- func (r *TenantRepo) CreateAPIKey(ctx context.Context, tenantID uuid.UUID, name string, permissions []string, ...) (*APIKeyCreationResult, error)
- func (r *TenantRepo) CreateAPIKeyExpiringIn(ctx context.Context, tenantID uuid.UUID, name string, permissions []string, ...) (*APIKeyCreationResult, error)
- func (r *TenantRepo) CreateTenant(ctx context.Context, p CreateTenantParams) (*domain.Tenant, error)
- func (r *TenantRepo) GetLimits(ctx context.Context, tenantID uuid.UUID) (*domain.TenantLimits, error)
- func (r *TenantRepo) GetTenant(ctx context.Context, id uuid.UUID) (*domain.Tenant, error)
- func (r *TenantRepo) ListTenants(ctx context.Context) ([]domain.Tenant, error)
- func (r *TenantRepo) LookupAPIKeyBySecret(ctx context.Context, rawSecret string) (*domain.APIKey, *domain.Tenant, error)
- func (r *TenantRepo) ReconcileUsage(ctx context.Context, tenantID uuid.UUID) (int64, error)
- func (r *TenantRepo) RevokeAPIKey(ctx context.Context, keyID uuid.UUID) error
- type WorkerRepo
Constants ¶
const JobsChannel = "qf_jobs"
JobsChannel is the Postgres LISTEN/NOTIFY channel that EnqueueJob signals on commit.
Variables ¶
This section is empty.
Functions ¶
func HashSecret ¶
func RunMigrations ¶
RunMigrations applies versioned migrations using goose
Types ¶
type APIKeyCreationResult ¶
type AuditEntry ¶
type AuditEntry struct {
ID int64 `json:"id"`
OccurredAt time.Time `json:"occurred_at"`
TenantID *uuid.UUID `json:"tenant_id,omitempty"`
ActorType string `json:"actor_type"` // "api_key" | "operator"
ActorID string `json:"actor_id"`
Action string `json:"action"`
TargetID string `json:"target_id,omitempty"`
StatusCode int `json:"status_code,omitempty"`
RequestID string `json:"request_id,omitempty"`
RemoteAddr string `json:"remote_addr,omitempty"`
Details json.RawMessage `json:"details,omitempty"`
}
AuditEntry is one control-plane action.
type AuditRepo ¶
type AuditRepo struct {
// contains filtered or unexported fields
}
func NewAuditRepo ¶
type ClaimResult ¶
type CreateScheduleParams ¶
type CreateTenantParams ¶
type EnqueueParams ¶
type EnqueueParams struct {
TenantID uuid.UUID
TaskType string
QueueName string
Payload json.RawMessage
Priority int16
RunAt time.Time
IsExplicitRunAt bool
MaxAttempts int
TimeoutSeconds int
IdempotencyKey string
IdempotencyTTL time.Duration
ScheduleID *uuid.UUID
ScheduledFor *time.Time
TraceID string // correlation id; generated if empty
}
type EnqueueResult ¶
type HeartbeatParams ¶
type JobRepo ¶
type JobRepo struct {
// contains filtered or unexported fields
}
func NewJobRepo ¶
func (*JobRepo) ClaimJob ¶
func (r *JobRepo) ClaimJob(ctx context.Context, workerID uuid.UUID, allowedQueues, supportedTaskTypes []string, leaseDuration time.Duration) (*ClaimResult, error)
ClaimJob leases a single job. See ClaimJobs.
func (*JobRepo) ClaimJobs ¶
func (r *JobRepo) ClaimJobs(ctx context.Context, workerID uuid.UUID, allowedQueues, supportedTaskTypes []string, leaseDuration time.Duration, max int) ([]*ClaimResult, error)
ClaimJobs leases up to max jobs in one transaction (usually one statement). Jobs are chosen by priority then FIFO across all tenants, subject to each tenant's free execution slots. It returns fewer than max jobs when nothing more is claimable.
func (*JobRepo) CompleteJobCancelled ¶
func (*JobRepo) CompleteJobFailure ¶
func (*JobRepo) CompleteJobSuccess ¶
func (r *JobRepo) CompleteJobSuccess(ctx context.Context, tenantID, jobID, leaseToken uuid.UUID, result json.RawMessage, durationMs int64) error
CompleteJobSuccess records a successful attempt, releases the tenant slot and decrements the outstanding-jobs counter in ONE statement (data-modifying CTEs). One statement is atomic on its own, so there is no explicit transaction: 1 round trip and 1 commit instead of 6 round trips.
Fencing: every row change hangs off the job update, which matches only if the caller still holds the current lease_token. A worker that lost its lease updates nothing and gets ErrLeaseLost.
func (*JobRepo) CompleteJobsSuccessBatch ¶
func (r *JobRepo) CompleteJobsSuccessBatch(ctx context.Context, items []SuccessCompletion) ([]bool, error)
CompleteJobsSuccessBatch records many successful jobs in ONE statement (one round trip, one commit) with exactly the guarantees of CompleteJobSuccess: each row is fenced by its own lease token, its attempt row is finished, its execution slot is released and the tenant's outstanding counter drops by the number of jobs actually completed. The returned slice is aligned with items; false means that job's lease was no longer valid (stale completion, rejected).
Callers must fall back to CompleteJobSuccess on error: a batch can fail as a whole (for example a deadlock between two batches touching the same tenant rows) even though each job is fine.
func (*JobRepo) EnqueueBatch ¶
func (r *JobRepo) EnqueueBatch(ctx context.Context, items []EnqueueParams) (results []*EnqueueResult, failed *EnqueueResult, err error)
EnqueueBatch enqueues several jobs atomically in ONE transaction: either all are accepted or none is (a rate-limit, quota or other rejection rolls everything back). One commit for N jobs is much cheaper than N separate commits. On error, failed carries the rejected item's result (for RetryAfterHint) when there is one.
func (*JobRepo) EnqueueJob ¶
func (r *JobRepo) EnqueueJob(ctx context.Context, p EnqueueParams) (*EnqueueResult, error)
EnqueueJob enqueues a job in its own transaction.
func (*JobRepo) EnqueueJobTx ¶
func (r *JobRepo) EnqueueJobTx(ctx context.Context, tx pgx.Tx, p EnqueueParams) (*EnqueueResult, error)
EnqueueJobTx enqueues a job inside a transaction the CALLER owns, so the job and the caller's own business rows commit or roll back together (the transactional-outbox pattern). It uses a savepoint, so a rate-limit, quota or idempotency rejection undoes only the enqueue and leaves the caller's transaction usable. The job becomes visible to workers (and the NOTIFY is delivered) only when the caller commits. Only available when your code shares QueueForge's PostgreSQL database.
func (*JobRepo) GetJobAttempts ¶
func (*JobRepo) GetQueueStats ¶
func (*JobRepo) ListenForJobs ¶
ListenForJobs blocks until ctx is cancelled, sending a (non-blocking) signal on wake every time a job is enqueued. It holds one dedicated connection for LISTEN and transparently reconnects.
NOTIFY is only an optimisation: it removes the average poll-interval/2 pickup latency. Workers must still poll, because notifications are best-effort (a worker may be disconnected when one is sent) and delayed/retried jobs become runnable with the passage of time, not an event.
func (*JobRepo) RecoverExpiredLeases ¶
func (r *JobRepo) RecoverExpiredLeases(ctx context.Context, batchSize int, backoffPolicy *domain.BackoffPolicy) (int, error)
RecoverExpiredLeases finds running jobs whose lease has expired (the worker crashed, hung or was partitioned) and requeues them with backoff, or marks them dead once attempts are exhausted.
Each job is recovered in its OWN transaction. Doing the whole batch in one transaction (as an earlier version did) meant a single failing row aborted the Postgres transaction and silently rolled back every other recovery in the batch, on every tick, forever. Now a poison row is reported in the returned error while all healthy rows are still recovered.
func (*JobRepo) RenewLease ¶
type ListJobsFilter ¶
type RenewLeaseResult ¶
type RenewLeaseResult struct {
CancellationRequested bool
}
type RetentionCleanupResult ¶
type RetentionRepo ¶
type RetentionRepo struct {
// contains filtered or unexported fields
}
func NewRetentionRepo ¶
func NewRetentionRepo(db *DB) *RetentionRepo
func (*RetentionRepo) CleanupExpired ¶
func (r *RetentionRepo) CleanupExpired(ctx context.Context, terminalRetention time.Duration, batchSize int) (*RetentionCleanupResult, error)
CleanupExpired deletes expired idempotency keys, terminal jobs older than terminalRetention (with their attempts, audit events and idempotency keys) and stale worker registrations.
It keeps deleting batch after batch until the backlog is gone, the context ends, or maxRetentionRounds is reached. An earlier version deleted ONE batch of 100 per hourly tick, a ceiling of 2,400 jobs/day: any real workload grew the table (and the disk) without bound.
Each batch is one atomic statement. If a batch fails (say one poisoned row), the rows of that batch are retried one at a time so a single bad row cannot stall retention; every failure is returned (errors.Join), never swallowed.
type ScheduleRepo ¶
type ScheduleRepo struct {
// contains filtered or unexported fields
}
func NewScheduleRepo ¶
func NewScheduleRepo(db *DB) *ScheduleRepo
func (*ScheduleRepo) CreateSchedule ¶
func (r *ScheduleRepo) CreateSchedule(ctx context.Context, p CreateScheduleParams) (*domain.JobSchedule, error)
func (*ScheduleRepo) DisableSchedule ¶
func (*ScheduleRepo) GetSchedule ¶
func (r *ScheduleRepo) GetSchedule(ctx context.Context, tenantID, scheduleID uuid.UUID) (*domain.JobSchedule, error)
func (*ScheduleRepo) ListSchedules ¶
func (r *ScheduleRepo) ListSchedules(ctx context.Context, tenantID uuid.UUID) ([]domain.JobSchedule, error)
func (*ScheduleRepo) ParseNextOccurrence ¶
func (*ScheduleRepo) ProcessDueSchedules ¶
func (r *ScheduleRepo) ProcessDueSchedules(ctx context.Context, batchSize int, misfireGrace time.Duration) (int, error)
ProcessDueSchedules processes due recurring schedules with concurrency protection and misfire grace
func (*ScheduleRepo) SetSchedulePaused ¶
func (r *ScheduleRepo) SetSchedulePaused(ctx context.Context, tenantID, scheduleID uuid.UUID, paused bool) (*domain.JobSchedule, error)
type SuccessCompletion ¶
type SuccessCompletion struct {
TenantID, JobID, LeaseToken uuid.UUID
Result json.RawMessage
DurationMs int64
}
SuccessCompletion is one finished job to record in CompleteJobsSuccessBatch.
type TenantRepo ¶
type TenantRepo struct {
// contains filtered or unexported fields
}
func NewTenantRepo ¶
func NewTenantRepo(db *DB) *TenantRepo
func (*TenantRepo) CreateAPIKey ¶
func (*TenantRepo) CreateAPIKeyExpiringIn ¶
func (r *TenantRepo) CreateAPIKeyExpiringIn(ctx context.Context, tenantID uuid.UUID, name string, permissions []string, d time.Duration) (*APIKeyCreationResult, error)
CreateAPIKeyExpiringIn creates a key that expires d after now, where "now" is the DATABASE clock (see ADR-014), so expiry is unaffected by the operator's own clock.
func (*TenantRepo) CreateTenant ¶
func (r *TenantRepo) CreateTenant(ctx context.Context, p CreateTenantParams) (*domain.Tenant, error)
func (*TenantRepo) GetLimits ¶
func (r *TenantRepo) GetLimits(ctx context.Context, tenantID uuid.UUID) (*domain.TenantLimits, error)
func (*TenantRepo) ListTenants ¶
func (*TenantRepo) LookupAPIKeyBySecret ¶
func (*TenantRepo) ReconcileUsage ¶
func (*TenantRepo) RevokeAPIKey ¶
type WorkerRepo ¶
type WorkerRepo struct {
// contains filtered or unexported fields
}
func NewWorkerRepo ¶
func NewWorkerRepo(db *DB) *WorkerRepo
func (*WorkerRepo) Heartbeat ¶
func (r *WorkerRepo) Heartbeat(ctx context.Context, p HeartbeatParams) error
func (*WorkerRepo) ListActiveWorkers ¶
func (r *WorkerRepo) ListActiveWorkers(ctx context.Context, maxAge time.Duration) ([]domain.WorkerInstance, error)