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, and consume them by ranging over the channel returned by Out:
q, err := cascadeq.New("myqueue", "/var/data/queues")
if err != nil {
log.Fatal(err)
}
defer q.Close()
// Producer
if err := q.Put([]byte("hello")); err != nil {
log.Println(err)
}
// Consumer
for {
select {
case item := <-q.Out():
process(item)
case <-q.Empty():
return // no more items right now
case <-q.Done():
return // queue closed
}
}
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
Persistence and file format ¶
Overflow files are written to the directory supplied to New and named {name}-{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-example-*")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(dir)
q, err := cascadeq.New("example", 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 (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-example-*")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(dir)
q, err := cascadeq.New("example", 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 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) Empty() <-chan struct{}
- func (q *Queue) Name() string
- func (q *Queue) Out() <-chan []byte
- func (q *Queue) Put(item []byte) (err 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.
Functions ¶
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.
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 (*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) 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.