• Apfeltalk ändert einen Teil seiner Allgemeinen Geschäftsbedingungen (AGB), das Löschen von Useraccounts betreffend.
    Näheres könnt Ihr hier nachlesen: AGB-Änderung
  • Viele hassen ihn, manche schwören auf ihn, wir aber möchten unbedingt sehen, welche Bilder Ihr vor Eurem geistigen Auge bzw. vor der Linse Eures iPhone oder iPad sehen könnt, wenn Ihr dieses Wort hört oder lest. Macht mit und beteiligt Euch an unserem Frühjahrsputz ---> Klick

PHP/MySQL - Emailverteilersystem

.holger

Borowitzky
Registriert
13.09.04
Beiträge
8.970
Moin moin,

ich habe ein Emailverteiler geschrieben, der so langsam an seine Grenzen stößt. DIe Anzahl der, in einer MySQL Tabelle eingetragen, Adressen ist inzwischen so hoch, dass ich vermutlich beim versenden bald ein Timeout vom Server bekommen werde. Da ich am Server nicht viel verändern kann bzw. auch ohne eine Veränderung der Timeoutzeit möchte, dass das Script funktioniert suche ich nach einer Lösung.

Der Verteiler läuft wie folgt:

Ich schreibe die Nachricht, klicke an, welche Gruppen die Mail bekommen soll (Mitglieder, Presse, Interessenten,…), kann in ein Textfeld noch zusätzliche Adressen eintragen und klicke auf absenden.

Nun sucht sich das Script aus der Tabelle alle Adressen der jeweiligen Gruppenmitglieder und schreibt diese in ein Array, an das Array werden auch die zusätzlichen Adressen geschrieben.
WEnn alle Adressen in dem Array sind, wird eine Schleife durchlaufen, bei der der E-Mailtext angepasst wird (ein individueller - aus dem Verteileraustragelink wird eingebaut) und dann wird jede Mail einzeln verschickt (mit mail(); ).
Dies dauerte bisher schon sehr lange (mit rund 144 Adressen) - jetzt habe ich noch rund 300 weitere Adressen eingetragen (der Presseverteiler ist mit dem Mitgliederverteiler zusammengelegt worden).

Ich würde mich freuen, wenn jemand dafür ein Workarround hätte (vielleicht kann ich nach 50 verschickten mails das Script stoppen, mir die Position im Array merken, die Seite neuladen und von dann das Script erneut starten - mit der alten Position als Ausgangswert) - könnte das klappen? Habt ihr eine bessere Idee?

Gruß Holger
 

ruelpsnase

Tokyo Rose
Registriert
07.07.07
Beiträge
71
Hmm....ohne das Skript zu kennen oder gesehen zu haben, wie Du das gelöst hast, würde ich sagen, dass Du mit Deinem Skript ein grundsätzliches Performanceproblem hast. Ob 150 oder 450 Adressen in einer Tabelle gespeichert sind, dürfte auch bei schwächeren Rechnern kein Problem darstellen.
Ich habe mich mit MySQL selber schon seit Version 4.x nicht mehr genauer beschäftig, aber ich denke, seit Version 5.x kann man sog. "Stored Procedures" benutzen. Das sind kleine "Miniprogramme", die im MySQL-Server ablaufen - man könnte also die eh schon in der Datenbank vorkommenden Datensätze direkt dort verarbeiten und vielleicht nur die komplett fertige Mail an das PHP-Skript zurückgeben. Vielleicht hilft Dir dieser Hinweis.

Oder bevor Du die ca. 450 Mails versendest, solltest Du vielleicht nur EINE Mail an dich versenden und die ganzen restlichen Empfänger in das "BCC"-Feld eintragen. Damit bekommt jeder Empfänger die Mail, ohne dass er sieht, wer die Mail noch bekommen hat. Damit würdest Du die Last des Versendens der Mails auf den Mailserver verschieben.

Es kann auch sein, dass Dein PHP-Skript über dauernde "Repostings" stolpert, d.h. dass bei jeder Anfrage (jedem Klick auf der Seite) die ganze Seite neu generiert und dem Browser zugeschickt werden muss. Was sich mittlerweile durch AJAX bzw. Web 2.0 vermeiden lässt - ist aber meist viel Programmieraufwand.

Aber wie eingangs schon erwähnt - sehr wahrscheinlich handelt es sich um ein "Designproblem" im PHP-Skript ;)
 

pepi

Cellini
Registriert
03.09.05
Beiträge
8.740
[...]
Oder bevor Du die ca. 450 Mails versendest, solltest Du vielleicht nur EINE Mail an dich versenden und die ganzen restlichen Empfänger in das "BCC"-Feld eintragen. Damit bekommt jeder Empfänger die Mail, ohne dass er sieht, wer die Mail noch bekommen hat. Damit würdest Du die Last des Versendens der Mails auf den Mailserver verschieben.[...])
Was leider hochgradig unsinnig ist, da so ein "individueller Austragelink" nicht mehr möglich ist...

