Using An In-Memory Database With ColdFusion (Out Of The Box)

Posted By : todd sharp Posted At : January 27, 2011 1:45 PM Posted In: ColdFusion

37

I've been struggling with how to implement a particular piece of logic for one of my apps lately. The app in question requires that I notify a user if another user has made a recent change to some piece of data. My first thought was to use websockets to notify the user when the data has changed. Unfortunately the corporate firewall blocks websockets so that solution won't quite work for this application. My next thought was to set up a timer with JavaScript and hit the server every 60 seconds to check for changes - poor mans polling if you will. This works out fine for my needs so it's the route I'll end up going.

The next step, however, is where things can get tricky. I could easily query the database server and find out the data that I need to, but that could get quite expensive (especially the more data elements that you need to keep track of). I could easily stuff the important data into an array or struct in the application scope when another user changes it, but retrieval then becomes another task (how do you easily find the records that another user is concerned with). At this point I started thinking about queries. It's easy to manually create a query object with ColdFusion and retrieve records via query of queries, but inserting and deleting from that query object is much more complicated (as it should be since query objects aren't meant to replace databases). I could easily create a database table strictly for logging to insert/delete/query the messages, but again we're talking tons of database hits.

What would be really cool is if you could create and utilize a mostly full featured dbms like Apache Derby directly within RAM, right? Well it turns out you actually can. With ColdFusion. Out of the box.

Read that again. An actual database in memory that supports inserts, updates, deletes and reads. Turns out that the version of Apache Derby that ships with CF has had this kind of support for quite a while. The baddest-ass part of this is how stupid easy it actually is to do.

The first step is to create the DB. I'm choosing to store the connection object in the application scope, which is probably a good place for it.

if(!structKeyExists(application, 'conn')){
    application.dm = createObject('java', 'java.sql.DriverManager');
    application.conn = application.dm.getConnection('jdbc:derby:memory:test;create=true');
}

The important part to remember when creating the connection is adding "memory:SCHEMANAME" to the JDBC connection string. Next it's just a matter of creating a table:

<cfsavecontent variable="createSQL">
CREATE TABLE testTable (
testID INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1),
test VARCHAR(50)
)
</cfsavecontent>

<cfscript>
createTbl = application.conn.prepareStatement(createSQL);

try{
    createTbl.execute();    
}
catch(Any E){
    //table probably already exists...    
}
</cfscript>

At this point you're off to the friggin races. Here's an insert:

<cfsavecontent variable="insertSQL">
INSERT INTO testTable (test) VALUES ('foo')
</cfsavecontent>

<cfscript>
ins = application.conn.prepareStatement(insertSQL, application.conn.createStatement().RETURN_GENERATED_KEYS);
ins.executeUpdate();
</cfscript>

Running a select statement works the same way:

<cfsavecontent variable="selectSQL">
select *
from testTable
</cfsavecontent>

<cfscript>
sel = application.conn.prepareStatement(selectSQL);
results = sel.executeQuery();
writeDump(results);
</cfscript>

You may notice that when you dump out the results of the select that you get a java.sql.ResultSet object. No problem, just do this:

cfquery = createObject('java', 'coldfusion.sql.QueryTable').init(results);
writeDump(cfquery);

So this feature gives me the ability to create a disposable table when my application starts and do inserts, updates, deletes and selects from that table as the needs of my application dictate. How badass is that? The potential uses for this feature are pretty limitless. And it all works straight up out of the box with ColdFusion.

Comments (37)

Raymond Camden's Gravatar I know you create the connection in the App scope, but I bet the JDBC URL is system wide. Make another Application and use the same JDBC url. I bet it points to the exact same db.

todd sharp's Gravatar Sure, I'd bet that works. The main purpose of using the app scope is calling the prepareStatement() methods later on when you need to query.

todd sharp's Gravatar Should probably mention that it wouldn't be necessary to add create=true to the jdbc url if you're using it somewhere else. It might even throw an error...

Steve Durette's Gravatar So, just out of curiosity, why don't you just set up a trigger on the database to deal with when the change is made? The db can then fire the response (hit the needed page, etc) when it actually happens and then there is no over head having to check the status continually.

todd sharp's Gravatar @Steve:

It's a one page Ajax application so that really wouldn't work for this app.

