Zero deps · v1.2.1

Batch.
Flush.
Move on.

A generic, goroutine-safe buffer for batching any type of entry — log events, metrics, webhooks, DB rows. Flush on size, on a timer, or on demand.

go get  github.com/dogxhood/go-buffer
main.go
package main

import (
    "context"
    "time"
    buf "github.com/dogxhood/go-buffer"
)

func main() {
    b := buf.New(
        buf.WithMaxEntries(100),
        buf.WithFlushInterval(10*time.Second),
        buf.WithFlushHandler(
            func(ctx context.Context,
                 entries []interface{}) error {
                ship(entries)
                return nil
            }),
    )
    defer b.Stop()

    b.Push("event_42")  // thread-safe
    b.FlushSync()     // drain on exit
}
0
external dependencies
~130
lines of production code
Go 1.14
minimum required version
// why go-buffer

Small API.
No nonsense.

Four methods. Seven options. Does one thing and does it well.

01

Flush on size

Set WithMaxEntries(n) and the buffer auto-flushes the moment it fills up — no polling, no guesswork.

02

Flush on interval

WithFlushInterval(d) starts a background goroutine that drains the buffer on a ticker. Set to 0 to disable.

03

FlushSync for shutdown

Call FlushSync() before your process exits and every pending entry makes it out — clean shutdown guaranteed.

04

Automatic retry

Return a Temporary() error from your handler and the buffer retries up to MaxRetries before calling your error handler.

05

Flush timeout

WithFlushTimeout(d) wraps each flush in a bounded context. Slow downstreams can't stall your pipeline.

06

Goroutine-safe

Mutex-protected internals. Push from as many goroutines as you like — no external locking needed.

// interactive demo

Buffer.
Right here.

Push entries and watch the buffer behave exactly as it would in production.

0
Pending
0
Flushed
0
Batches
Config
MaxEntries (auto-flush threshold)
FlushInterval in seconds (0 = off)
Push entry value
Fill level 0 / 5
Empty
Event log
No events. Push an entry to start.
// usage

Copy.
Paste.
Ship.

Real, production-grade patterns.

Quick start
Log pipeline
Retry errors
Graceful shutdown
main.go
package main

import (
    "context"
    "fmt"
    "time"

    buffer "github.com/dogxhood/go-buffer"
)

func main() {
    // Flush every 5 s or when 10 entries accumulate.
    b := buffer.New(
        buffer.WithFlushHandler(func(ctx context.Context, entries []interface{}) error {
            fmt.Printf("flushing %d entries\n", len(entries))
            return nil
        }),
        buffer.WithMaxEntries(10),
        buffer.WithFlushInterval(5 * time.Second),
    )
    defer b.Stop()

    for i := 0; i < 42; i++ {
        b.Push(fmt.Sprintf("event_%d", i))
    }

    b.FlushSync() // drain before exit
}
logger/pipeline.go
package logger

import (
    "context"
    "encoding/json"
    "net/http"
    "time"

    buffer "github.com/dogxhood/go-buffer"
)

type LogEvent struct {
    Level   string
    Message string
    At      time.Time
}

// NewLogPipeline batches log events and ships them in bulk.
func NewLogPipeline(endpoint string) *buffer.Buffer {
    client := &http.Client{Timeout: 5 * time.Second}

    return buffer.New(
        buffer.WithMaxEntries(100),
        buffer.WithFlushInterval(10 * time.Second),
        buffer.WithFlushTimeout(8 * time.Second),
        buffer.WithFlushHandler(func(ctx context.Context, entries []interface{}) error {
            batch := make([]LogEvent, 0, len(entries))
            for _, e := range entries {
                batch = append(batch, e.(LogEvent))
            }
            body, _ := json.Marshal(batch)
            req, _ := http.NewRequestWithContext(ctx, "POST", endpoint, body)
            _, err := client.Do(req)
            return err
        }),
    )
}
retry.go
// Implement the Temporary() interface to signal a retryable error.
type tempErr struct{ msg string }
func (e *tempErr) Error() string    { return e.msg }
func (e *tempErr) Temporary() bool { return true }

b := buffer.New(
    buffer.WithMaxRetries(3),
    buffer.WithFlushHandler(func(ctx context.Context, entries []interface{}) error {
        if upstream.Unavailable() {
            return &tempErr{"upstream down"} // retried up to 3×
        }
        return upstream.Ship(entries)
    }),
    buffer.WithErrorHandler(func(err error) {
        // only called after MaxRetries exhausted
        log.Printf("permanent failure: %v", err)
    }),
)
shutdown.go
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)

