{"id":34808318,"url":"https://github.com/elgopher/batch","last_synced_at":"2025-12-25T12:11:27.047Z","repository":{"id":39033851,"uuid":"486730462","full_name":"elgopher/batch","owner":"elgopher","description":"Simple Go package for handling incoming requests in batches.","archived":false,"fork":false,"pushed_at":"2023-08-30T20:56:40.000Z","size":48,"stargazers_count":11,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-12-14T10:17:51.536Z","etag":null,"topics":["batch","batch-processing","go","golang","high-performance","high-throughput"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/elgopher.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-04-28T19:54:10.000Z","updated_at":"2024-08-25T05:12:53.000Z","dependencies_parsed_at":"2023-01-26T06:00:57.703Z","dependency_job_id":null,"html_url":"https://github.com/elgopher/batch","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/elgopher/batch","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elgopher%2Fbatch","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elgopher%2Fbatch/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elgopher%2Fbatch/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elgopher%2Fbatch/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/elgopher","download_url":"https://codeload.github.com/elgopher/batch/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elgopher%2Fbatch/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28028987,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-12-25T02:00:05.988Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["batch","batch-processing","go","golang","high-performance","high-throughput"],"created_at":"2025-12-25T12:11:26.321Z","updated_at":"2025-12-25T12:11:27.038Z","avatar_url":"https://github.com/elgopher.png","language":"Go","readme":"[![Build](https://github.com/elgopher/batch/actions/workflows/build.yml/badge.svg)](https://github.com/elgopher/batch/actions/workflows/build.yml)\n[![Go Reference](https://pkg.go.dev/badge/github.com/elgopher/batch.svg)](https://pkg.go.dev/github.com/elgopher/batch)\n[![Go Report Card](https://goreportcard.com/badge/github.com/elgopher/batch)](https://goreportcard.com/report/github.com/elgopher/batch)\n[![codecov](https://codecov.io/gh/elgopher/batch/branch/master/graph/badge.svg)](https://codecov.io/gh/elgopher/batch)\n[![Project Status: Active – The project has reached a stable, usable state and is being actively developed.](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active)\n\u003cimg src=\"logo.svg\" align=\"right\" width=\"140px\"\u003e\n\n## What it can be used for?\n\nTo **increase** database-driven web application **throughput** without sacrificing *data consistency* and *data durability*  or making source code and architecture complex.\n\nThe **batch** package simplifies writing Go applications that process incoming requests (HTTP, GRPC etc.) in a batch manner:\ninstead of processing each request separately, they group incoming requests to a batch and run whole group at once.\nThis method of processing can significantly speed up the application and reduce the consumption of disk, network or CPU.\n\nThe **batch** package can be used to write any type of *servers* that handle thousands of requests per second. \nThanks to this small library, you can create relatively simple code without the need to use low-level data structures.\n\n## Why batch processing improves performance?\n\nNormally a web application is using following pattern to modify data in the database:\n\n1. **Load resource** from database. **Resource** is some portion of data \nsuch as set of records from relational database, document from Document-oriented database or value from KV store\n(in Domain-Driven Design terms it is called an [aggregate](https://martinfowler.com/bliki/DDD_Aggregate.html)).\nLock the entire resource [optimistically](https://www.martinfowler.com/eaaCatalog/optimisticOfflineLock.html) \nby reading version number.\n2. **Apply change** to data in plain Go\n3. **Save resource** to database. Release the lock by running\natomic update with version check.\n\nBut such architecture does not scale well if the number of requests \nfor a single resource is very high\n(meaning hundreds or thousands of requests per second). \nThe lock contention in such case is very high and database is significantly \noverloaded. Also, round-trips between application server and database add latency.\nPractically, the number of concurrent requests is severely limited.  \n\nOne solution to this problem is to reduce the number of costly operations.\nBecause a single resource is loaded and saved thousands of times per second \nwe can instead:\n\n1. Load the resource **once** (let's say once per second) \n2. Execute all the requests from this period of time on an already loaded resource. Run them all sequentially to keep things simple and data consistent.\n3. Save the resource and send responses to all clients if data was stored successfully.\n\nSuch solution could improve the performance by a factor of 1000. And resource is still stored in a consistent state. \n\nThe **batch** package does exactly that. You configure the duration of window, provide functions\nto load and save resource and once the request comes in - you run a function:\n\n```go\n// Set up the batch processor:\nprocessor := batch.StartProcessor(\n    batch.Options[*YourResource]{ // YourResource is your own Go struct\n        MinDuration:  100 * time.Millisecond,\n        LoadResource: func(ctx context.Context, resourceKey string) (*YourResource, error){\n            // resourceKey uniquely identifies the resource\n            ...\n        },\n        SaveResource: ...,\n    },\n)\n\n// And use the processor inside http/grpc handler or technology-agnostic service.\n// ctx is a standard context.Context and resourceKey can be taken from request parameter\nerr := processor.Run(ctx, resourceKey, func(r *YourResource) {\n    // Here you put the code which will executed sequentially inside batch  \n})\n```\n\n**For real-life example see [example web application](https://github.com/elgopher/batch-example).**\n\n## Installation\n\n```sh\n# Add batch to your Go module:\ngo get github.com/elgopher/batch\n```\nPlease note that at least **Go 1.18** is required. The package is using generics, which was added in 1.18. \n\n## Scaling out\n\nSingle Go http server is able to handle up to tens of thousands of requests per second on a commodity hardware. \nThis is a lot, but very often you also need:\n\n* high availability (if one server goes down you want other to handle the traffic)\n* you want to handle hundred-thousands or millions of requests per second\n\nFor both cases you need to deploy **multiple servers** and put a **load balancer** in front of them. \nPlease note though, that you have to carefully configure the load balancing algorithm. \n_Round-robin_ is not an option here, because sooner or later you will have problems with locking \n(multiple server instances will run batches on the same resource). \nIdeal solution is to route requests based on URL path or query string parameters. \nFor example some http query string parameter could have a resource key. You can instruct load balancer\nto calculate hash on this parameter and always route requests with the same key \nto the same backend. If backend will be no longer available the load balancer should route request to a different \nserver. \n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Felgopher%2Fbatch","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Felgopher%2Fbatch","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Felgopher%2Fbatch/lists"}