https://github.com/adrielcodeco/gorm-autobatch
GORM plugin that automatically switches between individual and batch database operations based on measured P95 latency — reducing round-trips when your database is slow.
https://github.com/adrielcodeco/gorm-autobatch
batch database go golang gorm gorm-plugin orm performance
Last synced: 15 days ago
JSON representation
GORM plugin that automatically switches between individual and batch database operations based on measured P95 latency — reducing round-trips when your database is slow.
- Host: GitHub
- URL: https://github.com/adrielcodeco/gorm-autobatch
- Owner: adrielcodeco
- License: mit
- Created: 2026-05-12T15:55:03.000Z (about 1 month ago)
- Default Branch: main
- Last Pushed: 2026-05-12T16:15:14.000Z (about 1 month ago)
- Last Synced: 2026-05-12T18:14:52.754Z (about 1 month ago)
- Topics: batch, database, go, golang, gorm, gorm-plugin, orm, performance
- Language: Go
- Homepage:
- Size: 13.7 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- Funding: .github/FUNDING.yml
- License: LICENSE
Awesome Lists containing this project
README
# gorm-autobatch
A GORM plugin that automatically switches between individual and batch database operations based on measured P95 latency.
When latency is high, operations are buffered and flushed as a single transaction — reducing round-trips. When latency is low, operations are sent individually with no overhead.
## How it works
- Tracks operation latency using a **P95 sliding window** (Prometheus-style bucket ring, last 30s by default)
- When `P95 > LatencyThreshold` → **batch mode**: operations are buffered and flushed together
- When `P95 ≤ LatencyThreshold` → **individual mode**: operations pass through normally
- Flush triggers: elapsed time **or** buffer size, whichever comes first
- Batch semantics are **all-or-nothing** inside a single transaction
## Install
```bash
go get github.com/adrielcodeco/gorm-autobatch
```
> Requires Go 1.21+ and GORM v1.30+
## Usage
```go
import (
autobatch "github.com/adrielcodeco/gorm-autobatch"
"gorm.io/gorm"
)
db, err := gorm.Open(...)
threshold := 50 * time.Millisecond
err = db.Use(autobatch.New(autobatch.Config{
LatencyThreshold: &threshold, // nil = disabled; 0 = always batch; >0 = adaptive
FlushTimeout: 10 * time.Millisecond, // flush batch after 10ms idle
MaxBatchSize: 100, // or when 100 ops are buffered
WindowDuration: 30 * time.Second, // P95 measured over last 30s
}))
// Regular GORM calls — the plugin decides whether to batch transparently.
db.Create(&user)
db.Model(&user).Updates(&payload)
db.Delete(&record)
```
## Config
| Field | Default | Description |
|---|---|---|
| `LatencyThreshold` | `nil` | P95 above this switches to batch mode (`nil` disables batching) |
| `FlushTimeout` | `10ms` | Max wait before flushing a partial batch |
| `MaxBatchSize` | `100` | Max ops per batch before forced flush |
| `WindowDuration` | `30s` | Sliding window duration for P95 measurement |
All fields are optional — zero values use the defaults above.
## Supported operations
- `db.Create()`
- `db.Updates()`
- `db.Delete()`
Queries (`Find`, `First`, etc.) are not batched.
## Batch semantics
All operations in a batch run inside a **single transaction**. Each op is
wrapped in its own `SAVEPOINT`, so a per-op failure (e.g. a unique-constraint
violation) is isolated: only the failing caller gets the error, the rest of
the batch still commits.
Infrastructure failures of the outer transaction (BEGIN/COMMIT, connection
loss, savepoint unsupported) propagate to every caller in the batch.
Callers block transparently until their batch is flushed — from the caller's
perspective it looks like a normal synchronous GORM call.
## Limitations & caveats
- **Operations inside `db.Transaction(...)` or `db.Begin()` are never batched.**
They run inline on the user's transaction to preserve atomicity and rollback
semantics. The plugin detects this automatically.
- **Callbacks registered *after* `gorm:create`/`gorm:update`/`gorm:delete` are
skipped for batched ops.** Internally the plugin sets `DryRun = true` after
the batch executes the op via a separate session, which short-circuits the
rest of the callback chain on the caller's `*gorm.DB`. Hooks declared on the
model (e.g. `AfterCreate`) **do** run, but inside the batch session — not on
the caller's `*gorm.DB`.
- **`RowsAffected` is propagated** back to the caller's `*gorm.DB` after the
batch executes, but other `Statement` fields are not.
- **Graceful shutdown:** call `plugin.Close()` before exiting your process to
drain in-flight batches. After Close, intercepted ops return
`ErrBatcherClosed`.
## License
MIT