go func() {
    for i := 0; ; i++ {
        b.Push(fmt.Sprintf("metric_%d", i))
    }
}()

<-quit
log.Println("shutting down — draining buffer…")
b.Stop()      // stop the interval goroutine
b.FlushSync() // block until the last batch ships
log.Println("done")
// api reference

Seven options.
Four methods.

Options — pass to buffer.New()

OptionTypeDefaultDescription
WithFlushHandler(fn)OptionnoopCalled on every flush with a context and the batch. Required to do anything useful.
WithErrorHandler(fn)OptionnoopCalled after MaxRetries temporary failures — permanent error path.
WithMaxEntries(n)Option250Size-based flush threshold. Push triggers an async flush at this count.
WithMaxRetries(n)Option3Retries before the error handler is invoked on temporary errors.
WithFlushInterval(d)Option30sTimer-based flush cadence. Set to 0 to disable periodic flushing.
WithFlushTimeout(d)Option15sContext deadline passed to each flush invocation.

Methods — on *Buffer

MethodDescription
Push(v interface{})Add a value. Thread-safe. Auto-flushes asynchronously when MaxEntries is hit.
Flush()Flush pending entries asynchronously. Returns immediately.
FlushSync()Flush pending entries and block until the handler returns. Use on shutdown.
Stop()Stop the interval goroutine. Call FlushSync() afterwards to drain any remainder.
// changelog

History

v1.2.0
2020-08-06
  • Changed DefaultFlushInterval to 30s
v1.1.0
2020-08-03
  • Added FlushSync() for synchronous drain
v1.0.0
2020-07-01
  • Initial release
  • Functional options API
  • Retry on Temporary() errors
// roadmap

What's coming
in 2026

Public roadmap — August through December 2026.

Aug 2026
Released

Product-ready site

Landing page, interactive demo, API reference, OG image & full SEO.

  • gobuffer.fun live
  • Interactive demo
  • OG image & favicon
  • JSON-LD structured data
Sep 2026
In progress

Generics API

Type-safe Buffer[T] using Go 1.18+ generics. No more interface{} casts.

  • Buffer[T any] type
  • WithFlushHandler[T]
  • Backward-compatible v1.x shim
Oct 2026
Planned

Observability

First-class metrics via OpenTelemetry — flush latency, queue depth, error rate.

  • OTEL counter + histogram
  • Prometheus exporter
  • WithMeter() option
Nov 2026
Planned

Backpressure

Configurable overflow strategies — block, drop, or sample when the buffer is full.

  • WithOverflowPolicy()
  • Block / Drop / Sample modes
  • Overflow callback hook
Dec 2026
Planned

v2.0 Stable

Full generics API, observability, backpressure — stable release with migration guide.

  • v2.0.0 release
  • Migration guide v1 → v2
  • Playground & examples repo
// about

Built for
production pipelines.

go-buffer exists because batching should be a one-liner, not a design problem.

Why it exists

Every production system eventually needs to batch writes — log events to an aggregator, bulk DB inserts, webhook calls hitting a rate limit. The naive approach is channels and goroutines, but the bookkeeping compounds fast: you need a ticker, a mutex, a shutdown signal, and retry logic before you've written a single line of business logic. go-buffer handles all of that. You write the flush handler. The library handles everything else.

Design principles

  • Zero external dependencies. The only import is the Go standard library. No transitive surprises, no version conflicts.
  • Functional options API. Configure exactly what you need — every option has a sensible default, so New(WithFlushHandler(fn)) is a complete, production-ready setup.
  • Goroutine-safe by default. Mutex-protected internals mean you can Push() from any number of goroutines without external locking.
  • Explicit shutdown path. FlushSync() blocks until the last batch clears, giving you a clean drain on process exit — no lost events.

Who it's for

Go developers building log pipelines, metrics collectors, event streaming systems, or anything where calling a downstream sink once per event is wasteful or unsafe. If you're writing a loop that forwards individual records to a database, queue, or HTTP endpoint — go-buffer is the missing layer between your code and your sink.

License & source

go-buffer is released under the MIT License — free to use in any project, commercial or otherwise, with no strings attached. The source code and full documentation are available on the package registry.

pkg.go.dev → Changelog