I'm building an auction site and I want to make sure that there are no issues with mutiple auctions closing at similar times. I have a basic understand of cflock and was wondering how this, or others like cfthread, could be applied optimally to protect this process from race conditions.
So far, my processing goes like this:
When an auction reaches its closing time, I initially set the status of "1" to indicate that the auction is closing. Site users then see the message "Closing" while the closure process takes place.
close_auction.cfm
<cfset resUpdateStatus = oListing.updateStatus(listing_id=url.listing_id, status=1)>
Then the call to the closeAuction() function:
<cfset resCloseAuction = oListing.closeAuction(listing_id=url.listing_id)>
listing.cfc
The closeAuction() function, with a cflock I'm currently using:
<cflock timeout="30" name="closeAuction_#arguments.listing_id#" type="exclusive">
<cftransaction>
<!--- if for whatever reason auction closure time not yet reached, delay a few seconds --->
<cfloop condition = "now() lt qListing.end_date">
<cfset oSystem.pause(TimeDelay=5)>
</cfloop>
<!--- processing here --->
- select on listing table to ensure listing still exists
- select on bids table - check high bid, determine if winner
- update listing table - status, winner_id, close_date etc
- update user_subscription table
- delete scheduled task that auto-closes the auction
- update tracking table
- delete listing from users' watchlists
</cftransaction>
</cflock>
This works fine while testing single auctions, but I'm concerned about what could happen under heavy load with many auctions closing potentially within seconds of each other. Any advice bullet-proofing this would be appreciated.
I'm on CF12
UPDATE
Updated to show processing inside cftransaction
Related
We have a three-panel display in which Panel One shows a list of countries, Panel Two shows a list of organizations, and Panel Three shows a list of aircraft. If you click a country, both the organizations and aircraft are filtered by that country, using IDs for each.
This is all working fine, but the list of aircraft comes from a "linking table" -- each record contains aircraft ID and name, and operator ID and name. Any given type of aircraft can be listed multiple times, depending on how many organizations operate it. For example, "AW159 Wildcat" is listed twice, once for Army Air Corps and once for Royal Navy. The way it works right now, "AW159" is listed in Panel Three twice when the page initially loads.
I would like any given aircraft to be displayed only once on initial page load, regardless of how many times it may be listed in this linking table. But, I want all the "duplicates" available to be shown, if somebody filters on the operating organization.
Is there a way to loop over all the aircraft but somehow hide duplicates of aircraft ID? Or, might there be a larger problem with the way our database is designed? I am open to suggestions. Thank you all very much in advance!
EDIT: sorry for not posting code; here is some. Thanks everybody for all of your help!
1. Query and array population in cfc:
<cfquery name="queryByOperator" datasource="vertipedia">
SELECT view_aircraftbyoperators.aircraftID
, view_aircraftbyoperators.Aircraft
, view_aircraftbyoperators.operatorID
, view_aircraftbyoperators.Operator
, tbl_companies.country
, tbl_companies.companyShortName
FROM view_aircraftbyoperators JOIN tbl_companies
ON tbl_companies.ID = view_aircraftbyoperators.operatorID
ORDER BY Aircraft
</cfquery>
<cfset count = 1>
<cfset aircraftByOperator = arrayNew(2)>
<cfloop query="queryByOperator">
<cfset aircraftByOperator[count][1] = aircraftID>
<cfset aircraftByOperator[count][2] = Aircraft>
<cfset aircraftByOperator[count][3] = operatorID>
<cfset aircraftByOperator[count][4] = Operator>
<cfset aircraftByOperator[count][5] = country>
<cfset count = count+1>
</cfloop>
2. Outputting on page:
<ul>
<cfloop from="1" to="#ArrayLen(aircraftByOperator)#" index="i">
<cfoutput>
<li name="#aircraftByOperator[i][3]#" class="#aircraftByOperator[i][5]#">
<p>
#aircraftByOperator[i][2]#
</p>
</li>
</cfoutput>
</cfloop>
</ul>
[i][1], [i][3] and [i][5] are all IDs. "operatorID" is the one that will change depending on who operates the aircraft. I hope this helps explain what I am trying, and again, many thanks!
Dan Bracuk's comment "Does this mean you are pre-loading all your data into javascript? It might be more efficient to throw some ajax in there and just get what you need when you need it" ended up being the way to go for us. I would like to mark his comment as the answer, but am unable to see how to do that.
For others who may read this post, we
Loaded all of the aircraft initially
When a visitor filters by country and/or organization, a new query is sent via ajax
I was having a hard time formatting the returned data, so I used jQuery's load() to format it and drop it in the proper place in the output panel.
As Dan said, it is very fast and much easier to work with, especially as our database continues to grow.
Thanks Dan!
i am using the following code to handle the insets: but it is taking too much time, how can i enhance it or make in work faster:
<cfspreadsheet action="read" excludeheaderrow="yes" headerrow="1" rows="2-65536" query="mySheet" src="#dest#\#newfile#" sheet="1">
<cftry>
<cfquery datasource="#request.dsn#" name="myInsert">
<cfoutput>
<cfloop query="mySheet">
INSERT INTO mytable(memberID,lastname,firstname,pid,pname,gender,dob,workphone,homephone,address1,address2,city,county,state,zip,marketValue)
values('#mySheet.mem_id##RandRange(1,100)#','#mySheet.MEM_LAST_NAME#','#mySheet.MEM_FIRST_NAME#','#mySheet.PCP_ID#','#mySheet.pcp_name#','#mySheet.gender#','#mySheet.dob#','#mySheet.WORKTELEPHONENUMBER#','#mySheet.HOMETELEPHONENUMBER#','#mySheet.ADDRESSLINE1#','#mySheet.ADDRESSLINE2#','#mySheet.CITY#','#mySheet.COUNTY#','#mySheet.state#','#mySheet.zip#','#mySheet.GROUPNAME#')
</cfloop>
</cfoutput>
</cfquery>
records in excel sheet are more than 50000
Here's one way to handle it:
Put the entire thing (cfspreadsheet and all inserts) into a threaded function so it can run in the background
Save the spreadsheet query (upload results) into a session variable
After the upload is complete, do your insert queries in batches; in each batch add any issues into a feedback array that is also in the session scope
On the user's end, have a page that tells the user that things are "Working..." and re-loads and displays the feedback issues as they come up (best to do with ajax)
I am trying to use memcached for the first time but I have a few questions when it comes to queries with dynamic variables.
This is a sample of my code so far. I got everything working but if you see any mistakes on my code please let me know.
<cfoutput>
<cfset variables.memcachedFactory = createObject("component","memcachedfactory").init("127.0.0.1:11211")>
<cfset variables.memcached = variables.memcachedFactory.getmemcached()>
<cfset email = "sbcglobal"> //Pretend that this is a list with multiple choices for the user to choose from.
//The query below would have multiple outputs based on the selection of the user above
<cfquery name="test" datasource="#ds#">
select top 10 * from contact where email like '%#email#%'
</cfquery>
//Setting the key
<cfset set100 = variables.memcached.set(key="queryTest2",value=test,expiry="5000")>
//Getting the key
<Cfset test6 = variables.memcached.get("queryTest2")>
<br><br>this is the query test - did we retrieve it correctly? - <br />
<Cfdump var="#Test6#"><br />
</cfoutput>
Now, do i have to set a key for every possible outcome of the query? (which in my case would be a few hundred keys to cover all possible outcomes)
My understanding is that memcached saves the value to a key after the first request and after that you get the output without db calls.
If I use memcached in my case would it just be a waste of code since each query would be different based on the user's choice?
Also, how do you update the values in cache in case the output changes? Is this something that I would have to do manually? My database get 100's of transactions daily, a select * from contacts at 10 am will not have the same output at 5pm
The idea behind caching (in this case) is that you first check memcached, and if the key exists, you get the data from the cache. If it doesn't exist, you go to the database, get the data, stick it in memcached, then return it to the user.
For your use case here, you want a unique key for each unique record set you're returning. Take your example. If email address is the unique key you are using to identify a record set, then use that as the key in memcached. Pseudo-code:
<cfset results = variables.memcached.set(key="foo_#email#",value=test,expiry="5000")>
foo_ lets you provide a "namespace" for the keys you are putting into memcached, which makes it easier to manage and avoid key collisions.
Say I have the following script on page.cfm. Everytime somebody comes to page.cfm, an e-mail is sent to me. So if 1000 people come to page.cfm, I get 1000 e-mails.
<cfmail to="joe#smith.com"
from="error#smith.com"
subject="Error"
type="text">
A person visited.
</cfmail>
I would like to limit it so I only get e-mails when the first 5 people visit in one day.
So if 2 people visit today, I get 2 e-mails. if 5 people visit I get 5, but if 100 people visit, I still get only 5 e-mails. And then the cycle continues the next day (if 2 people visit only 2 e-mails are send, but if 100 people visit only 5 e-mails are sent)
How can I do this with ColdFusion only? (without cfschedule)
The simplest way I can think of is to put a counter and a date stamp in a pair of Application variables.
Then, for each request, check the date you've recorded. If it's not today, reset the counter variable to 1 and re-set the date to today.
Then, where you're putting the cfmail tag, do a check to see if the counter has reached your limit. If not, send the mail. Either way, increment the counter.
In Application.cfc onApplicationStart():
<cfset application.alertDate = now()>
<cfset application.alertCount = 1>
(the above is mostly to ensure that the variables exist when used later)
In Application.cfc onRequestStart():
<cfif dateCompare(application.alertDate,now(),"d") EQ -1)>
<cfset application.alertCount = 1>
<cfset application.alertDate = now()>
</cfif>
In page.cfm:
<cfset application.alertCount++>
<cfif application.alertCount LTE 5>
<cfmail ... >
</cfif>
Store the visit/email count in the Application scope, and reset the application scope every day by either
store the last visitor date in the application scope as well, and
add some logic to reset the count, or
use cfscheduler to reset the count to 0 every day
Late to the party, and I can't comment on previous answers just yet, so I'll do it this way. Both of the answers given will work, but you do need to know that those Application scope variables will disappear and reinitialize should your application end. If it's important to you, you will want to persist the values somewhere, something like an INI file accessed with getProfileString/setProfileString would work, or a table in a database.
Why does this not work? My welcome message, it just doesn't show up:
<p>Welcome <cfoutput>#Recordset1.UserID#</cfoutput>.</p>
The session variable on the login page I created is:
<cflock timeout=999 scope="Session" type="Exclusive">
<cfset Session.IDUsers =''>
</cflock>
is this incorrect? On the index page where I'm trying to display my welcome message I have:
<cfquery name="Recordset1" datasource="cfGossip">
SELECT *
FROM users
WHERE users.IDUsers = <cfqueryparam value="#Session.IDUsers#">
</cfquery>
I'm not sure if this works, or is necessary?
If you set the userid stored in the session to be the empty string, when you query on it, you will only get users for whom the id is the empty string, which shouldn't be any of them. Therefore, the query is returning an empty set, and your page is (correctly) not displaying a user id.
How are you initially identifying the user? Are you querying against a database when they log in? Are you storing a cookie? Reading Tarot cards? For this to work, at some point, you have to store the correct userid, probably in the session. To do that, you need to first identify who the user is.
Also, if you are using CF6+, you probably do not need the cflock. It is now used to prevent race conditions, as CF is now thread-safe.
Looks like you're just starting with CF, welcome to the community.
My understanding of your code makes the structure look like the following, if I'm understanding you correctly:
<cfset session.idUsers = '' />
<cfquery datasource = "cfgossip" name = "recordset1">
SELECT * FROM USERS WHERE USERS.ID_USERS = <cfqueryparam cfsqltype = "cf_sql_integer" value = "#session.idUsers# />
</cfquery>
<cfoutput>Welcome #recordset1.userID#</cfoutput>
The reason this doesn't work is because your session.idUsers value is blank. Assuming you have a user in your database with an ID_USERS value of 1, you could change the CFSET to look like and it should return results.
Additionally, while it's great to see you using CFQUERYPARAM, I'd recommend including a CFSQLTYPE attribute in the tag whenever possible to provide an added line of defense against injection attacks. You can check out http://livedocs.adobe.com/coldfusion/8/htmldocs/help.html?content=Tags_p-q_18.html to see the list of available types.
Is there anywhere in your code where you set your session.IDUsers? You initialize it as a blank ''. Coldfusion does not populate it for you. The session scope is a place that will remember things for that user that you put there for a specified period of time inactivity, usually 20 minutes. So hopefully, somewhere before you run your query you have additional logic that fills that in, otherwise you are asking the database for a user named, ''.
This is just a point of style, but the following may work better for you:
<cfset Session.IDUsers =''>
<!--- Do something here to populate Session.IDUsers --->
<!--- Creates a blank query - not necessary, but can reduce errors later --->
<cfset Recordset1 = queryNew("UserID")>
<!--- Populate the query from the database --->
<cfquery name="Recordset1" datasource="cfGossip">
SELECT *
FROM users
WHERE users.IDUsers = <cfqueryparam value="#Session.IDUsers#">
</cfquery>
<!--- If the query has data, use it, otherwise show generic message --->
<cfoutput>
<cfif Recordset1.recordcount>
<p>Welcome #Recordset1.UserID#.</p>
<cfelse>
<p>Welcome new user!</p>
</cfif>
</cfoutput>
<!--- OR since we used queryNew("userID"), we can simplify without throwing an error. ---->
<cfoutput>
<p>Welcome <cfif len(Recordset1.userID)>#Recordset1.userID#.<cfelse>new user!</cfif></p>
</cfoutput>
Putting the cfoutput outside the paragraph block will make it easier if you have additional variables to insert into the text. (but will work either way)
Regardless of all that, unless you forgot to share a bit more of the code, I think the issue is that the session.IDUsers is blank and needs to be populated before the query. I hope this helps!