https://github.com/aarondwi/singleflight
Coalesce multiple identical calls into one, preventing thundering-herd/stampede to database/other backends
https://github.com/aarondwi/singleflight
cache cache-stampede python singleflight thundering-herd
Last synced: 3 months ago
JSON representation
Coalesce multiple identical calls into one, preventing thundering-herd/stampede to database/other backends
- Host: GitHub
- URL: https://github.com/aarondwi/singleflight
- Owner: aarondwi
- License: mit
- Created: 2020-05-23T05:23:18.000Z (about 6 years ago)
- Default Branch: master
- Last Pushed: 2022-01-27T03:46:50.000Z (over 4 years ago)
- Last Synced: 2026-01-02T20:56:07.535Z (6 months ago)
- Topics: cache, cache-stampede, python, singleflight, thundering-herd
- Language: Python
- Homepage:
- Size: 26.4 KB
- Stars: 14
- Watchers: 1
- Forks: 4
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# singleflight
[](https://travis-ci.org/aarondwi/singleflight)
Coalesce multiple identical call into one, preventing thundering-herd/stampede to database/other backends
It is a python port of [golang's groupcache singleflight implementation](https://github.com/golang/groupcache/blob/master/singleflight/singleflight.go)
This module **does not** provide caching mechanism. Rather, this module can used behind a caching abstraction to deduplicate cache-filling call
Only support python 3.5+
Installation
-----------------------
```python
pip install singleflight
```
Usage
-----------------------
This modules has 3 implementation, which can be imported as follows
```python
from singleflight.basic import SingleFlight # for multi-threaded apps
from singleflight.gevent import SingleFlightGevent as SingleFlight # for gevent apps
from singleflight.asynchronous import SingleFlightAsync as SingleFlight # for asyncio/curio apps
```
Then you can use it as follows (the example shows the multi-threaded version). Note that, `key` is important for the modules to know which call should be de-duplicated
```python
from time import sleep
from concurrent.futures import ThreadPoolExecutor
from functools import partial
from singleflight.basic import SingleFlight
if __name__ == '__main__':
sf = SingleFlight()
executor = ThreadPoolExecutor(max_workers=10)
counter = 0
result = "this is the result"
def work(num):
global counter, result
sleep(0.1) # emulate bit slower call
counter += 1
return (result, num)
res = []
for i in range(10):
sfc = partial(sf.call, work, "key", i+1)
r = executor.submit(sfc)
res.append(r)
for r in res:
assert r.result()[0] == result
# because only the first one can get the lock
# and only that one request call
assert r.result()[1] == 1
assert counter == 1
```
For decorator fans, you can also use it to wrap your function.
```python
from time import sleep
from concurrent.futures import ThreadPoolExecutor
from functools import partial
from singleflight.basic import SingleFlight
if __name__ == '__main__':
sf = SingleFlight()
executor = ThreadPoolExecutor(max_workers=10)
# success case
counter = 0
result = "this is the result"
@sf.wrap
def work(num):
global counter, result
sleep(0.1) # emulate bit slower call
counter += 1
return (result, num)
res = []
for i in range(10):
sfc = partial(work, "key", i+1)
r = executor.submit(sfc)
res.append(r)
for r in res:
assert r.result()[0] == result
# because only the first one can get the lock
# and only that one request call
assert r.result()[1] == 1
assert counter == 1
```
All `*args` and `**kwargs` your function has is passed directly later. Exceptions are also raised normally.