Carl Von Stetten's Gravatar I've never used Apache Derby before. With ColdFusion's implementation of Derby, does the Derby database system share the same JVM and memory space as ColdFusion itself? Would using Derby in-memory databases impact the amount of memory available to ColdFusion?

todd sharp's Gravatar @Steve - web sockets or some other sort of push messaging really would be the best way to go, but the firewall is my issue with that.

todd sharp's Gravatar @Carl - good questions. I've not worked a ton with it so those are questions that would probably have to be answered before implementing this into production. I'm sure I'll dig in a bit more in the future so I'll post back if I find the answer.

Brian Swartzfager's Gravatar That's pretty cool! Looks like a nice way of managing a large amount of non-persistent records. You could also potentially use it as a poor man's database failover: keep a copy of critical (perhaps slightly aged) public-facing data in a Derby table, and if the app throws an error trying to query the normal back-end database, change a flag so the app/pages go against the Derby data.

Ed Andrade's Gravatar My question is, if this is an in-memory database, what happens if you have multiple app servers for your site? Can the in-memory db get sync'ed across all the servers so you do not need stick sessions? Or even how about the case of two users on two different servers accessing the same data and one of them updates it?

MikeG's Gravatar This would be a fairly simple way to retrofit a record locking capability
into an existing app without having to make changes to the existing database.
Just need to make sure during onSessionEnd() that you clear that a specific
user's records get wiped in case they abandon before save.

Could also be used to remember form data during a user's session
and would make managing a shopping cart easier too.

This is a great find!

todd sharp's Gravatar @Ed: very good questions - and i'm going to have to find the answers because my app is in a cluster. i'll let you know what i find out.

as far as 2 different servers i'd imagine it would act just like a normal apache derby db. jdbc supports transactions and locking i believe...

Seth Johnson's Gravatar No need to have cfadmin access?
[Runs to see if this works on a shared host]

todd sharp's Gravatar Nope, no need for admin - but - most shared hosts will block access to creating native java objects. Let us know what you find.

todd sharp's Gravatar @Ed - see this page from Charlie Arehart:

http://www.carehart.org/resourcelists/derby_for_cf...

Specifically the section on 'Can Derby be used in a Cluster?'. I'm going to look into those projects that Charlie mentions to see how feasible it would be to take advantage of something like that.

John Allen's Gravatar That is a pretty slick, cool, neat idea.

Robert Gatti's Gravatar Wicked cool! I started immediately trying to hijack this into a datasource in cfadmin. I'm sure there's a way to do it and I'm just not seeing it. I tried specifying 'memory' as the Database Folder (no path) and modifying the connection string for an Embedded db. It just created a folder in the default location called 'memory'. I also tried specifying the JDBC driver / conn string directly but that also didn't work. I'd love to know if anyone figures out how to set this up as a datasource where you can use cfquery.

Steve Bryant's Gravatar This is really cool!

How would you do parameterization with this?

todd sharp's Gravatar @Steve: Haven't tested it, but I have no doubt it'd work like any standard JDBC statement. IE:

stmt = application.conn.prepareStatement("insert into tbl (foo) values(?)");
stmt.setInt(1);
//or
stmt.setString('foo');

@Robert:

I quickly tried that at first (I left the folder blank) but it seems CF does a bit of messing with the connection string and it goofed it up. Understandable, but there still might be a way to do it. I'll mess with it again at some point.

todd sharp's Gravatar WOW! So I created a datasource (type='other') with the following settings:

http://twitpic.com/3tzk6f

And it WORKED. So I can now use standard <cfquery> syntax on an in-memory database:

<cftry>
   <cfquery name="createTbl" datasource="memory">
   CREATE TABLE testTable (
   testID INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1),
   test VARCHAR(50)
   )
   </cfquery>
<cfcatch type="any">
   <!--- table exists --->   
</cfcatch>   
</cftry>


<cfquery name="insert" datasource="memory">
insert into testTable
(test)
values(
   <cfqueryparam value="todd" cfsqltype="cf_sql_varchar" />
)   
</cfquery>

<cfquery name="select" datasource="memory">
select *
from testTable
</cfquery>

<cfdump var="#select#">

