@@ -59,19 +59,17 @@ def collection_for_write(self):
5959 db = router .db_for_write (self .cache_model_class )
6060 return connections [db ].get_collection (self ._collection_name )
6161
62- def get (self , key , default = None , version = None ):
63- return self .get_many ([key ], version ).get (key , default )
64-
6562 def _filter_expired (self , expired = False ):
6663 """
67- Create a filter to exclude expired data by default
68- or include only expired data if `expired` is True.
64+ Return MQL to exclude expired entries (needed because the MongoDB
65+ daemon does not remove expired entries precisely when it expires).
66+ If expired=True, return MQL to include only expired entries.
6967 """
70- return (
71- {"expires_at" : {"$lt" : datetime .utcnow ()}}
72- if expired
73- else { "expires_at" : { "$gte" : datetime . utcnow ()}}
74- )
68+ op = "$lt" if expired else "$gte"
69+ return {"expires_at" : {op : datetime .utcnow ()}}
70+
71+ def get ( self , key , default = None , version = None ):
72+ return self . get_many ([ key ], version ). get ( key , default )
7573
7674 def get_many (self , keys , version = None ):
7775 if not keys :
@@ -93,7 +91,7 @@ def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
9391 "$set" : {
9492 "key" : key ,
9593 "value" : self .serializer .dumps (value ),
96- "expires_at" : self ._get_expiration_time (timeout ),
94+ "expires_at" : self .get_backend_timeout (timeout ),
9795 }
9896 },
9997 upsert = True ,
@@ -111,7 +109,7 @@ def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
111109 "$set" : {
112110 "key" : key ,
113111 "value" : self .serializer .dumps (value ),
114- "expires_at" : self ._get_expiration_time (timeout ),
112+ "expires_at" : self .get_backend_timeout (timeout ),
115113 }
116114 },
117115 upsert = True ,
@@ -124,10 +122,13 @@ def _cull(self, num):
124122 if self ._cull_frequency == 0 :
125123 self .clear ()
126124 else :
125+ # The fraction of entries that are culled when MAX_ENTRIES is
126+ # reached is 1 / CULL_FREQUENCY. For example, in the default case
127+ # of CULL_FREQUENCY=3, 2/3 of the entries are kept.
127128 keep_num = num - num // self ._cull_frequency
128129 try :
129- # Find the first entry beyond the retention limit,
130- # prioritizing earlier expiration dates .
130+ # Find the first cache entry beyond the retention limit,
131+ # culling entries that expire the soonest .
131132 deleted_from = next (
132133 self .collection_for_write .aggregate (
133134 [
@@ -139,16 +140,19 @@ def _cull(self, num):
139140 )
140141 )
141142 except StopIteration :
142- # No entries found beyond the retention limit, nothing to delete.
143+ # If no entries are found, there is nothing to delete. It may
144+ # happen if the database removes expired entries between the
145+ # query to get `num` and the query to get `deleted_from`.
143146 pass
144147 else :
145- # Delete all entries with an earlier expiration date.
146- # If multiple entries share the same expiration date,
147- # delete those with a greater or equal key.
148+ # Cull the cache.
148149 self .collection_for_write .delete_many (
149150 {
150151 "$or" : [
152+ # Delete keys that expire before `deleted_from`...
151153 {"expires_at" : {"$lt" : deleted_from ["expires_at" ]}},
154+ # and the entries that share an expiration with
155+ # `deleted_from` but are alphabetically after it.
152156 {
153157 "$and" : [
154158 {"expires_at" : deleted_from ["expires_at" ]},
@@ -162,7 +166,7 @@ def _cull(self, num):
162166 def touch (self , key , timeout = DEFAULT_TIMEOUT , version = None ):
163167 key = self .make_and_validate_key (key , version = version )
164168 res = self .collection_for_write .update_one (
165- {"key" : key }, {"$set" : {"expires_at" : self ._get_expiration_time (timeout )}}
169+ {"key" : key }, {"$set" : {"expires_at" : self .get_backend_timeout (timeout )}}
166170 )
167171 return res .matched_count > 0
168172
@@ -185,10 +189,10 @@ def incr(self, key, delta=1, version=None):
185189 raise ValueError (f"Key '{ key } ' not found." ) from None
186190 return updated ["value" ]
187191
188- def _get_expiration_time (self , timeout = None ):
192+ def get_backend_timeout (self , timeout = DEFAULT_TIMEOUT ):
189193 if timeout is None :
190194 return datetime .max
191- timestamp = self .get_backend_timeout (timeout )
195+ timestamp = super () .get_backend_timeout (timeout )
192196 return datetime .fromtimestamp (timestamp , tz = timezone .utc )
193197
194198 def delete (self , key , version = None ):
@@ -205,12 +209,10 @@ def _delete_many(self, keys, version=None):
205209
206210 def has_key (self , key , version = None ):
207211 key = self .make_and_validate_key (key , version = version )
208- return (
209- self .collection_for_read .count_documents (
210- {"key" : key , ** self ._filter_expired (expired = False )}
211- )
212- > 0
212+ num = self .collection_for_read .count_documents (
213+ {"key" : key , ** self ._filter_expired (expired = False )}
213214 )
215+ return num > 0
214216
215217 def clear (self ):
216218 self .collection_for_write .delete_many ({})
0 commit comments