Skip to main content
Ben Nadel at cf.Objective() 2010 (Minneapolis, MN) with: Anne Porosoff
Ben Nadel at cf.Objective() 2010 (Minneapolis, MN) with: Anne Porosoff

cuid For ColdFusion - Collision-Resistant IDs Optimized For Horizontal Scaling And Performance

By
Published in Comments (4)

At InVision, we've decided to use Eric Elliott's cuid library when it comes to generating UUIDs (universally unique IDs) in our upcoming microservice-based, highly distributed version of the platform. The cuid library provides collision-resistant IDs that are optimized for horizontal scaling and performance. And while our platform rewrite doesn't contain ColdFusion, the legacy system will need to generate cuid tokens as part of the preparation and migration phase. As such, I've created a ColdFusion port of the cuid library.

View my cuid For ColdFusion project on GitHub.

Like the original cuid library, each cuid value starts with the letter `c` and contains only alpha-numeric characters, making it safe to use as both an HTML element's ID attribute and as a server-side record identifier. The length of the cuid is guaranteed to be 25-characters (in this ColdFusion implementation).

ASIDE: The original cuid library makes no guarantees about length. However, it will coincidentally generate cuid tokens that are 25-characters long. This is because the "Date.now()" timestamp is currently base36-encoded as 8-characters. However, somewhere around the year 2060, a base36-encoding of "Date.now()" will start returning a 9-character string, bumping the length of the cuid up to 26-characters.

The cuid library for ColdFusion is thread safe and uses Java's AtomicInteger class (java.util.concurrent.atomic.AtomicInteger) for synchronized counter manipulation. It is intended to be instantiated once within an application and cached for future usage. The cuid library exposes one public method, ".createCuid()", which will generate and return your cuid token:

application.cuid = new lib.Cuid();

// Generate as many cuid values as you want! Skies the limit! Go cra-cra!
writeOutput( "cuid: " & application.cuid.createCuid() & "<br />" );
writeOutput( "cuid: " & application.cuid.createCuid() & "<br />" );
writeOutput( "cuid: " & application.cuid.createCuid() & "<br />" );
writeOutput( "cuid: " & application.cuid.createCuid() & "<br />" );

Running the above code will produce the following output:

cuid: cjetsjdk40000ecdihhc50anj
cuid: cjetsjdk40001ecdij952y404
cuid: cjetsjdk40002ecdil65fefyh
cuid: cjetsjdk40003ecdit3o3usnj

Each cuid token is a 25-character string composed of five sub-segments:

  • "c" - All cuid tokens start with "c".
  • timestamp.
  • counter.
  • fingerprint - Generated or a custom one provided at instantiation time.
  • pseudo-random number - Uses the SHA1PRNG algorithm for greater randomness.

The cuid for ColdFusion library is collision resistant, not necessarily collision proof. However, the chances of generating a collision are intensely small. To try and test this property of the library, I created a script that runs 10 asynchronous CFThread blocks that all generate 50,000 cuid tokens in parallel. The script then waits for all the threads to return and checks to see if any duplicate or malformed tokens were created:

<cfscript>

	cuid = new lib.Cuid();

	// Since CUID for ColdFusion will be running in a multi-threaded environment, we are
	// going to try and simulate contention by spawning multiple asynchronous threads
	// and trying to create thousands of CUID tokens at the same time. Because threads
	// don't spawn immediately, there is not guarantee that this will work; but, it's
	// worth a shot.
	goalThreadCount = 10;
	goalCuidCount = 50000;

	for ( i = 0 ; i < 10 ; i++ ) {

		thread
			name = "cuid-test-#i#"
			action = "run"
			goalCuidCount = goalCuidCount
			{

			thread.cuids = [];

			for ( var i = 0 ; i < goalCuidCount ; i++ ) {

				arrayAppend( thread.cuids, cuid.createCuid() );

			}

		}

	}


	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	startedAt = getTickCount();

	// Block and wait until all the asynchronous threads have completed.
	thread action = "join";

	writeOutput( "Done collecting: " & numberFormat( getTickCount() - startedAt ) & "ms<br />" );

	cuidCount = 0;

	// NOTE: Using a HashMap instead of a ColdFusion Struct because ColdFusion seemed
	// to be having some issues managing memory with the struct as it grew - my machine
	// seemed to just get progressively slower, with jstack pointing to struct keys.
	cuidTokens = createObject( "java", "java.util.HashMap" )
		.init( javaCast( "int", ( goalThreadCount * goalCuidCount ) ) )
	;

	// Now that all of the CFThreads have re-joined the page, let's iterate over the
	// generated CUID tokens and see if we found any collisions or anomalies.
	for ( threadName in structKeyArray( cfthread ) ) {

		for ( cuidValue in cfthread[ threadName ].cuids ) {

			cuidCount++;

			// If the CUID token has already been recorded, note the conflict.
			if ( structKeyExists( cuidTokens, cuidValue ) ) {

				writeOutput( "Collision: #cuidValue# <br />" );

			}

			cuidTokens[ cuidValue ] = true;

			// If any of the CUID values are an unexpected length, stop processing -
			// we need to investigate.
			if ( len( cuidValue ) != 25 ) {

				writeOutput( "Invalid length: #cuidValue#" );
				abort;

			}

		}

	}

	writeOutput( "Done testing. <br />" );
	writeOutput( "Found: #numberFormat( cuidCount )# tokens. <br />" );
	writeOutput( "One last test: " & cuid.createCuid() );

</cfscript>

As you can see, this attempts to create 500,000 cuid values in parallel. And, when we run the above code, we get the following output:

cuid for ColdFusion test results for randomness and collisions.

Not only can the cuid for ColdFusion library produce 500,000 cuid tokens in just a few seconds, none of them collide and none of them are malformed.

At this time, I've omitted the "slug()" method that is present in the original library. To me, the slug concept seems like a separate concern and is not related to cuid token generation. If I added it, I would add it as a separate library.

ColdFusion, of course, already has a createUUID() method for unique token generation. But, we needed a cuid library for ColdFusion in order to make the tokens compatible with other systems in our platform. As such, I am not sure that I can (or should) try to sell you on arbitrarily using cuid over ColdFusion's native UUID functionality, unless, of course, you need it for "reasons." But, if you do need it, hopefully this library makes your life a little easier.

Want to use code from this post? Check out the license.

Reader Comments

84 Comments

Nice. I like using generators that have multi-language support.

Have you used hashIds yet? (I did a quick search on google and amazingly this is one library you haven't blogged about yet.) hashIds "generates (collision free) short unique ids from integers". The values that it generates are similar to YouTube video IDs. hashIds is available in JavaScript, Ruby, Python, Java, Scala, PHP, Perl, Perl 6, Swift, Clojure, Objective-C, C, C++11, D, F#, Go, Erlang, Lua, Haskell, OCaml, Elixir, Rust, Smalltalk, ColdFusion, Groovy, Kotlin, Nim, VBA, Haxe, Crystal, Elm, ActionScript, CoffeeScript, Bash, R, TSQL, PostgreSQL and for .NET.
http://hashids.org/

hashIds is not the same thing as a UID, but you can encode and decode your simple integer IDs back and forth without being guessable or incremental. You can also pass a list/array of integers in a single hashId. You can also define length, which characters are used and a unique salt.

84 Comments

I recommend adding slug support. That way "ColdFusion" (er, CFML) can be officially listed along with Ruby, .Net, Go, PHP, Elixir, Haskell, Python, Clojure, and Java.

It would be a beneficial feature. We often generate smaller IDs when creating entry codes or "one-time" URLs.

15,848 Comments

@James,

I'll buy that as a reason to add Slug :D Getting it listed would be cool.

I'll also take a look at HashID - seems like a cool piece of functionality.

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel