Skip to content

rushairer/batchflow

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

63 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

BatchFlow

Release Go Reference Go Report Card License

BatchFlow is a Go ingestion runtime built on go-pipeline. It provides one batching model for SQL databases, Redis, PostgreSQL/Hologres COPY FROM, and custom batch sinks: enqueue records, flush asynchronously, execute through pluggable backends, apply retry/concurrency controls, and expose production diagnostics.

Chinese documentation: README.zh-CN.md.

Current RC2 API

The public module path is:

github.com/rushairer/batchflow/v2

Because v2 has not been formally released yet, the public runtime API intentionally uses clean names instead of version-decorated names:

cfg := batchflow.DefaultConfig(executor)
flow, err := batchflow.New(ctx, cfg)

Versioning lives in the module path and release tag, not in type or function names.

Features

  • Clean runtime API: Config, RuntimeConfig, Flow, DefaultConfig, and New.
  • Unified executor model for SQL, Redis, COPY FROM, and custom BatchExecutor implementations.
  • Runtime sharding with hash, round-robin, and least-loaded routing.
  • Runtime backpressure with block, reject, and timeout modes.
  • Estimated queue memory limiter for OOM protection.
  • Adaptive tuning policy engine for flush-size and latency recommendations.
  • SQL upsert controls for explicit conflict keys, update columns, and in-batch duplicate-key coalescing.
  • PostgreSQL/Hologres COPY FROM fast path through CopyFromExecutor and optional adapters/pgxcopy.
  • Retry, timeout, concurrency limit, structured error classification, metrics, and safe diagnostics.
  • Complete lifecycle controls with Close(), Wait(), and Done().

Install

go get github.com/rushairer/batchflow/v2@v2.0.0-rc.2

Quick Start: SQL runtime

package main

import (
	"context"
	"database/sql"
	"log"
	"time"

	_ "github.com/go-sql-driver/mysql"
	batchflow "github.com/rushairer/batchflow/v2"
)

func main() {
	ctx := context.Background()

	db, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/testdb?parseTime=true")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	executor := batchflow.NewSQLThrottledBatchExecutorWithDriver(db, batchflow.DefaultMySQLDriver).
		WithConcurrencyLimit(8).
		WithRetryConfig(batchflow.RetryConfig{
			Enabled:     true,
			MaxAttempts: 3,
			BackoffBase: 20 * time.Millisecond,
			MaxBackoff:  500 * time.Millisecond,
		})

	cfg := batchflow.DefaultConfig(executor)
	cfg.Pipeline.BufferSize = 10000
	cfg.Pipeline.FlushSize = 1000
	cfg.Pipeline.FlushInterval = 50 * time.Millisecond
	cfg.Runtime.ShardCount = 4
	cfg.Runtime.Routing = batchflow.ShardRoutingHash
	cfg.Runtime.Backpressure = batchflow.BackpressureConfig{
		Enabled:       true,
		Mode:          batchflow.BackpressureTimeout,
		HighWatermark: 8000,
		Timeout:       500 * time.Millisecond,
	}
	cfg.Runtime.MemoryLimit = batchflow.MemoryLimitConfig{
		Enabled:         true,
		MaxQueueBytes:   512 << 20,
		AvgRequestBytes: 512,
		Mode:            batchflow.BackpressureTimeout,
		Timeout:         500 * time.Millisecond,
	}

	flow, err := batchflow.New(ctx, cfg)
	if err != nil {
		log.Fatal(err)
	}
	defer func() {
		if err := flow.Close(); err != nil {
			log.Printf("batchflow close: %v", err)
		}
	}()

	schema := batchflow.NewSQLSchema(
		"users",
		batchflow.ConflictUpdateOperationConfig.
			WithConflictColumns("id").
			WithUpdateColumns("name", "email"),
		"id", "name", "email",
	)

	req := batchflow.NewRequest(schema).
		SetUint64("id", 1).
		SetString("name", "alice").
		SetString("email", "alice@example.com")

	if err := flow.Submit(ctx, req); err != nil {
		log.Fatal(err)
	}
}

COPY FROM fast path

For PostgreSQL/Hologres append-only ingestion, use CopyFromExecutor. The root module only depends on the minimal CopyFromClient interface. The pgx implementation lives in the optional adapter module:

go get github.com/rushairer/batchflow/adapters/pgxcopy@v0.1.0-rc.2

adapters/pgxcopy is an independent Go module. In this repository, its Git tag should be adapters/pgxcopy/v0.1.0-rc.2, while the root module tag remains v2.0.0-rc.2.

import (
	batchflow "github.com/rushairer/batchflow/v2"
	"github.com/rushairer/batchflow/adapters/pgxcopy"
)

