Posted in ColdFusion | Posted on 01-01-2010 | 2,656 views
I recently released an update to BlogCFC. One of the fixes includes a performance update that I'd like to detail. It isn't that big of a deal. In fact, I assume most folks will consider it quite obvious when they see it. But I missed it for years and figured it would be good to share. To give you a clue - the issue only came about when I updated to the latest ColdFish and also involved the large number of subscribers I have at the blog here. I had noticed for a while now that whenever I published a blog entry, it took a good 10-15 seconds to go through. Let me show you the code and tell me if you can see the issue:
2 hint="Handles email for the blog.">
3 <cfargument name="entryid" type="uuid" required="true">
4 <cfset var entry = getEntry(arguments.entryid,true)>
5 <cfset var subscribers = getSubscribers(true)>
6 <cfset var theMessage = "">
7 <cfset var mailBody = "">
8
9 <cfloop query="subscribers">
10
11 <cfsavecontent variable="theMessage">
12 <cfoutput>
13<h2>#entry.title#</h2>
14<b>URL:</b> <a href="#makeLink(entry.id)#">#makeLink(entry.id)#</a><br />
15<b>Author:</b> #entry.name#<br />
16
17#renderEntry(entry.body,false,entry.enclosure)#<cfif len(entry.morebody)>
18<a href="#makeLink(entry.id)#">[Continued at Blog]</a></cfif>
19
20<p>
21You are receiving this email because you have subscribed to this blog.<br />
22To unsubscribe, please go to this URL:
23<a href="#getRootURL()#unsubscribe.cfm?email=#email#&token=#token#">#getRootURL()#unsubscribe.cfm?email=#email#&token=#token#</a>
24</p>
25 </cfoutput>
26 </cfsavecontent>
27
28 <cfif instance.mailserver is "">
29 <cfmail to="#email#" from="#instance.owneremail#" subject="#variables.utils.htmlToPlainText(htmlEditFormat(instance.blogtitle))# / #variables.utils.htmlToPlainText(entry.title)#" type="html">#theMessage#</cfmail>
30 <cfelse>
31 <cfmail to="#email#" from="#instance.owneremail#" subject="#variables.utils.htmlToPlainText(htmlEditFormat(instance.blogtitle))# / #variables.utils.htmlToPlainText(entry.title)#"
32 server="#instance.mailserver#" username="#instance.mailusername#" password="#instance.mailpassword#" type="html">#theMessage#</cfmail>
33 </cfif>
34 </cfloop>
35
36</cffunction>
(Note, I removed a few lines that weren't relevant.) See the issue? Notice that I loop over the subscribers query. Remember that for my blog here, that query was 300+ users. I loop over every single user and for each, I'm generating a rendered blog entry. Imagine a blog entry with 4 code blocks in it. I ended up running ColdFish's rendering code 1200 times. Ouch.
I rewrote the code to simply pull out the function calls, like so:
2<cfset var theLink = makeLink(entry.id)>
3<cfset var rootURL = getRootURL()>
4
5<cfloop query="subscribers">
6
7 <cfsavecontent variable="theMessage">
8 <cfoutput>
9<h2>#entry.title#</h2>
10<b>URL:</b> <a href="#theLink#">#theLink#</a><br />
11<b>Author:</b> #entry.name#<br />
12
13#renderedText#<cfif len(entry.morebody)>
14<a href="#theLink#">[Continued at Blog]</a></cfif>
15
16<p>
17You are receiving this email because you have subscribed to this blog.<br />
18To unsubscribe, please go to this URL:
19<a href="#rooturl#unsubscribe.cfm?email=#email#&token=#token#">#rooturl#unsubscribe.cfm?email=#email#&token=#token#</a>
20</p>
21 </cfoutput>
22 </cfsavecontent>
There are probably other ways to do this too - but I guess the important take from this is - remember to pay special attention to loops. If you see yourself running some type of function within the loop, consider pulling that out (if possible) so you don't repeat the calls.


<cfset var renderedText = renderEntry(entry.body,false,entry.enclosure)>
<cfset var theLink = makeLink(entry.id)>
<cfset var rootURL = getRootURL()>
<cfsavecontent variable="theMessage">
<cfoutput>
<h2>#entry.title#</h2>
<b>URL:</b> <a href="#theLink#">#theLink#</a><br />
<b>Author:</b> #entry.name#<br />
#renderedText#<cfif len(entry.morebody)>
<a href="#theLink#">[Continued at Blog]</a></cfif>
<p>
You are receiving this email because you have subscribed to this blog.<br />
To unsubscribe, please go to this URL:
<a href="#rooturl#unsubscribe.cfm?email=#email#&token=#token#">#rooturl#unsubscribe.cfm?email=#email#&token=#token#</a>
</p>
</cfoutput>
</cfsavecontent>
<cfloop query="subscribers">
<cfif instance.mailserver is "">
<cfmail to="#email#" from="#instance.owneremail#" subject="#variables.utils.htmlToPlainText(htmlEditFormat(instance.blogtitle))# / #variables.utils.htmlToPlainText(entry.title)#" type="html">#theMessage#</cfmail>
<cfelse>
<cfmail to="#email#" from="#instance.owneremail#" subject="#variables.utils.htmlToPlainText(htmlEditFormat(instance.blogtitle))# / #variables.utils.htmlToPlainText(entry.title)#"
server="#instance.mailserver#" username="#instance.mailusername#" password="#instance.mailpassword#" type="html">#theMessage#</cfmail>
</cfif>
</cfloop>
p.s. Fixing to go to inlaws for a few hours so replies to comments will be delayed.
<cfset var renderedText = renderEntry(entry.body,false,entry.enclosure)>
<cfset var theLink = makeLink(entry.id)>
<cfset var rootURL = getRootURL()>
<!--- Let's local scope a few more variables --->
<cfset var theMessage = "">
<cfset var tmp = "">
<cfset var attr = StructNew()>
<!--- Then we'll set the mail body content that doesn't change --->
<cfsavecontent variable="theMessage"><cfoutput>
<h2>#entry.title#</h2>
<b>URL:</b> <a href="#theLink#">#theLink#</a><br />
<b>Author:</b> #entry.name#<br />
#renderedText#<cfif len(entry.morebody)>
<a href="#theLink#">[Continued at Blog]</a></cfif>
<p>
You are receiving this email because you have subscribed to this blog.<br />
To unsubscribe, please go to this URL:
</cfoutput></cfsavecontent>
<!--- Then we'll set the mail attributes that come from the instance, as they won't change on each iteration --->
<cfset attr.from = instance.owneremail>
<cfset attr.subject = variables.utils.htmlToPlainText(htmlEditFormat(instance.blogtitle)) & " / " & variables.utils.htmlToPlainText(entry.title)>
<cfset attr.type = "html">
<!--- We'll add the mail server, if we need it --->
<cfif Len(Trim(instance.mailserver))>
<cfset attr.server = instance.mailserver>
<cfset attr.username = instance.mailusername>
<cfset attr.password = instance.mailpassword>
</cfif>
<cfloop query="subscribers">
<cfset attr.to = email>
<!--- Then we'll concantonate our mail body with the subscriber specific info --->
<cfset tmp = theMessage & "<a href='#rooturl#unsubscribe.cfm?email=#email#&token=#token#'>#rooturl#unsubscribe.cfm?email=#email#&token=#token#</a></p>">
<!--- And use the attribute collection for the cfmail tag, mailserver or not --->
<cfmail attributeCollection="#attr#">#tmp#</cfmail>
</cfloop>
(Call me Cutter) Yeah, my feed aggregator hadn't gotten all the comments when I wrote the code block. I didn't see that when I posted.
Great minds... ;)
<cfloop query="subscribers">
That way, when the blog sends email to 3000 subscribers it will still run in about 0 seconds. No need to join them back, just let them run and die
[Add Comment] [Subscribe to Comments]