ajax -> php mail() sehr langsam

Momo95

Erfahrenes Mitglied
Hallo zusammen,

ich habe ein kleines Newsletter-Tool für meinen Verein geschrieben. Teilweise gehen so 400 Emails raus.

In einem PHP-Script lade ich mir alle Email-Adressen in ein js-Array und rufe mit Ajax für jeweil ein weiteres PHP-Script mit einer Mail-Function auf.

Nach den ersten 300(+/-) Emails wird das ganze furchtbar langsam.

Ausschnitt des Scripts ohne HTML:
Code:
$ergebnis = mysqli_query($db, "SELECT DISTINCT email FROM email ".$query);
while($row = mysqli_fetch_object($ergebnis))
{
echo 'emails.push("'.$row->email.'");';
}
}
?>
var anzahl = emails.length;
var sent = 0;
$.each( emails, function( index, value ){
    $.ajax({
      method: "POST",
      url: "sendMail.php",
      data: { absender: "<?php echo $absender; ?>", email: value, id: "<?php echo $id; ?>"},
      cache: false,
      async: false
    })
      .done(function( msg ) {
          sent++;
          var valeur = Math.round(sent/anzahl*100);
       $('.progress-bar').css('width', valeur+'%').attr('aria-valuenow', valeur).html(sent+"/"+anzahl); 
       if(sent == anzahl) {
           $('#back_button').prop('disabled', false);
       }
      });
});

});

sendMail.php:
PHP:
function encrypt_decrypt($action, $string) {
    $output = false;

    $encrypt_method = "AES-256-CBC";
    $secret_key = 'This is my secret key';
    $secret_iv = 'This is my secret iv';

    // hash
    $key = hash('sha256', $secret_key);
   
    // iv - encrypt method AES-256-CBC expects 16 bytes - else you will get a warning
    $iv = substr(hash('sha256', $secret_iv), 0, 16);

    if( $action == 'encrypt' ) {
        $output = openssl_encrypt($string, $encrypt_method, $key, 0, $iv);
        $output = base64_encode($output);
    }
    else if( $action == 'decrypt' ){
        $output = openssl_decrypt(base64_decode($string), $encrypt_method, $key, 0, $iv);
    }

    return $output;
}
include("connect.php");

include("MailFunction.php");
$absender = $_POST['absender'];
$email = $_POST['email'];
$id = $_POST['id'];

$sql = "SELECT `text`, `betreff` FROM `newsletter_sent` WHERE `id` = $id";
$result = mysqli_query($db, $sql);
$row = mysqli_fetch_object($result);
$betreff = $row->betreff;
$text = $row->text;
$hashmail = encrypt_decrypt('encrypt', $email);
$text = str_replace("##HASHMAIL##", $hashmail, $text);

//function email ($absender, $empfaenger, $betreff, $mailtext)
email($absender, $email, $betreff, $text);


Woran könnte es liegen, dass es so langsam wird. Ist vielleicht auch die Datenbank überlastet und ich sollte den Mailtext über Ajax übergeben? Oder macht der Server irgenwann "dicht".

Vielen Dank und Grüße

Momo
 
Hi

bist du dir sicher, dass die PHP-Datei langsam ist, und nicht evt. der Browser mit deinen hunderten gleichzeitigen Ajax-Requests?

(Und warum überhaupt Einzelaufrufe per Ajax, statt in PHP eine Schleife über alle Mailadressen zu machen?)
 
Obligatorische Anmerkung: Dein Code beinhaltet eine SQL Injection Lücke an dem Punkt, an dem du $id in $sql interpolierst. Nutze am besten Prepared Statements. ;)
 
Vielen Dank für Eure Antworten.

Es kann natürlich sein, dass es tatsächlich der Browser ist. Die ajax-Aufrufe sind aber aber nicht asynchron. Es wird immer einer nach dem andren aufgerufen. Sie sollten also nicht parallel ablaufen. Vielleicht hat der Browser aber mit 300 Aufrufen nacheinander ein Problem.

Könnte ein kleiner .delay() im ajax-Aufruf den Browser etwas Luft zum Atmen geben?

Wenn ich das ganze direkt in PHP durchschleife, bekomme ich ein timeout. Alternativ müsste ich über LIMIT und Weiterleitung immer kleine Blöcke laden. Cronjobs wären auch noch eine Alternative.
Zumindest bei der letzen Lösung würde mir der schöne Ladebalken fehlen.

Habt ihr Erfahrung, wie man am besten diese Menge an Mails versendet.
 
Hi,

eine Art Batch-Job-Lauf.
- Du erstellst eine Datenbanktabelle, welche JobId, Status und Adresse speichert.
- Diese befüllst du wenn du die Mails schicken willst mit einer ID (für jeden Lauf unterschiedlich; newsletter ID?), Status 0 (zu verarbeiten) und die gewünschten Mailadressen.
- Du richtest einen Cronjob ein, welcher alle 5 Minuten läuft
- Anschliessend läuft dein Cronjob los und startet so lange das Versendescript bis alles verarbeitet ist
-- Das Script nimmt sich die nächsten 100 Adressen mit Status 0 und max(JobId) und verschickt die Mails
-- Nach dem versenden machst du ein Update auf Status = 2 (verarbeitet) der bearbeiteten Einträge
-- Als Output liefert das Script die Anzahl der versenden Mails
- Sobald deine Schleife im Cron "0" als Output bekommt, ist alles versendet
- Als Ladebalken auf dem Client kannst du dann per count den Status abfragen.

Das ganze nur als grober Vorschlag aus dem Kopf, kann man sicherlich noch verfeinern.
Um das ganze z.B auch parallel zu machen, könntest du im Versendescript z.B. folgendes in einer Transaktion machen:
SQL:
begin;
SELECT adresse FROM jobs WHERE  status = 0 and job_id = max(job_id) LIMIT 0,1;
UPDATE jobs SET status = 1 WHERE job_id = max(job_id) and adresse = [obige adresse];
commit;
-- Update failed? => Nochmal obiges versuchen

---- XXX start
-- Mail senden

begin;
UPDATE jobs SET status = 2 WHERE job_id = max(job_id) and adresse = [obige adresse];
---- XXX end
commit;
Hierzu am Anfang des Scripts die maximale Laufzeit holen und immer wieder prüfen, wie nahe du da ran kommst. Sobald nur noch 5 Sekunden (oder so) übrig sind, die Verabeitung abbrechen und Summe ausgeben.

Sobald alle verarbeitet wurden einen anderen Cronjob starten, welcher die liegen gebliebenen 1er-Status durchgeht und hier mails verschickt. Diese sollten aber an einer Hand abzuzählen sein; Kann nur passieren wenn das Script oben innerhalb von XXX abbricht

Weitere Gedanken:
* Den check mit "job_id = max(job_id)" kannst du dir evtl sparen, wenn du nicht nur den neuesten Newsletter versenden willst, sondern auch ältere, noch nicht versendete
* Du könntest auch den Cronjob ohne Schleife laufen lassen, also fix alle 5 Minuten. Das würde auch das Versenden etwas entzerren. (Spamerkennung?)

Grüsse,
BK
 
Zuletzt bearbeitet:
Vielen Dank, Bratkartoffel!
Ich werde es mal so versuchen. Erstmal alle Empfänger mit Status in eine weitere Tabelle zu kopieren, scheint mir sehr Sinnvoll. Falls das Script abbricht, etc., wird im Nachlauf nochmals versucht, die Email zu senden. Das ist ja bis jetzt nicht der Fall. Wenn der Benutzer das Browserfenster schließt, werden 100 Emails nicht verschickt. Mit einem Cronjob im Hintergrund ist das kein Problem mehr. Auf den Ladebalken verzichte ich dann erstmal - ein Meldung, dass der Newsletter jetzt verschickt wird, muss reichen.

In der Übersicht der gesendeten Newsletter mache ich denn eine Ampel, die auf grün Springt, wenn alle Emails verschickt sind - oder eben auf rot, wenn Fehler aufgetreten sind.

Das war wirklich sehr hilfreich - danke!
 

Neue Beiträge

Zurück