@@ -2,61 +2,11 @@ import type { CacheInvalidationOptions, CacheProvider, CacheEntry } from '../typ
22import { Redis } from 'ioredis'
33import { getTotalTtl } from '../utils'
44
5- interface ZenStackRedisCommands {
6- invalidate : ( options : string ) => Promise < void >
7- invalidateAll : ( ) => Promise < void >
8- }
9-
10- declare module 'ioredis' {
11- interface Redis extends ZenStackRedisCommands { }
12- }
13-
145export class RedisCacheProvider implements CacheProvider {
156 private readonly redis : Redis
167
178 constructor ( options : RedisCacheProviderOptions ) {
18- this . redis = new Redis ( options . url , {
19- scripts : {
20- invalidate : {
21- numberOfKeys : 0 ,
22- lua : `
23- local options = cjson.decode(ARGV[1])
24- local keysToDelete = {}
25-
26- if (options.tags) then
27- for _, tag in ipairs(options.tags) do
28- local formattedTag = 'zenstack:tag:' .. tag
29- local keys = redis.call('SMEMBERS', formattedTag)
30-
31- for _, key in ipairs(keys) do
32- keysToDelete[#keysToDelete + 1] = key
33- end
34-
35- redis.call('DEL', formattedTag)
36- end
37- end
38-
39- if (#keysToDelete > 0) then
40- redis.call('DEL', unpack(keysToDelete))
41- end
42- ` ,
43- } ,
44-
45- invalidateAll : {
46- numberOfKeys : 0 ,
47- lua : `
48- local keys = redis.call('SMEMBERS', 'zenstack:key')
49-
50- for i = 1, #keys do
51- local key = keys[i]
52- redis.call('DEL', key)
53- end
54-
55- redis.call('DEL', 'zenstack:key')
56- ` ,
57- } ,
58- } ,
59- } )
9+ this . redis = new Redis ( options . url )
6010 }
6111
6212 async get ( key : string ) {
@@ -74,7 +24,6 @@ export class RedisCacheProvider implements CacheProvider {
7424 const formattedKey = formatQueryKey ( key )
7525
7626 multi . set ( formattedKey , JSON . stringify ( entry ) )
77- multi . sadd ( 'zenstack:key' , formattedKey )
7827
7928 const totalTtl = getTotalTtl ( entry )
8029
@@ -87,19 +36,50 @@ export class RedisCacheProvider implements CacheProvider {
8736 const formattedTagKey = formatTagKey ( tag )
8837
8938 multi . sadd ( formattedTagKey , formattedKey )
90- multi . sadd ( 'zenstack:key' , formattedTagKey )
39+
40+ if ( totalTtl > 0 ) {
41+ multi . expire ( formattedTagKey , totalTtl , 'GT' )
42+ multi . expire ( formattedTagKey , totalTtl , 'NX' )
43+ }
9144 }
9245 }
9346
9447 await multi . exec ( )
9548 }
9649
9750 async invalidate ( options : CacheInvalidationOptions ) {
98- await this . redis . invalidate ( JSON . stringify ( options ) )
51+ if ( options . tags && options . tags . length > 0 ) {
52+ await Promise . all ( options . tags . map ( tag => {
53+ return new Promise ( ( resolve , reject ) => {
54+ const stream = this . redis . sscanStream ( formatTagKey ( tag ) , {
55+ count : 100 ,
56+ } )
57+
58+ stream . on ( 'data' , async ( keys : string [ ] ) => {
59+ await this . redis . del ( ...keys )
60+ } )
61+
62+ stream . on ( 'error' , reject )
63+ stream . on ( 'end' , resolve )
64+ } )
65+ } ) )
66+ }
9967 }
10068
10169 async invalidateAll ( ) {
102- await this . redis . invalidateAll ( )
70+ await new Promise ( ( resolve , reject ) => {
71+ const stream = this . redis . scanStream ( {
72+ count : 100 ,
73+ match : 'zenstack:cache:*' ,
74+ } )
75+
76+ stream . on ( 'data' , async keys => {
77+ await this . redis . del ( ...keys )
78+ } )
79+
80+ stream . on ( 'error' , reject )
81+ stream . on ( 'end' , resolve )
82+ } )
10383 }
10484}
10585
@@ -108,9 +88,9 @@ export type RedisCacheProviderOptions = {
10888}
10989
11090function formatQueryKey ( key : string ) {
111- return `zenstack:query:${ key } `
91+ return `zenstack:cache: query:${ key } `
11292}
11393
11494function formatTagKey ( key : string ) {
115- return `zenstack:tag:${ key } `
116- }
95+ return `zenstack:cache: tag:${ key } `
96+ }
0 commit comments