pizza

package module
v0.0.0-...-8c55ffc Latest Latest
Warning

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

Go to latest
Published: Apr 24, 2026 License: AGPL-3.0 Imports: 11 Imported by: 0

README

What is Pizza?

Pizza is a distributed task engine and actor runtime built on top of Kubernetes without redis/postgres. It provides a simple and scalable way to run tasks and actors in a distributed environment, similar to asynq but designed to run natively on Kubernetes StatefulSets — no Redis or external broker required.

Key Features

  • Kubernetes-native: Designed to run as a Kubernetes StatefulSet, leveraging stable pod identities for reliable task ownership and heartbeat tracking.
  • No external dependencies: Pizza uses Kubernetes itself as the coordination layer — no Redis, no Postgres.
  • Distributed task processing: Enqueue and process tasks across multiple workers with at-least-once delivery and heartbeat-based failure recovery.
  • Actor runtime: Supports long-running actors alongside one-shot tasks.

Example

package main

import (
	"context"
	"encoding/json"
	"log/slog"
	"os"
	"os/signal"
	"sync"
	"syscall"
	"time"

	"github.com/abit2/pizza"
	"github.com/abit2/pizza/db"
	"github.com/abit2/pizza/log"
	"github.com/abit2/pizza/task/task/generated"
	"github.com/abit2/pizza/utils"
	badger "github.com/dgraph-io/badger/v4"
)

// EmailHandler processes "send_email" tasks.
type EmailHandler struct{}

func (h *EmailHandler) Process(ctx context.Context, task *generated.Task) error {
	// task.GetPayload() contains the raw bytes you enqueued
	slog.Info("sending email", "payload", string(task.GetPayload()))
	return nil
}

func main() {
	// 1. Open an embedded Badger database.
	//    On a Kubernetes StatefulSet mount this path on a PersistentVolume so
	//    each pod keeps its own shard of the queue across restarts.
	bdb, err := badger.Open(badger.DefaultOptions("./data"))
	if err != nil {
		panic(err)
	}
	defer bdb.Close()

	// 2. Wrap Badger in Pizza's DB layer.
	clock := utils.NewRealClock()
	lvl := new(slog.LevelVar)
	lvl.Set(slog.LevelInfo)
	logger := log.NewLogger(slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: lvl})))

	dbWrap, err := db.New(bdb, logger, nil, clock)
	if err != nil {
		panic(err)
	}

	// 3. Enqueue a task.
	headers, _ := json.Marshal(map[string]string{
		pizza.TaskType: "send_email", // routes to the matching Handler
	})
	_, err = dbWrap.Enqueue([]byte("notifications"), []byte(`{"to":"user@example.com"}`), headers, 3)
	if err != nil {
		panic(err)
	}

	// 4. Start the server.
	srv := pizza.NewServer(logger, dbWrap, &pizza.ServerConfig{
		Queues:                  []string{"notifications"},
		Concurrency:             10,
		PromiseInterval:         5 * time.Second,
		RecoveryInterval:        10 * time.Second,
		GracefulShutdownTimeout: 5 * time.Second,
	}, clock)

	ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
	defer stop()

	var wg sync.WaitGroup
	srv.Run(ctx, &wg)
	// NOTE: run the srv before registering the Handlers
	srv.SetupHandler(map[string]pizza.Handler{
		"send_email": &EmailHandler{},
	})

	<-ctx.Done()
	srv.Stop()
	wg.Wait()
}
How it maps to a Kubernetes StatefulSet

Each StatefulSet replica opens its own Badger volume (via a volumeClaimTemplate). Tasks are enqueued into a shared queue name; any worker (running concurrently) can dequeue and process them. Heartbeat-based lease recovery ensures that if a pod crashes mid-task, the work is automatically re-queued and processed by the worker.

Documentation

Index

Constants

View Source
const (
	TaskType  = "task-type"
	LeaseTill = "lease-till"
)

Variables

This section is empty.

Functions

This section is empty.

Types

type Clock

type Clock interface {
	Now() time.Time
}

type DB

type DB interface {
	Dequeue(queue []byte) (*generated.Task, error)
	MoveToRetryFromActive(queue []byte, taskID string) error
	MoveToArchivedFromActive(queue []byte, taskID string) error
	MoveToCompletedFromActive(queue []byte, taskID string) error
}

type Forwarder

type Forwarder interface {
	Forward(queue []byte, now int64) error
}

TODO: write tests for promise/forwarder

type Handler

type Handler interface {
	Process(ctx context.Context, req *generated.Task) error
}

type HeartBeat

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

HeartBeat - only covers crashes of the server itself, which will recover the tasks in it.

func NewHearBeater

func NewHearBeater(l *log.Logger, interval time.Duration, claimed, finished <-chan *taskInfoHeartBeat, expiredTasks chan<- *taskInfoHeartBeat, db heartBeatDB, durationBeforeLeaseExpiry time.Duration) *HeartBeat

type Processor

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

func NewProcessor

func NewProcessor(l *log.Logger, db DB, cfg *ProcessorConfig, claimedTasks chan *taskInfoHeartBeat, finishedTasks chan *taskInfoHeartBeat, cl Clock) *Processor

type ProcessorConfig

type ProcessorConfig struct {
	MaxConcurrency int
	Queues         []string
}

type Promise

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

Promise is responsible to move tasks from retry to pending queue

func NewPromise

func NewPromise(l *log.Logger, interval time.Duration, queues []string, f Forwarder, cl Clock) *Promise

type Recovery

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

func NewRecovery

func NewRecovery(l *log.Logger,
	interval time.Duration,
	expiredTasks <-chan *taskInfoHeartBeat,
	db recoverDB,
) *Recovery

type Server

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

func NewServer

func NewServer(l *log.Logger, db *db.DB, cfg *ServerConfig, cl Clock) *Server

func (*Server) Run

func (s *Server) Run(ctx context.Context, wg *sync.WaitGroup)

func (*Server) SetupHandler

func (s *Server) SetupHandler(handleMapping map[string]Handler)

func (*Server) Stop

func (s *Server) Stop()

type ServerConfig

type ServerConfig struct {
	Queues           []string
	Concurrency      int
	PromiseInterval  time.Duration
	RecoveryInterval time.Duration

	GracefulShutdownTimeout time.Duration
}

Directories

Path Synopsis
task

Jump to

Keyboard shortcuts

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