1414from redis .asyncio .sentinel import Sentinel
1515from redis .exceptions import RedisError , WatchError
1616
17- from .constants import default_queue_name , expires_extra_ms , job_key_prefix , result_key_prefix
17+ from .constants import default_queue_name , expires_extra_ms , job_key_prefix , result_key_prefix , stream_key_suffix , \
18+ job_message_id_prefix
1819from .jobs import Deserializer , Job , JobDef , JobResult , Serializer , deserialize_job , serialize_job
20+ from .lua_script import publish_job_lua
1921from .utils import timestamp_ms , to_ms , to_unix_ms
2022
2123logger = logging .getLogger ('arq.connections' )
@@ -117,6 +119,7 @@ def __init__(
117119 if pool_or_conn :
118120 kwargs ['connection_pool' ] = pool_or_conn
119121 self .expires_extra_ms = expires_extra_ms
122+ self .publish_job_sha = None
120123 super ().__init__ (** kwargs )
121124
122125 async def enqueue_job (
@@ -129,6 +132,7 @@ async def enqueue_job(
129132 _defer_by : Union [None , int , float , timedelta ] = None ,
130133 _expires : Union [None , int , float , timedelta ] = None ,
131134 _job_try : Optional [int ] = None ,
135+ _use_stream : bool = False ,
132136 distribution : Optional [str ] = None , # example 5:2
133137 ** kwargs : Any ,
134138 ) -> Optional [Job ]:
@@ -167,6 +171,9 @@ async def enqueue_job(
167171 defer_by_ms = to_ms (_defer_by )
168172 expires_ms = to_ms (_expires )
169173
174+ if _use_stream is True and self .publish_job_sha is None :
175+ self .publish_job_sha = await self .script_load (publish_job_lua )
176+
170177 async with self .pipeline (transaction = True ) as pipe :
171178 await pipe .watch (job_key )
172179 if await pipe .exists (job_key , result_key_prefix + job_id ):
@@ -186,14 +193,37 @@ async def enqueue_job(
186193 job = serialize_job (function , args , kwargs , _job_try , enqueue_time_ms , serializer = self .job_serializer )
187194 pipe .multi ()
188195 pipe .psetex (job_key , expires_ms , job )
189- pipe .zadd (_queue_name , {job_id : score })
196+
197+ if _use_stream is False :
198+ pipe .zadd (_queue_name , {job_id : score })
199+ else :
200+ stream_key = _queue_name + stream_key_suffix
201+ job_message_id_key = job_message_id_prefix + job_id
202+
203+ pipe .evalsha (
204+ self .publish_job_sha ,
205+ 2 ,
206+ # keys
207+ stream_key ,
208+ job_message_id_key ,
209+ # args
210+ job_id ,
211+ str (enqueue_time_ms ),
212+ str (expires_ms ),
213+ )
190214 try :
191215 await pipe .execute ()
192216 except WatchError :
193217 # job got enqueued since we checked 'job_exists'
194218 return None
195219 return Job (job_id , redis = self , _queue_name = _queue_name , _deserializer = self .job_deserializer )
196220
221+ async def get_stream_size (self , queue_name : str | None = None , include_delayed_tasks : bool = True ) -> int :
222+ if queue_name is None :
223+ queue_name = self .default_queue_name
224+
225+ return await self .xlen (queue_name + stream_key_suffix )
226+
197227 def _get_queue_index (self , distribution : Optional [str ]) -> int :
198228 ratios = list (map (lambda x : int (x ), distribution .split (':' ))) # type: ignore[union-attr]
199229 ratios_sum = sum (ratios )
0 commit comments