You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
-**Type annotated:** All cacheme API are type annotated with generics.
9
-
-**High hit ratio in-memory cache:** TinyLFU written in Rust with little memory overhead.
10
-
-**Thundering herd protection:** Simultaneously requests to same key are blocked by asyncio Event and only load from source once.
9
+
-**Thundering herd protection:** Simultaneously requests to same key are blocked by asyncio Event and only load from source once. See Benchemark section.
11
10
-**Cache stats API:** Stats of each node and colected automatically.
11
+
-**Performance:** See Benchemark section.
12
12
13
13
Related projects:
14
14
- High performance in-memory cache: https://github.com/Yiling-J/theine
Node is the core part of cache. Each node has its own key function, load function and storage options. Stats of each node are collected independently. You can place all node definations into one package/module, so everyone knows exactly what is cached now and how they are cached. All cacheme API are based on node.
58
+
Node is the core part of cache. Each node has its own key function, load function and storage options. Stats of each node are collected independently. You can place all node definations into one package/module, so everyone knows exactly what is cached and how they are cached. All cacheme API are based on node.
56
59
57
60
Each node contains:
58
61
- Key attritubes and `key` method, which are used to generate cache key. Here the `UserInfoNode` is a dataclass, so `__init__` method is generated automatically.
@@ -249,20 +252,21 @@ BloomFilter is cleared automatically when requests count == size.
249
252
## Cache Storage
250
253
251
254
#### Local Storage
252
-
Local storage uses dictionary to store data. A policy is used to evict keys when cache is full.
255
+
Local storage use the state-of-the-art library **Theine** to store data. If your use case in simple, also consider using [Theine](https://github.com/Yiling-J/theine) directly, which will have the best performance.
256
+
253
257
```python
254
258
# lru policy
255
259
Storage(url="local://lru", size=10000)
256
260
257
-
# tinylfu policy
261
+
#w-tinylfu policy
258
262
Storage(url="local://tlfu", size=10000)
259
263
260
264
```
261
265
Parameters:
262
266
263
267
-`url`: `local://{policy}`. 2 policies are currently supported:
264
268
-`lru`
265
-
-`tlfu`: TinyLfu policy, see https://arxiv.org/pdf/1512.00727.pdf
269
+
-`tlfu`: W-TinyLfu policy
266
270
267
271
-`size`: size of the storage. Policy will be used to evict key when cache is full.
268
272
@@ -324,11 +328,97 @@ Parameters:
324
328
-`table`: cache table name.
325
329
-`pool_size`: connection pool size, default 50.
326
330
331
+
## How Thundering Herd Protection Works
332
+
333
+
If you are familar with Go [singleflight](https://pkg.go.dev/golang.org/x/sync/singleflight), you may have an idea how Cacheme works. Cacheme group concurrent requests to same resource(node) into a singleflight with asyncio Event, which will **load from remote cache OR data source only once**. That's why in next Benchmarks section, you will find Cacheme even reduce total redis GET command count under high concurrency.
334
+
335
+
327
336
## Benchmarks
328
-
- Local Storage Hit Ratios(hit_count/request_count)
329
-

330
-
[source code](benchmarks/tlfu_hit.py)
331
337
332
-
- Throughput Benchmark of different storages
338
+
### continuous benchmark
339
+
https://github.com/Yiling-J/cacheme-benchmark
340
+
341
+
### 200k concurrent requests
342
+
343
+
aiocache: https://github.com/aio-libs/aiocache
344
+
345
+
cashews: https://github.com/Krukov/cashews
346
+
347
+
source code:
348
+
349
+
How this benchmark run:
350
+
351
+
1. Initialize Cacheme/Aiocache/Cashews with Redis backend, use Redis blocking pool and set pool size to 100.
352
+
2. Decorate Aiocache/Cashews/Cacheme with a function which accept a number and sleep 0.1s. This function also record how many times it is called.
353
+
3. Register Redis response callback, so we can know how many times GET command are called.
354
+
4. Create 200k coroutines use a zipf generator and put them in async queue(around 50k-60k unique numbers).
355
+
5. Run coroutines in queue with N concurrent workers.
356
+
6. Collect results.
357
+
358
+
Result:
359
+
- Time: How long it takes to finish bench.
360
+
- Redis GET: How many times Redis GET command are called, use this to evaluate pressure to remote cache server.
361
+
- Load Hits: How many times the load function(which sleep 0.1s) are called, use this to evaluate pressure to load source(database or something else).
362
+
363
+
#### 1k concurrency
364
+
365
+
|| Time | Redis GET | Load Hits |
366
+
|------------|-------|------------|-----------|
367
+
| Cacheme | 30 s | 166454 | 55579 |
368
+
| Aiocache | 46 s | 200000 | 56367 |
369
+
| Aiocache-2 | 63 s | 256492 | 55417 |
370
+
| Cashews | 51 s | 200000 | 56920 |
371
+
| cashews-2 | 134 s | 200000 | 55450 |
372
+
373
+
374
+
#### 10k concurrency
375
+
376
+
|| Time | Redis GET | Load Hits |
377
+
|------------|-------|-----------|-----------|
378
+
| Cacheme | 32 s | 123704 | 56736 |
379
+
| Aiocache | 67 s | 200000 | 62568 |
380
+
| Aiocache-2 | 113 s | 263195 | 55507 |
381
+
| Cashews | 68 s | 200000 | 66036 |
382
+
| cashews-2 | 175 s | 200000 | 55709 |
383
+
384
+
385
+
#### 100k concurrency
386
+
387
+
|| Time | Redis GET | Load Hits |
388
+
|------------|-------|-----------|-----------|
389
+
| Cacheme | 30 s | 60990 | 56782 |
390
+
| Aiocache | 80 s | 200000 | 125085 |
391
+
| Aiocache-2 | 178 s | 326417 | 65598 |
392
+
| Cashews | 88 s | 200000 | 87894 |
393
+
| cashews-2 | 236 s | 200000 | 55647 |
394
+
395
+
### 20k concurrent batch requests
396
+
397
+
source code:
398
+
399
+
How this benchmark run:
400
+
401
+
1. Initialize Cacheme with Redis backend, use Redis blocking pool and set pool size to 100.
402
+
2. Decorate Cacheme with a function which accept a number and sleep 0.1s. This function also record how many times it is called.
403
+
3. Register Redis response callback, so we can know how many times MGET command are called.
404
+
4. Create 20k `get_all` coroutines use a zipf generator and put them in async queue(around 50k-60k unique numbers). Each `get_all` request will get 20 unique numbers in batch. So totally 400k numbers.
405
+
5. Run coroutines in queue with N concurrent workers.
406
+
6. Collect results.
407
+
408
+
Result:
409
+
- Time: How long it takes to finish bench.
410
+
- Redis MGET: How many times Redis MGET command are called, use this to evaluate pressure to remote cache server.
411
+
- Load Hits: How many times the load function(which sleep 0.1s) are called, use this to evaluate pressure to load source(database or something else).
412
+
413
+
#### 1k concurrency
414
+
415
+
|| Time | Redis MGET | Load Hits |
416
+
|------------|------|------------|-----------|
417
+
| Cacheme | 12 s | 9996 | 55902 |
418
+
419
+
420
+
#### 10k concurrency
333
421
334
-
See [benchmark](https://github.com/Yiling-J/cacheme-benchmark)
0 commit comments