1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

株式会社TORICOAdvent Calendar 2023

Day 19

djangoのメール送信とawsサーバーを並列化した話

Posted at

会社でメールマガジンを送る際、送信対象人数が多いとサーバーが止まってしまうという不具合があったので負荷分散するために送信処理とサーバーを並列化しました。
その際に、並列化のコードやサーバー側の設定で勉強になったことやつまづいたことを備忘録として書きます。

メール送信の並列化について

送信処理の並列化をスレッドプールかプロセスプールで並列化予定だったので、まずはスレッドプールとプロセスプールについて調べました。

スレッドプール

スレッドプールは、複数のスレッドをプールで管理し、必要に応じてスレッドを再利用する仕組みです。これにより、スレッドの生成と破棄にかかるオーバーヘッドを削減し、効率的に処理を行うことが期待できます。

プロセスプール

プロセスプールは、複数のプロセスを生成し、それぞれが独立して処理を行います。スレッドプールと比べてリソースをより分離することができるため、安定して大規模な処理を行うことが可能です。

並列化ではメールの送信は主にI/O操作が中心なためスレッドプールで実装しました。

送信処理について

送信処理を作成する際、pythonのconcurrent.futuresモジュールを使用して並列化しました。

with ThreadPoolExecutor(max_workers=3) as executor:
    executor.map(送信処理)

というようなコードで3並列で送信をしてくコードを作成しました。
送信処理は実装コードによって変わりますが、djangoの場合は下記のように送信用のコードがあるので、コードに沿って送信処理を作成します。
djangoのメール送信の詳細

def send_mail(subject, message, from_email, recipient_list,
              fail_silently=False, auth_user=None, auth_password=None,
              connection=None, html_message=None):
    """
    Easy wrapper for sending a single message to a recipient list. All members
    of the recipient list will see the other recipients in the 'To' field.

    If from_email is None, use the DEFAULT_FROM_EMAIL setting.
    If auth_user is None, use the EMAIL_HOST_USER setting.
    If auth_password is None, use the EMAIL_HOST_PASSWORD setting.

    Note: The API for this method is frozen. New code wanting to extend the
    functionality should use the EmailMessage class directly.
    """
    connection = connection or get_connection(
        username=auth_user,
        password=auth_password,
        fail_silently=fail_silently,
    )
    mail = EmailMultiAlternatives(subject, message, from_email, recipient_list, connection=connection)
    if html_message:
        mail.attach_alternative(html_message, 'text/html')

    return mail.send()

awsサーバーの並列化について

awsの場合はec2で送信サーバーを作った後、サーバー接続用のコードを作成します。 サーバー接続のコードはdjangoの場合は send_mail() と同じでサーバーの接続用のコードがあります それを参考にしてください

サーバーの並列化については送信処理の部分でsend_mail()に渡す値としてconnectionがあり、その部分に処理ごとに接続先サーバーを渡せばサーバーごとに処理します。

私は、接続先サーバーのリストを作り、itertoolsのcycleを使い処理ごとに順次割り当てしています。

送信確認

ec2などで作成したメールサーバーに接続ができるか、送信は可能かを調べる場合は netcat などを使用して、ターミナル(Mac)やコマンドライン(Windows)から直接確認することができます。

nc [オプション] [ホスト名またはIPアドレス] [ポート番号]

nc example.com 587

上記の場合はexample.comに587ポートで接続します
ポート番号についてはwikipediaなどにも載っているので一度見ておくとわかりやすいかもです。

メール送信関連だけなら25、587、465、2525などを見れば問題ないです。

実行結果(接続成功)
220 example.com ESMTP Postfix (Ubuntu)

上記のような文字が表示されれば接続できています。

メール送信のpostfixについて

私が一番苦戦した部分がpostfixの設定です。
postfixの修正をする場合はsshなどを利用し、送信サーバに接続して、/etc/postfix/のファイルを修正します。
主な修正ファイルはmain.cfとmaster.cfです。

詳細な設定はサーバーによって変わったり、すると思うので修正の際に出たエラーの対処方法を記載します。
対処する際はPostfixの設定パラメータを参考にしてください

Recipient address rejected: Access denied

このエラーはAccess deniedとあるようにアクセスの拒否が原因です。 main.cfを確認して permit_sasl_authenticated , reject , permit_mynetworks , reject_unauth_destination などを設定してみてください。 またmain.cfに設定があるのにAccess deniedが出る場合master.cfのsmtpd_recipient_restrictionsを確認して、permit_mynetworksなどがあるか確認してください。 私が修正した際はmaster.cfが permit_sasl_authenticated と rejectのみだったためプロトコルで認証した場合のみ許可されていたのでAccess deniedが発生しました。

Relay access denied

このエラーもアクセス拒否のエラーですが、こちらは単純にmain.cfのmynetworksの設定でIPアドレスを追加すれば解決します。 サーバーを並列化する場合は送信処理をするサーバーで送信コマンドを打ったサーバーのIPアドレスを入力してください(localや送信サーバーから直接など)からみて他のIPアドレスを追加します。 例えばlocalから送信コマンドを使用した場合はそれぞれの送信サーバーのmain.cfのmynetworksにlocalのIPアドレスを記載 送信サーバーから送信コマンドを使用した場合は他の送信サーバーに送信コマンドを使用したのIPアドレスを記載します。
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?