Multithreading for faster email

I created an email campaign for a website through Amazon SAS. It is encoded in C #.

Each email takes 3.3 seconds to send via the Amazon SAS API. This means that using a single-threaded application, I can only send 3 letters per second.

I implemented a manufacturer / consumer application, a multi-threaded application with 1 manufacturer, to request email settings for each client, and 25 consumers pulled out of the queue and sent emails.

My multi-threaded application sends 12 emails per second (four times increase). I would expect a larger increase in speed from an application with 25 threads.

My question is: How can I speed up sending a mail program on a single processor machine? ? Can my profit seem reasonable or is it more related to my speed due to coding than to my inability to quickly process mroe mail?

Thanks in advance!

UPDATE: If others are facing the same problem ... connecting to AWS to send email takes a long time. The following topic on the AWS Developer forums provides some insight (you may need to scroll down to get more helpful posts).

https://forums.aws.amazon.com/thread.jspa?threadID=78737

+8
performance multithreading c # amazon-web-services producer-consumer
source share
8 answers

My question is: how much can I speed up sending a mail program? on a single processor machine? Is my winnings reasonable or mine likely due to encoding than the inability of the computer to process emails faster?

In general, acceleration by 4 times to increase the number of threads by 25 times is not outrageous, but this is also not so.

A single processor will only become a bottleneck when your CPU usage is high. You can tell if this applies to you by looking at the total CPU usage when the application is running. Theoretically, sending bulk emails should be a limited I / O operation; if this does not apply to you, then your code may have problems.

Although I have not used Amazon SES, I know that other Amazon products definitely use various forms of bandwidth / request throttling. It is possible (likely) that your bandwidth is more limited by Amazon than your application.

I wrote a high-performance mass mail application a while ago, and I did this:

  • Use async I / O as much as possible, in addition to multiple threads. Thus, if one request is slow, it does not consume the entire thread.
  • A message was sent directly to the end servers, and not through an intermediate gateway. This required using P / Invoke to call DNS to get the required MX or A records. After that I used the standard SmtpClient class (which has the SendAsync method) to actually send mail.

This approach also allows me to see and record errors when sending mail, which in turn provides better user feedback. An alternative is to rely on receiving and parsing mail with the gateway server, which is at least error prone.

+2
source share

You can speed things up even if it's a single processor machine.

Sending email does not consume a large amount of CPU , it is an IO binding operation. Therefore, you will significantly increase your productivity by doing parallel work.

+4
source share

I wrote about my decision. Basically you use a Parallel.ForEach loop with MaxDegreeOfParallelism , remember to increase the maxconnection counter in app.config .

The following is a sample app.config :

 <system.net> <connectionManagement> <add address="*" maxconnection="392" /> </connectionManagement> <mailSettings> <smtp from="form@company.com" deliveryMethod="Network"> <network host="email-smtp.us-east-1.amazonaws.com" userName="SmtpUsername" password="SmtpPassword" enableSsl="true" port="587" /> </smtp> </mailSettings> </system.net> 

And here is an example of a Parallel.ForEach loop:

 class Program { static readonly object syncRoot = new object(); private readonly static int maxParallelEmails = 196; static void Main(string[] args) { IList<Model.SendEmailTo> recipients = _emailerService.GetEmailsToSend(); int cnt = 0; int totalCnt = recipients.Count; Parallel.ForEach(recipients.AsParallel(), new ParallelOptions { MaxDegreeOfParallelism = maxParallelEmails }, recipient => { // Do any other logic // Build the email HTML // Send the email, make sure to log exceptions // Track email, etc lock (syncRoot) cnt++; Console.WriteLine(String.Format("{0}/{1} - Sent newsletter email to: {2}", cnt, totalCnt, recipient.Email)); }); } } 

My blog explains this in more detail: http://michaeldimoudis.com/blog/2013/5/25/reliably-and-speedily-send-mass-emails-via-amazon-ses-in-c

+3
source share

In a multi-threaded application running on a multi-core (or multi-processor) system, the golden rule is that (as a rule) you cannot achieve better acceleration than N times in a row, when N is the number of cores. Therefore, if you have an activity that takes 12 seconds, and you run it in parallel on 4 cores, you can not do better than 3 seconds.

Conversely, if previously you could perform one action in one unit of time, with four cores, you cannot do better than 4 actions in one unit of time.

In addition, this upper bound is not always achieved due to several factors that generally affect the performance of parallel programs: disk I / O bottlenecks, memory saturation, lock conflict, etc.

+1
source share

consumer-producer with only one queue does not scale. The queue becomes a bottleneck as you add more consumers or producers.

If you have a multiprocessor architecture, you can use several processes to send letters. You can still use your multi-threaded version of your manufacturer, but now it will be one foreach process; this will speed things up a bit (as Tudor explained), but the problem remains.

however, you can have only one network manager or a similar object for the entire system that sends messages (for example, htttp messages) and one network interface card. Now the bottleneck could be this network manager. I'd like to know more about the system architecture :)

+1
source share

I was in a similar situation a few months ago. Despite the fact that we need many factors to tell you what causes lower performance, you can try with the mirco instance of the EC2 instance to try and send emails.

This turned out to work well in my case, and it was a suitable solution since I was working on a web application.

0
source share

The task is not related either to the processor or to the IO binding. The task makes an SES request to send email (with limited data or IO), and then waits. Thus, use the largest number of threads that you can use for available RAM.

0
source share

As commented, this is an I / O problem because you need to find a large number of jobs with the size of the infra and bandwidth

Use the queue template,

Example:

1 - End Email

2 - "N" Jobs send email

-2
source share

All Articles