@@ -248,6 +248,9 @@ def refresh(
248248
249249 self ._save_index_metadata (entry )
250250
251+ # Clean up indexes beyond the 2 most recent terms
252+ self ._evict_old_indexes ()
253+
251254 logger .info ("Index ready: %s %s (%d courses)" , term_semester , year , len (course_texts ))
252255 return entry
253256 finally :
@@ -502,12 +505,52 @@ def fetch_available_terms(self) -> List[Tuple[int, str]]:
502505 except Exception :
503506 return []
504507
505- def build_startup_indexes (self , max_startup_terms : int = 4 ) -> None :
506- """Load indexes from Redis and queue builds for recent terms only.
508+ def _evict_old_indexes (self , max_terms : int = 2 ) -> None :
509+ """Delete Redis indexes beyond the most recent max_terms terms."""
510+ try :
511+ all_meta = []
512+ cursor = 0
513+ while True :
514+ cursor , keys = self ._redis .scan (cursor , match = f"{ INDEX_PREFIX } :meta:*" , count = 100 )
515+ for key in keys :
516+ raw = self ._redis .get (key )
517+ if not raw :
518+ continue
519+ try :
520+ meta = json .loads (raw )
521+ meta ["_meta_key" ] = key
522+ all_meta .append (meta )
523+ except Exception :
524+ pass
525+ if cursor == 0 :
526+ break
527+
528+ # Sort newest first
529+ all_meta .sort (
530+ key = lambda m : (m .get ("year" , 0 ), SEMESTER_ORDER .get (m .get ("semester" , "" ), 0 )),
531+ reverse = True ,
532+ )
507533
508- Only the most recent *max_startup_terms* terms are built on startup.
509- Older terms are built on-demand when actually searched.
510- """
534+ for meta in all_meta [max_terms :]:
535+ try :
536+ index_name = self ._get_index_name (meta ["year" ], meta ["semester" ], meta .get ("allowed_subjects" ))
537+ schema = self ._build_schema (index_name )
538+ old_index = SearchIndex .from_dict (schema , redis_url = REDIS_URI )
539+ if old_index .exists ():
540+ old_index .delete (drop = True )
541+ self ._redis .delete (meta ["_meta_key" ])
542+ # Also evict from in-memory cache
543+ stale_key = self ._key (meta ["year" ], meta ["semester" ], meta .get ("allowed_subjects" ))
544+ with self ._lock :
545+ self ._indices .pop (stale_key , None )
546+ logger .info ("Evicted old index: %s %s" , meta .get ("semester" ), meta .get ("year" ))
547+ except Exception as exc :
548+ logger .warning ("Failed to evict index %s %s: %s" , meta .get ("semester" ), meta .get ("year" ), exc )
549+ except Exception as exc :
550+ logger .warning ("Failed to scan for old indexes: %s" , exc )
551+
552+ def build_startup_indexes (self , max_startup_terms : int = 2 ) -> None :
553+ """Load indexes from Redis and queue builds for the 2 most recent terms only."""
511554 # Fetch available terms
512555 available_terms = self .fetch_available_terms ()
513556 if not available_terms :
@@ -519,12 +562,12 @@ def build_startup_indexes(self, max_startup_terms: int = 4) -> None:
519562 # Sort terms: newest first
520563 available_terms .sort (key = lambda t : (t [0 ], SEMESTER_ORDER .get (t [1 ], 0 )), reverse = True )
521564
522- # Only consider recent terms for startup (older ones build on-demand)
523- startup_terms = available_terms [:max_startup_terms ]
565+ # Only consider the most recent terms
566+ keep_terms = available_terms [:max_startup_terms ]
524567
525568 # Load from Redis if available, otherwise queue for building
526569 terms_to_build = []
527- for year , semester in startup_terms :
570+ for year , semester in keep_terms :
528571 loaded = self ._load_redis_index (year , semester , None )
529572 if loaded :
530573 key = self ._key (loaded .year , loaded .semester , loaded .allowed_subjects )
@@ -533,14 +576,6 @@ def build_startup_indexes(self, max_startup_terms: int = 4) -> None:
533576 else :
534577 terms_to_build .append ((year , semester ))
535578
536- # Also load any OTHER indexes already in Redis (from previous runs)
537- for year , semester in available_terms [max_startup_terms :]:
538- loaded = self ._load_redis_index (year , semester , None )
539- if loaded :
540- key = self ._key (loaded .year , loaded .semester , loaded .allowed_subjects )
541- with self ._lock :
542- self ._indices [key ] = loaded
543-
544579 # Queue only recent terms that weren't found in Redis
545580 self ._build_queue = terms_to_build
546581 if terms_to_build :
0 commit comments