Weist Du wo genau das Performance Nadelöhr entsteht? An 400 Einträgen in der Datenbank kann es ja wohl kaum liegen würde ich sagen. Wenn Du PHP Timeouts bekommst, könntest Du die PHP Timeout Zeit möglicherweise etwas länger stellen so Du das beim Hoster darfst. Ebenso hilft vieleicht die RAM Zuteilung pro PHP Skript ein wenig zu erhöhen (falls möglich).

Ansonsten würde ich das vielleicht ein wenig aufteilen, und für jeden Anfangsbuchsaben im Alphabet einzeln aufrufen lassen. (Nacheinander quasi) Wobei ich wie gesagt auch vermute, daß es da woanders krankt. An dem bissl Mailen kanns kaum liegen.
Gruß Pepi
 

AAPL

Roter Delicious
Registriert
17.08.07
Beiträge
93
Sensationell wie Ihr Analysen macht ohne eine Zeile des Codes gesehen zu haben ;)
Helfen kann dir nur wer:

1. Die Umgebung kennt
2. Den Code kennt

alles andere sind Spekulationen.
 

.holger

Borowitzky
Registriert
13.09.04
Beiträge
8.970
also die Seite ist bei nodeeps.de gehostet - also kein eigener Server - das können wir uns leider nicht leisten.

hier der relevante Code:
Code:
<?
if (isset($_REQUEST['abschicken'])){
	$empfaenger =  $_REQUEST['empfaenger'];
	$nachricht =  $_REQUEST['nachricht'];
	$betreff =  $_REQUEST['betreff'];
	if (isset($_REQUEST['weitere'])) {
	
		$string =  $_REQUEST['adressen'];
		$adressen = explode(",", $string);
	}
	$comma_separated = implode("','", $empfaenger);
	$comma_separated = "'".$comma_separated."'";
	$gespeicherteadressen = mysql_query("SELECT Email FROM $emailadressen
	WHERE Art IN(".$comma_separated.") ORDER BY id");

	while ($row = mysql_fetch_row($gespeicherteadressen))
	{
	$adressen = array_merge((array)$adressen, (array)$row[0]);
	}
	
	$Trenner = md5(uniqid(time()));
	$textzumsenden = $nachricht;
	
	
		$Header = "From: XXXXXXXXXXXXXXXXX"; 
		$Header .= "\n"; 
		$Header .= "MIME-Version: 1.0"; 
		$Header .= "\n"; 
		$Header .= "Content-Type: multipart/mixed; boundary=$Trenner"; 
		$Header .= "\n\n"; 
		$Header .= "This is a multi-part message in MIME format"; 
		$Header .= "\n"; 
		$Header .= "--$Trenner"; 
		$Header .= "\n"; 
		$Header .= "Content-Type: text/html; charset=utf-8;"; 
		
		$Header .= "\n"; 
		$Header .= "Content-Transfer-Encoding: 8bit;"; 
		
		$Header .= "\n\n"; 
		$Header .= $textzumsenden ."\n\n";
		$Header .= "--$Trenner"; 
		
		if(isset($_REQUEST['anhangfrage'])){
	 
		
		$Header .= "\n"; 
		$Header .= "Content-Type: ";
		$Header .= $_FILES['Anhang']['type'];
		$Header .= "; name=";
		$Header .= $_FILES['Anhang']['name']; 
		$Header .= "\n"; 
		$Header .= "Content-Transfer-Encoding: base64"; 
		$Header .= "\n"; 
		$Header .= "Content-Disposition: attachment; filename=";
		$Header .= $_FILES['Anhang']['name']; 
		$Header .= "\n\n"; 
		$Dateiinhalt = fread(fopen($_FILES['Anhang']['tmp_name'], "r"), $_FILES['Anhang']['size']);
		$Header .= chunk_split(base64_encode($Dateiinhalt));
		$Header .= "\n"; 
		$Header .= "--$Trenner--"; 
		}
		
		
		
	$i = 0;
	foreach($adressen as $adresse)
	{
	
	$adresse = str_replace(" ", "", $adresse);
	
	$textzumsenden .= "-------------------------";
	$textzumsenden .= "\n\n";
	$textzumsenden .= "Um dich abzumelden gehe auf";
	$textzumsenden .= "\n http://".$_SERVER['HTTP_HOST']."".$_SERVER['PHP_SELF']."?austragen=true&email=".$adresse;
	
	mail($adresse, $betreff, "", $Header); 

    $i++;
	
	}
		$time = time();
	$empfaengersave = implode(", ", $empfaenger);
		if (isset($_REQUEST['weitere'])) {
	
		$string =  $_REQUEST['adressen'];
	}
	$empfaengersave .= ", ".$string;
	$eintragen = MYSQL_QUERY("INSERT into $nachrichten (Betreff, Email, Time, Empfaenger) VALUES ('$betreff','$nachricht', '$time', '$empfaengersave')");
	
	echo "Es wurden ". $i ." Emails verschickt";
}
 

Hilarious

Gelbe Schleswiger Reinette
Registriert
10.08.05
Beiträge
1.759
Ich habe für eine ganz ähnliche Anforderung das Thema anders gelöst. Daher kann ich Dir raten, dass Du, wenn Du Performance-Probleme hast, durchaus kleinere Einheiten zusammenpacken kannst, so wie Du oben schon selbst überlegt hattest.

Ich mache es ungefähr so:
Vor dem Versand wird die Tabelle der Empfänger, die zu dem Zeitpunkt eingeschrieben sind, in mehrere temporäre Tabellen kopiert. Dazu nutze ich nicht die temporären Tabellen von MySQL selbst sondern zu diesem Zweck eigene (dauerhaftere) Temporärtabellen an. Die Primärschlüssel übernehme ich dabei von der Quelltabelle. Möchte sich ein Empfänger abmelden, wird dies dann in der Quelltabelle verzeichnet und nicht in einer dieser Caches.

Anschließend nehme ich ein E-Mail-Template, in dem ich mir Platzhalter definiert habe und spiele Suchen und Ersetzen mit regulären Ausdrücken. Hier die relevante Zeile (sollte sich selbst erklären):
Code:
$this->TextBody	=preg_replace($this->TemplatePatterns, $this->TemplateReplacements, $this->TextBody, -1);

Dabei habe ich die Properties »TemplatePatterns« und »TemplateReplacements« vorher harmonisch als Hashtables angelegt, lese den Text der E-Mail ein, und ersetze in einem Rutsch alle möglichen Fundstellen. So könnten die TemplatePatterns, also die Platzhalter zum Beispiel so aussehen:
Code:
[[ANREDE]]
[[VORNAME]] [[NACHNAME]]
Code:
$this->TemplatePatterns 	=	array (	0	=>	"/\[\[ANREDE\]\]/i",
								1	=>	"/\[\[VORNAME\]\]/i",
								2	=>	"/\[\[NACHNAME\]\]/i",
								3	=>	"/\[\[DATUM_HEUTE\]\]/i",
								);

Zusätzlich würde ich Dir empfehlen, grundsätzlich bei E-Mails zwecks gesteigerter Kompatibiltät mit den leidigen »anderen« mit CR/LF und nicht nur mit LF zu arbeiten, also mit »\r\n«.

Desweiteren könntest Du das array_merge weglassen und die E-Mails innerhalb der Schleife der Abfrage-Ergebnis-Menge zusammenbauen. In Deinem Falle sähe das evtl. so aus:
Code:
while ($_detail = mysql_fetch_assoc($result_resource)) {
    TextEinlesen();
    TextErsetzen();
    MailVersenden();
    KontrollAusgabe();
}
 

red

Allington Pepping
Registriert
18.06.07
Beiträge
190
Ein Modul unseres Content-Management-Systems versendet in Verbindung mit der Pear Mail_Queue Funktion in 5 Minuten bis zu 10.000 E-Mails.

Im Groben funktionierts so:
- Newsletter wird generiert und die E-Mails werden in der Datenbank mit "Mail_Queue" gespeichert
- das Eintragen in die Datenbank erfolgt in 100er oder 500er - Paketen damit kein Server Time Out entsteht ... gelöst wird das mit Cookies und einem automatischen Reload nach jedem Paket ... dies geht aber sauschnell, da die Pear Funktion sehr leistungsstark ist
- Bei 10.000 E-Mails dauert das ca. 2 - 5 Minuten ... die Datenbank hat dann schon mal 100 MB, aber das ist kein Problem
- Für den User des CMS ist der "Versand" nun abgeschlossen, der eigentliche E-Mail-Versand erfolgt nun im Hintergrund wiederum in 500er Paketen per CronJob ... 1/4 stündlich ... 2.000 E-Mails pro Stunde ... die Datenbank wird dabei wieder geleert
- solls mal schnell gehen, kann das Script auch manuell ausgeführt werden und die 10.000 Mails auch in 15 - 30 Minuten versendet werden

... dies nur mal als Denkanstoss ... hab das ganze in einem Tag programmiert gehabt und es läuft hervorragend
 

tjp

Altgelds Küchenapfel
Registriert
07.07.04
Beiträge
4.059
also die Seite ist bei nodeeps.de gehostet - also kein eigener Server - das können wir uns leider nicht leisten.
Am sinnvollsten wäre es natürlich, wenn man ein externes Skript anstoßen könnte. Aber bei einem Hoster wird das wohl schwierig werden, da Sicherheitsgründe dagegen sprechen.
 

Hilarious

Gelbe Schleswiger Reinette
Registriert
10.08.05
Beiträge
1.759
Ein Modul unseres Content-Management-Systems versendet in Verbindung mit der Pear Mail_Queue Funktion in 5 Minuten bis zu 10.000 E-Mails.

Im Groben funktionierts so:
- Newsletter wird generiert und die E-Mails werden in der Datenbank mit "Mail_Queue" gespeichert
- das Eintragen in die Datenbank erfolgt in 100er oder 500er - Paketen damit kein Server Time Out entsteht ... gelöst wird das mit Cookies und einem automatischen Reload nach jedem Paket ... dies geht aber sauschnell, da die Pear Funktion sehr leistungsstark ist
- Bei 10.000 E-Mails dauert das ca. 2 - 5 Minuten ... die Datenbank hat dann schon mal 100 MB, aber das ist kein Problem
- Für den User des CMS ist der "Versand" nun abgeschlossen, der eigentliche E-Mail-Versand erfolgt nun im Hintergrund wiederum in 500er Paketen per CronJob ... 1/4 stündlich ... 2.000 E-Mails pro Stunde ... die Datenbank wird dabei wieder geleert
- solls mal schnell gehen, kann das Script auch manuell ausgeführt werden und die 10.000 Mails auch in 15 - 30 Minuten versendet werden

... dies nur mal als Denkanstoss ... hab das ganze in einem Tag programmiert gehabt und es läuft hervorragend

Auch eine sehr schöne Lösung. Einziger Wermutstropfen: Manche E-Mail-Provider (wie zB web.de) blockieren Dich, wenn Du mehr E-Mails als üblich in kurzer Zeit bei Ihnen anlieferst.
 

red

Allington Pepping
Registriert
18.06.07
Beiträge
190
Auch eine sehr schöne Lösung. Einziger Wermutstropfen: Manche E-Mail-Provider (wie zB web.de) blockieren Dich, wenn Du mehr E-Mails als üblich in kurzer Zeit bei Ihnen anlieferst.

Stimmt leider, aber zum Glück umfassen die Verteiler der Kunden max. 2.000 E-Mail-Adressen und das Versenden im Hintergrund dauert automatisch ja doch ne Weile.
Reports über jeden Versand geben dem Kunden Aufschluss wer den Newsletter bekommen, geöffnet und evtl. Links im Newsletter angeklickt hat.
Unser Tool ist nicht mit diesen MonsterNewsletterApplikationen zu vergleichen die Unsummen kosten und im Grunde nur zu 1/5 genutzt werden, ein Versand an 10.000 E-Mail Adressen aber ohne Probleme möglich.
 

ruelpsnase

Tokyo Rose
Registriert
07.07.07
Beiträge
71
also die Seite ist bei nodeeps.de gehostet - also kein eigener Server - das können wir uns leider nicht leisten.

hier der relevante Code:
Code:
schnippschnapp

also an dem Quelltext konnte ich bis jetzt nix Verfängliches entdecken. Wenn Du die Chance hast, dass zu Debuggen, dann versuche doch 'mal festzustellen, welche Stelle tatsächlich so lahm wird. Kann ja durchaus sein, dass einfach nur der "mail()"-Befehl langsam ist, weil der Hostrechner das (wie Vorredner schon angedeutet haben) künstlich verlangsamt.
Wenn der Debug nicht möglich ist, dann versuche hinter allen relevanten Schritten Datum und Uhrzeit in eine Datei zu schreiben und sieh dir hinterher an, was am meisten Zeit gekostet hat.

Also ersteinmal genau feststellen, wo das PHP-Pferd wirklich lahmt.
 

rok°!

Gala
Registriert
26.10.04
Beiträge
50
Hallo!
Das Performance Problem besteht bei der Auslieferung an den Mail-Dienst. Das dauert i.d.R. bis zu max. 2 Sekunden pro Mail (je nach Mailgröße und Hardwareausstattung des Servers). Markiere vor dem Versenden die Mails die zu versenden sind und lösche die Markierung nach jedem erfolgreichen Versand an den Empfänger. Dann kannst du auch nach einem Abbruch an die Leute weiter versenden, die noch keinen Newsletter bekommen haben.

Alternativ kannst du noch folgende Option einbringen, welche die Seite nach (meinetwegen) 50 versendeten Mails einfach neu lädt. Anhand der oben erklärten Funktionsweise, weiß dann das Skript, wo es weiter machen soll. Ein Seiten-Reload bedeutet auch den Neustart des PHP-Skripts. Somit wird zumindest nicht vom Server abgebrochen und die Mails werden sauber versendet.

*hust*
Oder du probierst was aus, welches das oben erwähnte schon alles kann *mitzaunpfahlaufsignaturzeig* :)