Documentation
¶
Overview ¶
Package cascadeq implements a persistent, disk-backed FIFO queue for []byte items. It operates primarily in memory and spills to disk only when the configured memory limits are reached, making it efficient for workloads that fit in memory while remaining durable under backpressure.
Memory model ¶
The queue maintains two in-memory deques at all times:
- headQ: the read-from queue, holding the oldest unconsumed items
- tailQ: the write-to queue, holding the newest items
The configured memory/item limits are divided equally between headQ and tailQ. When tailQ is full, its contents are flushed to a numbered .dat file on disk unless headQ has room to absorb them directly. Items are read back by loading the next numbered file into headQ; when no files remain, tailQ and headQ are swapped in O(1).
Usage ¶
Create a Queue with New, write items with Put or PutBatch, and consume them via the channel returned by Out or by calling Drain:
q, err := cascadeq.New("/var/data/queues/myqueue")
if err != nil {
log.Fatal(err)
}
defer q.Close()
// Single-item producer
if err := q.Put([]byte("hello")); err != nil {
log.Println(err)
}
// Batch producer — one round-trip for the whole slice
items := [][]byte{[]byte("a"), []byte("b"), []byte("c")}
if err := q.PutBatch(items); err != nil {
log.Println(err)
}
// Channel consumer
for {
select {
case item := <-q.Out():
process(item)
case <-q.Empty():
return // no more items right now
case <-q.Done():
return // queue closed
}
}
// Batch consumer — drain up to N items in one round-trip
dst := make([][]byte, 64)
if n := q.Drain(dst); n > 0 {
process(dst[:n])
}
Options ¶
Behaviour is configured via functional options passed to New:
- WithMaxMemory: cap total in-memory bytes (default 1 MiB)
- WithMaxMemItems: cap total in-memory item count (default 4096)
- WithMaxItemSize: reject items above this byte size (default 64 KiB)
- WithMinItemSize: reject items below this byte size (default 0)
- WithGzip: enable gzip compression of disk files
- WithSnapshotInterval: periodically persist in-memory state when idle
- WithLogger: replace the default JSON slog.Logger
Persistence and file format ¶
Overflow files are written to the directory supplied to New and named cq-{hexnum}.dat (or .dat.gz when compression is enabled). Each file is a sequence of big-endian int32 length-prefixed byte records. File number 0 is reserved for the headQ snapshot written on close or idle snapshot; higher numbers are sequential tailQ overflow files. On restart, New re-discovers these files and resumes from where the queue left off. Corrupt files are renamed with a .bad extension rather than deleted.
Concurrency ¶
All state mutations run inside a single goroutine. Put, Clear, and Stats are safe to call from multiple goroutines concurrently. Close is idempotent and safe to call from any goroutine.
Example (Basic) ¶
Example_basic demonstrates creating a queue, writing items, and reading them back until the queue is empty.
package main
import (
"fmt"
"log"
"os"
"github.com/gammazero/cascadeq"
)
func main() {
dir, err := os.MkdirTemp("", "cascadeq-example1-*")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(dir)
q, err := cascadeq.New(dir)
if err != nil {
log.Fatal(err)
}
defer q.Close()
for i := range 3 {
if err := q.Put(fmt.Appendf(nil, "item-%d", i)); err != nil {
log.Fatal(err)
}
}
for {
select {
case item := <-q.Out():
fmt.Println(string(item))
case <-q.Empty():
return
}
}
}
Output: item-0 item-1 item-2
Example (Batch) ¶
Example_batch demonstrates PutBatch and Drain for high-throughput batch I/O.
package main
import (
"fmt"
"log"
"os"
"github.com/gammazero/cascadeq"
)
func main() {
dir, err := os.MkdirTemp("", "cascadeq-example2-*")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(dir)
q, err := cascadeq.New(dir)
if err != nil {
log.Fatal(err)
}
defer q.Close()
items := [][]byte{[]byte("alpha"), []byte("beta"), []byte("gamma")}
if err := q.PutBatch(items); err != nil {
log.Fatal(err)
}
dst := make([][]byte, 10)
n := q.Drain(dst)
for _, item := range dst[:n] {
fmt.Println(string(item))
}
}
Output: alpha beta gamma
Example (Options) ¶
Example_options shows configuring memory limits and gzip compression.
package main
import (
"fmt"
"log"
"os"
"github.com/gammazero/cascadeq"
)
func main() {
dir, err := os.MkdirTemp("", "cascadeq-example3-*")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(dir)
q, err := cascadeq.New(dir,
cascadeq.WithMaxMemory(64*1024), // 64 KiB in-memory budget
cascadeq.WithMaxMemItems(128), // at most 128 items in memory
cascadeq.WithMaxItemSize(4*1024), // reject items larger than 4 KiB
cascadeq.WithGzip(true), // compress overflow files
)
if err != nil {
log.Fatal(err)
}
defer q.Close()
if err := q.Put([]byte("hello")); err != nil {
log.Fatal(err)
}
item := <-q.Out()
fmt.Println(string(item))
}
Output: hello
Index ¶
- Constants
- Variables
- func WithGzip(enable bool) func(*Queue)
- func WithLogger(logger *slog.Logger) func(*Queue)
- func WithMaxItemSize(maxSize int) func(*Queue)
- func WithMaxMemItems(maxItems int) func(*Queue)
- func WithMaxMemory(maxBytes int) func(*Queue)
- func WithMinItemSize(minSize int) func(*Queue)
- func WithSnapshotInterval(d time.Duration) func(*Queue)
- type Queue
- func (q *Queue) Clear() error
- func (q *Queue) Close() error
- func (q *Queue) Dir() string
- func (q *Queue) Done() <-chan struct{}
- func (q *Queue) Drain(dst [][]byte) int
- func (q *Queue) Empty() <-chan struct{}
- func (q *Queue) Name() string
- func (q *Queue) Out() <-chan []byte
- func (q *Queue) Put(item []byte) error
- func (q *Queue) PutBatch(items [][]byte) error
- func (q *Queue) Stats() Stats
- type Stats
Examples ¶
Constants ¶
const ( DefaultMaxMemory = 1024 * 1024 DefaultMaxMemItems = 4096 DefaultMaxItemSize = 65536 )
const (
BadFileExt = ".bad"
)
Variables ¶
var ErrClosed = errors.New("closed")
ErrClosed is returned when I/O is attempted on a closed Queue.
var ErrIsDirectory = errors.New("queue file is a directory")
ErrIsDirectory is returned when trying to read a queue file that is a directory.
Functions ¶
func WithLogger ¶ added in v0.0.2
WithLogger sets the slog.Logger instance to use for logging. This replaces the default cascadeq slog.Logger, which writes JSON to stderr. A nil logger is ignored, leaving the default in place.
func WithMaxItemSize ¶
WithMaxItemSize specifies the maximum allowed size of a single []byte item in the queue.
func WithMaxMemItems ¶
WithMaxMemItems sets the maximum number of items that the queue keeps in memory before items are written to disk. The value is rounded up to the next power of two, so the effective limit may be larger than requested. The effective limit is never less than 32.
func WithMaxMemory ¶
WithMaxMemory sets the maximum amount of memory used by all items in the queue before items are written to disk.
func WithMinItemSize ¶
WithMinItemSize specifies the minimum allowed size of a single []byte item in the queue.
func WithSnapshotInterval ¶
WithSnapshotInterval enables snapshots and sets the amount of time that the queue must be idle before saving a snapshot of the items stored in memory. The queue must be idle for at least half of the specified time and at most the entire specified time. Idle snapshots are disabled by default and are enabled when a positive value is specified for this option.
Types ¶
type Queue ¶
type Queue struct {
// contains filtered or unexported fields
}
Queue implements a filesystem backed FIFO queue.
func New ¶
New creates a new file-backed FIFO queue instance. Files for this queue are stored in the specified directory. A queue that stores different items must use a different directory.
func (*Queue) Close ¶
Close stops the queue's internal goroutine and prevents any more input or output with the queue. After calling Close, any attempted input or output results in an error.
func (*Queue) Done ¶
func (q *Queue) Done() <-chan struct{}
Done returns a channel that is closed when the Queue is closed.
func (*Queue) Drain ¶ added in v0.1.0
Drain fills dst with up to len(dst) items currently available in the queue and returns the number placed. Returns 0 if the queue is empty or closed, or if dst is nil or zero-length.
func (*Queue) Empty ¶
func (q *Queue) Empty() <-chan struct{}
Empty returns a channel that is signaled when the queue is empty. This is useful for exiting a select when there are currently no more queued items to read.
func (*Queue) Name ¶
Name returns the base of the directory path of this Queue, which serves as the queue's name.
func (*Queue) Put ¶
Put writes a []byte to the queue. A nil item is ignored and returns nil without being enqueued.
func (*Queue) PutBatch ¶ added in v0.1.0
PutBatch enqueues all items in a single event-loop visit. If items is nil or empty, it returns nil immediately. Nil items are skipped. Returns the first error encountered, whether from size validation or from writing overflow to disk; items before the failing one are already enqueued.