44Batch SQS utilities
55"""
66import logging
7+ import math
78import sys
8- from typing import Callable , Dict , List , Optional , Tuple , cast
9+ from concurrent .futures import ThreadPoolExecutor , as_completed
10+ from typing import Any , Callable , Dict , List , Optional , Tuple , cast
911
1012import boto3
1113from botocore .config import Config
@@ -73,6 +75,7 @@ def __init__(
7375 session = boto3_session or boto3 .session .Session ()
7476 self .client = session .client ("sqs" , config = config )
7577 self .suppress_exception = suppress_exception
78+ self .max_message_batch = 10
7679
7780 super ().__init__ ()
7881
@@ -120,23 +123,39 @@ def _prepare(self):
120123 self .success_messages .clear ()
121124 self .fail_messages .clear ()
122125
123- def _clean (self ):
126+ def _clean (self ) -> Optional [ List ] :
124127 """
125128 Delete messages from Queue in case of partial failure.
126129 """
130+
127131 # If all messages were successful, fall back to the default SQS -
128- # Lambda behaviour which deletes messages if Lambda responds successfully
132+ # Lambda behavior which deletes messages if Lambda responds successfully
129133 if not self .fail_messages :
130134 logger .debug (f"All { len (self .success_messages )} records successfully processed" )
131- return
135+ return None
132136
133137 queue_url = self ._get_queue_url ()
134138 entries_to_remove = self ._get_entries_to_clean ()
139+ # Batch delete up to 10 messages at a time (SQS limit)
140+ max_workers = math .ceil (len (entries_to_remove ) / self .max_message_batch )
135141
136- delete_message_response = None
137142 if entries_to_remove :
138- delete_message_response = self .client .delete_message_batch (QueueUrl = queue_url , Entries = entries_to_remove )
139-
143+ with ThreadPoolExecutor (max_workers = max_workers ) as executor :
144+ futures , results = [], []
145+ while entries_to_remove :
146+ futures .append (
147+ executor .submit (
148+ self ._delete_messages , queue_url , entries_to_remove [: self .max_message_batch ], self .client
149+ )
150+ )
151+ entries_to_remove = entries_to_remove [self .max_message_batch :]
152+ for future in as_completed (futures ):
153+ try :
154+ logger .debug ("Deleted batch of processed messages from SQS" )
155+ results .append (future .result ())
156+ except Exception :
157+ logger .exception ("Couldn't remove batch of processed messages from SQS" )
158+ raise
140159 if self .suppress_exception :
141160 logger .debug (f"{ len (self .fail_messages )} records failed processing, but exceptions are suppressed" )
142161 else :
@@ -147,6 +166,13 @@ def _clean(self):
147166 child_exceptions = self .exceptions ,
148167 )
149168
169+ return results
170+
171+ def _delete_messages (self , queue_url : str , entries_to_remove : List , sqs_client : Any ):
172+ delete_message_response = sqs_client .delete_message_batch (
173+ QueueUrl = queue_url ,
174+ Entries = entries_to_remove ,
175+ )
150176 return delete_message_response
151177
152178
0 commit comments