Sometimes it's the small mistakes...

This post is more than 2 years old.

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:

<cffunction name="mailEntry" access="public" returnType="void" output="false" hint="Handles email for the blog."> <cfargument name="entryid" type="uuid" required="true"> <cfset var entry = getEntry(arguments.entryid,true)> <cfset var subscribers = getSubscribers(true)> <cfset var theMessage = ""> <cfset var mailBody = "">
&lt;cfloop query="subscribers"&gt;
	
	&lt;cfsavecontent variable="theMessage"&gt;
	&lt;cfoutput&gt;

<h2>#entry.title#</h2> <b>URL:</b> <a href="#makeLink(entry.id)#">#makeLink(entry.id)#</a><br /> <b>Author:</b> #entry.name#<br />

#renderEntry(entry.body,false,entry.enclosure)#<cfif len(entry.morebody)> <a href="#makeLink(entry.id)#">[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="#getRootURL()#unsubscribe.cfm?email=#email#&token=#token#">#getRootURL()#unsubscribe.cfm?email=#email#&token=#token#</a> </p> </cfoutput> </cfsavecontent>

	&lt;cfif instance.mailserver is ""&gt;
		&lt;cfmail to="#email#" from="#instance.owneremail#" subject="#variables.utils.htmlToPlainText(htmlEditFormat(instance.blogtitle))# / #variables.utils.htmlToPlainText(entry.title)#" type="html"&gt;#theMessage#&lt;/cfmail&gt;
	&lt;cfelse&gt;
		&lt;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"&gt;#theMessage#&lt;/cfmail&gt;
	&lt;/cfif&gt;
&lt;/cfloop&gt;

</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:

<cfset var renderedText = renderEntry(entry.body,false,entry.enclosure)> <cfset var theLink = makeLink(entry.id)> <cfset var rootURL = getRootURL()>

<cfloop query="subscribers">

&lt;cfsavecontent variable="theMessage"&gt;
&lt;cfoutput&gt;

<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>

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.

Raymond Camden's Picture

About Raymond Camden

Raymond is a senior developer evangelist for Adobe. He focuses on document services, JavaScript, and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA https://www.raymondcamden.com

Archived Comments

Comment 1 by Phillip Senn posted on 1/1/2010 at 9:47 PM

There's no /cfloop in your second example.

Comment 2 by Raymond Camden posted on 1/1/2010 at 10:00 PM

Yes there is. Line 5. But code block 2 is just a subset.

Comment 3 by Gary Funk posted on 1/1/2010 at 10:08 PM

What about:

<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>

Comment 4 by Raymond Camden posted on 1/1/2010 at 10:10 PM

@Gary: Token is unique per subscriber. It allows me to unsubscribe myself from a entry thread. (Me being a subscriber.)

Comment 5 by Raymond Camden posted on 1/1/2010 at 10:10 PM

By the way - let me publicly thank Gary again. The "slow response to a new comment" issue he fixed has made me VERY happy!

p.s. Fixing to go to inlaws for a few hours so replies to comments will be delayed.

Comment 6 by Gary Funk posted on 1/1/2010 at 10:17 PM

Ah, right. I missed the token. You could still save some time and memory by putting the message in the 'theMessage' and append the link with the token in the loop.

Comment 7 by Mikkel Johansen posted on 1/2/2010 at 5:43 PM

Before it took 10-15 sec. What impact has the fix done to the performance, how many seconds does it take now ?

Comment 8 by Raymond Camden posted on 1/2/2010 at 7:07 PM

0. :)

Comment 9 by Steve &aposCutter&apos Blades posted on 1/4/2010 at 3:29 AM

How about something like this?:

<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>

Comment 10 by Gary Funk posted on 1/4/2010 at 4:29 AM

@Steve: Seems like someone else suggested that in comment # 6.

Comment 11 by Steve &aposCutter&apos Blades posted on 1/4/2010 at 6:39 AM

@Gary,

(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... ;)

Comment 12 by Gary Funk posted on 1/4/2010 at 7:50 AM

@Cutter: If only Ray was a programmer of our standard. LOL

Comment 13 by Raymond Camden posted on 1/4/2010 at 7:52 AM

Heh, I try...

Comment 14 by Gary Funk posted on 1/4/2010 at 8:30 AM

If you are trying to match my standard, Stop It. We don't need you down here with me.

Comment 15 by MikeG posted on 1/5/2010 at 3:21 AM

taking the other suggestions into account and using the closest code sample to this post, why not a cfthread right after
<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

Comment 16 by Raymond Camden posted on 1/5/2010 at 3:37 AM

BlogCFC runs on CF6, so cfthread isn't an option. I _can_ write code CF8 stuff (I've used cfthread for Ping), but it's a bit of a pain. As it stands, it takes 1 second now, so not worth the effort. ;)

Comment 17 by MikeG posted on 1/5/2010 at 4:36 AM

DOH - forgot about backwards compatibility - not used to thinking about community distributions; that has got to get frustrating.

Comment 18 by Raymond Camden posted on 1/5/2010 at 4:38 AM

BlogCFC 6 (yes, I will build it, honest, why is everyone looking at me and snickering?) will drop 6, maybe even 7.

Comment 19 by Gary Funk posted on 1/5/2010 at 5:18 AM

If you go with CF9 you can use the new features to keep up the database.

Comment 20 by Raymond Camden posted on 1/5/2010 at 5:27 AM

I'd _love_ to go with CF9, but I'd probably lose 99% of my customers. I've already lost a lot to Mango - don't want to lose the rest. ;)

Comment 21 by Gary Funk posted on 1/5/2010 at 6:36 AM

Hmm, Manog. Heard of it... naw. I like BlogCFC way too much.