{"id":15393478,"url":"https://github.com/kristoff-it/redis-memolock","last_synced_at":"2025-04-15T23:40:48.740Z","repository":{"id":35157153,"uuid":"186600280","full_name":"kristoff-it/redis-memolock","owner":"kristoff-it","description":"Redis MemoLock - Distributed Caching with Promises","archived":false,"fork":false,"pushed_at":"2022-03-25T19:34:02.000Z","size":100,"stargazers_count":77,"open_issues_count":3,"forks_count":12,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-29T02:42:05.193Z","etag":null,"topics":["distributed-cache","distributed-memoization","future","go","memoization","memolock","promise","redis"],"latest_commit_sha":null,"homepage":"","language":"C#","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/kristoff-it.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":"2019-05-14T10:31:26.000Z","updated_at":"2025-03-10T02:12:25.000Z","dependencies_parsed_at":"2022-07-24T18:17:21.187Z","dependency_job_id":null,"html_url":"https://github.com/kristoff-it/redis-memolock","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kristoff-it%2Fredis-memolock","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kristoff-it%2Fredis-memolock/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kristoff-it%2Fredis-memolock/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kristoff-it%2Fredis-memolock/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kristoff-it","download_url":"https://codeload.github.com/kristoff-it/redis-memolock/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249173058,"owners_count":21224481,"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","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":["distributed-cache","distributed-memoization","future","go","memoization","memolock","promise","redis"],"created_at":"2024-10-01T15:19:27.448Z","updated_at":"2025-04-15T23:40:48.708Z","avatar_url":"https://github.com/kristoff-it.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Redis MemoLock\nRedis MemoLock - Distributed Caching with Promises \n\n**[Check out my talk at NDC Oslo 2019 where I showcase the C# implementation of redis-memolock!](https://www.youtube.com/watch?v=BO-SKMS-D_g)**\n\n## What is a MemoLock?\n\n**A MemoLock is a form of distributed caching with promises. It's like \n    [memoization](https://en.wikipedia.org/wiki/Memoization), \nbut since the cache is shared by multiple consumers, each key has a locking \nmechanism that ensures that multiple concurrent requests for the same resource \ndon't cause unnecessary work.**\n\nWhile I claim to have come up with the name, the concept is not new (as always), \n    [here](https://instagram-engineering.com/thundering-herds-promises-82191c8af57d) \nyou can read about Instagram having a similar concept in their architecture, and before them\nmany others have approached the subject via r/w-through caches and other methods.\n\nThe implementations in this repository use Redis to cache values and Redis Pub/Sub to resolve\npromises across the network. Since Redis can be replicated and clustered, you can take this \nlibrary up to any scale.\n\n## Features \n\n### Polyglot\nIt works across different languages A client only needs a Redis client library and \nknowledge of the key naming scheme in use, and is able to generate/resolve promises with any other.\n\n### Scalable\nNo polling or other wasteful patterns, and it can scale efficiently in a clustered deployment.\\\nThis is something that Redis is in a unique position to provide.\n\n### Flexible\nIt tries to ensure that useless work doesn't happen but, being part of a distributed system, \nthere is no strong guarantee, as it would necessarily require much more coordination and, consequently, \nlead to lower scalability and lower ease of use.\\\nIt tries to get a good tradeoff in that regard. *Read more in later sections.*\n\n## How does it work?\n1. As a service instance, when we need to fetch `likes` for `kristoff` (i.e. `likes:kristoff`), we look for it in Redis.\n    If it's there, we're done.\n2. If the key is not present, we try to acquire `likes/lock:kristoff` using SET with NX.\n    The NX option will ensure that in case of concurrent requests, only one will be able to set the key succesfully.\n3. If we are able to acquire the lock, it means that it's our job to generate the value (e.g. fetch it from DB).\n    Once we're done, we save it to Redis and send a message on a Pub/Sub channel called `likes/notif:kristoff` \n    to notify all other potentially awaiting clients that the value is now available.\n4. If we were **not** able to acquire the lock, we just subscribe to `likes/notif:kristoff`.\n    The service instance that succeeded in locking the resource to notify us that the value is now available \n    *(as described in the previous step)*.\n\nThis is a high level description of what redis-memolock does for you.\n\nIn practice, to get the concurrency right, there are a few more branches involved, but it has no impact\non the public interface, so you only have to care about generating the content and handling time-outs.\n\n# Repository Contents\nThis repository will soon contain a few different implementations that are able to cooperate\n(i.e. can generate and resolve promises one from another). While I aim for all implementations\nto be good enough to work in production (i.e. no concurrency bugs), the main goal is to write\ncode that is clear and terse, so that anybody sufficiently motivated can make the right \nadjustments for their own use-cases.\n\nEach implementation has its own README with code examples.\n\n### C#\n[See `csharp/redis-memolock`](csharp/redis-memolock).\n\nInside the `csharp` directory you will find a ASP.NET Core WebApi project containing usage examples and a MemoLock implementation that uses a `System.Concurrent.Dictionary` with `TaskCompletionSource` (manually triggered Tasks) to handle concurrency.\n\n### Go\n[See `go/README.md`](go/).\n\nInside the `go/` directory you can find a Go module. This implementation makes good use of \ngoroutines and channels, and uses a single goroutine to write to the subscription multiplexer,\nas opposed to the C# version which has concurrent writers acquire control of a `ConcurrentDictionary`.\n\n\n## !! WARNING !!\nThis library is all about nimble locking for enhancing performance. It's ok to use it in combination\nwith external systems (e.g. store the result of the computation elsewhere, like a CDN if it's a PDF\nreport, and just save in Redis a token representing the location) but it's **NOT** ok to use it to \nlock computations that rely on mutual exclusion for correctness. **This locking mechanism is about \ndoing less work, not correct work.** \n\nA **good example** is locking database reads: two reads at the same time won't cause any problem\nand the last writer will win.\n\nA **not-so-good example** is trying to upload a file to an FTP server (or CDN) with a non-unique name: \nwhat happens if two writers try to write to the same filename?\\\n*Answer: in reasonable implementations one writer will fail and report an error.*\\\n*Fix: make sure filenames generated by different writers can't collide (e.g. use UUIDs), or catch the \nerror if you can distinguish it from other types of error (i.e. you get a FileAlreadyExistsError, and \nnot a GenericOpenError).*\n\nA **bad example** is using a MemoLock to guard a computation that might be corrupted by concurrent\nwriters. If your mistake is bad enough, you might end up in a situation where both writes succeed\nand the result becomes corrupted. Don't use this lock to do distributed transactions, for example.\\\n*Fix: just don't.*\n\nI'm writing this warning because distributed locking is a complex subject and it's easy to misuse\ntools if you expect from them greater guarantees than they actually provide. As stated previously,\nthis library tries to be lightweight to enhance performance, not guarantee full mutual exclusion.\nWhile not providing such functionality can be seen as a limitation, the upside is that such library\nwould not be able scale as much (because of a higher level of coordination) and would not allow you\nto use services that are not Redis-aware to store results, such as a CDN, for example.\n\n*Enjoy the simplicity and flexibility that springs from limiting the scope of our design.*\n\n## How can different implementations share promises?\nHere the term *promise* is used in a fairly abstract way with only a small connection to any specific language implementation.\nDifferent implementations can interoperate because they share a Redis client and the understanding of three concepts:\n\n1. Keys are stored using the scheme `\u003cresource tag\u003e:\u003cresource id\u003e`\\\n   (e.g. `likes:kristoff`)\n2. Locks are stored using the scheme`\u003cresource tag\u003e/lock:\u003cresource id\u003e`\\\n   (e.g. `likes/lock:kristoff`)\n3. Pub/Sub notifications are sent over the channel `\u003cresource tag\u003e/notif:\u003cresource id\u003e`\\\n   (e.g. `likes/notif:kristoff`)\n\nAny client that can `SET` and `GET` a key, and that can use Pub/Sub, can interoperate transparently with all others.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkristoff-it%2Fredis-memolock","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkristoff-it%2Fredis-memolock","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkristoff-it%2Fredis-memolock/lists"}