@@ -118,7 +118,7 @@ def __init__(
118118 self ,
119119 redis_url : str = "redis://localhost:6379/0" ,
120120 capacity : int = 50000 ,
121- evict_fraction : float = 0.2 ,
121+ evict_fraction : float = 0.1 ,
122122 key_prefix : str = "md5:" ,
123123 image_embed_dir : str = None ,
124124 path_ext : str = "-embed" ,
@@ -128,7 +128,7 @@ def __init__(
128128 - capacity: max count of md5 entries allowed in Redis
129129 - evict_fraction: fraction to evict when inserting a NEW md5 and at capacity
130130 - image_embed_dir: base directory for image embed files (e.g., "/afs/embeds")
131- - path_ext: file extension for embed files (default: ". embed")
131+ - path_ext: file extension for embed files (default: "- embed")
132132 """
133133 if not (0.0 <= evict_fraction <= 1.0 ):
134134 raise ValueError ("evict_fraction must be 0..1" )
@@ -152,7 +152,7 @@ def __init__(
152152 self ._evict_and_insert_script = self .r .register_script (self ._EVICT_AND_INSERT_LUA )
153153
154154 def insert (self , md5 : str ) -> Tuple [bool , List [str ]]:
155- """Insert a new md5 with default ref_count=0 . May trigger LRU eviction."""
155+ """Insert a new md5 with default ref_count=1 . May trigger LRU eviction."""
156156 # 等待任何正在进行的逐出操作
157157 self ._wait_if_eviction ()
158158
@@ -176,16 +176,20 @@ def insert(self, md5: str) -> Tuple[bool, List[str]]:
176176 success = bool (evict_res [0 ])
177177 victims = evict_res [1 :] if len (evict_res ) > 1 else []
178178
179- # 删除被逐出md5对应的AFS文件
180- if victims and self .image_embed_dir :
181- self ._delete_afs_files (victims )
182-
183- return success , victims
179+ if success :
180+ # 删除被逐出md5对应的AFS文件
181+ if victims and self .image_embed_dir :
182+ self ._delete_afs_files (victims )
183+ return True , victims
184+ else :
185+ # 逐出失败,短暂退避后重试
186+ time .sleep (0.01 )
187+ return self .insert (md5 )
184188 finally :
185189 self ._release_lock ()
186190 else :
187191 # 等待锁释放后重试
188- time .sleep (0.1 )
192+ time .sleep (0.01 )
189193 return self .insert (md5 )
190194 except Exception as e :
191195 self ._release_lock ()
@@ -199,7 +203,6 @@ def query(self, md5: str) -> bool:
199203 def query_and_incre (self , md5 : str ) -> bool :
200204 """Query if md5 exists and increment ref_count if found."""
201205 self ._wait_if_eviction ()
202-
203206 res = self ._query_incre_script (
204207 keys = [self .zset_key , self .ref_prefix ],
205208 args = [md5 ],
@@ -228,6 +231,11 @@ def stats(self) -> dict:
228231 "evict_fraction" : self .evict_fraction ,
229232 }
230233
234+ def get_ref (self , md5 : str ) -> int | None :
235+ self ._wait_if_eviction ()
236+ val = self .r .get (self .ref_prefix + md5 )
237+ return int (val ) if val is not None else None
238+
231239 def _wait_if_eviction (self ) -> None :
232240 max_wait = 30
233241 start_time = time .time ()
@@ -284,8 +292,8 @@ def _delete_afs_files(self, victims: List[str]) -> None:
284292
285293local size = redis.call('ZCARD', zset)
286294if size < capacity then
287- -- Insert with ref_count=0
288- redis.call('SET', ref_key, 0 )
295+ -- Insert with ref_count=1
296+ redis.call('SET', ref_key, 1 )
289297 local now = redis.call('TIME')[1] * 1000
290298 redis.call('ZADD', zset, now, md5)
291299 return {0} -- Success, no eviction
@@ -332,17 +340,16 @@ def _delete_afs_files(self, victims: List[str]) -> None:
332340
333341--ref 递减到 0 时保留键,只更新计数与 LRU
334342local rc = tonumber(val) - 1
335- if rc < 0 then
336- rc = 0
337- end
338-
343+ if rc < 0 then rc = 0 end
339344redis.call('SET', ref_key, rc)
340345
341- -- 更新 LRU 时间戳(最近释放的条目更不容易被立即逐出)
342- local now = redis.call('TIME')[1] * 1000
343- redis.call('ZADD', zset, now, md5)
346+ if rc > 0 then
347+ -- 只有仍被引用时才更新 LRU
348+ local now = redis.call('TIME')[1] * 1000
349+ redis.call('ZADD', zset, now, md5)
350+ end
344351
345- return {rc, 0} -- 未删除
352+ return {rc, 0}
346353"""
347354
348355 _EVICT_AND_INSERT_LUA = r"""
@@ -354,43 +361,64 @@ def _delete_afs_files(self, victims: List[str]) -> None:
354361local capacity = tonumber(ARGV[2])
355362local evict_fraction = tonumber(ARGV[3])
356363
357- -- 计算需要逐出的数量
358- local need = math.max(1, math.floor(capacity * evict_fraction + 0.5))
364+ local unpack = unpack or table.unpack
365+
366+ -- helper: now millis
367+ local function now_ms()
368+ local t = redis.call('TIME')
369+ return t[1] * 1000 + math.floor(t[2] / 1000)
370+ end
371+
372+ local new_ref_key = ref_prefix .. new_md5
373+
374+ -- If already exists, treat as a hit: bump ref_count and refresh LRU
375+ local cur = redis.call('GET', new_ref_key)
376+ if cur then
377+ local rc = tonumber(cur) + 1
378+ redis.call('SET', new_ref_key, rc)
379+ redis.call('ZADD', zset, now_ms(), new_md5)
380+ return {1} -- success, no victims
381+ end
382+
383+ -- If not at capacity, just insert
384+ local size = redis.call('ZCARD', zset)
385+ if size < capacity then
386+ redis.call('SET', new_ref_key, 1)
387+ redis.call('ZADD', zset, now_ms(), new_md5)
388+ return {1} -- success, no victims
389+ end
390+
391+ -- At capacity: try to evict up to max_try items with rc==0, but success if at least 1 is freed
392+ local max_try = math.max(1, math.floor(size * evict_fraction + 0.5))
359393local victims = {}
394+ local freed = 0
360395
361- -- 获取所有键并按LRU排序
396+ -- Scan from LRU (smallest score) to MRU
362397local all_keys = redis.call('ZRANGE', zset, 0, -1, 'WITHSCORES')
363398local i = 1
364-
365- -- 查找引用计数为0的键作为逐出候选
366- while #victims < need and i <= #all_keys do
367- local md5 = all_keys[i]
368- local ref_key = ref_prefix .. md5
369- local rc = redis.call('GET', ref_key)
370-
371- if rc and tonumber(rc) <= 0 then
372- table.insert(victims, md5)
373- end
374- i = i + 2 -- 跳过分数
399+ while freed < 1 and i <= #all_keys and #victims < max_try do
400+ local md5 = all_keys[i]
401+ local ref_key = ref_prefix .. md5
402+ local v = redis.call('GET', ref_key)
403+ if v and tonumber(v) <= 0 then
404+ table.insert(victims, md5)
405+ freed = freed + 1
406+ end
407+ i = i + 2 -- skip score
375408end
376409
377- -- 如果找到足够的候选,执行逐出
378- if #victims >= need then
379- -- 删除受害者
380- for _, v in ipairs(victims) do
381- local ref_key = ref_prefix .. v
382- redis.call('DEL', ref_key)
383- redis.call('ZREM', zset, v)
384- end
385-
386- -- 插入新的md5
387- local ref_key = ref_prefix .. new_md5
388- redis.call('SET', ref_key, 0)
389- local now = redis.call('TIME')[1] * 1000
390- redis.call('ZADD', zset, now, new_md5)
391-
392- return {1, unpack(victims)} -- success + victims
410+ if freed >= 1 then
411+ -- delete victims
412+ for _, v in ipairs(victims) do
413+ redis.call('DEL', ref_prefix .. v)
414+ redis.call('ZREM', zset, v)
415+ end
416+ -- insert new
417+ redis.call('SET', new_ref_key, 1)
418+ redis.call('ZADD', zset, now_ms(), new_md5)
419+ return {1, unpack(victims)}
393420else
394- return {0} -- 逐出失败,没有足够的候选
421+ -- no zero-ref items found
422+ return {0}
395423end
396424"""
0 commit comments