{"id":20651574,"url":"https://github.com/rafaeljusto/anicetus","last_synced_at":"2025-10-13T00:53:46.483Z","repository":{"id":262207362,"uuid":"864655603","full_name":"rafaeljusto/anicetus","owner":"rafaeljusto","description":"Solution for the thundering herd problem.","archived":false,"fork":false,"pushed_at":"2025-09-08T10:04:48.000Z","size":49,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-09-28T09:47:09.540Z","etag":null,"topics":["library","microservice","network"],"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/rafaeljusto.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":"rafaeljusto"}},"created_at":"2024-09-28T19:54:23.000Z","updated_at":"2025-09-08T10:04:51.000Z","dependencies_parsed_at":"2024-11-11T07:29:38.854Z","dependency_job_id":"cfd9b768-0c9f-406d-b8d8-ff74ae4a8484","html_url":"https://github.com/rafaeljusto/anicetus","commit_stats":null,"previous_names":["rafaeljusto/anicetus"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/rafaeljusto/anicetus","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rafaeljusto%2Fanicetus","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rafaeljusto%2Fanicetus/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rafaeljusto%2Fanicetus/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rafaeljusto%2Fanicetus/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rafaeljusto","download_url":"https://codeload.github.com/rafaeljusto/anicetus/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rafaeljusto%2Fanicetus/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279013646,"owners_count":26085298,"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-10-12T02:00:06.719Z","response_time":53,"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":["library","microservice","network"],"created_at":"2024-11-16T17:28:18.166Z","updated_at":"2025-10-13T00:53:46.463Z","avatar_url":"https://github.com/rafaeljusto.png","language":"Go","funding_links":["https://github.com/sponsors/rafaeljusto"],"categories":[],"sub_categories":[],"readme":"# Anicetus\n\n[![Go Reference](https://pkg.go.dev/badge/github.com/rafaeljusto/anicetus.svg)](https://pkg.go.dev/github.com/rafaeljusto/anicetus)\n![Test](https://github.com/rafaeljusto/anicetus/actions/workflows/test.yml/badge.svg)\n\nThe [thundering herd problem](https://en.wikipedia.org/wiki/Thundering_herd_problem)\nis a performance problem that occurs when a large number of processes or threads\nwake up at the same time and compete for a limited resource.\n\nThis project attempts to resolve this by detecting the sittuation, and only\nallowing a single request to pass through to the resource, while the others are\nblocked. Once the resource is available, the blocked requests are released. The\nreason is that allowing a single request to finish first creates a caching\neffect, and the other requests can be served from the cache.\n\nOut of the box, a web server (`anicetus-http`) is provided as a micro-service\nfor your network, but this library can be used in any Go project to plug-in with\nan existing solution.\n\n[![Anicetus Video](https://img.youtube.com/vi/vInKlTQMKBc/0.jpg)](https://www.youtube.com/watch?v=vInKlTQMKBc)\n\n## Design\n\n```mermaid\nflowchart LR\n  A((Start)):::start --\u003e B[Fingerprint]\n  B[Fingerprint]:::fingerprint --\u003e C[Detector]\n  C[Detector]:::detector --\u003e D{Cooldown?}:::detector\n  D:::detector --\u003e|yes| F(((Backend))):::success\n  D:::detector --\u003e|no| E{Thundering herd?}:::detector\n  E:::detector --\u003e|yes| G[Gatekeeper]:::gatekeeper\n  E:::detector --\u003e|no| F(((Backend))):::success\n  G[Gatekeeper]:::gatekeeper --\u003e H{First request?}:::gatekeeper\n  H:::gatekeeper --\u003e|yes| F(((Backend))):::success\n  H:::gatekeeper --\u003e|no| I{First request finished?}:::gatekeeper\n  I:::gatekeeper --\u003e|yes| F(((Backend))):::success\n  I:::gatekeeper --\u003e|no| J(((Block))):::error\n\n  classDef start fill:#092c9e,stroke:#333,stroke-width:2px;\n  classDef fingerprint fill:#a1880d,stroke:#333,stroke-width:2px;\n  classDef detector fill:#4c147a,stroke:#333,stroke-width:2px;\n  classDef gatekeeper fill:#0a6e6c,stroke:#333,stroke-width:2px;\n  classDef success fill:#118f0d,stroke:#333,stroke-width:2px;\n  classDef error fill:#a30303,stroke:#333,stroke-width:2px;\n```\n\nThere are some key concepts for this project that will be explained in the next\nsections.\n\n### Fingerprint\n\nThe fingerprint is a unique identifier for the request. It is used to identify a\ngroup of requests that are considered the same. For example, in a web\napplication, depending on the use case, the fingerprint could be the URL, some\nspecific HTTP headers, or a combination of both.\n\nAllowing to configure how the fingerprint is generated gives the flexibility to\ntarget specific users or not.\n\n\u003e [!IMPORTANT]\n\u003e Defining the correct fingerprint that matches the backend caching group is\n\u003e crucial for this solution to work. If the backend application cannot cache the\n\u003e incoming requests from the group of requests identified by the fingerprint,\n\u003e the thundering herd will still be a problem.\n\n### Detector\n\nOnce the fingerprint is generated, the detector will check if there are other\nrequests with the same fingerprint. Different algorithms can be used to detect a\nthundeting herd, and this library provides the [token\nbucket](https://en.wikipedia.org/wiki/Token_bucket) out-of-the-box (with a\npenalty strategy; bucket is drained while in thundering herd). When using the\ntoken bucket algorithm the detector needs to know how many same fingerprint\noccurences are allowed in a time window.\n\nAfter the thundering herd is handled, the detector will stop analysing the\nrequests for a while (cooldown period). This is to avoid the thundering herd to\nbe detected again in a short period of time. The cooldown period shoud be\nconfigured taking the stale cache risk of the backend into account.\n\n### Gatekeeper\n\nAfter a thundering herd is detected, the gatekeeping will allow the first\nrequest to pass through, and block the others. The blocked requests will be\nallowed to hit the backend once the first request execution is done.\n\n\u003e [!IMPORTANT]\n\u003e The backend MUST create a cache response for requests with the same\n\u003e fingerprint to avoid the thundering herd to hit the infrastructure.\n\n## Library\n\nThe library is the core of the project. It provides the necessary functions to\nbe used in any Go project. The library is designed to be flexible so you can\nimplement your own detector and gatekeeper storage.\n\n```go\npackage main\n\nimport (\n  \"context\"\n  \"net/http\"\n  \"time\"\n\n  \"github.com/rafaeljusto/anicetus/v2\"\n  \"github.com/rafaeljusto/anicetus/v2/detector\"\n  \"github.com/rafaeljusto/anicetus/v2/storage\"\n)\n\nfunc main() {\n  detector := detector.NewTokenBucketInMemory(\n    detector.WithLimitersBurst(1000),\n    detector.WithLimitersInterval(time.Minute),\n    detector.WithCoolDownInterval(10*time.Minute),\n  )\n\n  gatekeeperStorage := storage.NewInMemory()\n\n  anicetus := anicetus.NewAnicetus[fingerprint.HTTPRequest](detector, gatekeeperStorage)\n\n  // ...\n\n  // For each request in your application (here we simulate an HTTP request)\n  requestFingerprint := fingerprint.NewHTTPRequest(\u0026http.Request{})\n  status, err := anicetus.Evaluate(context.Background(), requestFingerprint)\n  if err != nil {\n    // handle error\n  }\n\n  switch status {\n  case anicetus.StatusProcess:\n    // thundering herd detected, allowing this single request to pass through\n  case anicetus.StatusWait:\n    // thundering herd detected, blocking this request\n  case anicetus.StatusOpenGates:\n    // business as usual\n  case anicetus.StatusFailed:\n    // something went wrong while evaluating the request\n\n    // you can optionally cleanup the request fingerprint state\n    if err := aniceuts.Cleanup(context.Background(), requestFingerprint); err != nil {\n      // handle error\n    }\n  }\n\n  // the single request needs to inform the gatekeeper that it finished\n  // processing the request\n  if err := anicetus.RequestDone(context.Background(), requestFingerprint); err != nil {\n    // handle error\n  }\n}\n```\n\n## FAQ\n\nYou will find here some common questions and answers.\n\n**Q: Is this a load balancer?**\n\nA: No, this is not a load balancer. This works as a semaphore service allowing\nrequests to pass through.\n\n**Q: Does it cache the responses?**\n\nA: No, it does not cache the responses. It only allows a single request to pass\nthrough. It's the responsibility of the backend application to cache the\nresponses and serve them from the cache. This solution will just give enough\ntime for the cache to be populated.\n\n**Q: Why Anicetus?**\n\nA: Anicetus comes from the Greek word Ἀνίκητος (Aníkētos, literally\n\"Unconquerable\"). A god and one of the guardians and gatekeepers of the gates of\nMount Olympus. It's a metaphor for the protector of the backend application. 😄🤞\n\n**Q: Why there are not too many options for detectors, gatekeeper storages and\nfingerprints in this library?**\n\nA: At first we are trying to minimize the number of Go dependencies to make this\nlibrary lightweight. We already added support for Redis detector and storage,\nand may add more options in the future.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frafaeljusto%2Fanicetus","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frafaeljusto%2Fanicetus","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frafaeljusto%2Fanicetus/lists"}