copyExecutor := pgxcopy.NewExecutor(pool)

cfg := batchflow.DefaultConfig(copyExecutor)
cfg.Pipeline.BufferSize = 50000
cfg.Pipeline.FlushSize = 5000
cfg.Pipeline.FlushInterval = 20 * time.Millisecond
cfg.Runtime.ShardCount = 8
cfg.Runtime.Backpressure = batchflow.BackpressureConfig{
	Enabled:       true,
	Mode:          batchflow.BackpressureTimeout,
	HighWatermark: 40000,
	Timeout:       time.Second,
}
cfg.Runtime.MemoryLimit = batchflow.MemoryLimitConfig{
	Enabled:         true,
	MaxQueueBytes:   1 << 30,
	AvgRequestBytes: 512,
	Mode:            batchflow.BackpressureTimeout,
	Timeout:         time.Second,
}

flow, err := batchflow.New(ctx, cfg)

COPY FROM is intentionally limited to append-only schemas. Upsert/update/replace flows should use the SQL executor path.

Core Semantics

Lifecycle

  • New(ctx, cfg) starts the runtime immediately.
  • Submit(ctx, req) enqueues data and may reject/block/timeout according to runtime backpressure and memory limits.
  • Close() stops accepting new records, closes input, triggers final flush, and waits for shutdown.
  • Wait() waits for shutdown without closing input.
  • Done() returns a read-only channel closed when the background runtime exits.

Always call Close() during application shutdown:

if err := flow.Close(); err != nil {
	return err
}

Batching

  • The pipeline groups requests by FlushSize or FlushInterval.
  • Each flush is split by SchemaInterface.
  • Each schema group calls BatchExecutor.ExecuteBatch(...) once.
  • ObserveBatchSize(n) reports the size of one schema execution batch, not the whole flush input size.

Requests

Use typed setters when available:

req := batchflow.NewRequest(schema).
	SetInt("retry_count", 3).
	SetUint64("id", 42).
	SetString("email", "alice@example.com").
	SetBool("enabled", true)

Other values can be set with Set(name, value) or SetNull(name).

SQL Update and Replace

Use explicit conflict keys for PostgreSQL/MySQL upserts:

schema := batchflow.NewSQLSchema(
	"users",
	batchflow.ConflictUpdateOperationConfig.
		WithConflictColumns("tenant_id", "user_id").
		WithUpdateColumns("name", "email"),
	"tenant_id", "user_id", "name", "email", "updated_at",
)

Rules:

  • If ConflictColumns is omitted, BatchFlow keeps the legacy fallback and uses the first schema column. Treat this as compatibility only.
  • ConflictUpdate updates non-conflict columns by default, or only UpdateColumns when configured.
  • PostgreSQL ConflictReplace means upsert overwrite with ON CONFLICT (...) DO UPDATE SET ....
  • MySQL ConflictReplace keeps native REPLACE INTO semantics.
  • In-batch duplicate conflict keys are coalesced before SQL generation to avoid PostgreSQL errors such as "cannot affect row a second time".

Dry-run the final SQL before production rollout:

preview, err := batchflow.GenerateSQLPreview(ctx, batchflow.DefaultPostgreSQLDriver, schema, rows)
if err != nil {
	return err
}
log.Printf("sql=%s fingerprint=%s args=%d input=%d output=%d dedup=%d",
	preview.SQL,
	preview.Fingerprint,
	preview.ArgsCount,
	preview.DedupStats.InputRows,
	preview.DedupStats.OutputRows,
	preview.DedupStats.DeduplicatedRows,
)

Do not log preview.Args in production unless the values are known to be safe.

Legacy convenience constructors

Legacy constructors remain available for simple migrations and quick tests:

flow := batchflow.NewMySQLBatchFlow(ctx, db, batchflow.PipelineConfig{
	BufferSize:    1000,
	FlushSize:     200,
	FlushInterval: 100 * time.Millisecond,
})

For new production code, prefer DefaultConfig(executor) plus New(ctx, cfg) so runtime sharding, memory limiter, and backpressure are explicit.

Documentation

Development

make fmt
make test
make lint
make docs-check

Release validation:

go test ./...
go test ./... -race
go test ./benchmark -bench=. -benchmem
cd adapters/pgxcopy && go test ./...

About

BatchFlow is a Go batch-processing framework built on go-pipeline. It provides one ingestion model for SQL databases, Redis, and custom batch sinks: collect records, flush asynchronously, execute with optional retry/concurrency control, and expose metrics plus safe diagnostics.

Resources

License

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Packages

 
 
 

Contributors