aphtk's Gravatar ...Apache Derby is one, SQLite is another. You could have done the same with SQLite and Zentus JDBC Driver... also a full blown RDBMS, supports functions, procs, views, DDL and DML, indexing the whole lot.
I've included the driver in the STAR stack...(http://aphatak.blogspot.com/2010/08/star-stack.htm... AND/OR http://aphatak.blogspot.com/2010/03/new-web-stack-...).

If your datasets are simple and not that huge why not just use Coldfusion's QueryNew() functions to create in memory tables. You could do relational querys. The pain would be to setup tables but you could have that functionality encap'ed in functions...They have very good performance too!

Ben Nadel's Gravatar This is insanely badass! Really interesting that you can hook it into a native ColdFusion data source that works with the CFQuery tag. Coolest things I've seen in a while!

Todd Rafferty's Gravatar @aphtk: I was wondering the same thing about QoQ in memory, but then again if you've ever had deal with maintaining that QoQ, you'd know what a pain in the ass it is.

It's a neat experiment though.

Seth Johnson's Gravatar Tried it on two shared hosts, one Railo 3.2 and CF9.

Both returned the following:
No suitable driver found for jdbc:derby:memory:test;create=true

Todd Rafferty's Gravatar @Seth: Railo does not package Derby, we use H2 instead. This doesn't stop you from trying to do it yourself, but you'll also have to create a new driver.cfc for it.

This doesn't explain why it failed for you on CF9 tho.

Robert Gatti's Gravatar @Seth:
I got the same error on my CF 9 box (it's running 9,0,0,251028).


@Todd:
I tried the 'other' datasource like you showed but I get an error:

Connection verification failed for data source: memdb
java.sql.SQLException: Timed out trying to establish connection
The root cause was that: java.sql.SQLException: Timed out trying to establish connection


I'm able to create a normal derby datasource so something else must be up.

Todd Rafferty's Gravatar Apparently you need to upgrade to 9.0.1 ( 9,0,1,274733 ) - I can set this up just fine.

todd sharp's Gravatar @Robert - Yeah, I just found out a bit ago that 9.0.1 is required. The in-memory stuff was added in Derby 10.5.1 I believe and the 9.0.1 updater brought the CF version from 10.4.2 to 10.5.3 if I remember right.

All:

I'm trying to play around with using an in-memory database with the Derby Network Server which will provide support for connections from multiple JVMs (IE: a cluster). I got it working, but I had to manually start the Derby Server from command line. That's pretty lame, so I'm looking at my options...

Sam Farmer's Gravatar Dude. Ridiculously cool. Especially being able to use a ColdFusion datasource for it.

Robert Gatti's Gravatar @Todd
Couldn't you use cfexecute in an onServer start to kick off the Derby Server?

Dan G. Switzer, II's Gravatar While I could see the usefulness of doing something like this for demo purposes, I'm not sold on using the technique for production environment. Seems like you're adding a lot of potential problems points for something that a modern database would handle effectively--even when you encompass the updates to a logging table.

It just doesn't seem like a properly configured table schema would have any performance issues handing what you're doing. If the performance really is that bad, then I think I'd be looking into things like memcache/ehCache--both of which can play nicely w/in a cluster.

Like I said, this interesting and has some potential use cases, but I'm for the use case described in this blog, I'd try not to get too clever since this should be something you can solve with a pretty straightforward audit log table.

todd sharp's Gravatar @Robert (and all):

Check out my latest post: http://cfsilence.com/blog/client/index.cfm/2011/1/...

I'll agree that the use case I described might not be the best use case for this. I'm still considering my options (my first problem is the app server is on CF 8...). Still - I'll argue that in-memory databases definitely have potential when it comes to super fast I/O on 'disposable' data.

Tristan Lee's Gravatar This is very nice! I have used Derby before in Java apps on a basic level, but didn't think about leveraging that in to CF... mainly because I haven't found myself in the situation to need it. Regardless, this is very cool!

Aaron Greenlee's Gravatar Great post.

Fusion Reactor does not seem to like this type of JDBC string: jdbc:derby:memory:test;create=true

Hopefully I can find a solution online.

Thanks!

Charles's Gravatar Out of the box, or out of the closet?

Tavs Dalaa's Gravatar Todd, thanks for this excellent excellent post. Had read about in-memory db's on the Derby changelog, but hadn't even thought of using it this way in CF. Now using it to store various non-persistent session relevant data. Super super fast.